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