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 __gshared HINSTANCE _hInstance;
63 __gshared int _cmdShow;
64 
65 static if (ENABLE_OPENGL) {
66     bool setupPixelFormat(HDC hDC)
67     {
68         PIXELFORMATDESCRIPTOR pfd = {
69             PIXELFORMATDESCRIPTOR.sizeof,  /* size */
70             1,                              /* version */
71             PFD_SUPPORT_OPENGL |
72                 PFD_DRAW_TO_WINDOW |
73                 PFD_DOUBLEBUFFER,               /* support double-buffering */
74             PFD_TYPE_RGBA,                  /* color type */
75             16,                             /* prefered color depth */
76             0, 0, 0, 0, 0, 0,               /* color bits (ignored) */
77             0,                              /* no alpha buffer */
78             0,                              /* alpha bits (ignored) */
79             0,                              /* no accumulation buffer */
80             0, 0, 0, 0,                     /* accum bits (ignored) */
81             16,                             /* depth buffer */
82             0,                              /* no stencil buffer */
83             0,                              /* no auxiliary buffers */
84             0,                              /* main layer PFD_MAIN_PLANE */
85             0,                              /* reserved */
86             0, 0, 0,                        /* no layer, visible, damage masks */
87         };
88         int pixelFormat;
89 
90         pixelFormat = ChoosePixelFormat(hDC, &pfd);
91         if (pixelFormat == 0) {
92             Log.e("ChoosePixelFormat failed.");
93             return false;
94         }
95 
96         if (SetPixelFormat(hDC, pixelFormat, &pfd) != TRUE) {
97             Log.e("SetPixelFormat failed.");
98             return false;
99         }
100         return true;
101     }
102 
103     HPALETTE setupPalette(HDC hDC)
104     {
105         import core.stdc.stdlib;
106         HPALETTE hPalette = NULL;
107         int pixelFormat = GetPixelFormat(hDC);
108         PIXELFORMATDESCRIPTOR pfd;
109         LOGPALETTE* pPal;
110         int paletteSize;
111 
112         DescribePixelFormat(hDC, pixelFormat, PIXELFORMATDESCRIPTOR.sizeof, &pfd);
113 
114         if (pfd.dwFlags & PFD_NEED_PALETTE) {
115             paletteSize = 1 << pfd.cColorBits;
116         } else {
117             return null;
118         }
119 
120         pPal = cast(LOGPALETTE*)
121             malloc(LOGPALETTE.sizeof + paletteSize * PALETTEENTRY.sizeof);
122         pPal.palVersion = 0x300;
123         pPal.palNumEntries = cast(ushort)paletteSize;
124 
125         /* build a simple RGB color palette */
126         {
127             int redMask = (1 << pfd.cRedBits) - 1;
128             int greenMask = (1 << pfd.cGreenBits) - 1;
129             int blueMask = (1 << pfd.cBlueBits) - 1;
130             int i;
131 
132             for (i=0; i<paletteSize; ++i) {
133                 pPal.palPalEntry[i].peRed = cast(ubyte)(
134                     (((i >> pfd.cRedShift) & redMask) * 255) / redMask);
135                 pPal.palPalEntry[i].peGreen = cast(ubyte)(
136                     (((i >> pfd.cGreenShift) & greenMask) * 255) / greenMask);
137                 pPal.palPalEntry[i].peBlue = cast(ubyte)(
138                     (((i >> pfd.cBlueShift) & blueMask) * 255) / blueMask);
139                 pPal.palPalEntry[i].peFlags = 0;
140             }
141         }
142 
143         hPalette = CreatePalette(pPal);
144         free(pPal);
145 
146         if (hPalette) {
147             SelectPalette(hDC, hPalette, FALSE);
148             RealizePalette(hDC);
149         }
150 
151         return hPalette;
152     }
153 
154     private __gshared bool DERELICT_GL3_RELOADED = false;
155 }
156 
157 const uint CUSTOM_MESSAGE_ID = WM_USER + 1;
158 
159 static if (ENABLE_OPENGL) {
160 
161     /// Shared opengl context helper
162     struct SharedGLContext {
163         import derelict.opengl3.wgl;
164 
165         HGLRC _hGLRC; // opengl context
166         HPALETTE _hPalette;
167         bool _error;
168         /// Init OpenGL context, if not yet initialized
169         bool init(HDC hDC) {
170             if (_hGLRC) {
171                 // just setup pixel format
172                 if (setupPixelFormat(hDC)) {
173                     Log.i("OpenGL context already exists. Setting pixel format.");
174                 } else {
175                     Log.e("Cannot setup pixel format");
176                 }
177                 return true;
178             }
179             if (_error)
180                 return false;
181             if (setupPixelFormat(hDC)) {
182                 _hPalette = setupPalette(hDC);
183                 _hGLRC = wglCreateContext(hDC);
184                 if (_hGLRC) {
185                     bind(hDC);
186                     bool initialized = initGLSupport(Platform.instance.GLVersionMajor < 3);
187                     unbind(hDC);
188                     if (!initialized) {
189                         uninit();
190                         Log.e("Failed to init OpenGL shaders");
191                         _error = true;
192                         return false;
193                     }
194                     return true;
195                 } else {
196                     _error = true;
197                     return false;
198                 }
199             } else {
200                 Log.e("Cannot setup pixel format");
201                 _error = true;
202                 return false;
203             }
204         }
205         void uninit() {
206             if (_hGLRC) {
207                 wglDeleteContext(_hGLRC);
208                 _hGLRC = null;
209             }
210         }
211         /// make this context current for DC
212         void bind(HDC hDC) {
213             if (!wglMakeCurrent(hDC, _hGLRC)) {
214                 import std.string : format;
215                 Log.e("wglMakeCurrent is failed. GetLastError=%x".format(GetLastError()));
216             }
217         }
218         /// make null context current for DC
219         void unbind(HDC hDC) {
220             //wglMakeCurrent(hDC, null);
221             wglMakeCurrent(null, null);
222         }
223         void swapBuffers(HDC hDC) {
224             SwapBuffers(hDC);
225         }
226     }
227 
228     /// OpenGL context to share between windows
229     __gshared SharedGLContext sharedGLContext;
230 }
231 
232 interface UnknownWindowMessageHandler {
233     /// return true if message is handled, put return value into result
234     bool onUnknownWindowMessage(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam, ref LRESULT result);
235 }
236 
237 class Win32Window : Window {
238     Win32Platform _platform;
239 
240     HWND _hwnd;
241     dstring _caption;
242     Win32ColorDrawBuf _drawbuf;
243     bool useOpengl;
244 
245     /// win32 only - return window handle
246     @property HWND windowHandle() {
247         return _hwnd;
248     }
249 
250     this(Win32Platform platform, dstring windowCaption, Window parent, uint flags, uint width = 0, uint height = 0) {
251         Win32Window w32parent = cast(Win32Window)parent;
252         HWND parenthwnd = w32parent ? w32parent._hwnd : null;
253         _dx = width;
254         _dy = height;
255         if (!_dx)
256             _dx = 600;
257         if (!_dy)
258             _dy = 400;
259         _platform = platform;
260         _caption = windowCaption;
261         _flags = flags;
262         uint ws = WS_CLIPCHILDREN | WS_CLIPSIBLINGS;
263         if (flags & WindowFlag.Resizable)
264             ws |= WS_OVERLAPPEDWINDOW;
265         else
266             ws |= WS_OVERLAPPED | WS_CAPTION | WS_CAPTION | WS_BORDER | WS_SYSMENU;
267         //if (flags & WindowFlag.Fullscreen)
268         //    ws |= SDL_WINDOW_FULLSCREEN;
269         _hwnd = CreateWindowW(toUTF16z(WIN_CLASS_NAME),      // window class name
270                             toUTF16z(windowCaption),  // window caption
271                             ws,  // window style
272                             CW_USEDEFAULT,        // initial x position
273                             CW_USEDEFAULT,        // initial y position
274                             _dx,        // initial x size
275                             _dy,        // initial y size
276                             parenthwnd,                 // parent window handle
277                             null,                 // window menu handle
278                             _hInstance,           // program instance handle
279                             cast(void*)this);                // creation parameters
280         static if (ENABLE_OPENGL) {
281             /* initialize OpenGL rendering */
282             HDC hDC = GetDC(_hwnd);
283 
284             if (openglEnabled) {
285                 useOpengl = sharedGLContext.init(hDC);
286             }
287         }
288     }
289 
290     static if (ENABLE_OPENGL) {
291         private void paintUsingOpenGL() {
292             // hack to stop infinite WM_PAINT loop
293             PAINTSTRUCT ps;
294             HDC hdc2 = BeginPaint(_hwnd, &ps);
295             EndPaint(_hwnd, &ps);
296 
297 
298             import derelict.opengl3.gl3;
299             import derelict.opengl3.wgl;
300             import dlangui.graphics.gldrawbuf;
301             //Log.d("onPaint() start drawing opengl viewport: ", _dx, "x", _dy);
302             //PAINTSTRUCT ps;
303             //HDC hdc = BeginPaint(_hwnd, &ps);
304             //scope(exit) EndPaint(_hwnd, &ps);
305             HDC hdc = GetDC(_hwnd);
306             sharedGLContext.bind(hdc);
307             //_glSupport = _gl;
308             glDisable(GL_DEPTH_TEST);
309             glViewport(0, 0, _dx, _dy);
310             float a = 1.0f;
311             float r = ((_backgroundColor >> 16) & 255) / 255.0f;
312             float g = ((_backgroundColor >> 8) & 255) / 255.0f;
313             float b = ((_backgroundColor >> 0) & 255) / 255.0f;
314             glClearColor(r, g, b, a);
315             glClear(GL_COLOR_BUFFER_BIT);
316 
317             GLDrawBuf buf = new GLDrawBuf(_dx, _dy, false);
318             buf.beforeDrawing();
319             static if (false) {
320                 // for testing for render
321                 buf.fillRect(Rect(100, 100, 200, 200), 0x704020);
322                 buf.fillRect(Rect(40, 70, 100, 120), 0x000000);
323                 buf.fillRect(Rect(80, 80, 150, 150), 0x80008000); // green
324                 drawableCache.get("exit").drawTo(buf, Rect(300, 100, 364, 164));
325                 drawableCache.get("btn_default_pressed").drawTo(buf, Rect(300, 200, 564, 264));
326                 drawableCache.get("btn_default_normal").drawTo(buf, Rect(300, 0, 400, 50));
327                 drawableCache.get("btn_default_selected").drawTo(buf, Rect(0, 0, 100, 50));
328                 FontRef fnt = currentTheme.font;
329                 fnt.drawText(buf, 40, 40, "Some Text 1234567890 !@#$^*", 0x80FF0000);
330             } else {
331                 onDraw(buf);
332             }
333             buf.afterDrawing();
334             sharedGLContext.swapBuffers(hdc);
335             //sharedGLContext.unbind(hdc);
336             destroy(buf);
337         }
338     }
339 
340     ~this() {
341         debug Log.d("Window destructor");
342         if (_drawbuf) {
343             destroy(_drawbuf);
344             _drawbuf = null;
345         }
346             
347         /*
348         static if (ENABLE_OPENGL) {
349             import derelict.opengl3.wgl;
350             if (_hGLRC) {
351                 //glSupport.uninitShaders();
352                 //destroy(_glSupport);
353                 //_glSupport = null;
354                 //_gl = null;
355                 wglMakeCurrent (null, null) ;
356                 wglDeleteContext(_hGLRC);
357                 _hGLRC = null;
358             }
359         }
360         */
361         if (_hwnd)
362             DestroyWindow(_hwnd);
363         _hwnd = null;
364     }
365 
366     /// post event to handle in UI thread (this method can be used from background thread)
367     override void postEvent(CustomEvent event) {
368         super.postEvent(event);
369         PostMessageW(_hwnd, CUSTOM_MESSAGE_ID, 0, event.uniqueId);
370     }
371 
372     /// set handler for files dropped to app window
373     override @property Window onFilesDropped(void delegate(string[]) handler) { 
374         super.onFilesDropped(handler);
375         DragAcceptFiles(_hwnd, handler ? TRUE : FALSE);
376         return this; 
377     }
378 
379     private long _nextExpectedTimerTs;
380     private UINT_PTR _timerId = 1;
381 
382     /// schedule timer for interval in milliseconds - call window.onTimer when finished
383     override protected void scheduleSystemTimer(long intervalMillis) {
384         if (intervalMillis < 10)
385             intervalMillis = 10;
386         long nextts = currentTimeMillis + intervalMillis;
387         if (_timerId && _nextExpectedTimerTs && _nextExpectedTimerTs < nextts + 10)
388             return; // don't reschedule timer, timer event will be received soon
389         if (_hwnd) {
390             //_timerId = 
391             SetTimer(_hwnd, _timerId, cast(uint)intervalMillis, null);
392             _nextExpectedTimerTs = nextts;
393         }
394     }
395 
396     void handleTimer(UINT_PTR timerId) {
397         //Log.d("handleTimer id=", timerId);
398         if (timerId == _timerId) {
399             KillTimer(_hwnd, timerId);
400             //_timerId = 0;
401             _nextExpectedTimerTs = 0;
402             onTimer();
403         }
404     }
405 
406     /// custom window message handler
407     Signal!UnknownWindowMessageHandler onUnknownWindowMessage;
408     private LRESULT handleUnknownWindowMessage(UINT message, WPARAM wParam, LPARAM lParam) {
409         if (onUnknownWindowMessage.assigned) {
410             LRESULT res;
411             if (onUnknownWindowMessage(_hwnd, message, wParam, lParam, res))
412                 return res;
413         }
414         return DefWindowProc(_hwnd, message, wParam, lParam);
415     }
416 
417     Win32ColorDrawBuf getDrawBuf() {
418         //RECT rect;
419         //GetClientRect(_hwnd, &rect);
420         //int dx = rect.right - rect.left;
421         //int dy = rect.bottom - rect.top;
422         if (_drawbuf is null)
423             _drawbuf = new Win32ColorDrawBuf(_dx, _dy);
424         else
425             _drawbuf.resize(_dx, _dy);
426         _drawbuf.resetClipping();
427         return _drawbuf;
428     }
429     override void show() {
430         if (!_mainWidget) {
431             Log.e("Window is shown without main widget");
432             _mainWidget = new Widget();
433         }
434         ReleaseCapture();
435         if (!(_flags & WindowFlag.Resizable) && _mainWidget) {
436             _mainWidget.measure(SIZE_UNSPECIFIED, SIZE_UNSPECIFIED);
437             int dx = _mainWidget.measuredWidth;
438             int dy = _mainWidget.measuredHeight;
439             dx += 2 * GetSystemMetrics(SM_CXDLGFRAME);
440             dy += GetSystemMetrics(SM_CYCAPTION) + 2 * GetSystemMetrics(SM_CYDLGFRAME);
441             // TODO: ensure size less than screen size
442             SetWindowPos(_hwnd, HWND_TOP, 0, 0, dx, dy, SWP_NOMOVE| SWP_SHOWWINDOW);
443         } else {
444             ShowWindow(_hwnd, SW_SHOWNORMAL);
445         }
446         if (_mainWidget)
447             _mainWidget.setFocus();
448         SetFocus(_hwnd);
449         //UpdateWindow(_hwnd);
450     }
451 
452     override @property dstring windowCaption() {
453         return _caption;
454     }
455 
456     override @property void windowCaption(dstring caption) {
457         _caption = caption;
458         if (_hwnd) {
459             Log.d("windowCaption ", caption);
460             SetWindowTextW(_hwnd, toUTF16z(_caption));
461         }
462     }
463 
464     /// change window state, position, or size; returns true if successful, false if not supported by platform
465     override bool setWindowState(WindowState newState, bool activate = false, Rect newWindowRect = RECT_VALUE_IS_NOT_SET) {
466         if (!_hwnd)
467             return false;
468         bool res = false;
469         // change state and activate support
470         switch(newState) {
471             case WindowState.unspecified:
472                 if (activate) {
473                     switch (_windowState) {
474                         case WindowState.hidden:
475                             // show hidden window
476                             ShowWindow(_hwnd, SW_SHOW);
477                             res = true;
478                             break;
479                         case WindowState.normal:
480                             ShowWindow(_hwnd, SW_SHOWNORMAL);
481                             res = true;
482                             break;
483                         case WindowState.minimized:
484                             ShowWindow(_hwnd, SW_SHOWMINIMIZED);
485                             res = true;
486                             break;
487                         case WindowState.maximized:
488                             ShowWindow(_hwnd, SW_SHOWMAXIMIZED);
489                             res = true;
490                             break;
491                         default:
492                             break;
493                     }
494                 }
495                 return true;
496             case WindowState.maximized:
497                 if (_windowState != WindowState.maximized || activate)
498                     ShowWindow(_hwnd, activate ? SW_SHOWMAXIMIZED : SW_MAXIMIZE);
499                 return true;
500             case WindowState.minimized:
501                 if (_windowState != WindowState.minimized || activate)
502                     ShowWindow(_hwnd, activate ? SW_SHOWMINIMIZED : SW_MINIMIZE);
503                 return true;
504             case WindowState.hidden:
505                 if (_windowState != WindowState.hidden)
506                     ShowWindow(_hwnd, SW_HIDE);
507                 return true;
508             case WindowState.normal:
509                 if (_windowState != WindowState.normal || activate) {
510                     ShowWindow(_hwnd, activate ? SW_SHOWNORMAL : SW_SHOWNA); // SW_RESTORE
511                 }
512                 res = true;
513                 break;
514             default:
515                 break;
516         }
517         // change size and/or position
518         if (newWindowRect != RECT_VALUE_IS_NOT_SET && (newState == WindowState.normal || newState == WindowState.unspecified)) {
519             UINT flags = SWP_NOOWNERZORDER | SWP_NOZORDER;
520             if (!activate)
521                 flags |= SWP_NOACTIVATE;
522             if (newWindowRect.top == int.min || newWindowRect.left == int.min) {
523                 // no position specified
524                 if (newWindowRect.bottom != int.min && newWindowRect.right != int.min) {
525                     // change size only
526                     SetWindowPos(_hwnd, NULL, 0, 0, newWindowRect.right, newWindowRect.bottom, flags | SWP_NOMOVE);
527                     return true;
528                 }
529             } else {
530                 if (newWindowRect.bottom != int.min && newWindowRect.right != int.min) {
531                     // change size and position
532                     SetWindowPos(_hwnd, NULL, newWindowRect.left, newWindowRect.top, newWindowRect.width, newWindowRect.height, flags);
533                     return true;
534                 } else {
535                     // change position only
536                     SetWindowPos(_hwnd, NULL, newWindowRect.left, newWindowRect.top, 0, 0, flags | SWP_NOSIZE);
537                     return true;
538                 }
539             }
540         }
541         return res;
542     }
543 
544     void onCreate() {
545         Log.d("Window onCreate");
546         _platform.onWindowCreated(_hwnd, this);
547     }
548     void onDestroy() {
549         Log.d("Window onDestroy");
550         _platform.onWindowDestroyed(_hwnd, this);
551     }
552 
553     /// close window
554     override void close() {
555         Log.d("Window.close()");
556         _platform.closeWindow(this);
557     }
558 
559     HICON _icon;
560 
561     uint _cursorType;
562 
563     HANDLE[ushort] _cursorCache;
564 
565     HANDLE loadCursor(ushort id) {
566         if (id in _cursorCache)
567             return _cursorCache[id];
568         HANDLE h = LoadCursor(null, MAKEINTRESOURCE(id));
569         _cursorCache[id] = h;
570         return h;
571     }
572 
573     void onSetCursorType() {
574         HANDLE winCursor = null;
575         switch (_cursorType) with(CursorType)
576         {
577             case None:
578                 winCursor = null;
579                 break;
580             case Parent:
581                 break;
582             case Arrow:
583                 winCursor = loadCursor(IDC_ARROW);
584                 break;
585             case IBeam:
586                 winCursor = loadCursor(IDC_IBEAM);
587                 break;
588             case Wait:
589                 winCursor = loadCursor(IDC_WAIT);
590                 break;
591             case Crosshair:
592                 winCursor = loadCursor(IDC_CROSS);
593                 break;
594             case WaitArrow:
595                 winCursor = loadCursor(IDC_APPSTARTING);
596                 break;
597             case SizeNWSE:
598                 winCursor = loadCursor(IDC_SIZENWSE);
599                 break;
600             case SizeNESW:
601                 winCursor = loadCursor(IDC_SIZENESW);
602                 break;
603             case SizeWE:
604                 winCursor = loadCursor(IDC_SIZEWE);
605                 break;
606             case SizeNS:
607                 winCursor = loadCursor(IDC_SIZENS);
608                 break;
609             case SizeAll:
610                 winCursor = loadCursor(IDC_SIZEALL);
611                 break;
612             case No:
613                 winCursor = loadCursor(IDC_NO);
614                 break;
615             case Hand:
616                 winCursor = loadCursor(IDC_HAND);
617                 break;
618             default:
619                 break;
620         }
621         SetCursor(winCursor);
622     }
623 
624     /// sets cursor type for window
625     override protected void setCursorType(uint cursorType) {
626         // override to support different mouse cursors
627         _cursorType = cursorType;
628         onSetCursorType();
629     }
630 
631     /// sets window icon
632     @property override void windowIcon(DrawBufRef buf) {
633         if (_icon)
634             DestroyIcon(_icon);
635         _icon = null;
636         ColorDrawBuf icon = cast(ColorDrawBuf)buf.get;
637         if (!icon) {
638             Log.e("Trying to set null icon for window");
639             return;
640         }
641         Win32ColorDrawBuf resizedicon = new Win32ColorDrawBuf(icon, 32, 32);
642         resizedicon.invertAlpha();
643         ICONINFO ii;
644         HBITMAP mask = resizedicon.createTransparencyBitmap();
645         HBITMAP color = resizedicon.destroyLeavingBitmap();
646         ii.fIcon = TRUE;
647         ii.xHotspot = 0;
648         ii.yHotspot = 0;
649         ii.hbmMask = mask;
650         ii.hbmColor = color;
651         _icon = CreateIconIndirect(&ii);
652         if (_icon) {
653             SendMessageW(_hwnd, WM_SETICON, ICON_SMALL, cast(LPARAM)_icon);
654             SendMessageW(_hwnd, WM_SETICON, ICON_BIG, cast(LPARAM)_icon);
655         } else {
656             Log.e("failed to create icon");
657         }
658         if (mask)
659             DeleteObject(mask);
660         DeleteObject(color);
661     }
662 
663     private void paintUsingGDI() {
664         PAINTSTRUCT ps;
665         HDC hdc = BeginPaint(_hwnd, &ps);
666         scope(exit) EndPaint(_hwnd, &ps);
667 
668         Win32ColorDrawBuf buf = getDrawBuf();
669         buf.fill(_backgroundColor);
670         onDraw(buf);
671         buf.drawTo(hdc, 0, 0);
672     }
673 
674     void onPaint() {
675         debug(DebugRedraw) Log.d("onPaint()");
676         long paintStart = currentTimeMillis;
677         static if (ENABLE_OPENGL) {
678             if (useOpengl && sharedGLContext._hGLRC) {
679                 paintUsingOpenGL();
680             } else {
681                 paintUsingGDI();
682             }
683         } else {
684             paintUsingGDI();
685         }
686         long paintEnd = currentTimeMillis;
687         debug(DebugRedraw) Log.d("WM_PAINT handling took ", paintEnd - paintStart, " ms");
688     }
689 
690     protected ButtonDetails _lbutton;
691     protected ButtonDetails _mbutton;
692     protected ButtonDetails _rbutton;
693 
694     private void updateButtonsState(uint flags) {
695         if (!(flags & MK_LBUTTON) && _lbutton.isDown)
696             _lbutton.reset();
697         if (!(flags & MK_MBUTTON) && _mbutton.isDown)
698             _mbutton.reset();
699         if (!(flags & MK_RBUTTON) && _rbutton.isDown)
700             _rbutton.reset();
701     }
702 
703     private bool _mouseTracking;
704     private bool onMouse(uint message, uint flags, short x, short y) {
705         debug(DebugMouseEvents) Log.d("Win32 Mouse Message ", message, " flags=", flags, " x=", x, " y=", y);
706         MouseButton button = MouseButton.None;
707         MouseAction action = MouseAction.ButtonDown;
708         ButtonDetails * pbuttonDetails = null;
709         short wheelDelta = 0;
710         switch (message) {
711             case WM_MOUSEMOVE:
712                 action = MouseAction.Move;
713                 updateButtonsState(flags);
714                 break;
715             case WM_LBUTTONDOWN:
716                 action = MouseAction.ButtonDown;
717                 button = MouseButton.Left;
718                 pbuttonDetails = &_lbutton;
719                 SetFocus(_hwnd);
720                 break;
721             case WM_RBUTTONDOWN:
722                 action = MouseAction.ButtonDown;
723                 button = MouseButton.Right;
724                 pbuttonDetails = &_rbutton;
725                 SetFocus(_hwnd);
726                 break;
727             case WM_MBUTTONDOWN:
728                 action = MouseAction.ButtonDown;
729                 button = MouseButton.Middle;
730                 pbuttonDetails = &_mbutton;
731                 SetFocus(_hwnd);
732                 break;
733             case WM_LBUTTONUP:
734                 action = MouseAction.ButtonUp;
735                 button = MouseButton.Left;
736                 pbuttonDetails = &_lbutton;
737                 break;
738             case WM_RBUTTONUP:
739                 action = MouseAction.ButtonUp;
740                 button = MouseButton.Right;
741                 pbuttonDetails = &_rbutton;
742                 break;
743             case WM_MBUTTONUP:
744                 action = MouseAction.ButtonUp;
745                 button = MouseButton.Middle;
746                 pbuttonDetails = &_mbutton;
747                 break;
748             case WM_MOUSELEAVE:
749                 debug(DebugMouseEvents) Log.d("WM_MOUSELEAVE");
750                 action = MouseAction.Leave;
751                 break;
752             case WM_MOUSEWHEEL:
753                 {
754                     action = MouseAction.Wheel;
755                     wheelDelta = (cast(short)(flags >> 16)) / 120;
756                     POINT pt;
757                     pt.x = x;
758                     pt.y = y;
759                     ScreenToClient(_hwnd, &pt);
760                     x = cast(short)pt.x;
761                     y = cast(short)pt.y;
762                 }
763                 break;
764             default:
765                 // unsupported event
766                 return false;
767         }
768         if (action == MouseAction.ButtonDown) {
769             pbuttonDetails.down(x, y, cast(ushort)flags);
770         } else if (action == MouseAction.ButtonUp) {
771             pbuttonDetails.up(x, y, cast(ushort)flags);
772         }
773         if (((message == WM_MOUSELEAVE) || (x < 0 || y < 0 || x >= _dx || y >= _dy)) && _mouseTracking) {
774             if (!isMouseCaptured() || (!_lbutton.isDown && !_rbutton.isDown && !_mbutton.isDown)) {
775                 action = MouseAction.Leave;
776                 debug(DebugMouseEvents) Log.d("Win32Window.onMouse releasing capture");
777                 _mouseTracking = false;
778                 ReleaseCapture();
779             }
780         }
781         if (message != WM_MOUSELEAVE && !_mouseTracking) {
782             if (x >=0 && y >= 0 && x < _dx && y < _dy) {
783                 debug(DebugMouseEvents) Log.d("Win32Window.onMouse Setting capture");
784                 _mouseTracking = true;
785                 SetCapture(_hwnd);
786             }
787         }
788         MouseEvent event = new MouseEvent(action, button, cast(ushort)flags, x, y, wheelDelta);
789         event.lbutton = _lbutton;
790         event.rbutton = _rbutton;
791         event.mbutton = _mbutton;
792         bool res = dispatchMouseEvent(event);
793         if (res) {
794             //Log.v("Calling update() after mouse event");
795             update();
796         }
797         return res;
798     }
799 
800 
801     protected uint _keyFlags;
802 
803     protected void updateKeyFlags(KeyAction action, KeyFlag flag, uint preserveFlag) {
804         if (action == KeyAction.KeyDown)
805             _keyFlags |= flag;
806         else {
807             if (preserveFlag && (_keyFlags & preserveFlag) == preserveFlag) {
808                 // e.g. when both lctrl and rctrl are pressed, and lctrl is up, preserve rctrl flag
809                 _keyFlags = (_keyFlags & ~flag) | preserveFlag;
810             } else {
811                 _keyFlags &= ~flag;
812             }
813         }
814     }
815 
816     bool onKey(KeyAction action, uint keyCode, int repeatCount, dchar character = 0, bool syskey = false) {
817         KeyEvent event;
818         if (syskey)
819             _keyFlags |= KeyFlag.Alt;
820         //else
821         //    _keyFlags &= ~KeyFlag.Alt;
822         if (action == KeyAction.KeyDown || action == KeyAction.KeyUp) {
823             switch(keyCode) {
824                 case KeyCode.LSHIFT:
825                     updateKeyFlags(action, KeyFlag.LShift, KeyFlag.RShift);
826                     break;
827                 case KeyCode.RSHIFT:
828                     updateKeyFlags(action, KeyFlag.RShift, KeyFlag.LShift);
829                     break;
830                 case KeyCode.LCONTROL:
831                     updateKeyFlags(action, KeyFlag.LControl, KeyFlag.RControl);
832                     break;
833                 case KeyCode.RCONTROL:
834                     updateKeyFlags(action, KeyFlag.RControl, KeyFlag.LControl);
835                     break;
836                 case KeyCode.LALT:
837                     updateKeyFlags(action, KeyFlag.LAlt, KeyFlag.RAlt);
838                     break;
839                 case KeyCode.RALT:
840                     updateKeyFlags(action, KeyFlag.RAlt, KeyFlag.LAlt);
841                     break;
842                 case KeyCode.LWIN:
843                     updateKeyFlags(action, KeyFlag.LMenu, KeyFlag.RMenu);
844                     break;
845                 case KeyCode.RWIN:
846                     updateKeyFlags(action, KeyFlag.RMenu, KeyFlag.LMenu);
847                     break;
848                 //case KeyCode.WIN:
849                 case KeyCode.CONTROL:
850                 case KeyCode.SHIFT:
851                 case KeyCode.ALT:
852                 //case KeyCode.WIN:
853                     break;
854                 default:
855                     updateKeyFlags((GetKeyState(VK_LCONTROL) & 0x8000) != 0 ? KeyAction.KeyDown : KeyAction.KeyUp, KeyFlag.LControl, KeyFlag.RControl);
856                     updateKeyFlags((GetKeyState(VK_RCONTROL) & 0x8000) != 0 ? KeyAction.KeyDown : KeyAction.KeyUp, KeyFlag.RControl, KeyFlag.LControl);
857                     updateKeyFlags((GetKeyState(VK_LSHIFT) & 0x8000) != 0 ? KeyAction.KeyDown : KeyAction.KeyUp, KeyFlag.LShift, KeyFlag.RShift);
858                     updateKeyFlags((GetKeyState(VK_RSHIFT) & 0x8000) != 0 ? KeyAction.KeyDown : KeyAction.KeyUp, KeyFlag.RShift, KeyFlag.LShift);
859                     updateKeyFlags((GetKeyState(VK_LWIN) & 0x8000) != 0 ? KeyAction.KeyDown : KeyAction.KeyUp, KeyFlag.LMenu, KeyFlag.RMenu);
860                     updateKeyFlags((GetKeyState(VK_RWIN) & 0x8000) != 0 ? KeyAction.KeyDown : KeyAction.KeyUp, KeyFlag.RMenu, KeyFlag.LMenu);
861                     updateKeyFlags((GetKeyState(VK_LMENU) & 0x8000) != 0 ? KeyAction.KeyDown : KeyAction.KeyUp, KeyFlag.LAlt, KeyFlag.RAlt);
862                     updateKeyFlags((GetKeyState(VK_RMENU) & 0x8000) != 0 ? KeyAction.KeyDown : KeyAction.KeyUp, KeyFlag.RAlt, KeyFlag.LAlt);
863                     if (action == KeyAction.KeyDown)
864                         Log.d("keydown, keyFlags=", _keyFlags);
865                     break;
866             }
867             //updateKeyFlags((GetKeyState(VK_CONTROL) & 0x8000) != 0 ? KeyAction.KeyDown : KeyAction.KeyUp, KeyFlag.Control);
868             //updateKeyFlags((GetKeyState(VK_SHIFT) & 0x8000) != 0 ? KeyAction.KeyDown : KeyAction.KeyUp, KeyFlag.Shift);
869             //updateKeyFlags((GetKeyState(VK_MENU) & 0x8000) != 0 ? KeyAction.KeyDown : KeyAction.KeyUp, KeyFlag.Alt);
870             if (keyCode == 0xBF)
871                 keyCode = KeyCode.KEY_DIVIDE;
872             event = new KeyEvent(action, keyCode, _keyFlags);
873         } else if (action == KeyAction.Text && character != 0) {
874             bool ctrlAZKeyCode = (character >= 1 && character <= 26);
875             if ((_keyFlags & (KeyFlag.Control | KeyFlag.Alt)) && ctrlAZKeyCode) {
876                 event = new KeyEvent(action, KeyCode.KEY_A + character - 1, _keyFlags);
877             } else {
878                 dchar[] text;
879                 text ~= character;
880                 event = new KeyEvent(action, 0, _keyFlags, cast(dstring)text);
881             }
882         }
883         bool res = false;
884         if (event !is null) {
885             res = dispatchKeyEvent(event);
886         }
887         if (res) {
888             debug(DebugRedraw) Log.d("Calling update() after key event");
889             update();
890         }
891         return res;
892     }
893 
894     /// request window redraw
895     override void invalidate() {
896         InvalidateRect(_hwnd, null, FALSE);
897         //UpdateWindow(_hwnd);
898     }
899 
900     /// after drawing, call to schedule redraw if animation is active
901     override void scheduleAnimation() {
902         invalidate();
903     }
904 
905 }
906 
907 class Win32Platform : Platform {
908     this() {
909     }
910     bool registerWndClass() {
911         //MSG  msg;
912         WNDCLASSW wndclass;
913 
914         wndclass.style         = CS_HREDRAW | CS_VREDRAW | CS_OWNDC;
915         wndclass.lpfnWndProc   = cast(WNDPROC)&WndProc;
916         wndclass.cbClsExtra    = 0;
917         wndclass.cbWndExtra    = 0;
918         wndclass.hInstance     = _hInstance;
919         wndclass.hIcon         = LoadIcon(null, IDI_APPLICATION);
920         wndclass.hCursor       = LoadCursor(null, IDC_ARROW);
921         wndclass.hbrBackground = cast(HBRUSH)GetStockObject(WHITE_BRUSH);
922         wndclass.lpszMenuName  = null;
923         wndclass.lpszClassName = toUTF16z(WIN_CLASS_NAME);
924 
925         if(!RegisterClassW(&wndclass))
926         {
927             return false;
928         }
929         HDC dc = CreateCompatibleDC(NULL);
930         SCREEN_DPI = GetDeviceCaps(dc, LOGPIXELSY);
931         DeleteObject(dc);
932 
933         return true;
934     }
935     override int enterMessageLoop() {
936         MSG  msg;
937         while (GetMessage(&msg, null, 0, 0))
938         {
939             TranslateMessage(&msg);
940             DispatchMessage(&msg);
941             destroyClosedWindows();
942         }
943         return cast(int)msg.wParam;
944     }
945 
946     private Win32Window[ulong] _windowMap;
947     private Win32Window[] _windowList;
948 
949     /// add window to window map
950     void onWindowCreated(HWND hwnd, Win32Window window) {
951         Log.v("created window, adding to map");
952         _windowMap[cast(ulong)hwnd] = window;
953         _windowList ~= window;
954     }
955     /// remove window from window map, returns true if there are some more windows left in map
956     bool onWindowDestroyed(HWND hwnd, Win32Window window) {
957         Log.v("destroyed window, removing from map");
958         Win32Window wnd = getWindow(hwnd);
959         if (wnd) {
960             _windowMap.remove(cast(ulong)hwnd);
961             _windowsToDestroy ~= window;
962             //destroy(window);
963         }
964         for (uint i = 0; i < _windowList.length; i++) {
965             if (window is _windowList[i]) {
966                 for (uint j = i; j + 1 < _windowList.length; j++)
967                     _windowList[j] = _windowList[j + 1];
968                 _windowList[$ - 1] = null;
969                 _windowList.length--;
970                 break;
971             }
972         }
973         return _windowMap.length > 0;
974     }
975     /// returns number of currently active windows
976     @property int windowCount() {
977         return cast(int)_windowMap.length;
978     }
979     /// returns window instance by HWND
980     Win32Window getWindow(HWND hwnd) {
981         if ((cast(ulong)hwnd) in _windowMap)
982             return _windowMap[cast(ulong)hwnd];
983         return null;
984     }
985     override Window createWindow(dstring windowCaption, Window parent, uint flags = WindowFlag.Resizable, uint width = 0, uint height = 0) {
986         Log.d("Platform.createWindow is called");
987         width = pointsToPixels(width);
988         height = pointsToPixels(height);
989         Log.v("Platform.createWindow : setDefaultLanguageAndThemeIfNecessary");
990         setDefaultLanguageAndThemeIfNecessary();
991         Log.v("Platform.createWindow : new Win32Window");
992         return new Win32Window(this, windowCaption, parent, flags, width, height);
993     }
994 
995     /// calls request layout for all windows
996     override void requestLayout() {
997         foreach(w; _windowMap) {
998             w.requestLayout();
999             w.invalidate();
1000         }
1001     }
1002 
1003     /// 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
1004     override bool hasModalWindowsAbove(Window w) {
1005         // override in platform specific class
1006         for (uint i = 0; i + 1 < _windowList.length; i++) {
1007             if (_windowList[i] is w) {
1008                 for (uint j = i + 1; j < _windowList.length; j++) {
1009                     if (_windowList[j].flags & WindowFlag.Modal)
1010                         return true;
1011                 }
1012                 return false;
1013             }
1014         }
1015         return false;
1016     }
1017 
1018     /// handle theme change: e.g. reload some themed resources
1019     override void onThemeChanged() {
1020         foreach(w; _windowMap)
1021             w.dispatchThemeChanged();
1022     }
1023 
1024     /// list of windows for deferred destroy in message loop
1025     Win32Window[] _windowsToDestroy;
1026 
1027     /// close window
1028     override void closeWindow(Window w) {
1029         Win32Window window = cast(Win32Window)w;
1030         _windowsToDestroy ~= window;
1031         SendMessage(window._hwnd, WM_CLOSE, 0, 0);
1032         //window
1033     }
1034 
1035     /// destroy window objects planned for destroy
1036     void destroyClosedWindows() {
1037         foreach(Window w; _windowsToDestroy) {
1038             destroy(w);
1039         }
1040         _windowsToDestroy.length = 0;
1041     }
1042 
1043     /// retrieves text from clipboard (when mouseBuffer == true, use mouse selection clipboard - under linux)
1044     override dstring getClipboardText(bool mouseBuffer = false) {
1045         dstring res = null;
1046         if (mouseBuffer)
1047             return res; // not supporetd under win32
1048         if (!IsClipboardFormatAvailable(CF_UNICODETEXT))
1049             return res; 
1050         if (!OpenClipboard(NULL)) 
1051             return res; 
1052 
1053         HGLOBAL hglb = GetClipboardData(CF_UNICODETEXT); 
1054         if (hglb != NULL) 
1055         { 
1056             LPWSTR lptstr = cast(LPWSTR)GlobalLock(hglb); 
1057             if (lptstr != NULL) 
1058             { 
1059                 wstring w = fromWStringz(lptstr);
1060                 res = toUTF32(w);
1061 
1062                 GlobalUnlock(hglb); 
1063             } 
1064         } 
1065 
1066         CloseClipboard();
1067         //Log.d("getClipboardText(", res, ")");
1068         return res;
1069     }
1070 
1071     /// sets text to clipboard (when mouseBuffer == true, use mouse selection clipboard - under linux)
1072     override void setClipboardText(dstring text, bool mouseBuffer = false) {
1073         //Log.d("setClipboardText(", text, ")");
1074         if (text.length < 1 || mouseBuffer)
1075             return;
1076         if (!OpenClipboard(NULL))
1077             return; 
1078         EmptyClipboard();
1079         wstring w = toUTF16(text);
1080         HGLOBAL hglbCopy = GlobalAlloc(GMEM_MOVEABLE, 
1081                            cast(uint)((w.length + 1) * TCHAR.sizeof)); 
1082         if (hglbCopy == NULL) { 
1083             CloseClipboard(); 
1084             return; 
1085         }
1086         LPWSTR lptstrCopy = cast(LPWSTR)GlobalLock(hglbCopy);
1087         for (int i = 0; i < w.length; i++) {
1088             lptstrCopy[i] = w[i];
1089         }
1090         lptstrCopy[w.length] = 0;
1091         GlobalUnlock(hglbCopy);
1092         SetClipboardData(CF_UNICODETEXT, hglbCopy); 
1093 
1094         CloseClipboard();
1095     }
1096 }
1097 
1098 extern(Windows)
1099 int DLANGUIWinMain(void* hInstance, void* hPrevInstance,
1100             char* lpCmdLine, int nCmdShow) {
1101     int result;
1102 
1103     try {
1104         Runtime.initialize();
1105 
1106         // call SetProcessDPIAware to support HI DPI - fix by Kapps
1107         auto ulib = LoadLibraryA("user32.dll");
1108         alias SetProcessDPIAwareFunc = int function();
1109         auto setDpiFunc = cast(SetProcessDPIAwareFunc)GetProcAddress(ulib, "SetProcessDPIAware");
1110         if(setDpiFunc) // Should never fail, but just in case...
1111             setDpiFunc();
1112 
1113         result = myWinMain(hInstance, hPrevInstance, lpCmdLine, nCmdShow);
1114         // TODO: fix hanging on multithreading app
1115         Runtime.terminate();
1116     }
1117     catch (Throwable e) // catch any uncaught exceptions
1118     {
1119         MessageBoxW(null, toUTF16z(e.toString()), "Error",
1120                     MB_OK | MB_ICONEXCLAMATION);
1121         result = 0;     // failed
1122     }
1123 
1124     return result;
1125 }
1126 
1127 string[] splitCmdLine(string line) {
1128     string[] res;
1129     int start = 0;
1130     bool insideQuotes = false;
1131     for (int i = 0; i <= line.length; i++) {
1132         char ch = i < line.length ? line[i] : 0;
1133         if (ch == '\"') {
1134             if (insideQuotes) {
1135                 if (i > start)
1136                     res ~= line[start .. i];
1137                 start = i + 1;
1138                 insideQuotes = false;
1139             } else {
1140                 insideQuotes = true;
1141                 start = i + 1;
1142             }
1143         } else if (!insideQuotes && (ch == ' ' || ch == '\t' || ch == 0)) {
1144             if (i > start) {
1145                 res ~= line[start .. i];
1146             }
1147             start = i + 1;
1148         }
1149     }
1150     return res;
1151 }
1152 
1153 private __gshared Win32Platform w32platform;
1154 
1155 static if (ENABLE_OPENGL) {
1156     import derelict.opengl3.gl3;
1157     import derelict.opengl3.gl;
1158 
1159     void initOpenGL() {
1160         try {
1161             Log.d("Loading Derelict GL");
1162             DerelictGL.load();
1163             DerelictGL3.load();
1164             Log.d("Derelict GL - loaded");
1165             //
1166             //// just to check OpenGL context
1167             //Log.i("Trying to setup OpenGL context");
1168             //Win32Window tmpWindow = new Win32Window(w32platform, ""d, null, 0);
1169             //destroy(tmpWindow);
1170             //if (openglEnabled)
1171             //    Log.i("OpenGL support is enabled");
1172             //else
1173             //    Log.w("OpenGL support is disabled");
1174             //// process messages
1175             //platform.enterMessageLoop();
1176         } catch (Exception e) {
1177             Log.e("Exception while trying to init OpenGL", e);
1178             setOpenglEnabled(false);
1179         }
1180     }
1181 }
1182 
1183 
1184 int myWinMain(void* hInstance, void* hPrevInstance, char* lpCmdLine, int iCmdShow)
1185 {
1186     initLogs();
1187 
1188     Log.d("myWinMain()");
1189     string basePath = exePath();
1190     Log.i("Current executable: ", exePath());
1191     string cmdline = fromStringz(lpCmdLine).dup;
1192     Log.i("Command line: ", cmdline);
1193     string[] args = splitCmdLine(cmdline);
1194     Log.i("Command line params: ", args);
1195 
1196     _cmdShow = iCmdShow;
1197     _hInstance = hInstance;
1198 
1199     Log.v("Creating platform");
1200     w32platform = new Win32Platform();
1201     Log.v("Registering window class");
1202     if (!w32platform.registerWndClass()) {
1203         MessageBoxA(null, "This program requires Windows NT!", "DLANGUI App".toStringz, MB_ICONERROR);
1204         return 0;
1205     }
1206     Platform.setInstance(w32platform);
1207 
1208     DOUBLE_CLICK_THRESHOLD_MS = GetDoubleClickTime();
1209 
1210     Log.v("Initializing font manager");
1211     if (!initFontManager()) {
1212         Log.e("******************************************************************");
1213         Log.e("No font files found!!!");
1214         Log.e("Currently, only hardcoded font paths implemented.");
1215         Log.e("Probably you can modify sdlapp.d to add some fonts for your system.");
1216         Log.e("******************************************************************");
1217         assert(false);
1218     }
1219     initResourceManagers();
1220 
1221     currentTheme = createDefaultTheme();
1222 
1223     static if (ENABLE_OPENGL) {
1224         initOpenGL();
1225     }
1226 
1227     // Load versions 1.2+ and all supported ARB and EXT extensions.
1228 
1229     Log.i("Entering UIAppMain: ", args);
1230     int result = -1;
1231     try {
1232         result = UIAppMain(args);
1233         Log.i("UIAppMain returned ", result);
1234     } catch (Exception e) {
1235         Log.e("Abnormal UIAppMain termination");
1236         Log.e("UIAppMain exception: ", e);
1237     }
1238 
1239     releaseResourcesOnAppExit();
1240 
1241     Log.d("Exiting main");
1242     debug {
1243         APP_IS_SHUTTING_DOWN = true;
1244         import core.memory : GC;
1245         Log.d("Calling GC.collect");
1246         GC.collect();
1247         Log.e("Non-zero DrawBuf instance count when exiting: ", DrawBuf.instanceCount);
1248     }
1249 
1250     return result;
1251 }
1252 
1253 
1254 extern(Windows)
1255 LRESULT WndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
1256 {
1257     HDC hdc;
1258     RECT rect;
1259 
1260     void * p = cast(void*)GetWindowLongPtr(hwnd, GWLP_USERDATA);
1261     Win32Window windowParam = p is null ? null : cast(Win32Window)(p);
1262     Win32Window window = w32platform.getWindow(hwnd);
1263     if (windowParam !is null && window !is null)
1264         assert(window is windowParam);
1265     if (window is null && windowParam !is null) {
1266         Log.e("Cannot find window in map by HWND");
1267     }
1268 
1269     switch (message)
1270     {
1271         case WM_CREATE:
1272             {
1273                 CREATESTRUCT * pcreateStruct = cast(CREATESTRUCT*)lParam;
1274                 window = cast(Win32Window)pcreateStruct.lpCreateParams;
1275                 void * ptr = cast(void*) window;
1276                 SetWindowLongPtr(hwnd, GWLP_USERDATA, cast(LONG_PTR)ptr);
1277                 window._hwnd = hwnd;
1278                 window.onCreate();
1279                 //window.handleUnknownWindowMessage(message, wParam, lParam);
1280             }
1281             return 0;
1282         case WM_DESTROY:
1283             if (window !is null) {
1284                 //window.handleUnknownWindowMessage(message, wParam, lParam);
1285                 window.onDestroy();
1286             }
1287             if (w32platform.windowCount == 0)
1288                 PostQuitMessage(0);
1289             return 0;
1290         case WM_WINDOWPOSCHANGED:
1291             {
1292                 if (window !is null) {
1293                     WINDOWPOS * pos = cast(WINDOWPOS*)lParam;
1294                     Log.d("WM_WINDOWPOSCHANGED: ", *pos);
1295                     GetClientRect(hwnd, &rect);
1296                     if (pos.x > -30000) {// to ignore minimized state
1297                         int dx = rect.right - rect.left;
1298                         int dy = rect.bottom - rect.top;
1299                         if (window.width != dx || window.height != dy) {
1300                             window.onResize(dx, dy);
1301                             InvalidateRect(hwnd, null, FALSE);
1302                         }
1303                     }
1304                 }
1305             }
1306             return 0;
1307         case CUSTOM_MESSAGE_ID:
1308             if (window !is null) {
1309                 window.handlePostedEvent(cast(uint)lParam);
1310             }
1311             return 1;
1312         case WM_ERASEBKGND:
1313             // processed
1314             return 1;
1315         case WM_PAINT:
1316             {
1317                 if (window !is null)
1318                     window.onPaint();
1319             }
1320             return 0; // processed
1321         case WM_SETCURSOR:
1322             {
1323                 if (window !is null) {
1324                     if (LOWORD(lParam) == HTCLIENT) {
1325                         window.onSetCursorType();
1326                         return 1;
1327                     }
1328                 }
1329             }
1330             break;
1331         case WM_MOUSELEAVE:
1332         case WM_MOUSEMOVE:
1333         case WM_LBUTTONDOWN:
1334         case WM_MBUTTONDOWN:
1335         case WM_RBUTTONDOWN:
1336         case WM_LBUTTONUP:
1337         case WM_MBUTTONUP:
1338         case WM_RBUTTONUP:
1339         case WM_MOUSEWHEEL:
1340             if (window !is null) {
1341                 if (window.onMouse(message, cast(uint)wParam, cast(short)(lParam & 0xFFFF), cast(short)((lParam >> 16) & 0xFFFF)))
1342                     return 0; // processed
1343             }
1344             // not processed - default handling
1345             return DefWindowProc(hwnd, message, wParam, lParam);
1346         case WM_KEYDOWN:
1347         case WM_SYSKEYDOWN:
1348         case WM_KEYUP:
1349         case WM_SYSKEYUP:
1350             if (window !is null) {
1351                 int repeatCount = lParam & 0xFFFF;
1352                 WPARAM vk = wParam;
1353                 WPARAM new_vk = vk;
1354                 UINT scancode = (lParam & 0x00ff0000) >> 16;
1355                 int extended  = (lParam & 0x01000000) != 0;
1356                 switch (vk) {
1357                     case VK_SHIFT:
1358                         new_vk = MapVirtualKey(scancode, 3); //MAPVK_VSC_TO_VK_EX
1359                         break;
1360                     case VK_CONTROL:
1361                         new_vk = extended ? VK_RCONTROL : VK_LCONTROL;
1362                         break;
1363                     case VK_MENU:
1364                         new_vk = extended ? VK_RMENU : VK_LMENU;
1365                         break;
1366                     default:
1367                         // not a key we map from generic to left/right specialized
1368                         //  just return it.
1369                         new_vk = vk;
1370                         break;    
1371                 }
1372 
1373                 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))
1374                     return 0; // processed
1375             }
1376             break;
1377         case WM_UNICHAR:
1378             if (window !is null) {
1379                 int repeatCount = lParam & 0xFFFF;
1380                 if (window.onKey(KeyAction.Text, cast(uint)wParam, repeatCount, wParam == UNICODE_NOCHAR ? 0 : cast(uint)wParam))
1381                     return 1; // processed
1382                 return 1;
1383             }
1384             break;
1385         case WM_CHAR:
1386             if (window !is null) {
1387                 int repeatCount = lParam & 0xFFFF;
1388                 if (window.onKey(KeyAction.Text, cast(uint)wParam, repeatCount, wParam == UNICODE_NOCHAR ? 0 : cast(uint)wParam))
1389                     return 1; // processed
1390                 return 1;
1391             }
1392             break;
1393         case WM_TIMER:
1394             if (window !is null) {
1395                 window.handleTimer(wParam);
1396                 return 0;
1397             }
1398             break;
1399         case WM_DROPFILES:
1400             if (window !is null) {
1401                 HDROP hdrop = cast(HDROP)wParam;
1402                 string[] files;
1403                 wchar[] buf;
1404                 auto count = DragQueryFileW(hdrop, 0xFFFFFFFF, cast(wchar*)NULL, 0);
1405                 for (int i = 0; i < count; i++) {
1406                     auto sz = DragQueryFileW(hdrop, i, cast(wchar*)NULL, 0); 
1407                     buf.length = sz + 2;
1408                     sz = DragQueryFileW(hdrop, i, buf.ptr, sz + 1); 
1409                     files ~= toUTF8(buf[0..sz]);
1410                 }
1411                 if (files.length)
1412                     window.handleDroppedFiles(files);
1413                 DragFinish(hdrop);
1414             }
1415             return 0;
1416         case WM_CLOSE:
1417             if (window !is null) {
1418                 bool canClose = window.handleCanClose();
1419                 if (!canClose) {
1420                     Log.d("WM_CLOSE: canClose is false");
1421                     return 0; // prevent closing
1422                 }
1423                 Log.d("WM_CLOSE: closing window ");
1424                 //destroy(window);
1425             }
1426             // default handler inside DefWindowProc will close window
1427             break;
1428         case WM_GETMINMAXINFO:
1429         case WM_NCCREATE:
1430         case WM_NCCALCSIZE:
1431         default:
1432             //Log.d("Unhandled message ", message);
1433             break;
1434     }
1435     if (window)
1436         return window.handleUnknownWindowMessage(message, wParam, lParam);
1437     return DefWindowProc(hwnd, message, wParam, lParam);
1438 }
1439 
1440 //===========================================
1441 // end of version(Windows)
1442 //===========================================
1443