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