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