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