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