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