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