1 // Written in the D programming language. 2 3 /** 4 This module contains implementation of SDL2 based backend for dlang library. 5 6 7 Synopsis: 8 9 ---- 10 import dlangui.platforms.sdl.sdlapp; 11 12 ---- 13 14 Copyright: Vadim Lopatin, 2014 15 License: Boost License 1.0 16 Authors: Vadim Lopatin, coolreader.org@gmail.com 17 */ 18 module dlangui.platforms.sdl.sdlapp; 19 20 public import dlangui.core.config; 21 static if (BACKEND_SDL): 22 23 import core.runtime; 24 import std.conv; 25 import std.string; 26 import std.utf : toUTF32, toUTF16z; 27 import std.stdio; 28 import std.algorithm; 29 import std.file; 30 31 import dlangui.core.logger; 32 import dlangui.core.events; 33 import dlangui.core.files; 34 import dlangui.graphics.drawbuf; 35 import dlangui.graphics.fonts; 36 import dlangui.graphics.ftfonts; 37 import dlangui.graphics.resources; 38 import dlangui.widgets.styles; 39 import dlangui.widgets.widget; 40 import dlangui.platforms.common.platform; 41 42 import derelict.sdl2.sdl; 43 import derelict.opengl3.gl3; 44 import derelict.opengl3.gl; 45 46 static if (ENABLE_OPENGL) { 47 import dlangui.graphics.gldrawbuf; 48 import dlangui.graphics.glsupport; 49 } 50 51 private derelict.util.exception.ShouldThrow missingSymFunc( string symName ) { 52 import std.algorithm : equal; 53 static import derelict.util.exception; 54 foreach(s; ["SDL_DestroyRenderer", "SDL_GL_DeleteContext", "SDL_DestroyWindow", "SDL_PushEvent", 55 "SDL_GL_SetAttribute", "SDL_GL_CreateContext", "SDL_GetError", 56 "SDL_CreateWindow", "SDL_CreateRenderer", "SDL_GetWindowSize", 57 "SDL_GL_GetDrawableSize", "SDL_GetWindowID", "SDL_SetWindowSize", 58 "SDL_ShowWindow", "SDL_SetWindowTitle", "SDL_CreateRGBSurfaceFrom", 59 "SDL_SetWindowIcon", "SDL_FreeSurface", "SDL_ShowCursor", 60 "SDL_SetCursor", "SDL_CreateSystemCursor", "SDL_DestroyTexture", 61 "SDL_CreateTexture", "SDL_UpdateTexture", "SDL_RenderCopy", 62 "SDL_GL_SwapWindow", "SDL_GL_MakeCurrent", "SDL_SetRenderDrawColor", 63 "SDL_RenderClear", "SDL_RenderPresent", "SDL_GetModState", 64 "SDL_RemoveTimer", "SDL_RemoveTimer", "SDL_PushEvent", 65 "SDL_RegisterEvents", "SDL_WaitEvent", "SDL_StartTextInput", 66 "SDL_Quit", "SDL_HasClipboardText", "SDL_GetClipboardText", 67 "SDL_free", "SDL_SetClipboardText", "SDL_Init", "SDL_GetNumVideoDisplays"]) {//"SDL_GetDisplayDPI" 68 if (symName.equal(s)) // Symbol is used 69 return derelict.util.exception.ShouldThrow.Yes; 70 } 71 // Don't throw for unused symbol 72 return derelict.util.exception.ShouldThrow.No; 73 } 74 75 private __gshared uint USER_EVENT_ID; 76 private __gshared uint TIMER_EVENT_ID; 77 private __gshared uint WINDOW_CLOSE_EVENT_ID; 78 79 class SDLWindow : Window { 80 SDLPlatform _platform; 81 SDL_Window * _win; 82 SDL_Renderer* _renderer; 83 84 SDLWindow[] _children; 85 SDLWindow _parent; 86 87 this(SDLPlatform platform, dstring caption, Window parent, uint flags, uint width = 0, uint height = 0) { 88 _platform = platform; 89 _caption = caption; 90 _windowState = WindowState.hidden; 91 92 _parent = cast(SDLWindow) parent; 93 if (_parent) 94 _parent._children~=this; 95 96 debug Log.d("Creating SDL window"); 97 _dx = width; 98 _dy = height; 99 create(flags); 100 _children.reserve(20); 101 Log.i(_enableOpengl ? "OpenGL is enabled" : "OpenGL is disabled"); 102 103 if (platform.defaultWindowIcon.length != 0) 104 windowIcon = drawableCache.getImage(platform.defaultWindowIcon); 105 } 106 107 ~this() { 108 debug Log.d("Destroying SDL window"); 109 110 if (_parent) { 111 ptrdiff_t index = countUntil(_parent._children,this); 112 if (index > -1 ) { 113 _parent._children=_parent._children.remove(index); 114 } 115 _parent = null; 116 } 117 118 if (_renderer) 119 SDL_DestroyRenderer(_renderer); 120 static if (ENABLE_OPENGL) { 121 if (_context) 122 SDL_GL_DeleteContext(_context); 123 } 124 if (_win) 125 SDL_DestroyWindow(_win); 126 if (_drawbuf) 127 destroy(_drawbuf); 128 } 129 130 131 private bool hasVisibleModalChild() { 132 foreach (SDLWindow w;_children) { 133 if (w.flags & WindowFlag.Modal && w._windowState != WindowState.hidden) 134 return true; 135 136 if (w.hasVisibleModalChild()) 137 return true; 138 } 139 return false; 140 } 141 142 143 private void restoreModalChilds() { 144 foreach (SDLWindow w;_children) { 145 if (w.flags & WindowFlag.Modal && w._windowState != WindowState.hidden) { 146 if (w._windowState == WindowState.maximized) 147 w.activateWindow(); 148 else 149 w.restoreWindow(true); 150 } 151 152 w.restoreModalChilds(); 153 } 154 } 155 156 private void minimizeModalChilds() { 157 foreach (SDLWindow w;_children) { 158 if (w.flags & WindowFlag.Modal && w._windowState != WindowState.hidden) 159 { 160 w.minimizeWindow(); 161 } 162 163 w.minimizeModalChilds(); 164 } 165 } 166 167 168 private void restoreParentWindows() { 169 SDLWindow[] tempWin; 170 if (!_platform) 171 return; 172 173 SDLWindow w = this; 174 175 while (true) { 176 if (w is null) 177 break; 178 179 tempWin~=w; 180 181 w = w._parent; 182 } 183 184 for (size_t i = tempWin.length ; i-- > 0 ; ) 185 tempWin[i].restoreWindow(true); 186 } 187 188 private void minimizeParentWindows() { 189 SDLWindow[] tempWin; 190 if (!_platform) 191 return; 192 193 SDLWindow w = this; 194 195 while (true) { 196 if (w is null) 197 break; 198 199 tempWin~=w; 200 201 w = w._parent; 202 } 203 204 for (size_t i = tempWin.length ; i-- > 0 ; ) 205 tempWin[i].minimizeWindow(); 206 } 207 208 /// post event to handle in UI thread (this method can be used from background thread) 209 override void postEvent(CustomEvent event) { 210 super.postEvent(event); 211 SDL_Event sdlevent; 212 sdlevent.user.type = USER_EVENT_ID; 213 sdlevent.user.code = cast(int)event.uniqueId; 214 sdlevent.user.windowID = windowId; 215 SDL_PushEvent(&sdlevent); 216 } 217 218 219 static if (ENABLE_OPENGL) 220 { 221 static private bool _gl3Reloaded = false; 222 private SDL_GLContext _context; 223 224 protected bool createContext(int versionMajor, int versionMinor) { 225 Log.i("Trying to create OpenGL ", versionMajor, ".", versionMinor, " context"); 226 SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, versionMajor); 227 SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, versionMinor); 228 SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_CORE); 229 _context = SDL_GL_CreateContext(_win); // Create the actual context and make it current 230 if (!_context) 231 Log.e("SDL_GL_CreateContext failed: ", fromStringz(SDL_GetError())); 232 else { 233 Log.i("Created successfully"); 234 _platform.GLVersionMajor = versionMajor; 235 _platform.GLVersionMinor = versionMinor; 236 } 237 return _context !is null; 238 } 239 } 240 241 bool create(uint flags) { 242 if (!_dx) 243 _dx = 600; 244 if (!_dy) 245 _dy = 400; 246 _flags = flags; 247 uint windowFlags = SDL_WINDOW_HIDDEN; 248 if (flags & WindowFlag.Resizable) 249 windowFlags |= SDL_WINDOW_RESIZABLE; 250 if (flags & WindowFlag.Fullscreen) 251 windowFlags |= SDL_WINDOW_FULLSCREEN; 252 windowFlags |= SDL_WINDOW_ALLOW_HIGHDPI; 253 static if (ENABLE_OPENGL) { 254 if (_enableOpengl) 255 windowFlags |= SDL_WINDOW_OPENGL; 256 } 257 _win = SDL_CreateWindow(toUTF8(_caption).toStringz, SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED, 258 _dx, _dy, 259 windowFlags); 260 static if (ENABLE_OPENGL) { 261 if (!_win) { 262 if (_enableOpengl) { 263 Log.e("SDL_CreateWindow failed - cannot create OpenGL window: ", fromStringz(SDL_GetError())); 264 _enableOpengl = false; 265 // recreate w/o OpenGL 266 windowFlags &= ~SDL_WINDOW_OPENGL; 267 _win = SDL_CreateWindow(toUTF8(_caption).toStringz, SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED, 268 _dx, _dy, 269 windowFlags); 270 } 271 } 272 } 273 if (!_win) { 274 Log.e("SDL2: Failed to create window"); 275 return false; 276 } 277 278 static if (ENABLE_OPENGL) { 279 if (_enableOpengl) { 280 bool success = createContext(_platform.GLVersionMajor, _platform.GLVersionMinor); 281 if (!success) { 282 Log.w("trying other versions of OpenGL"); 283 // Lazy conditions. 284 if(_platform.GLVersionMajor >= 4) 285 success = success || createContext(4, 0); 286 success = success || createContext(3, 3); 287 success = success || createContext(3, 2); 288 success = success || createContext(3, 1); 289 success = success || createContext(2, 1); 290 if (!success) { 291 _enableOpengl = false; 292 _platform.GLVersionMajor = 0; 293 _platform.GLVersionMinor = 0; 294 Log.w("OpenGL support is disabled"); 295 } 296 } 297 if (success) { 298 _enableOpengl = initGLSupport(_platform.GLVersionMajor < 3); 299 fixSize(); 300 } 301 } 302 } 303 if (!_enableOpengl) { 304 _renderer = SDL_CreateRenderer(_win, -1, SDL_RENDERER_ACCELERATED | SDL_RENDERER_PRESENTVSYNC); 305 if (!_renderer) { 306 _renderer = SDL_CreateRenderer(_win, -1, SDL_RENDERER_SOFTWARE); 307 if (!_renderer) { 308 Log.e("SDL2: Failed to create renderer"); 309 return false; 310 } 311 } 312 fixSize(); 313 } 314 setOpenglEnabled(_enableOpengl); 315 windowCaption = _caption; 316 int x = 0; 317 int y = 0; 318 SDL_GetWindowPosition(_win, &x, &y); 319 handleWindowStateChange(WindowState.unspecified, Rect(x, y, _dx, _dy)); 320 return true; 321 } 322 323 void fixSize() { 324 int w = 0; 325 int h = 0; 326 SDL_GetWindowSize(_win, &w, &h); 327 doResize(w, h); 328 } 329 330 void doResize(int width, int height) { 331 int w = 0; 332 int h = 0; 333 SDL_GL_GetDrawableSize(_win, &w, &h); 334 version (Windows) { 335 // DPI already calculated 336 } else { 337 // scale DPI 338 if (w > width && h > height && width > 0 && height > 0) 339 SCREEN_DPI = 96 * w / width; 340 } 341 onResize(std.algorithm.max(width, w), std.algorithm.max(height, h)); 342 } 343 344 @property uint windowId() { 345 if (_win) 346 return SDL_GetWindowID(_win); 347 return 0; 348 } 349 350 override void show() { 351 Log.d("SDLWindow.show() - ", windowCaption); 352 if (!_mainWidget) { 353 Log.e("Window is shown without main widget"); 354 _mainWidget = new Widget(); 355 } 356 if (_mainWidget) { 357 _mainWidget.measure(SIZE_UNSPECIFIED, SIZE_UNSPECIFIED); 358 if (flags & WindowFlag.MeasureSize) 359 resizeWindow(Point(_mainWidget.measuredWidth, _mainWidget.measuredHeight)); 360 else 361 adjustWindowOrContentSize(_mainWidget.measuredWidth, _mainWidget.measuredHeight); 362 } 363 364 adjustPositionDuringShow(); 365 366 SDL_ShowWindow(_win); 367 if (_mainWidget) 368 _mainWidget.setFocus(); 369 fixSize(); 370 SDL_RaiseWindow(_win); 371 invalidate(); 372 } 373 374 /// close window 375 override void close() { 376 Log.d("SDLWindow.close()"); 377 _platform.closeWindow(this); 378 } 379 380 override protected void handleWindowStateChange(WindowState newState, Rect newWindowRect = RECT_VALUE_IS_NOT_SET) { 381 super.handleWindowStateChange(newState, newWindowRect); 382 } 383 384 override bool setWindowState(WindowState newState, bool activate = false, Rect newWindowRect = RECT_VALUE_IS_NOT_SET) { 385 // override for particular platforms 386 387 if (_win is null) 388 return false; 389 390 bool res = false; 391 392 // change state 393 switch(newState) { 394 case WindowState.maximized: 395 if (_windowState != WindowState.maximized) 396 SDL_MaximizeWindow(_win); 397 res = true; 398 break; 399 case WindowState.minimized: 400 if (_windowState != WindowState.minimized) 401 SDL_MinimizeWindow(_win); 402 res = true; 403 break; 404 case WindowState.hidden: 405 if (_windowState != WindowState.hidden) 406 SDL_HideWindow(_win); 407 res = true; 408 break; 409 case WindowState.normal: 410 if (_windowState != WindowState.normal) { 411 SDL_RestoreWindow(_win); 412 version(linux) { 413 // some SDL versions, reset windows size and position to values from create window (SDL 2.0.4) on linux (don't know how it works on macOS) 414 if (newWindowRect.bottom == int.min && newWindowRect.right == int.min) 415 SDL_SetWindowSize(_win, _windowRect.right, _windowRect.bottom); 416 417 if (newWindowRect.top == int.min && newWindowRect.left == int.min) 418 SDL_SetWindowPosition(_win, _windowRect.left, _windowRect.top); 419 } 420 } 421 res = true; 422 break; 423 default: 424 break; 425 } 426 427 // change size and/or position 428 bool rectChanged = false; 429 if (newWindowRect != RECT_VALUE_IS_NOT_SET && (newState == WindowState.normal || newState == WindowState.unspecified)) { 430 // change position 431 if (newWindowRect.top != int.min && newWindowRect.left != int.min) { 432 SDL_SetWindowPosition(_win, newWindowRect.left, newWindowRect.top); 433 rectChanged = true; 434 res = true; 435 } 436 437 // change size 438 if (newWindowRect.bottom != int.min && newWindowRect.right != int.min) { 439 SDL_SetWindowSize(_win, newWindowRect.right, newWindowRect.bottom); 440 rectChanged = true; 441 res = true; 442 } 443 444 } 445 446 if (activate) { 447 SDL_RaiseWindow(_win); 448 res = true; 449 } 450 451 //needed here to make _windowRect and _windowState valid before SDL_WINDOWEVENT_RESIZED/SDL_WINDOWEVENT_MOVED/SDL_WINDOWEVENT_MINIMIZED/SDL_WINDOWEVENT_MAXIMIZED etc handled 452 //example: change size by resizeWindow() and make some calculations using windowRect 453 if (rectChanged) { 454 handleWindowStateChange(newState, Rect(newWindowRect.left == int.min ? _windowRect.left : newWindowRect.left, 455 newWindowRect.top == int.min ? _windowRect.top : newWindowRect.top, newWindowRect.right == int.min ? _windowRect.right : newWindowRect.right, 456 newWindowRect.bottom == int.min ? _windowRect.bottom : newWindowRect.bottom)); 457 } 458 else 459 handleWindowStateChange(newState, RECT_VALUE_IS_NOT_SET); 460 461 return res; 462 } 463 464 override @property Window parentWindow() { 465 return _parent; 466 } 467 468 override protected void handleWindowActivityChange(bool isWindowActive) { 469 super.handleWindowActivityChange(isWindowActive); 470 } 471 472 override @property bool isActive() { 473 uint flags = SDL_GetWindowFlags(_win); 474 return (flags & SDL_WINDOW_INPUT_FOCUS) == SDL_WINDOW_INPUT_FOCUS; 475 } 476 477 protected dstring _caption; 478 479 override @property dstring windowCaption() { 480 return _caption; 481 } 482 483 override @property void windowCaption(dstring caption) { 484 _caption = caption; 485 if (_win) 486 SDL_SetWindowTitle(_win, toUTF8(_caption).toStringz); 487 } 488 489 /// sets window icon 490 @property override void windowIcon(DrawBufRef buf) { 491 ColorDrawBuf icon = cast(ColorDrawBuf)buf.get; 492 if (!icon) { 493 Log.e("Trying to set null icon for window"); 494 return; 495 } 496 int iconw = 32; 497 int iconh = 32; 498 ColorDrawBuf iconDraw = new ColorDrawBuf(iconw, iconh); 499 iconDraw.fill(0xFF000000); 500 iconDraw.drawRescaled(Rect(0, 0, iconw, iconh), icon, Rect(0, 0, icon.width, icon.height)); 501 iconDraw.invertAndPreMultiplyAlpha(); 502 SDL_Surface *surface = SDL_CreateRGBSurfaceFrom(iconDraw.scanLine(0), iconDraw.width, iconDraw.height, 32, iconDraw.width * 4, 0x00ff0000,0x0000ff00,0x000000ff,0xff000000); 503 if (surface) { 504 // The icon is attached to the window pointer 505 SDL_SetWindowIcon(_win, surface); 506 // ...and the surface containing the icon pixel data is no longer required. 507 SDL_FreeSurface(surface); 508 } else { 509 Log.e("failed to set window icon"); 510 } 511 destroy(iconDraw); 512 } 513 514 /// after drawing, call to schedule redraw if animation is active 515 override void scheduleAnimation() { 516 invalidate(); 517 } 518 519 protected uint _lastCursorType = CursorType.None; 520 protected SDL_Cursor * [uint] _cursorMap; 521 /// sets cursor type for window 522 override protected void setCursorType(uint cursorType) { 523 // override to support different mouse cursors 524 if (_lastCursorType != cursorType) { 525 if (cursorType == CursorType.None) { 526 SDL_ShowCursor(SDL_DISABLE); 527 return; 528 } 529 if (_lastCursorType == CursorType.None) 530 SDL_ShowCursor(SDL_ENABLE); 531 _lastCursorType = cursorType; 532 SDL_Cursor * cursor; 533 // check for existing cursor in map 534 if (cursorType in _cursorMap) { 535 //Log.d("changing cursor to ", cursorType); 536 cursor = _cursorMap[cursorType]; 537 if (cursor) 538 SDL_SetCursor(cursor); 539 return; 540 } 541 // create new cursor 542 switch (cursorType) with(CursorType) 543 { 544 case Arrow: 545 cursor = SDL_CreateSystemCursor(SDL_SYSTEM_CURSOR_ARROW); 546 break; 547 case IBeam: 548 cursor = SDL_CreateSystemCursor(SDL_SYSTEM_CURSOR_IBEAM); 549 break; 550 case Wait: 551 cursor = SDL_CreateSystemCursor(SDL_SYSTEM_CURSOR_WAIT); 552 break; 553 case WaitArrow: 554 cursor = SDL_CreateSystemCursor(SDL_SYSTEM_CURSOR_WAITARROW); 555 break; 556 case Crosshair: 557 cursor = SDL_CreateSystemCursor(SDL_SYSTEM_CURSOR_CROSSHAIR); 558 break; 559 case No: 560 cursor = SDL_CreateSystemCursor(SDL_SYSTEM_CURSOR_NO); 561 break; 562 case Hand: 563 cursor = SDL_CreateSystemCursor(SDL_SYSTEM_CURSOR_HAND); 564 break; 565 case SizeNWSE: 566 cursor = SDL_CreateSystemCursor(SDL_SYSTEM_CURSOR_SIZENWSE); 567 break; 568 case SizeNESW: 569 cursor = SDL_CreateSystemCursor(SDL_SYSTEM_CURSOR_SIZENESW); 570 break; 571 case SizeWE: 572 cursor = SDL_CreateSystemCursor(SDL_SYSTEM_CURSOR_SIZEWE); 573 break; 574 case SizeNS: 575 cursor = SDL_CreateSystemCursor(SDL_SYSTEM_CURSOR_SIZENS); 576 break; 577 case SizeAll: 578 cursor = SDL_CreateSystemCursor(SDL_SYSTEM_CURSOR_SIZEALL); 579 break; 580 default: 581 // TODO: support custom cursors 582 cursor = SDL_CreateSystemCursor(SDL_SYSTEM_CURSOR_ARROW); 583 break; 584 } 585 _cursorMap[cursorType] = cursor; 586 if (cursor) { 587 debug(DebugSDL) Log.d("changing cursor to ", cursorType); 588 SDL_SetCursor(cursor); 589 } 590 } 591 } 592 593 SDL_Texture * _texture; 594 int _txw; 595 int _txh; 596 private void updateBufferSize() { 597 if (_texture && (_txw != _dx || _txh != _dy)) { 598 SDL_DestroyTexture(_texture); 599 _texture = null; 600 } 601 if (!_texture) { 602 _texture = SDL_CreateTexture(_renderer, 603 SDL_PIXELFORMAT_ARGB8888, 604 SDL_TEXTUREACCESS_STATIC, //SDL_TEXTUREACCESS_STREAMING, 605 _dx, 606 _dy); 607 _txw = _dx; 608 _txh = _dy; 609 } 610 } 611 612 private void draw(ColorDrawBuf buf) { 613 updateBufferSize(); 614 SDL_Rect rect; 615 rect.w = buf.width; 616 rect.h = buf.height; 617 SDL_UpdateTexture(_texture, 618 &rect, 619 cast(const void*)buf.scanLine(0), 620 buf.width * cast(int)uint.sizeof); 621 SDL_RenderCopy(_renderer, _texture, &rect, &rect); 622 } 623 624 void redraw() { 625 //Log.e("Widget instance count in SDLWindow.redraw: ", Widget.instanceCount()); 626 // check if size has been changed 627 fixSize(); 628 629 if (_enableOpengl) { 630 static if (ENABLE_OPENGL) { 631 SDL_GL_MakeCurrent(_win, _context); 632 glDisable(GL_DEPTH_TEST); 633 glViewport(0, 0, _dx, _dy); 634 float a = 1.0f; 635 float r = ((_backgroundColor >> 16) & 255) / 255.0f; 636 float g = ((_backgroundColor >> 8) & 255) / 255.0f; 637 float b = ((_backgroundColor >> 0) & 255) / 255.0f; 638 glClearColor(r, g, b, a); 639 glClear(GL_COLOR_BUFFER_BIT); 640 if (!_drawbuf) 641 _drawbuf = new GLDrawBuf(_dx, _dy); 642 _drawbuf.resize(_dx, _dy); 643 _drawbuf.beforeDrawing(); 644 onDraw(_drawbuf); 645 _drawbuf.afterDrawing(); 646 SDL_GL_SwapWindow(_win); 647 } 648 } else { 649 // Select the color for drawing. 650 ubyte r = cast(ubyte)((_backgroundColor >> 16) & 255); 651 ubyte g = cast(ubyte)((_backgroundColor >> 8) & 255); 652 ubyte b = cast(ubyte)((_backgroundColor >> 0) & 255); 653 SDL_SetRenderDrawColor(_renderer, r, g, b, 255); 654 // Clear the entire screen to our selected color. 655 SDL_RenderClear(_renderer); 656 657 if (!_drawbuf) 658 _drawbuf = new ColorDrawBuf(_dx, _dy); 659 _drawbuf.resize(_dx, _dy); 660 _drawbuf.fill(_backgroundColor); 661 onDraw(_drawbuf); 662 draw(cast(ColorDrawBuf)_drawbuf); 663 664 // Up until now everything was drawn behind the scenes. 665 // This will show the new, red contents of the window. 666 SDL_RenderPresent(_renderer); 667 } 668 } 669 670 DrawBuf _drawbuf; 671 672 //bool _exposeSent; 673 void processExpose() { 674 redraw(); 675 //_exposeSent = false; 676 } 677 678 protected ButtonDetails _lbutton; 679 protected ButtonDetails _mbutton; 680 protected ButtonDetails _rbutton; 681 ushort convertMouseFlags(uint flags) { 682 ushort res = 0; 683 if (flags & SDL_BUTTON_LMASK) 684 res |= MouseFlag.LButton; 685 if (flags & SDL_BUTTON_RMASK) 686 res |= MouseFlag.RButton; 687 if (flags & SDL_BUTTON_MMASK) 688 res |= MouseFlag.MButton; 689 return res; 690 } 691 692 MouseButton convertMouseButton(uint button) { 693 if (button == SDL_BUTTON_LEFT) 694 return MouseButton.Left; 695 if (button == SDL_BUTTON_RIGHT) 696 return MouseButton.Right; 697 if (button == SDL_BUTTON_MIDDLE) 698 return MouseButton.Middle; 699 return MouseButton.None; 700 } 701 702 ushort lastFlags; 703 short lastx; 704 short lasty; 705 void processMouseEvent(MouseAction action, uint button, uint state, int x, int y) { 706 707 // correct mouse coordinates for HIGHDPI on mac 708 int drawableW = 0; 709 int drawableH = 0; 710 int winW = 0; 711 int winH = 0; 712 SDL_GL_GetDrawableSize(_win, &drawableW, &drawableH); 713 SDL_GetWindowSize(_win, &winW, &winH); 714 if (drawableW != winW || drawableH != winH) { 715 if (drawableW > 0 && winW > 0 && drawableH > 0 && drawableW > 0) { 716 x = x * drawableW / winW; 717 y = y * drawableH / winH; 718 } 719 } 720 721 722 MouseEvent event = null; 723 if (action == MouseAction.Wheel) { 724 // handle wheel 725 short wheelDelta = cast(short)y; 726 if (_keyFlags & KeyFlag.Shift) 727 lastFlags |= MouseFlag.Shift; 728 else 729 lastFlags &= ~MouseFlag.Shift; 730 if (_keyFlags & KeyFlag.Control) 731 lastFlags |= MouseFlag.Control; 732 else 733 lastFlags &= ~MouseFlag.Control; 734 if (_keyFlags & KeyFlag.Alt) 735 lastFlags |= MouseFlag.Alt; 736 else 737 lastFlags &= ~MouseFlag.Alt; 738 if (wheelDelta) 739 event = new MouseEvent(action, MouseButton.None, lastFlags, lastx, lasty, wheelDelta); 740 } else { 741 lastFlags = convertMouseFlags(state); 742 if (_keyFlags & KeyFlag.Shift) 743 lastFlags |= MouseFlag.Shift; 744 if (_keyFlags & KeyFlag.Control) 745 lastFlags |= MouseFlag.Control; 746 if (_keyFlags & KeyFlag.Alt) 747 lastFlags |= MouseFlag.Alt; 748 lastx = cast(short)x; 749 lasty = cast(short)y; 750 MouseButton btn = convertMouseButton(button); 751 event = new MouseEvent(action, btn, lastFlags, lastx, lasty); 752 } 753 if (event) { 754 ButtonDetails * pbuttonDetails = null; 755 if (button == MouseButton.Left) 756 pbuttonDetails = &_lbutton; 757 else if (button == MouseButton.Right) 758 pbuttonDetails = &_rbutton; 759 else if (button == MouseButton.Middle) 760 pbuttonDetails = &_mbutton; 761 if (pbuttonDetails) { 762 if (action == MouseAction.ButtonDown) { 763 pbuttonDetails.down(cast(short)x, cast(short)y, lastFlags); 764 } else if (action == MouseAction.ButtonUp) { 765 pbuttonDetails.up(cast(short)x, cast(short)y, lastFlags); 766 } 767 } 768 event.lbutton = _lbutton; 769 event.rbutton = _rbutton; 770 event.mbutton = _mbutton; 771 bool res = dispatchMouseEvent(event); 772 if (res) { 773 debug(mouse) Log.d("Calling update() after mouse event"); 774 update(); 775 } 776 } 777 } 778 779 uint convertKeyCode(uint keyCode) { 780 switch(keyCode) { 781 case SDLK_0: 782 return KeyCode.KEY_0; 783 case SDLK_1: 784 return KeyCode.KEY_1; 785 case SDLK_2: 786 return KeyCode.KEY_2; 787 case SDLK_3: 788 return KeyCode.KEY_3; 789 case SDLK_4: 790 return KeyCode.KEY_4; 791 case SDLK_5: 792 return KeyCode.KEY_5; 793 case SDLK_6: 794 return KeyCode.KEY_6; 795 case SDLK_7: 796 return KeyCode.KEY_7; 797 case SDLK_8: 798 return KeyCode.KEY_8; 799 case SDLK_9: 800 return KeyCode.KEY_9; 801 case SDLK_a: 802 return KeyCode.KEY_A; 803 case SDLK_b: 804 return KeyCode.KEY_B; 805 case SDLK_c: 806 return KeyCode.KEY_C; 807 case SDLK_d: 808 return KeyCode.KEY_D; 809 case SDLK_e: 810 return KeyCode.KEY_E; 811 case SDLK_f: 812 return KeyCode.KEY_F; 813 case SDLK_g: 814 return KeyCode.KEY_G; 815 case SDLK_h: 816 return KeyCode.KEY_H; 817 case SDLK_i: 818 return KeyCode.KEY_I; 819 case SDLK_j: 820 return KeyCode.KEY_J; 821 case SDLK_k: 822 return KeyCode.KEY_K; 823 case SDLK_l: 824 return KeyCode.KEY_L; 825 case SDLK_m: 826 return KeyCode.KEY_M; 827 case SDLK_n: 828 return KeyCode.KEY_N; 829 case SDLK_o: 830 return KeyCode.KEY_O; 831 case SDLK_p: 832 return KeyCode.KEY_P; 833 case SDLK_q: 834 return KeyCode.KEY_Q; 835 case SDLK_r: 836 return KeyCode.KEY_R; 837 case SDLK_s: 838 return KeyCode.KEY_S; 839 case SDLK_t: 840 return KeyCode.KEY_T; 841 case SDLK_u: 842 return KeyCode.KEY_U; 843 case SDLK_v: 844 return KeyCode.KEY_V; 845 case SDLK_w: 846 return KeyCode.KEY_W; 847 case SDLK_x: 848 return KeyCode.KEY_X; 849 case SDLK_y: 850 return KeyCode.KEY_Y; 851 case SDLK_z: 852 return KeyCode.KEY_Z; 853 case SDLK_F1: 854 return KeyCode.F1; 855 case SDLK_F2: 856 return KeyCode.F2; 857 case SDLK_F3: 858 return KeyCode.F3; 859 case SDLK_F4: 860 return KeyCode.F4; 861 case SDLK_F5: 862 return KeyCode.F5; 863 case SDLK_F6: 864 return KeyCode.F6; 865 case SDLK_F7: 866 return KeyCode.F7; 867 case SDLK_F8: 868 return KeyCode.F8; 869 case SDLK_F9: 870 return KeyCode.F9; 871 case SDLK_F10: 872 return KeyCode.F10; 873 case SDLK_F11: 874 return KeyCode.F11; 875 case SDLK_F12: 876 return KeyCode.F12; 877 case SDLK_F13: 878 return KeyCode.F13; 879 case SDLK_F14: 880 return KeyCode.F14; 881 case SDLK_F15: 882 return KeyCode.F15; 883 case SDLK_F16: 884 return KeyCode.F16; 885 case SDLK_F17: 886 return KeyCode.F17; 887 case SDLK_F18: 888 return KeyCode.F18; 889 case SDLK_F19: 890 return KeyCode.F19; 891 case SDLK_F20: 892 return KeyCode.F20; 893 case SDLK_F21: 894 return KeyCode.F21; 895 case SDLK_F22: 896 return KeyCode.F22; 897 case SDLK_F23: 898 return KeyCode.F23; 899 case SDLK_F24: 900 return KeyCode.F24; 901 case SDLK_BACKSPACE: 902 return KeyCode.BACK; 903 case SDLK_SPACE: 904 return KeyCode.SPACE; 905 case SDLK_TAB: 906 return KeyCode.TAB; 907 case SDLK_RETURN: 908 return KeyCode.RETURN; 909 case SDLK_ESCAPE: 910 return KeyCode.ESCAPE; 911 case SDLK_DELETE: 912 case 0x40000063: // dirty hack for Linux - key on keypad 913 return KeyCode.DEL; 914 case SDLK_INSERT: 915 case 0x40000062: // dirty hack for Linux - key on keypad 916 return KeyCode.INS; 917 case SDLK_HOME: 918 case 0x4000005f: // dirty hack for Linux - key on keypad 919 return KeyCode.HOME; 920 case SDLK_PAGEUP: 921 case 0x40000061: // dirty hack for Linux - key on keypad 922 return KeyCode.PAGEUP; 923 case SDLK_END: 924 case 0x40000059: // dirty hack for Linux - key on keypad 925 return KeyCode.END; 926 case SDLK_PAGEDOWN: 927 case 0x4000005b: // dirty hack for Linux - key on keypad 928 return KeyCode.PAGEDOWN; 929 case SDLK_LEFT: 930 case 0x4000005c: // dirty hack for Linux - key on keypad 931 return KeyCode.LEFT; 932 case SDLK_RIGHT: 933 case 0x4000005e: // dirty hack for Linux - key on keypad 934 return KeyCode.RIGHT; 935 case SDLK_UP: 936 case 0x40000060: // dirty hack for Linux - key on keypad 937 return KeyCode.UP; 938 case SDLK_DOWN: 939 case 0x4000005a: // dirty hack for Linux - key on keypad 940 return KeyCode.DOWN; 941 case SDLK_KP_ENTER: 942 return KeyCode.RETURN; 943 case SDLK_LCTRL: 944 return KeyCode.LCONTROL; 945 case SDLK_LSHIFT: 946 return KeyCode.LSHIFT; 947 case SDLK_LALT: 948 return KeyCode.LALT; 949 case SDLK_RCTRL: 950 return KeyCode.RCONTROL; 951 case SDLK_RSHIFT: 952 return KeyCode.RSHIFT; 953 case SDLK_RALT: 954 return KeyCode.RALT; 955 case SDLK_LGUI: 956 return KeyCode.LWIN; 957 case SDLK_RGUI: 958 return KeyCode.RWIN; 959 case '/': 960 return KeyCode.KEY_DIVIDE; 961 default: 962 return 0x10000 | keyCode; 963 } 964 } 965 966 uint convertKeyFlags(uint flags) { 967 uint res; 968 if (flags & KMOD_CTRL) 969 res |= KeyFlag.Control; 970 if (flags & KMOD_SHIFT) 971 res |= KeyFlag.Shift; 972 if (flags & KMOD_ALT) 973 res |= KeyFlag.Alt; 974 if (flags & KMOD_GUI) 975 res |= KeyFlag.Menu; 976 if (flags & KMOD_RCTRL) 977 res |= KeyFlag.RControl | KeyFlag.Control; 978 if (flags & KMOD_RSHIFT) 979 res |= KeyFlag.RShift | KeyFlag.Shift; 980 if (flags & KMOD_RALT) 981 res |= KeyFlag.RAlt | KeyFlag.Alt; 982 if (flags & KMOD_LCTRL) 983 res |= KeyFlag.LControl | KeyFlag.Control; 984 if (flags & KMOD_LSHIFT) 985 res |= KeyFlag.LShift | KeyFlag.Shift; 986 if (flags & KMOD_LALT) 987 res |= KeyFlag.LAlt | KeyFlag.Alt; 988 return res; 989 } 990 991 bool processTextInput(const char * s) { 992 string str = fromStringz(s).dup; 993 dstring ds = toUTF32(str); 994 uint flags = convertKeyFlags(SDL_GetModState()); 995 //do not handle Ctrl+Space as text https://github.com/buggins/dlangui/issues/160 996 //but do hanlde RAlt https://github.com/buggins/dlangide/issues/129 997 debug(KeyInput) Log.d(" processTextInput char=", ds, "(", cast(int)ds[0], ") flags=", "%04x"d.format(flags)); 998 if ((flags & KeyFlag.Alt) && (flags & KeyFlag.Control)) { 999 flags &= (~(KeyFlag.LRAlt)) & (~(KeyFlag.LRControl)); 1000 debug(KeyInput) Log.d(" processTextInput removed Ctrl+Alt flags char=", ds, "(", cast(int)ds[0], ") flags=", "%04x"d.format(flags)); 1001 } 1002 1003 if (flags & KeyFlag.Control || (flags & KeyFlag.LAlt) == KeyFlag.LAlt || flags & KeyFlag.Menu) 1004 return true; 1005 1006 bool res = dispatchKeyEvent(new KeyEvent(KeyAction.Text, 0, flags, ds)); 1007 if (res) { 1008 debug(DebugSDL) Log.d("Calling update() after text event"); 1009 update(); 1010 } 1011 return res; 1012 } 1013 1014 static bool isNumLockEnabled() 1015 { 1016 version(Windows) { 1017 return !!(GetKeyState( VK_NUMLOCK ) & 1); 1018 } else { 1019 return !!(SDL_GetModState() & KMOD_NUM); 1020 } 1021 } 1022 1023 uint _keyFlags; 1024 bool processKeyEvent(KeyAction action, uint keyCodeIn, uint flags) { 1025 debug(DebugSDL) Log.d("processKeyEvent ", action, " SDL key=0x", format("%08x", keyCodeIn), " SDL flags=0x", format("%08x", flags)); 1026 uint keyCode = convertKeyCode(keyCodeIn); 1027 flags = convertKeyFlags(flags); 1028 if (action == KeyAction.KeyDown) { 1029 switch(keyCode) { 1030 case KeyCode.ALT: 1031 flags |= KeyFlag.Alt; 1032 break; 1033 case KeyCode.RALT: 1034 flags |= KeyFlag.Alt | KeyFlag.RAlt; 1035 break; 1036 case KeyCode.LALT: 1037 flags |= KeyFlag.Alt | KeyFlag.LAlt; 1038 break; 1039 case KeyCode.CONTROL: 1040 flags |= KeyFlag.Control; 1041 break; 1042 case KeyCode.LWIN: 1043 case KeyCode.RWIN: 1044 flags |= KeyFlag.Menu; 1045 break; 1046 case KeyCode.RCONTROL: 1047 flags |= KeyFlag.Control | KeyFlag.RControl; 1048 break; 1049 case KeyCode.LCONTROL: 1050 flags |= KeyFlag.Control | KeyFlag.LControl; 1051 break; 1052 case KeyCode.SHIFT: 1053 flags |= KeyFlag.Shift; 1054 break; 1055 case KeyCode.RSHIFT: 1056 flags |= KeyFlag.Shift | KeyFlag.RShift; 1057 break; 1058 case KeyCode.LSHIFT: 1059 flags |= KeyFlag.Shift | KeyFlag.LShift; 1060 break; 1061 1062 default: 1063 break; 1064 } 1065 } 1066 _keyFlags = flags; 1067 1068 debug(DebugSDL) Log.d("processKeyEvent ", action, " converted key=0x", format("%08x", keyCode), " converted flags=0x", format("%08x", flags)); 1069 if (action == KeyAction.KeyDown || action == KeyAction.KeyUp) { 1070 if ((keyCodeIn >= SDLK_KP_1 && keyCodeIn <= SDLK_KP_0 1071 || keyCodeIn == SDLK_KP_PERIOD 1072 //|| keyCodeIn >= 0x40000059 && keyCodeIn 1073 ) && isNumLockEnabled) 1074 return false; 1075 } 1076 bool res = dispatchKeyEvent(new KeyEvent(action, keyCode, flags)); 1077 // if ((keyCode & 0x10000) && (keyCode & 0xF000) != 0xF000) { 1078 // dchar[1] text; 1079 // text[0] = keyCode & 0xFFFF; 1080 // res = dispatchKeyEvent(new KeyEvent(KeyAction.Text, keyCode, flags, cast(dstring)text)) || res; 1081 // } 1082 if (res) { 1083 debug(DebugSDL) Log.d("Calling update() after key event"); 1084 update(); 1085 } 1086 return res; 1087 } 1088 1089 uint _lastRedrawEventCode; 1090 /// request window redraw 1091 override void invalidate() { 1092 _platform.sendRedrawEvent(windowId, ++_lastRedrawEventCode); 1093 } 1094 1095 void processRedrawEvent(uint code) { 1096 if (code == _lastRedrawEventCode) 1097 redraw(); 1098 } 1099 1100 1101 private long _nextExpectedTimerTs; 1102 private SDL_TimerID _timerId = 0; 1103 1104 /// schedule timer for interval in milliseconds - call window.onTimer when finished 1105 override protected void scheduleSystemTimer(long intervalMillis) { 1106 if (intervalMillis < 10) 1107 intervalMillis = 10; 1108 long nextts = currentTimeMillis + intervalMillis; 1109 if (_timerId && _nextExpectedTimerTs && _nextExpectedTimerTs < nextts + 10) 1110 return; // don't reschedule timer, timer event will be received soon 1111 if (_win) { 1112 if (_timerId) { 1113 SDL_RemoveTimer(_timerId); 1114 _timerId = 0; 1115 } 1116 _timerId = SDL_AddTimer(cast(uint)intervalMillis, &myTimerCallbackFunc, cast(void*)windowId); 1117 _nextExpectedTimerTs = nextts; 1118 } 1119 } 1120 1121 void handleTimer(SDL_TimerID timerId) { 1122 SDL_RemoveTimer(_timerId); 1123 _timerId = 0; 1124 _nextExpectedTimerTs = 0; 1125 onTimer(); 1126 } 1127 } 1128 1129 private extern(C) uint myTimerCallbackFunc(uint interval, void *param) nothrow { 1130 uint windowId = cast(uint)param; 1131 SDL_Event sdlevent; 1132 sdlevent.user.type = TIMER_EVENT_ID; 1133 sdlevent.user.code = 0; 1134 sdlevent.user.windowID = windowId; 1135 SDL_PushEvent(&sdlevent); 1136 return(interval); 1137 } 1138 1139 private __gshared bool _enableOpengl; 1140 1141 class SDLPlatform : Platform { 1142 this() { 1143 } 1144 1145 private SDLWindow[uint] _windowMap; 1146 1147 ~this() { 1148 foreach(ref SDLWindow wnd; _windowMap) { 1149 destroy(wnd); 1150 wnd = null; 1151 } 1152 destroy(_windowMap); 1153 } 1154 1155 SDLWindow getWindow(uint id) { 1156 if (id in _windowMap) 1157 return _windowMap[id]; 1158 return null; 1159 } 1160 1161 /// close window 1162 override void closeWindow(Window w) { 1163 SDLWindow window = cast(SDLWindow)w; 1164 SDL_Event sdlevent; 1165 sdlevent.user.type = WINDOW_CLOSE_EVENT_ID; 1166 sdlevent.user.code = 0; 1167 sdlevent.user.windowID = window.windowId; 1168 SDL_PushEvent(&sdlevent); 1169 } 1170 1171 /// calls request layout for all windows 1172 override void requestLayout() { 1173 foreach(w; _windowMap) { 1174 w.requestLayout(); 1175 w.invalidate(); 1176 } 1177 } 1178 1179 /// handle theme change: e.g. reload some themed resources 1180 override void onThemeChanged() { 1181 if (currentTheme) 1182 currentTheme.onThemeChanged(); 1183 foreach(w; _windowMap) 1184 w.dispatchThemeChanged(); 1185 } 1186 1187 private uint _redrawEventId; 1188 1189 void sendRedrawEvent(uint windowId, uint code) { 1190 if (!_redrawEventId) 1191 _redrawEventId = SDL_RegisterEvents(1); 1192 SDL_Event event; 1193 event.type = _redrawEventId; 1194 event.user.windowID = windowId; 1195 event.user.code = code; 1196 SDL_PushEvent(&event); 1197 } 1198 1199 override Window createWindow(dstring windowCaption, Window parent, uint flags = WindowFlag.Resizable, uint width = 0, uint height = 0) { 1200 setDefaultLanguageAndThemeIfNecessary(); 1201 int oldDPI = SCREEN_DPI; 1202 int newwidth = width; 1203 int newheight = height; 1204 version(Windows) { 1205 newwidth = pointsToPixels(width); 1206 newheight = pointsToPixels(height); 1207 } 1208 SDLWindow res = new SDLWindow(this, windowCaption, parent, flags, newwidth, newheight); 1209 _windowMap[res.windowId] = res; 1210 if (sdlUpdateScreenDpi() || oldDPI != SCREEN_DPI) { 1211 version(Windows) { 1212 newwidth = pointsToPixels(width); 1213 newheight = pointsToPixels(height); 1214 if (newwidth != width || newheight != height) 1215 SDL_SetWindowSize(res._win, newwidth, newheight); 1216 } 1217 onThemeChanged(); 1218 } 1219 return res; 1220 } 1221 1222 override bool hasModalWindowsAbove(Window w) { 1223 SDLWindow sdlWin = cast (SDLWindow) w; 1224 if (sdlWin) { 1225 return sdlWin.hasVisibleModalChild(); 1226 } 1227 return false; 1228 } 1229 1230 1231 //void redrawWindows() { 1232 // foreach(w; _windowMap) 1233 // w.redraw(); 1234 //} 1235 1236 private bool _windowsMinimized = false; 1237 1238 override int enterMessageLoop() { 1239 Log.i("entering message loop"); 1240 SDL_Event event; 1241 bool quit = false; 1242 bool skipNextQuit = false; 1243 while(!quit) { 1244 //redrawWindows(); 1245 if (SDL_WaitEvent(&event)) { 1246 1247 //Log.d("Event.type = ", event.type); 1248 1249 if (event.type == SDL_QUIT) { 1250 if (!skipNextQuit) { 1251 Log.i("event.type == SDL_QUIT"); 1252 quit = true; 1253 break; 1254 } 1255 skipNextQuit = false; 1256 } 1257 if (_redrawEventId && event.type == _redrawEventId) { 1258 // user defined redraw event 1259 uint windowID = event.user.windowID; 1260 SDLWindow w = getWindow(windowID); 1261 if (w) { 1262 w.processRedrawEvent(event.user.code); 1263 } 1264 continue; 1265 } 1266 switch (event.type) { 1267 case SDL_WINDOWEVENT: 1268 { 1269 // WINDOW EVENTS 1270 uint windowID = event.window.windowID; 1271 SDLWindow w = getWindow(windowID); 1272 if (!w) { 1273 Log.w("SDL_WINDOWEVENT ", event.window.event, " received with unknown id ", windowID); 1274 break; 1275 } 1276 // found window 1277 switch (event.window.event) { 1278 case SDL_WINDOWEVENT_RESIZED: 1279 debug(DebugSDL) Log.d("SDL_WINDOWEVENT_RESIZED win=", event.window.windowID, " pos=", event.window.data1, 1280 ",", event.window.data2); 1281 //redraw not needed here: SDL_WINDOWEVENT_RESIZED is following SDL_WINDOWEVENT_SIZE_CHANGED if the size was changed by an external event (window manager, user) 1282 break; 1283 case SDL_WINDOWEVENT_SIZE_CHANGED: 1284 debug(DebugSDL) Log.d("SDL_WINDOWEVENT_SIZE_CHANGED win=", event.window.windowID, " pos=", event.window.data1, 1285 ",", event.window.data2); 1286 w.handleWindowStateChange(WindowState.unspecified, Rect(w.windowRect().left, w.windowRect().top, event.window.data1, event.window.data2)); 1287 w.redraw(); 1288 break; 1289 case SDL_WINDOWEVENT_CLOSE: 1290 if (!w.hasVisibleModalChild()) { 1291 if (w.handleCanClose()) { 1292 debug(DebugSDL) Log.d("SDL_WINDOWEVENT_CLOSE win=", event.window.windowID); 1293 _windowMap.remove(windowID); 1294 destroy(w); 1295 } else { 1296 skipNextQuit = true; 1297 } 1298 } 1299 break; 1300 case SDL_WINDOWEVENT_SHOWN: 1301 debug(DebugSDL) Log.d("SDL_WINDOWEVENT_SHOWN - ", w.windowCaption); 1302 if (w.windowState()!=WindowState.normal) 1303 w.handleWindowStateChange(WindowState.normal); 1304 if (!_windowsMinimized && w.hasVisibleModalChild()) 1305 w.restoreModalChilds(); 1306 break; 1307 case SDL_WINDOWEVENT_HIDDEN: 1308 debug(DebugSDL) Log.d("SDL_WINDOWEVENT_HIDDEN - ", w.windowCaption); 1309 if (w.windowState()!=WindowState.hidden) 1310 w.handleWindowStateChange(WindowState.hidden); 1311 break; 1312 case SDL_WINDOWEVENT_EXPOSED: 1313 debug(DebugSDL) Log.d("SDL_WINDOWEVENT_EXPOSED - ", w.windowCaption); 1314 w.invalidate(); 1315 break; 1316 case SDL_WINDOWEVENT_MOVED: 1317 debug(DebugSDL) Log.d("SDL_WINDOWEVENT_MOVED - ", w.windowCaption); 1318 w.handleWindowStateChange(WindowState.unspecified, Rect(event.window.data1, event.window.data2, w.windowRect().right, w.windowRect().bottom)); 1319 if (!_windowsMinimized && w.hasVisibleModalChild()) 1320 w.restoreModalChilds(); 1321 break; 1322 case SDL_WINDOWEVENT_MINIMIZED: 1323 debug(DebugSDL) Log.d("SDL_WINDOWEVENT_MINIMIZED - ", w.windowCaption); 1324 if (w.windowState()!=WindowState.minimized) 1325 w.handleWindowStateChange(WindowState.minimized); 1326 if (!_windowsMinimized && w.hasVisibleModalChild()) 1327 w.minimizeModalChilds(); 1328 if (!_windowsMinimized && w.flags & WindowFlag.Modal) 1329 w.minimizeParentWindows(); 1330 _windowsMinimized = true; 1331 break; 1332 case SDL_WINDOWEVENT_MAXIMIZED: 1333 debug(DebugSDL) Log.d("SDL_WINDOWEVENT_MAXIMIZED - ", w.windowCaption); 1334 if (w.windowState()!=WindowState.maximized) 1335 w.handleWindowStateChange(WindowState.maximized); 1336 _windowsMinimized = false; 1337 break; 1338 case SDL_WINDOWEVENT_RESTORED: 1339 debug(DebugSDL) Log.d("SDL_WINDOWEVENT_RESTORED - ", w.windowCaption); 1340 _windowsMinimized = false; 1341 if (w.flags & WindowFlag.Modal) { 1342 w.restoreParentWindows(); 1343 w.restoreWindow(true); 1344 } 1345 1346 if (w.windowState()!=WindowState.normal) 1347 w.handleWindowStateChange(WindowState.normal); 1348 1349 if (w.hasVisibleModalChild()) 1350 w.restoreModalChilds(); 1351 version(linux) { //not sure if needed on Windows or OSX. Also need to check on FreeBSD 1352 w.invalidate(); 1353 } 1354 break; 1355 case SDL_WINDOWEVENT_ENTER: 1356 debug(DebugSDL) Log.d("SDL_WINDOWEVENT_ENTER - ", w.windowCaption); 1357 break; 1358 case SDL_WINDOWEVENT_LEAVE: 1359 debug(DebugSDL) Log.d("SDL_WINDOWEVENT_LEAVE - ", w.windowCaption); 1360 break; 1361 case SDL_WINDOWEVENT_FOCUS_GAINED: 1362 debug(DebugSDL) Log.d("SDL_WINDOWEVENT_FOCUS_GAINED - ", w.windowCaption); 1363 if (!_windowsMinimized) 1364 w.restoreModalChilds(); 1365 w.handleWindowActivityChange(true); 1366 break; 1367 case SDL_WINDOWEVENT_FOCUS_LOST: 1368 debug(DebugSDL) Log.d("SDL_WINDOWEVENT_FOCUS_LOST - ", w.windowCaption); 1369 w.handleWindowActivityChange(false); 1370 break; 1371 default: 1372 break; 1373 } 1374 break; 1375 } 1376 case SDL_KEYDOWN: 1377 SDLWindow w = getWindow(event.key.windowID); 1378 if (w && !w.hasVisibleModalChild()) { 1379 w.processKeyEvent(KeyAction.KeyDown, event.key.keysym.sym, event.key.keysym.mod); 1380 SDL_StartTextInput(); 1381 } 1382 break; 1383 case SDL_KEYUP: 1384 SDLWindow w = getWindow(event.key.windowID); 1385 if (w) { 1386 if (w.hasVisibleModalChild()) 1387 w.restoreModalChilds(); 1388 else 1389 w.processKeyEvent(KeyAction.KeyUp, event.key.keysym.sym, event.key.keysym.mod); 1390 } 1391 break; 1392 case SDL_TEXTEDITING: 1393 debug(DebugSDL) Log.d("SDL_TEXTEDITING"); 1394 break; 1395 case SDL_TEXTINPUT: 1396 debug(DebugSDL) Log.d("SDL_TEXTINPUT"); 1397 SDLWindow w = getWindow(event.text.windowID); 1398 if (w && !w.hasVisibleModalChild()) { 1399 w.processTextInput(event.text.text.ptr); 1400 } 1401 break; 1402 case SDL_MOUSEMOTION: 1403 SDLWindow w = getWindow(event.motion.windowID); 1404 if (w && !w.hasVisibleModalChild()) { 1405 w.processMouseEvent(MouseAction.Move, 0, event.motion.state, event.motion.x, event.motion.y); 1406 } 1407 break; 1408 case SDL_MOUSEBUTTONDOWN: 1409 SDLWindow w = getWindow(event.button.windowID); 1410 if (w && !w.hasVisibleModalChild()) { 1411 w.processMouseEvent(MouseAction.ButtonDown, event.button.button, event.button.state, event.button.x, event.button.y); 1412 } 1413 break; 1414 case SDL_MOUSEBUTTONUP: 1415 SDLWindow w = getWindow(event.button.windowID); 1416 if (w) { 1417 if (w.hasVisibleModalChild()) 1418 w.restoreModalChilds(); 1419 else 1420 w.processMouseEvent(MouseAction.ButtonUp, event.button.button, event.button.state, event.button.x, event.button.y); 1421 } 1422 break; 1423 case SDL_MOUSEWHEEL: 1424 SDLWindow w = getWindow(event.wheel.windowID); 1425 if (w && !w.hasVisibleModalChild()) { 1426 debug(DebugSDL) Log.d("SDL_MOUSEWHEEL x=", event.wheel.x, " y=", event.wheel.y); 1427 w.processMouseEvent(MouseAction.Wheel, 0, 0, event.wheel.x, event.wheel.y); 1428 } 1429 break; 1430 default: 1431 // not supported event 1432 if (event.type == USER_EVENT_ID) { 1433 SDLWindow w = getWindow(event.user.windowID); 1434 if (w) { 1435 w.handlePostedEvent(cast(uint)event.user.code); 1436 } 1437 } else if (event.type == TIMER_EVENT_ID) { 1438 SDLWindow w = getWindow(event.user.windowID); 1439 if (w) { 1440 w.handleTimer(cast(uint)event.user.code); 1441 } 1442 } else if (event.type == WINDOW_CLOSE_EVENT_ID) { 1443 SDLWindow windowToClose = getWindow(event.user.windowID); 1444 if(windowToClose) { 1445 if (windowToClose.windowId in _windowMap) { 1446 Log.i("Platform.closeWindow()"); 1447 _windowMap.remove(windowToClose.windowId); 1448 Log.i("windowMap.length=", _windowMap.length); 1449 destroy(windowToClose); 1450 } 1451 } 1452 } 1453 break; 1454 } 1455 if (_windowMap.length == 0) { 1456 SDL_Quit(); 1457 quit = true; 1458 } 1459 } 1460 } 1461 Log.i("exiting message loop"); 1462 return 0; 1463 } 1464 1465 /// check has clipboard text 1466 override bool hasClipboardText(bool mouseBuffer = false) { 1467 return (SDL_HasClipboardText() == SDL_TRUE); 1468 } 1469 1470 /// retrieves text from clipboard (when mouseBuffer == true, use mouse selection clipboard - under linux) 1471 override dstring getClipboardText(bool mouseBuffer = false) { 1472 char * txt = SDL_GetClipboardText(); 1473 if (!txt) 1474 return ""d; 1475 string s = fromStringz(txt).dup; 1476 SDL_free(txt); 1477 return toUTF32(s); 1478 } 1479 1480 /// sets text to clipboard (when mouseBuffer == true, use mouse selection clipboard - under linux) 1481 override void setClipboardText(dstring text, bool mouseBuffer = false) { 1482 string s = toUTF8(text); 1483 SDL_SetClipboardText(s.toStringz); 1484 } 1485 } 1486 1487 version (Windows) { 1488 import core.sys.windows.windows; 1489 import dlangui.platforms.windows.win32fonts; 1490 pragma(lib, "gdi32.lib"); 1491 pragma(lib, "user32.lib"); 1492 extern(Windows) 1493 int DLANGUIWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, 1494 LPSTR lpCmdLine, int nCmdShow) 1495 { 1496 int result; 1497 1498 try 1499 { 1500 Runtime.initialize(); 1501 1502 // call SetProcessDPIAware to support HI DPI - fix by Kapps 1503 auto ulib = LoadLibraryA("user32.dll"); 1504 alias SetProcessDPIAwareFunc = int function(); 1505 auto setDpiFunc = cast(SetProcessDPIAwareFunc)GetProcAddress(ulib, "SetProcessDPIAware"); 1506 if(setDpiFunc) // Should never fail, but just in case... 1507 setDpiFunc(); 1508 1509 // Get screen DPI 1510 HDC dc = CreateCompatibleDC(NULL); 1511 SCREEN_DPI = GetDeviceCaps(dc, LOGPIXELSY); 1512 DeleteObject(dc); 1513 1514 Log.i("Win32 API SCREEN_DPI detected as ", SCREEN_DPI); 1515 1516 //SCREEN_DPI = 96 * 3 / 2; 1517 1518 result = myWinMain(hInstance, hPrevInstance, lpCmdLine, nCmdShow); 1519 Log.i("calling Runtime.terminate()"); 1520 // commented out to fix hanging runtime.terminate when there are background threads 1521 Runtime.terminate(); 1522 } 1523 catch (Throwable e) // catch any uncaught exceptions 1524 { 1525 MessageBoxW(null, toUTF16z(e.toString ~ "\nStack trace:\n" ~ defaultTraceHandler.toString), "Error", 1526 MB_OK | MB_ICONEXCLAMATION); 1527 result = 0; // failed 1528 } 1529 1530 return result; 1531 } 1532 1533 /// split command line arg list; prepend with executable file name 1534 string[] splitCmdLine(string line) { 1535 string[] res; 1536 res ~= exeFilename(); 1537 int start = 0; 1538 bool insideQuotes = false; 1539 for (int i = 0; i <= line.length; i++) { 1540 char ch = i < line.length ? line[i] : 0; 1541 if (ch == '\"') { 1542 if (insideQuotes) { 1543 if (i > start) 1544 res ~= line[start .. i]; 1545 start = i + 1; 1546 insideQuotes = false; 1547 } else { 1548 insideQuotes = true; 1549 start = i + 1; 1550 } 1551 } else if (!insideQuotes && (ch == ' ' || ch == '\t' || ch == 0)) { 1552 if (i > start) { 1553 res ~= line[start .. i]; 1554 } 1555 start = i + 1; 1556 } 1557 } 1558 return res; 1559 } 1560 1561 int myWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int iCmdShow) 1562 { 1563 //Log.d("myWinMain()"); 1564 string basePath = exePath(); 1565 //Log.i("Current executable: ", exePath()); 1566 string cmdline = fromStringz(lpCmdLine).dup; 1567 //Log.i("Command line: ", cmdline); 1568 string[] args = splitCmdLine(cmdline); 1569 //Log.i("Command line params: ", args); 1570 1571 return sdlmain(args); 1572 } 1573 } else { 1574 1575 extern(C) int DLANGUImain(string[] args) 1576 { 1577 return sdlmain(args); 1578 } 1579 } 1580 1581 /// try to get screen resolution and update SCREEN_DPI; returns true if SCREEN_DPI is changed by this check 1582 bool sdlUpdateScreenDpi(int displayIndex = 0) { 1583 if (SDL_GetDisplayDPI is null) { 1584 Log.w("SDL_GetDisplayDPI is not found: cannot detect screen DPI"); 1585 return false; 1586 } 1587 int numDisplays = SDL_GetNumVideoDisplays(); 1588 if (numDisplays < displayIndex + 1) 1589 return false; 1590 float hdpi = 0; 1591 if (SDL_GetDisplayDPI(displayIndex, null, &hdpi, null)) 1592 return false; 1593 int idpi = cast(int)hdpi; 1594 if (idpi < 32 || idpi > 2000) 1595 return false; 1596 Log.i("sdlUpdateScreenDpi: SCREEN_DPI=", idpi); 1597 if (SCREEN_DPI != idpi) { 1598 Log.i("sdlUpdateScreenDpi: SCREEN_DPI is changed from ", SCREEN_DPI, " to ", idpi); 1599 SCREEN_DPI = idpi; 1600 return true; 1601 } 1602 return false; 1603 } 1604 1605 int sdlmain(string[] args) { 1606 1607 initLogs(); 1608 1609 if (!initFontManager()) { 1610 Log.e("******************************************************************"); 1611 Log.e("No font files found!!!"); 1612 Log.e("Currently, only hardcoded font paths implemented."); 1613 Log.e("Probably you can modify sdlapp.d to add some fonts for your system."); 1614 Log.e("TODO: use fontconfig"); 1615 Log.e("******************************************************************"); 1616 assert(false); 1617 } 1618 initResourceManagers(); 1619 1620 version (Windows) { 1621 DOUBLE_CLICK_THRESHOLD_MS = GetDoubleClickTime(); 1622 } 1623 1624 try { 1625 DerelictSDL2.missingSymbolCallback = &missingSymFunc; 1626 // Load the SDL 2 library. 1627 DerelictSDL2.load(); 1628 } catch (Exception e) { 1629 Log.e("Cannot load SDL2 library", e); 1630 return 1; 1631 } 1632 1633 static if (ENABLE_OPENGL) { 1634 try { 1635 DerelictGL3.missingSymbolCallback = &gl3MissingSymFunc; 1636 DerelictGL3.load(); 1637 DerelictGL.missingSymbolCallback = &gl3MissingSymFunc; 1638 DerelictGL.load(); 1639 _enableOpengl = true; 1640 } catch (Exception e) { 1641 Log.e("Cannot load opengl library", e); 1642 } 1643 } 1644 1645 SDL_DisplayMode displayMode; 1646 if (SDL_Init(SDL_INIT_VIDEO|SDL_INIT_TIMER|SDL_INIT_EVENTS|SDL_INIT_NOPARACHUTE) != 0) { 1647 Log.e("Cannot init SDL2: ", SDL_GetError().to!string()); 1648 return 2; 1649 } 1650 scope(exit)SDL_Quit(); 1651 1652 USER_EVENT_ID = SDL_RegisterEvents(1); 1653 TIMER_EVENT_ID = SDL_RegisterEvents(1); 1654 WINDOW_CLOSE_EVENT_ID = SDL_RegisterEvents(1); 1655 1656 int request = SDL_GetDesktopDisplayMode(0, &displayMode); 1657 1658 static if (ENABLE_OPENGL) { 1659 // Set OpenGL attributes 1660 SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER, 1); 1661 SDL_GL_SetAttribute(SDL_GL_DEPTH_SIZE, 24); 1662 // Share textures between contexts 1663 SDL_GL_SetAttribute(SDL_GL_SHARE_WITH_CURRENT_CONTEXT, 1); 1664 } 1665 1666 auto sdl = new SDLPlatform; 1667 1668 Platform.setInstance(sdl); 1669 1670 currentTheme = createDefaultTheme(); 1671 1672 sdlUpdateScreenDpi(0); 1673 1674 Platform.instance.uiTheme = "theme_default"; 1675 1676 int res = 0; 1677 1678 version (unittest) { 1679 } else { 1680 res = UIAppMain(args); 1681 } 1682 1683 //Log.e("Widget instance count after UIAppMain: ", Widget.instanceCount()); 1684 1685 Log.d("Destroying SDL platform"); 1686 Platform.setInstance(null); 1687 static if (ENABLE_OPENGL) 1688 glNoContext = true; 1689 1690 releaseResourcesOnAppExit(); 1691 1692 Log.d("Exiting main"); 1693 APP_IS_SHUTTING_DOWN = true; 1694 1695 return res; 1696 }