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