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