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