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