1 // Written in the D programming language.
2 
3 /**
4 
5 This module contains implementation of Win32 platform support
6 
7 Provides Win32Window and Win32Platform classes.
8 
9 Usually you don't need to use this module directly.
10 
11 
12 Synopsis:
13 
14 ----
15 import dlangui.platforms.windows.winapp;
16 ----
17 
18 Copyright: Vadim Lopatin, 2014
19 License:   Boost License 1.0
20 Authors:   Vadim Lopatin, coolreader.org@gmail.com
21 */
22 module dlangui.platforms.windows.winapp;
23 
24 public import dlangui.core.config;
25 
26 static if (BACKEND_WIN32):
27 
28 import core.runtime;
29 import core.sys.windows.windows;
30 import std.string;
31 import std.utf;
32 import std.stdio;
33 import std.algorithm;
34 import std.file;
35 import dlangui.platforms.common.platform;
36 import dlangui.platforms.windows.win32fonts;
37 import dlangui.platforms.windows.win32drawbuf;
38 import dlangui.widgets.styles;
39 import dlangui.widgets.widget;
40 import dlangui.graphics.drawbuf;
41 import dlangui.graphics.images;
42 import dlangui.graphics.fonts;
43 import dlangui.core.logger;
44 import dlangui.core.files;
45 
46 static if (ENABLE_OPENGL) {
47     import dlangui.graphics.glsupport;
48 }
49 
50 // specify debug=DebugMouseEvents for logging mouse handling
51 // specify debug=DebugRedraw for logging drawing and layouts handling
52 // specify debug=DebugKeys for logging of key events
53 
54 pragma(lib, "gdi32.lib");
55 pragma(lib, "user32.lib");
56 
57 /// this function should be defined in user application!
58 extern (C) int UIAppMain(string[] args);
59 
60 immutable WIN_CLASS_NAME = "DLANGUI_APP";
61 
62 /* This is a pretty dirty hack to get multisampling to work */
63 private __gshared bool isInitialized = false;
64 
65 __gshared HINSTANCE _hInstance;
66 __gshared int _cmdShow;
67 
68 // TODO: Encapsulate this better
69 private string GetErrorMessage(DWORD error) @trusted
70 {
71     char[] err = new char[256];
72     err[0 .. $] = 0;
73     int chars = FormatMessageA(FORMAT_MESSAGE_FROM_SYSTEM, NULL, error,
74             MAKELANGID(LANG_ENGLISH, SUBLANG_DEFAULT), err.ptr, 255, NULL);
75     return cast(string) err[0 .. chars];
76 }
77 
78 static if (ENABLE_OPENGL) {
79 
80   // WGL stuff
81 
82   // WGL_ARB_pixel_format
83   enum WGL_DRAW_TO_WINDOW_ARB  = 0x2001;
84   enum WGL_DRAW_TO_BITMAP_ARB  = 0x2002;
85   enum WGL_ACCELERATION_ARB    = 0x2003;
86   enum WGL_SUPPORT_GDI_ARB     = 0x200F;
87   enum WGL_SUPPORT_OPENGL_ARB  = 0x2010;
88   enum WGL_DOUBLE_BUFFER_ARB   = 0x2011;
89   enum WGL_STEREO_ARB          = 0x2012;
90   enum WGL_PIXEL_TYPE_ARB      = 0x2013;
91   enum WGL_COLOR_BITS_ARB      = 0x2014;
92   enum WGL_DEPTH_BITS_ARB      = 0x2022;
93   enum WGL_STENCIL_BITS_ARB    = 0x2023;
94 
95   enum WGL_NO_ACCELERATION_ARB      = 0x2025;
96   enum WGL_GENERIC_ACCELERATION_ARB = 0x2026;
97   enum WGL_FULL_ACCELERATION_ARB    = 0x2027;
98 
99   enum WGL_TYPE_RGBA_ARB       = 0x202B;
100   enum WGL_TYPE_COLORINDEX_ARB = 0x202C;
101 
102   // WGL_ARB_create_context_profile
103   enum WGL_CONTEXT_MAJOR_VERSION_ARB = 0x2091;
104   enum WGL_CONTEXT_MINOR_VERSION_ARB = 0x2092;
105   enum WGL_CONTEXT_FLAGS_ARB         = 0x2094;
106   enum WGL_CONTEXT_PROFILE_MASK_ARB  = 0x9126;
107 
108   // WGL_CONTEXT_FLAGS bits
109   enum WGL_CONTEXT_DEBUG_BIT_ARB = 0x0001;
110   enum WGL_CONTEXT_FORWARD_COMPATIBLE_BIT_ARB = 0x0002;
111 
112   // WGL_CONTEXT_PROFILE_MASK_ARB bits
113   enum WGL_CONTEXT_CORE_PROFILE_BIT_ARB = 0x00000001;
114   enum WGL_CONTEXT_COMPATIBILITY_PROFILE_BIT_ARB = 0x00000002;
115 
116   enum GL_NUM_EXTENSIONS = 0x821D;
117 
118   enum WGL_ALPHA_BITS_ARB = 0x201B;
119 
120   enum WGL_SAMPLE_BUFFERS_ARB = 0x2041;
121   enum WGL_SAMPLES_ARB = 0x2042;
122 
123     bool setupPixelFormat(HDC hDC, int multisamples = 0)
124     {
125         PIXELFORMATDESCRIPTOR pfd = {
126             PIXELFORMATDESCRIPTOR.sizeof,  /* size */
127             1,                              /* version */
128             PFD_SUPPORT_OPENGL |
129                 PFD_DRAW_TO_WINDOW |
130                 PFD_DOUBLEBUFFER,               /* support double-buffering */
131             PFD_TYPE_RGBA,                  /* color type */
132             24,                             /* prefered color depth */
133             0, 0, 0, 0, 0, 0,               /* color bits (ignored) */
134             0,                              /* no alpha buffer */
135             0,                              /* alpha bits (ignored) */
136             0,                              /* no accumulation buffer */
137             0, 0, 0, 0,                     /* accum bits (ignored) */
138             16,                             /* depth buffer */
139             0,                              /* no stencil buffer */
140             0,                              /* no auxiliary buffers */
141             0,                              /* main layer PFD_MAIN_PLANE */
142             0,                              /* reserved */
143             0, 0, 0,                        /* no layer, visible, damage masks */
144         };
145         int pixelFormat;
146 
147         pixelFormat = multisamples > 0 ? sharedGLContext.multisampleFormat(hDC, multisamples) : ChoosePixelFormat(hDC, &pfd);
148         if (pixelFormat == 0) {
149             Log.e("ChoosePixelFormat failed.");
150             return false;
151         }
152 
153         if (SetPixelFormat(hDC, pixelFormat, &pfd) != TRUE) {
154             Log.e("SetPixelFormat failed.");
155             return false;
156         }
157         return true;
158     }
159 
160     HPALETTE setupPalette(HDC hDC)
161     {
162         import core.stdc.stdlib;
163         HPALETTE hPalette = NULL;
164         int pixelFormat = GetPixelFormat(hDC);
165         PIXELFORMATDESCRIPTOR pfd;
166         LOGPALETTE* pPal;
167         int paletteSize;
168 
169         DescribePixelFormat(hDC, pixelFormat, PIXELFORMATDESCRIPTOR.sizeof, &pfd);
170 
171         if (pfd.dwFlags & PFD_NEED_PALETTE) {
172             paletteSize = 1 << pfd.cColorBits;
173         } else {
174             return null;
175         }
176 
177         pPal = cast(LOGPALETTE*)
178             malloc(LOGPALETTE.sizeof + paletteSize * PALETTEENTRY.sizeof);
179         pPal.palVersion = 0x300;
180         pPal.palNumEntries = cast(ushort)paletteSize;
181 
182         /* build a simple RGB color palette */
183         {
184             int redMask = (1 << pfd.cRedBits) - 1;
185             int greenMask = (1 << pfd.cGreenBits) - 1;
186             int blueMask = (1 << pfd.cBlueBits) - 1;
187             int i;
188 
189             for (i=0; i<paletteSize; ++i) {
190                 pPal.palPalEntry[i].peRed = cast(ubyte)(
191                     (((i >> pfd.cRedShift) & redMask) * 255) / redMask);
192                 pPal.palPalEntry[i].peGreen = cast(ubyte)(
193                     (((i >> pfd.cGreenShift) & greenMask) * 255) / greenMask);
194                 pPal.palPalEntry[i].peBlue = cast(ubyte)(
195                     (((i >> pfd.cBlueShift) & blueMask) * 255) / blueMask);
196                 pPal.palPalEntry[i].peFlags = 0;
197             }
198         }
199 
200         hPalette = CreatePalette(pPal);
201         free(pPal);
202 
203         if (hPalette) {
204             SelectPalette(hDC, hPalette, FALSE);
205             RealizePalette(hDC);
206         }
207 
208         return hPalette;
209     }
210 
211     private __gshared bool BINDBC_GL3_RELOADED = false; // is this even used?
212 }
213 
214 const uint CUSTOM_MESSAGE_ID = WM_USER + 1;
215 
216 static if (ENABLE_OPENGL) {
217 
218     alias PFNWGLCHOOSEPIXELFORMATARBPROC = extern(C) BOOL function(HDC hdc, const(int)* attributes, const(FLOAT)* fAttributes, UINT maxFormats, int* pixelFormat, UINT *numFormats);
219     PFNWGLCHOOSEPIXELFORMATARBPROC wglChoosePixelFormatARB;
220 
221     alias PFNWGLCREATECONTEXTATTRIBSARBPROC = extern(C) HGLRC function(HDC hdc, HGLRC hShareContext, const int *attribList);
222     PFNWGLCREATECONTEXTATTRIBSARBPROC wglCreateContextAttribsARB;
223 
224     /// Shared opengl context helper
225     struct SharedGLContext {
226         import bindbc.opengl;
227 
228         HGLRC _hGLRC; // opengl context
229         HPALETTE _hPalette;
230         bool _error;
231         /// Init OpenGL context, if not yet initialized
232         bool init(HDC hDC) {
233             if (_hGLRC) {
234                 // just setup pixel format
235                 if (setupPixelFormat(hDC)) {
236                     Log.i("OpenGL context already exists. Setting pixel format.");
237                 } else {
238                     Log.e("Cannot setup pixel format");
239                 }
240                 return true;
241             }
242             if (_error)
243                 return false;
244             if (setupPixelFormat(hDC)) {
245                 _hPalette = setupPalette(hDC);
246                 _hGLRC = wglCreateContext(hDC);
247                 if (_hGLRC) {
248                     bind(hDC);
249                     wglChoosePixelFormatARB = cast(PFNWGLCHOOSEPIXELFORMATARBPROC)wglGetProcAddress("wglChoosePixelFormatARB");
250                     unbind(hDC);
251                     return true;
252                 } else {
253                     _error = true;
254                     return false;
255                 }
256             } else {
257                 Log.e("Cannot setup pixel format");
258                 _error = true;
259                 return false;
260             }
261         }
262 
263         bool initGLBindings(HDC hDC)
264         {
265             bind(hDC);
266             scope(exit) unbind(hDC);
267             bool initialized = initGLSupport(Platform.instance.GLVersionMajor < 3);
268             if (!initialized) {
269                 uninit();
270                 Log.e("Failed to init OpenGL shaders");
271                 _error = true;
272                 return false;
273             }
274             return true;
275         }
276 
277         /// A helper function to reinit a context to use multisampling
278         bool reinit(HDC hDC, int samples)
279         {
280             if(setupPixelFormat(hDC, samples))
281             {
282                 _hPalette = setupPalette(hDC);
283                 _hGLRC = wglCreateContext(hDC);
284                 if (_hGLRC) {
285                     return true;
286                 }
287                 else
288                 {
289                     _error = true;
290                     return false;
291                 }
292             }
293             else
294             {
295                 Log.e("Cannot reinit pixel format");
296                 _error = true;
297                 return false;
298             }
299         }
300 
301         void uninit() {
302             if (_hGLRC) {
303                 wglDeleteContext(_hGLRC);
304                 _hGLRC = null;
305             }
306         }
307         /// make this context current for DC
308         void bind(HDC hDC) {
309             if (!wglMakeCurrent(hDC, _hGLRC)) {
310                 import std.string : format;
311                 Log.e("wglMakeCurrent is failed. ", GetErrorMessage(GetLastError()));
312             }
313         }
314         /// make null context current for DC
315         void unbind(HDC hDC) {
316             //wglMakeCurrent(hDC, null);
317             wglMakeCurrent(null, null);
318         }
319         void swapBuffers(HDC hDC) {
320             SwapBuffers(hDC);
321         }
322 
323         int multisampleFormat(HDC hdc, int samples)
324         {
325             GLint pixelFormat;
326             BOOL valid;
327             GLuint numFormats;
328 
329             float[] fattribs = [0.0f, 0.0f];
330 
331             int[] attribs =
332             [
333                 WGL_DRAW_TO_WINDOW_ARB, GL_TRUE,
334                 WGL_SUPPORT_OPENGL_ARB, GL_TRUE,
335                 WGL_ACCELERATION_ARB, WGL_FULL_ACCELERATION_ARB,
336                 WGL_COLOR_BITS_ARB, 24,
337                 WGL_ALPHA_BITS_ARB, 8,
338                 WGL_DEPTH_BITS_ARB, 24,
339                 WGL_STENCIL_BITS_ARB, 0,
340                 WGL_DOUBLE_BUFFER_ARB, GL_TRUE,
341                 WGL_SAMPLE_BUFFERS_ARB, GL_TRUE,
342                 WGL_SAMPLES_ARB, Platform.instance.multisamples,
343                 0
344             ];
345 
346             valid = wglChoosePixelFormatARB(hdc, attribs.ptr, fattribs.ptr, 1, &pixelFormat, &numFormats);
347             if(!valid)
348             {
349                 Log.e("wglChoosePixelFormatARB failed. GetLastError=%x".format(GetLastError()));
350                 return 0;
351             }
352 
353             return pixelFormat;
354         }
355 
356         bool createCoreRC(HDC hDC)
357         {
358             int[] attribs =
359             [
360                 WGL_CONTEXT_MAJOR_VERSION_ARB, Platform.instance.GLVersionMajor,
361                 WGL_CONTEXT_MINOR_VERSION_ARB, Platform.instance.GLVersionMinor,
362                 WGL_CONTEXT_PROFILE_MASK_ARB, WGL_CONTEXT_COMPATIBILITY_PROFILE_BIT_ARB,
363                 0
364             ];
365             bind(hDC);
366             wglCreateContextAttribsARB = cast(PFNWGLCREATECONTEXTATTRIBSARBPROC)wglGetProcAddress("wglCreateContextAttribsARB");
367             HGLRC tmpRC = wglCreateContextAttribsARB(hDC, null, attribs.ptr);
368             if(!tmpRC)
369                 return false;
370             wglMakeCurrent(hDC, null);
371             wglDeleteContext(_hGLRC);
372             _hGLRC = tmpRC;
373             wglMakeCurrent(hDC, tmpRC);
374             unbind(hDC);
375             return true;
376         }
377     }
378 
379     /// OpenGL context to share between windows
380     __gshared SharedGLContext sharedGLContext;
381 }
382 
383 interface UnknownWindowMessageHandler {
384     /// return true if message is handled, put return value into result
385     bool onUnknownWindowMessage(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam, ref LRESULT result);
386 }
387 
388 class Win32Window : Window {
389     Win32Platform _platform;
390 
391     HWND _hwnd;
392     dstring _caption;
393     Win32ColorDrawBuf _drawbuf;
394     private Win32Window _w32parent;
395     bool useOpengl;
396 
397     /// win32 only - return window handle
398     @property HWND windowHandle() {
399         return _hwnd;
400     }
401 
402     this(Win32Platform platform, dstring windowCaption, Window parent, uint flags, uint width = 0, uint height = 0) {
403         _w32parent = cast(Win32Window)parent;
404         HWND parenthwnd = _w32parent ? _w32parent._hwnd : null;
405         _dx = width;
406         _dy = height;
407         if (!_dx)
408             _dx = 600;
409         if (!_dy)
410             _dy = 400;
411         _platform = platform;
412         _caption = windowCaption;
413         _windowState = WindowState.hidden;
414         _flags = flags;
415         uint ws = WS_CLIPCHILDREN | WS_CLIPSIBLINGS;
416         if (flags & WindowFlag.Resizable)
417             ws |= WS_OVERLAPPEDWINDOW;
418         else
419             ws |= WS_OVERLAPPED | WS_CAPTION | WS_CAPTION | WS_BORDER | WS_SYSMENU;
420         //if (flags & WindowFlag.Fullscreen)
421         //    ws |= SDL_WINDOW_FULLSCREEN;
422         Rect screenRc = getScreenDimensions();
423         Log.d("Screen dimensions: ", screenRc);
424 
425         int x = CW_USEDEFAULT;
426         int y = CW_USEDEFAULT;
427 
428         if (flags & WindowFlag.Fullscreen) {
429             // fullscreen
430             x = screenRc.left;
431             y = screenRc.top;
432             _dx = screenRc.width;
433             _dy = screenRc.height;
434             ws = WS_POPUP;
435         }
436         if (flags & WindowFlag.Borderless) {
437             ws = WS_POPUP | WS_SYSMENU;
438         }
439 
440 
441         _hwnd = CreateWindowW(toUTF16z(WIN_CLASS_NAME),      // window class name
442                             toUTF16z(windowCaption),  // window caption
443                             ws,  // window style
444                             x,        // initial x position
445                             y,        // initial y position
446                             _dx,        // initial x size
447                             _dy,        // initial y size
448                             parenthwnd,                 // parent window handle
449                             null,                 // window menu handle
450                             _hInstance,           // program instance handle
451                             platform.getMultisamples ? cast(void*)null : cast(void*)this); // creation parameters
452         static if (ENABLE_OPENGL) {
453             /* initialize OpenGL rendering */
454             HDC hDC = GetDC(_hwnd);
455 
456             if (openglEnabled) {
457                 useOpengl = sharedGLContext.init(hDC);
458 
459                 if(platform.multisamples != 0)
460                 {
461                     sharedGLContext.uninit();
462                     ReleaseDC(_hwnd, hDC);
463 
464                     // Recreate window with multisampling (copy-paste from above)
465                     DestroyWindow(_hwnd);
466                     _hwnd = CreateWindowW(toUTF16z(WIN_CLASS_NAME),      // window class name
467                         toUTF16z(windowCaption),  // window caption
468                         ws,  // window style
469                         x,        // initial x position
470                         y,        // initial y position
471                         _dx,        // initial x size
472                         _dy,        // initial y size
473                         parenthwnd,                 // parent window handle
474                         null,                 // window menu handle
475                         _hInstance,           // program instance handle
476                         cast(void*)this);                // creation parameters
477 
478                     hDC = GetDC(_hwnd);
479                     useOpengl = sharedGLContext.reinit(hDC, platform.multisamples);
480 
481                     if(!sharedGLContext.createCoreRC(hDC))
482                     {
483                         Log.d("Unable to create Core OpenGL");
484                         throw new Exception("Unable to create Core OpenGL");
485                     }
486                 }
487             }
488             sharedGLContext.initGLBindings(hDC);
489         }
490 
491         isInitialized = true;
492 
493         RECT rect;
494         GetWindowRect(_hwnd, &rect);
495         handleWindowStateChange(WindowState.unspecified, Rect(rect.left, rect.top, _dx, _dy));
496 
497         if (platform.defaultWindowIcon.length != 0)
498             windowIcon = drawableCache.getImage(platform.defaultWindowIcon);
499     }
500 
501     static if (ENABLE_OPENGL) {
502         private void paintUsingOpenGL() {
503             // hack to stop infinite WM_PAINT loop
504             PAINTSTRUCT ps;
505             HDC hdc2 = BeginPaint(_hwnd, &ps);
506             EndPaint(_hwnd, &ps);
507 
508             import bindbc.opengl; //3.gl3;
509             import bindbc.opengl; //3.wgl;
510             import dlangui.graphics.gldrawbuf;
511             //Log.d("onPaint() start drawing opengl viewport: ", _dx, "x", _dy);
512             //PAINTSTRUCT ps;
513             //HDC hdc = BeginPaint(_hwnd, &ps);
514             //scope(exit) EndPaint(_hwnd, &ps);
515             HDC hdc = GetDC(_hwnd);
516             sharedGLContext.bind(hdc);
517             //_glSupport = _gl;
518             glDisable(GL_DEPTH_TEST);
519             glViewport(0, 0, _dx, _dy);
520             float a = 1.0f;
521             float r = ((_backgroundColor >> 16) & 255) / 255.0f;
522             float g = ((_backgroundColor >> 8) & 255) / 255.0f;
523             float b = ((_backgroundColor >> 0) & 255) / 255.0f;
524             glClearColor(r, g, b, a);
525             glClear(GL_COLOR_BUFFER_BIT);
526 
527             GLDrawBuf buf = new GLDrawBuf(_dx, _dy, false);
528             buf.beforeDrawing();
529             static if (false) {
530                 // for testing for render
531                 buf.fillRect(Rect(100, 100, 200, 200), 0x704020);
532                 buf.fillRect(Rect(40, 70, 100, 120), 0x000000);
533                 buf.fillRect(Rect(80, 80, 150, 150), 0x80008000); // green
534                 drawableCache.get("exit").drawTo(buf, Rect(300, 100, 364, 164));
535                 drawableCache.get("btn_default_pressed").drawTo(buf, Rect(300, 200, 564, 264));
536                 drawableCache.get("btn_default_normal").drawTo(buf, Rect(300, 0, 400, 50));
537                 drawableCache.get("btn_default_selected").drawTo(buf, Rect(0, 0, 100, 50));
538                 FontRef fnt = currentTheme.font;
539                 fnt.drawText(buf, 40, 40, "Some Text 1234567890 !@#$^*", 0x80FF0000);
540             } else {
541                 onDraw(buf);
542             }
543             buf.afterDrawing();
544             sharedGLContext.swapBuffers(hdc);
545             sharedGLContext.unbind(hdc);
546             destroy(buf);
547         }
548     }
549 
550     protected Rect getScreenDimensions() {
551         MONITORINFO monitor_info;
552         monitor_info.cbSize = monitor_info.sizeof;
553         HMONITOR hMonitor;
554         if (_hwnd) {
555             hMonitor = MonitorFromWindow(_hwnd, MONITOR_DEFAULTTONEAREST);
556         } else {
557             hMonitor = MonitorFromPoint(POINT(0,0), MONITOR_DEFAULTTOPRIMARY);
558         }
559         GetMonitorInfo(hMonitor,
560                        &monitor_info);
561         Rect res;
562         res.left = monitor_info.rcMonitor.left;
563         res.top = monitor_info.rcMonitor.top;
564         res.right = monitor_info.rcMonitor.right;
565         res.bottom = monitor_info.rcMonitor.bottom;
566         return res;
567     }
568 
569     protected bool _destroying;
570     ~this() {
571         debug Log.d("Window destructor");
572         _destroying = true;
573         if (_drawbuf) {
574             destroy(_drawbuf);
575             _drawbuf = null;
576         }
577 
578         /*
579         static if (ENABLE_OPENGL) {
580             import derelict.opengl3.wgl;
581             if (_hGLRC) {
582                 //glSupport.uninitShaders();
583                 //destroy(_glSupport);
584                 //_glSupport = null;
585                 //_gl = null;
586                 wglMakeCurrent (null, null) ;
587                 wglDeleteContext(_hGLRC);
588                 _hGLRC = null;
589             }
590         }
591         */
592         if (_hwnd)
593             DestroyWindow(_hwnd);
594         _hwnd = null;
595     }
596 
597     /// post event to handle in UI thread (this method can be used from background thread)
598     override void postEvent(CustomEvent event) {
599         super.postEvent(event);
600         PostMessageW(_hwnd, CUSTOM_MESSAGE_ID, 0, event.uniqueId);
601     }
602 
603     /// set handler for files dropped to app window
604     override @property Window onFilesDropped(void delegate(string[]) handler) {
605         super.onFilesDropped(handler);
606         DragAcceptFiles(_hwnd, handler ? TRUE : FALSE);
607         return this;
608     }
609 
610     private long _nextExpectedTimerTs;
611     private UINT_PTR _timerId = 1;
612 
613     /// schedule timer for interval in milliseconds - call window.onTimer when finished
614     override protected void scheduleSystemTimer(long intervalMillis) {
615         if (intervalMillis < 10)
616             intervalMillis = 10;
617         long nextts = currentTimeMillis + intervalMillis;
618         if (_timerId && _nextExpectedTimerTs && _nextExpectedTimerTs < nextts + 10)
619             return; // don't reschedule timer, timer event will be received soon
620         if (_hwnd) {
621             //_timerId =
622             SetTimer(_hwnd, _timerId, cast(uint)intervalMillis, null);
623             _nextExpectedTimerTs = nextts;
624         }
625     }
626 
627     void handleTimer(UINT_PTR timerId) {
628         //Log.d("handleTimer id=", timerId);
629         if (timerId == _timerId) {
630             KillTimer(_hwnd, timerId);
631             //_timerId = 0;
632             _nextExpectedTimerTs = 0;
633             onTimer();
634         }
635     }
636 
637     /// custom window message handler
638     Signal!UnknownWindowMessageHandler onUnknownWindowMessage;
639     private LRESULT handleUnknownWindowMessage(UINT message, WPARAM wParam, LPARAM lParam) {
640         if (onUnknownWindowMessage.assigned) {
641             LRESULT res;
642             if (onUnknownWindowMessage(_hwnd, message, wParam, lParam, res))
643                 return res;
644         }
645         return DefWindowProc(_hwnd, message, wParam, lParam);
646     }
647 
648     Win32ColorDrawBuf getDrawBuf() {
649         //RECT rect;
650         //GetClientRect(_hwnd, &rect);
651         //int dx = rect.right - rect.left;
652         //int dy = rect.bottom - rect.top;
653         if (_drawbuf is null)
654             _drawbuf = new Win32ColorDrawBuf(_dx, _dy);
655         else
656             _drawbuf.resize(_dx, _dy);
657         _drawbuf.resetClipping();
658         return _drawbuf;
659     }
660     override void show() {
661         if (!_mainWidget) {
662             Log.e("Window is shown without main widget");
663             _mainWidget = new Widget();
664         }
665         ReleaseCapture();
666         if (_mainWidget) {
667             _mainWidget.measure(SIZE_UNSPECIFIED, SIZE_UNSPECIFIED);
668             if (flags & WindowFlag.MeasureSize)
669                 resizeWindow(Point(_mainWidget.measuredWidth, _mainWidget.measuredHeight));
670             else
671                 adjustWindowOrContentSize(_mainWidget.measuredWidth, _mainWidget.measuredHeight);
672         }
673 
674         adjustPositionDuringShow();
675 
676         if (_flags & WindowFlag.Fullscreen) {
677             Rect rc = getScreenDimensions();
678             SetWindowPos(_hwnd, HWND_TOPMOST, 0, 0, rc.width, rc.height, SWP_SHOWWINDOW);
679             _windowState = WindowState.fullscreen;
680         } else {
681             ShowWindow(_hwnd, SW_SHOWNORMAL);
682             _windowState = WindowState.normal;
683         }
684         if (_mainWidget)
685             _mainWidget.setFocus();
686         SetFocus(_hwnd);
687         //UpdateWindow(_hwnd);
688     }
689 
690     override @property Window parentWindow() {
691         return _w32parent;
692     }
693 
694     override protected void handleWindowActivityChange(bool isWindowActive) {
695         super.handleWindowActivityChange(isWindowActive);
696     }
697 
698     override @property bool isActive() {
699         return _hwnd == GetForegroundWindow();
700     }
701 
702     override @property dstring windowCaption() const {
703         return _caption;
704     }
705 
706     override @property void windowCaption(dstring caption) {
707         _caption = caption;
708         if (_hwnd) {
709             Log.d("windowCaption ", caption);
710             SetWindowTextW(_hwnd, toUTF16z(_caption));
711         }
712     }
713 
714     /// change window state, position, or size; returns true if successful, false if not supported by platform
715     override bool setWindowState(WindowState newState, bool activate = false, Rect newWindowRect = RECT_VALUE_IS_NOT_SET) {
716         if (!_hwnd)
717             return false;
718         bool res = false;
719         // change state and activate support
720         switch(newState) {
721             case WindowState.unspecified:
722                 if (activate) {
723                     switch (_windowState) {
724                         case WindowState.hidden:
725                             // show hidden window
726                             ShowWindow(_hwnd, SW_SHOW);
727                             res = true;
728                             break;
729                         case WindowState.normal:
730                             ShowWindow(_hwnd, SW_SHOWNORMAL);
731                             res = true;
732                             break;
733                         case WindowState.fullscreen:
734                             ShowWindow(_hwnd, SW_SHOWNORMAL);
735                             res = true;
736                             break;
737                         case WindowState.minimized:
738                             ShowWindow(_hwnd, SW_SHOWMINIMIZED);
739                             res = true;
740                             break;
741                         case WindowState.maximized:
742                             ShowWindow(_hwnd, SW_SHOWMAXIMIZED);
743                             res = true;
744                             break;
745                         default:
746                             break;
747                     }
748                     res = true;
749                 }
750                 break;
751             case WindowState.maximized:
752                 if (_windowState != WindowState.maximized || activate) {
753                     ShowWindow(_hwnd, activate ? SW_SHOWMAXIMIZED : SW_MAXIMIZE);
754                     res = true;
755                 }
756                 break;
757             case WindowState.minimized:
758                 if (_windowState != WindowState.minimized || activate) {
759                     ShowWindow(_hwnd, activate ? SW_SHOWMINIMIZED : SW_MINIMIZE);
760                     res = true;
761                 }
762                 break;
763             case WindowState.hidden:
764                 if (_windowState != WindowState.hidden) {
765                     ShowWindow(_hwnd, SW_HIDE);
766                     res = true;
767                 }
768                 break;
769             case WindowState.normal:
770                 if (_windowState != WindowState.normal || activate) {
771                     ShowWindow(_hwnd, activate ? SW_SHOWNORMAL : SW_SHOWNA); // SW_RESTORE
772                     res = true;
773                 }
774                 break;
775 
776             default:
777                 break;
778         }
779         // change size and/or position
780         bool rectChanged = false;
781         if (newWindowRect != RECT_VALUE_IS_NOT_SET && (newState == WindowState.normal || newState == WindowState.unspecified)) {
782             UINT flags = SWP_NOOWNERZORDER | SWP_NOZORDER;
783             if (!activate)
784                 flags |= SWP_NOACTIVATE;
785             if (newWindowRect.top == int.min || newWindowRect.left == int.min) {
786                 // no position specified
787                 if (newWindowRect.bottom != int.min && newWindowRect.right != int.min) {
788                     // change size only
789                     SetWindowPos(_hwnd, NULL, 0, 0, newWindowRect.right + 2 * GetSystemMetrics(SM_CXDLGFRAME), newWindowRect.bottom + GetSystemMetrics(SM_CYCAPTION) + 2 * GetSystemMetrics(SM_CYDLGFRAME), flags | SWP_NOMOVE);
790                     rectChanged = true;
791                     res = true;
792                 }
793             } else {
794                 if (newWindowRect.bottom != int.min && newWindowRect.right != int.min) {
795                     // change size and position
796                     SetWindowPos(_hwnd, NULL, newWindowRect.left, newWindowRect.top, newWindowRect.right + 2 * GetSystemMetrics(SM_CXDLGFRAME), newWindowRect.bottom + GetSystemMetrics(SM_CYCAPTION) + 2 * GetSystemMetrics(SM_CYDLGFRAME), flags);
797                     rectChanged = true;
798                     res = true;
799                 } else {
800                     // change position only
801                     SetWindowPos(_hwnd, NULL, newWindowRect.left, newWindowRect.top, 0, 0, flags | SWP_NOSIZE);
802                     rectChanged = true;
803                     res = true;
804                 }
805             }
806         }
807 
808         if (rectChanged) {
809             handleWindowStateChange(newState, Rect(newWindowRect.left == int.min ? _windowRect.left : newWindowRect.left,
810                 newWindowRect.top == int.min ? _windowRect.top : newWindowRect.top, newWindowRect.right == int.min ? _windowRect.right : newWindowRect.right,
811                 newWindowRect.bottom == int.min ? _windowRect.bottom : newWindowRect.bottom));
812         }
813         else
814             handleWindowStateChange(newState, RECT_VALUE_IS_NOT_SET);
815 
816         return res;
817     }
818 
819     void onCreate() {
820         Log.d("Window onCreate");
821         _platform.onWindowCreated(_hwnd, this);
822     }
823     void onDestroy() {
824         Log.d("Window onDestroy");
825         _platform.onWindowDestroyed(_hwnd, this);
826     }
827 
828     protected bool _closeCalled;
829     /// close window
830     override void close() {
831         if (_closeCalled)
832             return;
833         _closeCalled = true;
834         Log.d("Window.close()");
835         _platform.closeWindow(this);
836     }
837 
838     override protected void handleWindowStateChange(WindowState newState, Rect newWindowRect = RECT_VALUE_IS_NOT_SET) {
839         if (_destroying)
840             return;
841         super.handleWindowStateChange(newState, newWindowRect);
842     }
843 
844     HICON _icon;
845 
846     uint _cursorType;
847 
848     HANDLE[ushort] _cursorCache;
849 
850     HANDLE loadCursor(ushort id) {
851         if (id in _cursorCache)
852             return _cursorCache[id];
853         HANDLE h = LoadCursor(null, MAKEINTRESOURCE(id));
854         _cursorCache[id] = h;
855         return h;
856     }
857 
858     void onSetCursorType() {
859         HANDLE winCursor = null;
860         switch (_cursorType) with(CursorType)
861         {
862             case None:
863                 winCursor = null;
864                 break;
865             case NotSet:
866                 break;
867             case Arrow:
868                 winCursor = loadCursor(IDC_ARROW);
869                 break;
870             case IBeam:
871                 winCursor = loadCursor(IDC_IBEAM);
872                 break;
873             case Wait:
874                 winCursor = loadCursor(IDC_WAIT);
875                 break;
876             case Crosshair:
877                 winCursor = loadCursor(IDC_CROSS);
878                 break;
879             case WaitArrow:
880                 winCursor = loadCursor(IDC_APPSTARTING);
881                 break;
882             case SizeNWSE:
883                 winCursor = loadCursor(IDC_SIZENWSE);
884                 break;
885             case SizeNESW:
886                 winCursor = loadCursor(IDC_SIZENESW);
887                 break;
888             case SizeWE:
889                 winCursor = loadCursor(IDC_SIZEWE);
890                 break;
891             case SizeNS:
892                 winCursor = loadCursor(IDC_SIZENS);
893                 break;
894             case SizeAll:
895                 winCursor = loadCursor(IDC_SIZEALL);
896                 break;
897             case No:
898                 winCursor = loadCursor(IDC_NO);
899                 break;
900             case Hand:
901                 winCursor = loadCursor(IDC_HAND);
902                 break;
903             default:
904                 break;
905         }
906         SetCursor(winCursor);
907     }
908 
909     /// sets cursor type for window
910     override protected void setCursorType(uint cursorType) {
911         // override to support different mouse cursors
912         _cursorType = cursorType;
913         onSetCursorType();
914     }
915 
916     /// sets window icon
917     @property override void windowIcon(DrawBufRef buf) {
918         if (_icon)
919             DestroyIcon(_icon);
920         _icon = null;
921         ColorDrawBuf icon = cast(ColorDrawBuf)buf.get;
922         if (!icon) {
923             Log.e("Trying to set null icon for window");
924             return;
925         }
926         Win32ColorDrawBuf resizedicon = new Win32ColorDrawBuf(icon, 32, 32);
927         resizedicon.invertAlpha();
928         ICONINFO ii;
929         HBITMAP mask = resizedicon.createTransparencyBitmap();
930         HBITMAP color = resizedicon.destroyLeavingBitmap();
931         ii.fIcon = TRUE;
932         ii.xHotspot = 0;
933         ii.yHotspot = 0;
934         ii.hbmMask = mask;
935         ii.hbmColor = color;
936         _icon = CreateIconIndirect(&ii);
937         if (_icon) {
938             SendMessageW(_hwnd, WM_SETICON, ICON_SMALL, cast(LPARAM)_icon);
939             SendMessageW(_hwnd, WM_SETICON, ICON_BIG, cast(LPARAM)_icon);
940         } else {
941             Log.e("failed to create icon");
942         }
943         if (mask)
944             DeleteObject(mask);
945         DeleteObject(color);
946     }
947 
948     private void paintUsingGDI() {
949         PAINTSTRUCT ps;
950         HDC hdc = BeginPaint(_hwnd, &ps);
951         scope(exit) EndPaint(_hwnd, &ps);
952 
953         Win32ColorDrawBuf buf = getDrawBuf();
954         buf.fill(_backgroundColor);
955         onDraw(buf);
956         buf.drawTo(hdc, 0, 0);
957     }
958 
959     void onPaint() {
960         debug(DebugRedraw) Log.d("onPaint()");
961         long paintStart = currentTimeMillis;
962         static if (ENABLE_OPENGL) {
963             if (useOpengl && sharedGLContext._hGLRC) {
964                 paintUsingOpenGL();
965             } else {
966                 paintUsingGDI();
967             }
968         } else {
969             paintUsingGDI();
970         }
971         long paintEnd = currentTimeMillis;
972         debug(DebugRedraw) Log.d("WM_PAINT handling took ", paintEnd - paintStart, " ms");
973     }
974 
975     protected ButtonDetails _lbutton;
976     protected ButtonDetails _mbutton;
977     protected ButtonDetails _rbutton;
978 
979     private void updateButtonsState(uint flags) {
980         if (!(flags & MK_LBUTTON) && _lbutton.isDown)
981             _lbutton.reset();
982         if (!(flags & MK_MBUTTON) && _mbutton.isDown)
983             _mbutton.reset();
984         if (!(flags & MK_RBUTTON) && _rbutton.isDown)
985             _rbutton.reset();
986     }
987 
988     private bool _mouseTracking;
989     private bool onMouse(uint message, uint flags, short x, short y) {
990         debug(DebugMouseEvents) Log.d("Win32 Mouse Message ", message, " flags=", flags, " x=", x, " y=", y);
991         MouseButton button = MouseButton.None;
992         MouseAction action = MouseAction.ButtonDown;
993         ButtonDetails * pbuttonDetails = null;
994         short wheelDelta = 0;
995         switch (message) {
996             case WM_MOUSEMOVE:
997                 action = MouseAction.Move;
998                 updateButtonsState(flags);
999                 break;
1000             case WM_LBUTTONDOWN:
1001                 action = MouseAction.ButtonDown;
1002                 button = MouseButton.Left;
1003                 pbuttonDetails = &_lbutton;
1004                 SetFocus(_hwnd);
1005                 break;
1006             case WM_RBUTTONDOWN:
1007                 action = MouseAction.ButtonDown;
1008                 button = MouseButton.Right;
1009                 pbuttonDetails = &_rbutton;
1010                 SetFocus(_hwnd);
1011                 break;
1012             case WM_MBUTTONDOWN:
1013                 action = MouseAction.ButtonDown;
1014                 button = MouseButton.Middle;
1015                 pbuttonDetails = &_mbutton;
1016                 SetFocus(_hwnd);
1017                 break;
1018             case WM_LBUTTONUP:
1019                 action = MouseAction.ButtonUp;
1020                 button = MouseButton.Left;
1021                 pbuttonDetails = &_lbutton;
1022                 break;
1023             case WM_RBUTTONUP:
1024                 action = MouseAction.ButtonUp;
1025                 button = MouseButton.Right;
1026                 pbuttonDetails = &_rbutton;
1027                 break;
1028             case WM_MBUTTONUP:
1029                 action = MouseAction.ButtonUp;
1030                 button = MouseButton.Middle;
1031                 pbuttonDetails = &_mbutton;
1032                 break;
1033             case WM_MOUSELEAVE:
1034                 debug(DebugMouseEvents) Log.d("WM_MOUSELEAVE");
1035                 action = MouseAction.Leave;
1036                 break;
1037             case WM_MOUSEWHEEL:
1038                 {
1039                     action = MouseAction.Wheel;
1040                     wheelDelta = (cast(short)(flags >> 16)) / 120;
1041                     POINT pt;
1042                     pt.x = x;
1043                     pt.y = y;
1044                     ScreenToClient(_hwnd, &pt);
1045                     x = cast(short)pt.x;
1046                     y = cast(short)pt.y;
1047                 }
1048                 break;
1049             default:
1050                 // unsupported event
1051                 return false;
1052         }
1053         if (action == MouseAction.ButtonDown) {
1054             pbuttonDetails.down(x, y, cast(ushort)flags);
1055         } else if (action == MouseAction.ButtonUp) {
1056             pbuttonDetails.up(x, y, cast(ushort)flags);
1057         }
1058         if (((message == WM_MOUSELEAVE) || (x < 0 || y < 0 || x >= _dx || y >= _dy)) && _mouseTracking) {
1059             if (!isMouseCaptured() || (!_lbutton.isDown && !_rbutton.isDown && !_mbutton.isDown)) {
1060                 action = MouseAction.Leave;
1061                 debug(DebugMouseEvents) Log.d("Win32Window.onMouse releasing capture");
1062                 _mouseTracking = false;
1063                 ReleaseCapture();
1064             }
1065         }
1066         if (message != WM_MOUSELEAVE && !_mouseTracking) {
1067             if (x >=0 && y >= 0 && x < _dx && y < _dy) {
1068                 debug(DebugMouseEvents) Log.d("Win32Window.onMouse Setting capture");
1069                 _mouseTracking = true;
1070                 SetCapture(_hwnd);
1071             }
1072         }
1073         MouseEvent event = new MouseEvent(action, button, cast(ushort)flags, x, y, wheelDelta);
1074         event.lbutton = _lbutton;
1075         event.rbutton = _rbutton;
1076         event.mbutton = _mbutton;
1077         bool res = dispatchMouseEvent(event);
1078         if (res) {
1079             //Log.v("Calling update() after mouse event");
1080             update();
1081         }
1082         return res;
1083     }
1084 
1085 
1086     protected uint _keyFlags;
1087 
1088     protected void updateKeyFlags(KeyAction action, KeyFlag flag, uint preserveFlag) {
1089         if (action == KeyAction.KeyDown)
1090             _keyFlags |= flag;
1091         else {
1092             if (preserveFlag && (_keyFlags & preserveFlag) == preserveFlag) {
1093                 // e.g. when both lctrl and rctrl are pressed, and lctrl is up, preserve rctrl flag
1094                 _keyFlags = (_keyFlags & ~flag) | preserveFlag;
1095             } else {
1096                 _keyFlags &= ~flag;
1097             }
1098         }
1099     }
1100 
1101     bool onKey(KeyAction action, uint keyCode, int repeatCount, dchar character = 0, bool syskey = false) {
1102         debug(KeyInput) Log.d("enter onKey action=", action, " keyCode=", keyCode, " char=", character, "(", cast(int)character, ")", " syskey=", syskey, "    _keyFlags=", "%04x"d.format(_keyFlags));
1103         KeyEvent event;
1104         if (syskey)
1105             _keyFlags |= KeyFlag.Alt;
1106         //else
1107         //    _keyFlags &= ~KeyFlag.Alt;
1108         uint oldFlags = _keyFlags;
1109         if (action == KeyAction.KeyDown || action == KeyAction.KeyUp) {
1110             switch(keyCode) {
1111                 case KeyCode.LSHIFT:
1112                     updateKeyFlags(action, KeyFlag.LShift, KeyFlag.RShift);
1113                     break;
1114                 case KeyCode.RSHIFT:
1115                     updateKeyFlags(action, KeyFlag.RShift, KeyFlag.LShift);
1116                     break;
1117                 case KeyCode.LCONTROL:
1118                     updateKeyFlags(action, KeyFlag.LControl, KeyFlag.RControl);
1119                     break;
1120                 case KeyCode.RCONTROL:
1121                     updateKeyFlags(action, KeyFlag.RControl, KeyFlag.LControl);
1122                     break;
1123                 case KeyCode.LALT:
1124                     updateKeyFlags(action, KeyFlag.LAlt, KeyFlag.RAlt);
1125                     break;
1126                 case KeyCode.RALT:
1127                     updateKeyFlags(action, KeyFlag.RAlt, KeyFlag.LAlt);
1128                     break;
1129                 case KeyCode.LWIN:
1130                     updateKeyFlags(action, KeyFlag.LMenu, KeyFlag.RMenu);
1131                     break;
1132                 case KeyCode.RWIN:
1133                     updateKeyFlags(action, KeyFlag.RMenu, KeyFlag.LMenu);
1134                     break;
1135                 //case KeyCode.WIN:
1136                 case KeyCode.CONTROL:
1137                 case KeyCode.SHIFT:
1138                 case KeyCode.ALT:
1139                 //case KeyCode.WIN:
1140                     break;
1141                 default:
1142                     updateKeyFlags((GetKeyState(VK_LCONTROL) & 0x8000) != 0 ? KeyAction.KeyDown : KeyAction.KeyUp, KeyFlag.LControl, KeyFlag.RControl);
1143                     updateKeyFlags((GetKeyState(VK_RCONTROL) & 0x8000) != 0 ? KeyAction.KeyDown : KeyAction.KeyUp, KeyFlag.RControl, KeyFlag.LControl);
1144                     updateKeyFlags((GetKeyState(VK_LSHIFT) & 0x8000) != 0 ? KeyAction.KeyDown : KeyAction.KeyUp, KeyFlag.LShift, KeyFlag.RShift);
1145                     updateKeyFlags((GetKeyState(VK_RSHIFT) & 0x8000) != 0 ? KeyAction.KeyDown : KeyAction.KeyUp, KeyFlag.RShift, KeyFlag.LShift);
1146                     updateKeyFlags((GetKeyState(VK_LWIN) & 0x8000) != 0 ? KeyAction.KeyDown : KeyAction.KeyUp, KeyFlag.LMenu, KeyFlag.RMenu);
1147                     updateKeyFlags((GetKeyState(VK_RWIN) & 0x8000) != 0 ? KeyAction.KeyDown : KeyAction.KeyUp, KeyFlag.RMenu, KeyFlag.LMenu);
1148                     updateKeyFlags((GetKeyState(VK_LMENU) & 0x8000) != 0 ? KeyAction.KeyDown : KeyAction.KeyUp, KeyFlag.LAlt, KeyFlag.RAlt);
1149                     updateKeyFlags((GetKeyState(VK_RMENU) & 0x8000) != 0 ? KeyAction.KeyDown : KeyAction.KeyUp, KeyFlag.RAlt, KeyFlag.LAlt);
1150                     //updateKeyFlags((GetKeyState(VK_LALT) & 0x8000) != 0 ? KeyAction.KeyDown : KeyAction.KeyUp, KeyFlag.LAlt, KeyFlag.RAlt);
1151                     //updateKeyFlags((GetKeyState(VK_RALT) & 0x8000) != 0 ? KeyAction.KeyDown : KeyAction.KeyUp, KeyFlag.RAlt, KeyFlag.LAlt);
1152                     break;
1153             }
1154             //updateKeyFlags((GetKeyState(VK_CONTROL) & 0x8000) != 0 ? KeyAction.KeyDown : KeyAction.KeyUp, KeyFlag.Control);
1155             //updateKeyFlags((GetKeyState(VK_SHIFT) & 0x8000) != 0 ? KeyAction.KeyDown : KeyAction.KeyUp, KeyFlag.Shift);
1156             //updateKeyFlags((GetKeyState(VK_MENU) & 0x8000) != 0 ? KeyAction.KeyDown : KeyAction.KeyUp, KeyFlag.Alt);
1157             if (keyCode == 0xBF)
1158                 keyCode = KeyCode.KEY_DIVIDE;
1159 
1160             debug(KeyInput) {
1161                 if (oldFlags != _keyFlags) {
1162                     debug(KeyInput) Log.d(" flags updated: onKey action=", action, " keyCode=", keyCode, " char=", character, "(", cast(int)character, ")", " syskey=", syskey, "    _keyFlags=", "%04x"d.format(_keyFlags));
1163                 }
1164                 //if (action == KeyAction.KeyDown)
1165                 //    Log.d("keydown, keyFlags=", _keyFlags);
1166             }
1167 
1168             event = new KeyEvent(action, keyCode, _keyFlags);
1169         } else if (action == KeyAction.Text && character != 0) {
1170             bool ctrlAZKeyCode = (character >= 1 && character <= 26);
1171             if ((_keyFlags & (KeyFlag.Control | KeyFlag.Alt)) && ctrlAZKeyCode) {
1172                 event = new KeyEvent(action, KeyCode.KEY_A + character - 1, _keyFlags);
1173             } else {
1174                 dchar[] text;
1175                 text ~= character;
1176                 uint newFlags = _keyFlags;
1177                 if ((newFlags & KeyFlag.Alt) && (newFlags & KeyFlag.Control)) {
1178                     newFlags &= (~(KeyFlag.LRAlt)) & (~(KeyFlag.LRControl));
1179                     debug(KeyInput) Log.d(" flags updated for text: onKey action=", action, " keyCode=", keyCode, " char=", character, "(", cast(int)character, ")", " syskey=", syskey, "    _keyFlags=", "%04x"d.format(_keyFlags));
1180                 }
1181                 event = new KeyEvent(action, 0, newFlags, cast(dstring)text);
1182             }
1183         }
1184         bool res = false;
1185         if (event !is null) {
1186             res = dispatchKeyEvent(event);
1187         }
1188         if (res) {
1189             debug(DebugRedraw) Log.d("Calling update() after key event");
1190             update();
1191         }
1192         return res;
1193     }
1194 
1195     /// request window redraw
1196     override void invalidate() {
1197         InvalidateRect(_hwnd, null, FALSE);
1198         //UpdateWindow(_hwnd);
1199     }
1200 
1201     /// after drawing, call to schedule redraw if animation is active
1202     override void scheduleAnimation() {
1203         invalidate();
1204     }
1205 
1206 }
1207 
1208 class Win32Platform : Platform {
1209     this() {
1210     }
1211     bool registerWndClass() {
1212         //MSG  msg;
1213         WNDCLASSW wndclass;
1214 
1215         wndclass.style         = CS_HREDRAW | CS_VREDRAW | CS_OWNDC;
1216         wndclass.lpfnWndProc   = cast(WNDPROC)&WndProc;
1217         wndclass.cbClsExtra    = 0;
1218         wndclass.cbWndExtra    = 0;
1219         wndclass.hInstance     = _hInstance;
1220         wndclass.hIcon         = LoadIcon(null, IDI_APPLICATION);
1221         wndclass.hCursor       = LoadCursor(null, IDC_ARROW);
1222         wndclass.hbrBackground = cast(HBRUSH)GetStockObject(WHITE_BRUSH);
1223         wndclass.lpszMenuName  = null;
1224         wndclass.lpszClassName = toUTF16z(WIN_CLASS_NAME);
1225 
1226         if(!RegisterClassW(&wndclass))
1227         {
1228             return false;
1229         }
1230         HDC dc = CreateCompatibleDC(NULL);
1231         SCREEN_DPI = GetDeviceCaps(dc, LOGPIXELSY);
1232         DeleteObject(dc);
1233 
1234         return true;
1235     }
1236     override int enterMessageLoop() {
1237         MSG  msg;
1238         while (GetMessage(&msg, null, 0, 0))
1239         {
1240             TranslateMessage(&msg);
1241             DispatchMessage(&msg);
1242             destroyClosedWindows();
1243         }
1244         return cast(int)msg.wParam;
1245     }
1246 
1247     private Win32Window[ulong] _windowMap;
1248     private Win32Window[] _windowList;
1249 
1250     /// add window to window map
1251     void onWindowCreated(HWND hwnd, Win32Window window) {
1252         Log.v("created window, adding to map");
1253         _windowMap[cast(ulong)hwnd] = window;
1254         _windowList ~= window;
1255     }
1256     /// remove window from window map, returns true if there are some more windows left in map
1257     bool onWindowDestroyed(HWND hwnd, Win32Window window) {
1258         Log.v("destroyed window, removing from map");
1259         Win32Window wnd = getWindow(hwnd);
1260         if (wnd) {
1261             _windowMap.remove(cast(ulong)hwnd);
1262             _windowsToDestroy ~= window;
1263             //destroy(window);
1264         }
1265         for (uint i = 0; i < _windowList.length; i++) {
1266             if (window is _windowList[i]) {
1267                 for (uint j = i; j + 1 < _windowList.length; j++)
1268                     _windowList[j] = _windowList[j + 1];
1269                 _windowList[$ - 1] = null;
1270                 _windowList.length--;
1271                 break;
1272             }
1273         }
1274         return _windowMap.length > 0;
1275     }
1276     /// returns number of currently active windows
1277     @property int windowCount() {
1278         return cast(int)_windowMap.length;
1279     }
1280     /// returns window instance by HWND
1281     Win32Window getWindow(HWND hwnd) {
1282         if ((cast(ulong)hwnd) in _windowMap)
1283             return _windowMap[cast(ulong)hwnd];
1284         return null;
1285     }
1286     override Window createWindow(dstring windowCaption, Window parent, uint flags = WindowFlag.Resizable, uint width = 0, uint height = 0) {
1287         Log.d("Platform.createWindow is called");
1288         width = pointsToPixels(width);
1289         height = pointsToPixels(height);
1290         Log.v("Platform.createWindow : setDefaultLanguageAndThemeIfNecessary");
1291         setDefaultLanguageAndThemeIfNecessary();
1292         Log.v("Platform.createWindow : new Win32Window");
1293         return new Win32Window(this, windowCaption, parent, flags, width, height);
1294     }
1295 
1296     /// calls request layout for all windows
1297     override void requestLayout() {
1298         foreach(w; _windowMap) {
1299             w.requestLayout();
1300             w.invalidate();
1301         }
1302     }
1303 
1304     /// returns true if there is some modal window opened above this window, and this window should not process mouse/key input and should not allow closing
1305     override bool hasModalWindowsAbove(Window w) {
1306         // override in platform specific class
1307         for (uint i = 0; i + 1 < _windowList.length; i++) {
1308             if (_windowList[i] is w) {
1309                 for (uint j = i + 1; j < _windowList.length; j++) {
1310                     if (_windowList[j].flags & WindowFlag.Modal && _windowList[j].windowState != WindowState.hidden)
1311                         return true;
1312                 }
1313                 return false;
1314             }
1315         }
1316         return false;
1317     }
1318 
1319     /// handle theme change: e.g. reload some themed resources
1320     override void onThemeChanged() {
1321         super.onThemeChanged();
1322         if (currentTheme)
1323             currentTheme.onThemeChanged();
1324         foreach(w; _windowMap)
1325             w.dispatchThemeChanged();
1326     }
1327 
1328     /// list of windows for deferred destroy in message loop
1329     Win32Window[] _windowsToDestroy;
1330 
1331     /// close window
1332     override void closeWindow(Window w) {
1333         Win32Window window = cast(Win32Window)w;
1334         _windowsToDestroy ~= window;
1335         SendMessage(window._hwnd, WM_CLOSE, 0, 0);
1336         //window
1337     }
1338 
1339     /// destroy window objects planned for destroy
1340     void destroyClosedWindows() {
1341         foreach(Window w; _windowsToDestroy) {
1342             destroy(w);
1343         }
1344         _windowsToDestroy.length = 0;
1345     }
1346 
1347     /// check has clipboard text
1348     override bool hasClipboardText(bool mouseBuffer = false) {
1349         if (mouseBuffer)
1350             return false;
1351         return (IsClipboardFormatAvailable(CF_UNICODETEXT) != 0);
1352     }
1353 
1354     /// retrieves text from clipboard (when mouseBuffer == true, use mouse selection clipboard - under linux)
1355     override dstring getClipboardText(bool mouseBuffer = false) {
1356         dstring res = null;
1357         if (mouseBuffer)
1358             return res; // not supporetd under win32
1359         if (!IsClipboardFormatAvailable(CF_UNICODETEXT))
1360             return res;
1361         if (!OpenClipboard(NULL))
1362             return res;
1363 
1364         HGLOBAL hglb = GetClipboardData(CF_UNICODETEXT);
1365         if (hglb != NULL)
1366         {
1367             LPWSTR lptstr = cast(LPWSTR)GlobalLock(hglb);
1368             if (lptstr != NULL)
1369             {
1370                 wstring w = fromWStringz(lptstr);
1371                 res = normalizeEndOfLineCharacters(toUTF32(w));
1372 
1373                 GlobalUnlock(hglb);
1374             }
1375         }
1376 
1377         CloseClipboard();
1378         //Log.d("getClipboardText(", res, ")");
1379         return res;
1380     }
1381 
1382     /// sets text to clipboard (when mouseBuffer == true, use mouse selection clipboard - under linux)
1383     override void setClipboardText(dstring text, bool mouseBuffer = false) {
1384         //Log.d("setClipboardText(", text, ")");
1385         if (text.length < 1 || mouseBuffer)
1386             return;
1387         if (!OpenClipboard(NULL))
1388             return;
1389         EmptyClipboard();
1390         wstring w = toUTF16(text);
1391         HGLOBAL hglbCopy = GlobalAlloc(GMEM_MOVEABLE,
1392                            cast(uint)((w.length + 1) * TCHAR.sizeof));
1393         if (hglbCopy == NULL) {
1394             CloseClipboard();
1395             return;
1396         }
1397         LPWSTR lptstrCopy = cast(LPWSTR)GlobalLock(hglbCopy);
1398         for (int i = 0; i < w.length; i++) {
1399             lptstrCopy[i] = w[i];
1400         }
1401         lptstrCopy[w.length] = 0;
1402         GlobalUnlock(hglbCopy);
1403         SetClipboardData(CF_UNICODETEXT, hglbCopy);
1404 
1405         CloseClipboard();
1406     }
1407 }
1408 
1409 extern(Windows)
1410 int DLANGUIWinMain(void* hInstance, void* hPrevInstance,
1411             char* lpCmdLine, int nCmdShow) {
1412     int result;
1413 
1414     try {
1415         Runtime.initialize();
1416 
1417         // call SetProcessDPIAware to support HI DPI - fix by Kapps
1418         auto ulib = LoadLibraryA("user32.dll");
1419         alias SetProcessDPIAwareFunc = int function();
1420         auto setDpiFunc = cast(SetProcessDPIAwareFunc)GetProcAddress(ulib, "SetProcessDPIAware");
1421         if(setDpiFunc) // Should never fail, but just in case...
1422             setDpiFunc();
1423 
1424         result = myWinMain(hInstance, hPrevInstance, lpCmdLine, nCmdShow);
1425         // TODO: fix hanging on multithreading app
1426         Runtime.terminate();
1427     }
1428     catch (Throwable e) // catch any uncaught exceptions
1429     {
1430         MessageBoxW(null, toUTF16z(e.toString()), "Error",
1431                     MB_OK | MB_ICONEXCLAMATION);
1432         result = 0;     // failed
1433     }
1434 
1435     return result;
1436 }
1437 
1438 extern(Windows)
1439 int DLANGUIWinMainProfile(string[] args)
1440 {
1441     int result;
1442 
1443     try {
1444         // call SetProcessDPIAware to support HI DPI - fix by Kapps
1445         auto ulib = LoadLibraryA("user32.dll");
1446         alias SetProcessDPIAwareFunc = int function();
1447         auto setDpiFunc = cast(SetProcessDPIAwareFunc)GetProcAddress(ulib, "SetProcessDPIAware");
1448         if(setDpiFunc) // Should never fail, but just in case...
1449             setDpiFunc();
1450 
1451         result = myWinMainProfile(args);
1452     }
1453     catch (Throwable e) // catch any uncaught exceptions
1454     {
1455         MessageBoxW(null, toUTF16z(e.toString()), "Error",
1456                     MB_OK | MB_ICONEXCLAMATION);
1457         result = 0;     // failed
1458     }
1459 
1460     return result;
1461 }
1462 
1463 /// split command line arg list; prepend with executable file name
1464 string[] splitCmdLine(string line) {
1465     string[] res;
1466     res ~= exeFilename();
1467     int start = 0;
1468     bool insideQuotes = false;
1469     for (int i = 0; i <= line.length; i++) {
1470         char ch = i < line.length ? line[i] : 0;
1471         if (ch == '\"') {
1472             if (insideQuotes) {
1473                 if (i > start)
1474                     res ~= line[start .. i];
1475                 start = i + 1;
1476                 insideQuotes = false;
1477             } else {
1478                 insideQuotes = true;
1479                 start = i + 1;
1480             }
1481         } else if (!insideQuotes && (ch == ' ' || ch == '\t' || ch == 0)) {
1482             if (i > start) {
1483                 res ~= line[start .. i];
1484             }
1485             start = i + 1;
1486         }
1487     }
1488     return res;
1489 }
1490 
1491 private __gshared Win32Platform w32platform;
1492 
1493 static if (ENABLE_OPENGL) {
1494     import bindbc.opengl;
1495     import bindbc.opengl.config : GLSupportVersion = GLSupport;
1496 
1497     void initOpenGL() {
1498         try {
1499             Log.d("Loading bindbc-opengl");
1500             auto glVer = loadOpenGL();
1501             if(glVer < GLSupportVersion.gl32) {
1502                 import std.format : format;
1503                 throw new Exception(format!"OpenGL 3.2 or higher is required, got: %s"(glVer));
1504             }
1505             Log.d("bindbc-opengl - loaded");
1506             //
1507             //// just to check OpenGL context
1508             //Log.i("Trying to setup OpenGL context");
1509             //Win32Window tmpWindow = new Win32Window(w32platform, ""d, null, 0);
1510             //destroy(tmpWindow);
1511             //if (openglEnabled)
1512             //    Log.i("OpenGL support is enabled");
1513             //else
1514             //    Log.w("OpenGL support is disabled");
1515             //// process messages
1516             //platform.enterMessageLoop();
1517         } catch (Exception e) {
1518             Log.e("Exception while trying to init OpenGL", e);
1519             setOpenglEnabled(false);
1520         }
1521     }
1522 }
1523 
1524 
1525 int myWinMain(void* hInstance, void* hPrevInstance, char* lpCmdLine, int iCmdShow)
1526 {
1527     initLogs();
1528 
1529     Log.d("myWinMain()");
1530     string basePath = exePath();
1531     Log.i("Current executable: ", exePath());
1532     string cmdline = fromStringz(lpCmdLine).dup;
1533     Log.i("Command line: ", cmdline);
1534     string[] args = splitCmdLine(cmdline);
1535     Log.i("Command line params: ", args);
1536 
1537     _cmdShow = iCmdShow;
1538     _hInstance = hInstance;
1539 
1540     Log.v("Creating platform");
1541     w32platform = new Win32Platform();
1542     Log.v("Registering window class");
1543     if (!w32platform.registerWndClass()) {
1544         MessageBoxA(null, "This program requires Windows NT!", "DLANGUI App".toStringz, MB_ICONERROR);
1545         return 0;
1546     }
1547     Platform.setInstance(w32platform);
1548 
1549     DOUBLE_CLICK_THRESHOLD_MS = GetDoubleClickTime();
1550 
1551     Log.v("Initializing font manager");
1552     if (!initFontManager()) {
1553         Log.e("******************************************************************");
1554         Log.e("No font files found!!!");
1555         Log.e("Currently, only hardcoded font paths implemented.");
1556         Log.e("Probably you can modify sdlapp.d to add some fonts for your system.");
1557         Log.e("******************************************************************");
1558         assert(false);
1559     }
1560     initResourceManagers();
1561 
1562     currentTheme = createDefaultTheme();
1563 
1564     Log.i("Entering UIAppMain: ", args);
1565     int result = -1;
1566     try {
1567         result = UIAppMain(args);
1568         Log.i("UIAppMain returned ", result);
1569     } catch (Exception e) {
1570         Log.e("Abnormal UIAppMain termination");
1571         Log.e("UIAppMain exception: ", e);
1572     }
1573 
1574     releaseResourcesOnAppExit();
1575 
1576     Log.d("Exiting main");
1577     debug {
1578         APP_IS_SHUTTING_DOWN = true;
1579         import core.memory : GC;
1580         Log.d("Calling GC.collect");
1581         GC.collect();
1582         if (DrawBuf.instanceCount)
1583             Log.d("Non-zero DrawBuf instance count when exiting: ", DrawBuf.instanceCount);
1584     }
1585 
1586     return result;
1587 }
1588 
1589 int myWinMainProfile(string[] args)
1590 {
1591     initLogs();
1592 
1593     Log.d("myWinMain()");
1594     string basePath = exePath();
1595     Log.i("Current executable: ", exePath());
1596     Log.i("Command line params: ", args);
1597 
1598     _cmdShow = SW_SHOW;
1599     _hInstance = GetModuleHandle(NULL);
1600 
1601     Log.v("Creating platform");
1602     w32platform = new Win32Platform();
1603     Log.v("Registering window class");
1604     if (!w32platform.registerWndClass()) {
1605         MessageBoxA(null, "This program requires Windows NT!", "DLANGUI App".toStringz, MB_ICONERROR);
1606         return 0;
1607     }
1608     Platform.setInstance(w32platform);
1609 
1610     DOUBLE_CLICK_THRESHOLD_MS = GetDoubleClickTime();
1611 
1612     Log.v("Initializing font manager");
1613     if (!initFontManager()) {
1614         Log.e("******************************************************************");
1615         Log.e("No font files found!!!");
1616         Log.e("Currently, only hardcoded font paths implemented.");
1617         Log.e("Probably you can modify sdlapp.d to add some fonts for your system.");
1618         Log.e("******************************************************************");
1619         assert(false);
1620     }
1621     initResourceManagers();
1622 
1623     currentTheme = createDefaultTheme();
1624 
1625     Log.i("Entering UIAppMain: ", args);
1626     int result = -1;
1627     try {
1628         result = UIAppMain(args);
1629         Log.i("UIAppMain returned ", result);
1630     } catch (Exception e) {
1631         Log.e("Abnormal UIAppMain termination");
1632         Log.e("UIAppMain exception: ", e);
1633     }
1634 
1635     releaseResourcesOnAppExit();
1636 
1637     Log.d("Exiting main");
1638     debug {
1639         APP_IS_SHUTTING_DOWN = true;
1640         import core.memory : GC;
1641         Log.d("Calling GC.collect");
1642         GC.collect();
1643         if (DrawBuf.instanceCount)
1644             Log.d("Non-zero DrawBuf instance count when exiting: ", DrawBuf.instanceCount);
1645     }
1646 
1647     return result;
1648 }
1649 
1650 
1651 extern(Windows)
1652 LRESULT WndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
1653 {
1654     HDC hdc;
1655     RECT rect;
1656 
1657     void * p = cast(void*)GetWindowLongPtr(hwnd, GWLP_USERDATA);
1658     Win32Window windowParam = p is null ? null : cast(Win32Window)(p);
1659     Win32Window window = w32platform.getWindow(hwnd);
1660     if (windowParam !is null && window !is null)
1661         assert(window is windowParam);
1662     if (window is null && windowParam !is null) {
1663         Log.e("Cannot find window in map by HWND");
1664     }
1665 
1666     switch (message)
1667     {
1668         case WM_CREATE:
1669             {
1670                 CREATESTRUCT * pcreateStruct = cast(CREATESTRUCT*)lParam;
1671                 window = cast(Win32Window)pcreateStruct.lpCreateParams;
1672                 if(window !is null)
1673                 {
1674                     void * ptr = cast(void*) window;
1675                     SetWindowLongPtr(hwnd, GWLP_USERDATA, cast(LONG_PTR)ptr);
1676                     window._hwnd = hwnd;
1677                     window.onCreate();
1678                 }
1679                 //window.handleUnknownWindowMessage(message, wParam, lParam);
1680             }
1681             return 0;
1682         case WM_DESTROY:
1683             if (window !is null) {
1684                 //window.handleUnknownWindowMessage(message, wParam, lParam);
1685                 window.onDestroy();
1686             }
1687             if (w32platform.windowCount == 0 && isInitialized)
1688                 PostQuitMessage(0);
1689             return 0;
1690         case WM_WINDOWPOSCHANGED:
1691             {
1692                 if (window !is null) {
1693 
1694                     if (IsIconic(hwnd)) {
1695                         window.handleWindowStateChange(WindowState.minimized);
1696                     }
1697                     else {
1698                         WINDOWPOS * pos = cast(WINDOWPOS*)lParam;
1699                         //Log.d("WM_WINDOWPOSCHANGED: ", *pos);
1700 
1701                         GetClientRect(hwnd, &rect);
1702                         int dx = rect.right - rect.left;
1703                         int dy = rect.bottom - rect.top;
1704                         WindowState state = WindowState.unspecified;
1705                         if (IsZoomed(hwnd))
1706                             state = WindowState.maximized;
1707                         else if (IsIconic(hwnd))
1708                             state = WindowState.minimized;
1709                         else if (IsWindowVisible(hwnd))
1710                             state = WindowState.normal;
1711                         else
1712                             state = WindowState.hidden;
1713                         window.handleWindowStateChange(state,
1714                             Rect(pos.x, pos.y, dx, dy));
1715                         if (window.width != dx || window.height != dy) {
1716                             window.onResize(dx, dy);
1717                             InvalidateRect(hwnd, null, FALSE);
1718                         }
1719                     }
1720                 }
1721             }
1722             return 0;
1723         case WM_ACTIVATE:
1724             {
1725                 if (window) {
1726                     if (wParam == WA_INACTIVE)
1727                         window.handleWindowActivityChange(false);
1728                     else if (wParam == WA_ACTIVE || wParam == WA_CLICKACTIVE)
1729                         window.handleWindowActivityChange(true);
1730                 }
1731             }
1732             return 0;
1733         case CUSTOM_MESSAGE_ID:
1734             if (window !is null) {
1735                 window.handlePostedEvent(cast(uint)lParam);
1736             }
1737             return 1;
1738         case WM_ERASEBKGND:
1739             // processed
1740             return 1;
1741         case WM_PAINT:
1742             {
1743                 if (window !is null)
1744                     window.onPaint();
1745             }
1746             return 0; // processed
1747         case WM_SETCURSOR:
1748             {
1749                 if (window !is null) {
1750                     if (LOWORD(lParam) == HTCLIENT) {
1751                         window.onSetCursorType();
1752                         return 1;
1753                     }
1754                 }
1755             }
1756             break;
1757         case WM_MOUSELEAVE:
1758         case WM_MOUSEMOVE:
1759         case WM_LBUTTONDOWN:
1760         case WM_MBUTTONDOWN:
1761         case WM_RBUTTONDOWN:
1762         case WM_LBUTTONUP:
1763         case WM_MBUTTONUP:
1764         case WM_RBUTTONUP:
1765         case WM_MOUSEWHEEL:
1766             if (window !is null) {
1767                 if (window.onMouse(message, cast(uint)wParam, cast(short)(lParam & 0xFFFF), cast(short)((lParam >> 16) & 0xFFFF)))
1768                     return 0; // processed
1769             }
1770             // not processed - default handling
1771             return DefWindowProc(hwnd, message, wParam, lParam);
1772         case WM_KEYDOWN:
1773         case WM_SYSKEYDOWN:
1774         case WM_KEYUP:
1775         case WM_SYSKEYUP:
1776             if (window !is null) {
1777                 int repeatCount = lParam & 0xFFFF;
1778                 WPARAM vk = wParam;
1779                 WPARAM new_vk = vk;
1780                 UINT scancode = (lParam & 0x00ff0000) >> 16;
1781                 int extended  = (lParam & 0x01000000) != 0;
1782                 switch (vk) {
1783                     case VK_SHIFT:
1784                         new_vk = MapVirtualKey(scancode, 3); //MAPVK_VSC_TO_VK_EX
1785                         break;
1786                     case VK_CONTROL:
1787                         new_vk = extended ? VK_RCONTROL : VK_LCONTROL;
1788                         break;
1789                     case VK_MENU:
1790                         new_vk = extended ? VK_RMENU : VK_LMENU;
1791                         break;
1792                     default:
1793                         // not a key we map from generic to left/right specialized
1794                         //  just return it.
1795                         new_vk = vk;
1796                         break;
1797                 }
1798 
1799                 if (window.onKey(message == WM_KEYDOWN || message == WM_SYSKEYDOWN ? KeyAction.KeyDown : KeyAction.KeyUp, cast(uint)new_vk, repeatCount, 0, message == WM_SYSKEYUP || message == WM_SYSKEYDOWN))
1800                     return 0; // processed
1801             }
1802             break;
1803         case WM_UNICHAR:
1804             if (window !is null) {
1805                 int repeatCount = lParam & 0xFFFF;
1806                 dchar ch = wParam == UNICODE_NOCHAR ? 0 : cast(uint)wParam;
1807                 debug(KeyInput) Log.d("WM_UNICHAR ", ch, " (", cast(int)ch, ")");
1808                 if (window.onKey(KeyAction.Text, cast(uint)wParam, repeatCount, ch))
1809                     return 1; // processed
1810                 return 1;
1811             }
1812             break;
1813         case WM_CHAR:
1814             if (window !is null) {
1815                 int repeatCount = lParam & 0xFFFF;
1816                 dchar ch = wParam == UNICODE_NOCHAR ? 0 : cast(uint)wParam;
1817                 debug(KeyInput) Log.d("WM_CHAR ", ch, " (", cast(int)ch, ")");
1818                 if (window.onKey(KeyAction.Text, cast(uint)wParam, repeatCount, ch))
1819                     return 1; // processed
1820                 return 1;
1821             }
1822             break;
1823         case WM_TIMER:
1824             if (window !is null) {
1825                 window.handleTimer(wParam);
1826                 return 0;
1827             }
1828             break;
1829         case WM_DROPFILES:
1830             if (window !is null) {
1831                 HDROP hdrop = cast(HDROP)wParam;
1832                 string[] files;
1833                 wchar[] buf;
1834                 auto count = DragQueryFileW(hdrop, 0xFFFFFFFF, cast(wchar*)NULL, 0);
1835                 for (int i = 0; i < count; i++) {
1836                     auto sz = DragQueryFileW(hdrop, i, cast(wchar*)NULL, 0);
1837                     buf.length = sz + 2;
1838                     sz = DragQueryFileW(hdrop, i, buf.ptr, sz + 1);
1839                     files ~= toUTF8(buf[0..sz]);
1840                 }
1841                 if (files.length)
1842                     window.handleDroppedFiles(files);
1843                 DragFinish(hdrop);
1844             }
1845             return 0;
1846         case WM_CLOSE:
1847             if (window !is null) {
1848                 bool canClose = window.handleCanClose();
1849                 if (!canClose) {
1850                     Log.d("WM_CLOSE: canClose is false");
1851                     return 0; // prevent closing
1852                 }
1853                 Log.d("WM_CLOSE: closing window ");
1854                 //destroy(window);
1855             }
1856             // default handler inside DefWindowProc will close window
1857             break;
1858         case WM_GETMINMAXINFO:
1859         case WM_NCCREATE:
1860         case WM_NCCALCSIZE:
1861         default:
1862             //Log.d("Unhandled message ", message);
1863             break;
1864     }
1865     if (window)
1866         return window.handleUnknownWindowMessage(message, wParam, lParam);
1867     return DefWindowProc(hwnd, message, wParam, lParam);
1868 }
1869 
1870 //===========================================
1871 // end of version(Windows)
1872 //===========================================
1873