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