comparison dynamin/gui/windows_window.d @ 0:aa4efef0f0b1

Initial commit of code.
author Jordan Miner <jminer7@gmail.com>
date Mon, 15 Jun 2009 22:10:48 -0500
parents
children 7a7e5f9bd1ae
comparison
equal deleted inserted replaced
-1:000000000000 0:aa4efef0f0b1
1 // Written in the D programming language
2 // www.digitalmars.com/d/
3
4 /*
5 * The contents of this file are subject to the Mozilla Public License Version
6 * 1.1 (the "License"); you may not use this file except in compliance with
7 * the License. You may obtain a copy of the License at
8 * http://www.mozilla.org/MPL/
9 *
10 * Software distributed under the License is distributed on an "AS IS" basis,
11 * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
12 * for the specific language governing rights and limitations under the
13 * License.
14 *
15 * The Original Code is the Dynamin library.
16 *
17 * The Initial Developer of the Original Code is Jordan Miner.
18 * Portions created by the Initial Developer are Copyright (C) 2006-2009
19 * the Initial Developer. All Rights Reserved.
20 *
21 * Contributor(s):
22 * Jordan Miner <jminer7@gmail.com>
23 *
24 */
25
26 module dynamin.gui.windows_window;
27
28 public import dynamin.c.windows;
29 public import dynamin.c.cairo;
30 public import dynamin.c.cairo_win32;
31 public import dynamin.all_core;
32 public import dynamin.all_gui;
33 public import dynamin.gui.window;
34 public import dynamin.gui.key;
35 public import dynamin.all_painting;
36 public import tango.io.Stdout;
37
38 ///
39 enum WindowsVersion {
40 ///
41 Windows95, ///
42 Windows98, ///
43 WindowsMe, ///
44 Windows2000,///
45 WindowsXP, ///
46 WindowsVista
47 }
48 /**
49 * Returns true if the version of Windows that is runninng now is the
50 * specified version or newer.
51 */
52 bool checkWindowsVersion(WindowsVersion ver) {
53 // Windows Server "Longhorn" is 6.0
54 // Windows Vista is 6.0
55 // Windows Server 2003 is 5.2
56 // Windows XP is 5.1
57 // Windows Me is 4.90
58 // Windows 98 is 4.10
59 // Windows 95 is 4.0
60 // Windows NT is 4.0
61 OSVERSIONINFO info;
62 info.dwOSVersionInfoSize = OSVERSIONINFO.sizeof;
63 GetVersionEx(&info);
64 DWORD major, minor;
65 switch(ver) {
66 case WindowsVersion.Windows95: major = 4; minor = 0; break;
67 case WindowsVersion.Windows98: major = 4; minor = 10; break;
68 case WindowsVersion.WindowsMe: major = 4; minor = 90; break;
69 case WindowsVersion.Windows2000: major = 5; minor = 0; break;
70 case WindowsVersion.WindowsXP: major = 5; minor = 1; break;
71 case WindowsVersion.WindowsVista: major = 6; minor = 0; break;
72 }
73 return info.dwMajorVersion > major ||
74 (info.dwMajorVersion == major && info.dwMinorVersion >= minor);
75 }
76 /* unittest {
77 Stdout.format("Windows95 or newer: {}", checkWindowsVersion(WindowsVersion.Windows95)).newline;
78 Stdout.format("Windows98 or newer: {}", checkWindowsVersion(WindowsVersion.Windows98)).newline;
79 Stdout.format("WindowsMe or newer: {}", checkWindowsVersion(WindowsVersion.WindowsMe)).newline;
80 Stdout.format("Windows2000 or newer: {}", checkWindowsVersion(WindowsVersion.Windows2000)).newline;
81 Stdout.format("WindowsXP or newer: {}", checkWindowsVersion(WindowsVersion.WindowsXP)).newline;
82 } */
83
84 // TODO: the way I have stored references using SetProp() will not work
85 // if/when D gets a copying collector. I will need to store a key with
86 // SetProp() and use that key to store the reference in either an
87 // array or a hashtable.
88 Window[HWND] windows;
89 void setControl(HWND hwnd, Window win) {
90 if(win is null)
91 windows.remove(hwnd);
92 else
93 windows[hwnd] = win;
94 }
95 /**
96 * Returns: the Dynamin NativeControl that wraps the specified handle
97 */
98 // TODO: change return type to NativeControl
99 Window getControl(HWND hwnd) {
100 assert(IsWindow(hwnd), "Invalid HWND");
101 auto tmp = hwnd in windows;
102 return tmp is null ? null : *tmp;
103 }
104
105 template ApplicationBackend() {
106 void backend_run(Window w) {
107 bool isWindowVisible() {
108 if(w is null) return true;
109 return w.visible;
110 }
111 MSG msg;
112 BOOL ret;
113 while(isWindowVisible() && (ret = GetMessage(&msg, null, 0, 0)) != 0) {
114 if(ret == -1)
115 Stdout("GetMessage() failed!").newline;
116 TranslateMessage(&msg);
117 DispatchMessage(&msg);
118 }
119 }
120 }
121
122 /*
123 * The reason backends use the backend_ prefix and:
124 * mixin Backend();
125 * instead of using just the method name and
126 * mixin Backend() backend;
127 * is that D mistakenly calls the method in the class, rather than
128 * the one mixed-in...causing infinite recursion/stack overflow
129 */
130 //{{{ WindowBackend
131 template WindowBackend() {
132 HWND _handle;
133 bool backend_handleCreated() { return _handle !is null; }
134 //WS_CAPTION == WS_BORDER | WS_DLGFRAME;
135 void backend_recreateHandle() {
136 LONG style, exStyle;
137 backend_getWindowStyles(style, exStyle);
138 style &= ~WS_VISIBLE; // don't create visible
139 // TODO: set the owner with CreateWindowEx
140 HWND newHandle = CreateWindowEx(exStyle, "DynaminWindow", _text.toWcharPtr(),
141 style, cast(int)x, cast(int)y, cast(int)width, cast(int)height,
142 null, null, GetModuleHandle(null), null);
143 if(!newHandle)
144 Stdout("CreateWindowEx() failed").newline;
145 setControl(newHandle, this);
146
147 // Windows does not completely obey the styles given in CreateWindowEx()
148 SetWindowLong(newHandle, GWL_STYLE, style);
149 SetWindowLong(newHandle, GWL_EXSTYLE, exStyle);
150 SetWindowPos(newHandle, null, 0, 0, 0, 0,
151 SWP_NOMOVE | SWP_NOSIZE | SWP_NOZORDER | SWP_FRAMECHANGED);
152
153 if(handleCreated) {
154 // TODO: move native children to new window?
155
156 // set z-order to right above old window
157 // SetWindowPos() puts the window above the specified
158 // window in the z-order
159 SetWindowPos(newHandle, _handle, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE);
160 if(IsWindowVisible(_handle))
161 ShowWindow(newHandle, SW_SHOWNA);
162 DestroyWindow(_handle);
163 }
164 _handle = newHandle;
165 backend_nativeToBorderSize();
166 }
167 extern(C) static void freeDC(void* hdc) {
168 ReleaseDC(null, hdc);
169 }
170 Graphics backend_quickCreateGraphics() {
171 HDC hdc = GetDC(handle);
172 cairo_surface_t* surface = cairo_win32_surface_create(hdc);
173 cairo_surface_set_user_data(surface, cast(cairo_user_data_key_t*)1,
174 hdc, &freeDC);
175 cairo_t* cr = cairo_create(surface);
176 cairo_surface_destroy(surface);
177 cairo_translate(cr, -borderSize.left, -borderSize.top);
178 auto g = new Graphics(cr);
179 cairo_destroy(cr);
180 return g;
181 }
182 void backend_visible(bool b) {
183 //if not created, create the handle by calling Handle()
184 ShowWindow(handle, b ? SW_SHOW : SW_HIDE);
185 }
186 void backend_borderStyle(WindowBorderStyle border) {
187 backend_updateWindowStyles();
188 }
189 void backend_repaint(Rect rect) {
190 RECT wrect;
191 wrect.left = cast(int)(rect.x-_borderSize.left);
192 wrect.top = cast(int)(rect.y-_borderSize.top);
193 wrect.right = wrect.left+cast(int)rect.width;
194 wrect.bottom = wrect.top+cast(int)rect.height;
195 InvalidateRect(handle, &wrect, false);
196 }
197 void backend_resizable(bool b) {
198 backend_updateWindowStyles();
199 }
200 void backend_contentMinSizeChanged() {
201 }
202 void backend_contentMaxSizeChanged() {
203 backend_updateWindowStyles();
204 }
205 void backend_location(Point pt) {
206 SetWindowPos(handle, null,
207 cast(int)pt.x, cast(int)pt.y, 0, 0,
208 SWP_NOACTIVATE | SWP_NOSIZE | SWP_NOZORDER);
209 }
210 void backend_size(Size size) {
211 SetWindowPos(handle, null,
212 0, 0, cast(int)size.width, cast(int)size.height,
213 SWP_NOACTIVATE | SWP_NOMOVE | SWP_NOZORDER);
214 }
215 void backend_text(string str) {
216 SetWindowText(handle, str.toWcharPtr());
217 }
218 //{{{ backend specific
219
220 void backend_nativeToLocationSize() {
221 RECT winRect;
222 GetWindowRect(handle, &winRect);
223 _location.x = winRect.left;
224 _location.y = winRect.top;
225 _size.width = winRect.right-winRect.left;
226 _size.height = winRect.bottom-winRect.top;
227 }
228 package void backend_nativeToBorderSize() {
229 RECT clientRect, winRect;
230 POINT clientLoc;
231 GetClientRect(handle, &clientRect);
232 GetWindowRect(handle, &winRect);
233 ClientToScreen(handle, &clientLoc);
234 _borderSize.left = clientLoc.x-winRect.left;
235 _borderSize.top = clientLoc.y-winRect.top;
236 _borderSize.right = winRect.right-clientLoc.x-clientRect.right;
237 _borderSize.bottom = winRect.bottom-clientLoc.y-clientRect.bottom;
238 backend_nativeToLocationSize();
239 }
240 void backend_getWindowStyles(out LONG style, out LONG exStyle) {
241 if(handleCreated) {
242 style = GetWindowLong(handle, GWL_STYLE);
243 exStyle = GetWindowLong(handle, GWL_EXSTYLE);
244 }
245 void SetIf(LONG s, bool b) {
246 // if condition satisfied, add style, otherwise clear style
247 b ? (style |= s) : (style &= ~s);
248 }
249 SetIf(WS_DLGFRAME, borderStyle != WindowBorderStyle.None);
250 SetIf(WS_BORDER, borderStyle != WindowBorderStyle.None);
251 SetIf(WS_THICKFRAME,
252 resizable && borderStyle != WindowBorderStyle.None);
253 SetIf(WS_MINIMIZEBOX, borderStyle == WindowBorderStyle.Normal);
254 SetIf(WS_MAXIMIZEBOX,
255 borderStyle == WindowBorderStyle.Normal && resizable &&
256 content.maxWidth == 0 && content.maxHeight == 0);
257 SetIf(WS_SYSMENU, borderStyle != WindowBorderStyle.None);
258 if(borderStyle == WindowBorderStyle.Tool)
259 exStyle |= WS_EX_TOOLWINDOW;
260 else
261 exStyle &= ~WS_EX_TOOLWINDOW;
262 }
263 void backend_updateWindowStyles() {
264 if(!handleCreated)
265 return;
266 LONG style, exStyle;
267 backend_getWindowStyles(style, exStyle);
268 SetWindowLong(handle, GWL_STYLE, style);
269 SetWindowLong(handle, GWL_EXSTYLE, exStyle);
270 SetWindowPos(handle, null, 0, 0, 0, 0,
271 SWP_NOMOVE | SWP_NOSIZE | SWP_NOZORDER | SWP_FRAMECHANGED);
272 }
273 //--FixedToorWindow--
274 //Style: 0x16c80000
275 //ExStyle: 0x10180
276 //--SizableToorWindow--
277 //Style: 0x16cc0000
278 //ExStyle: 0x10180
279 //--FixedDialog--
280 //Style: 0x16c80000
281 //ExStyle: 0x10101
282 //--None--
283 //Style: 0x16010000
284 //ExStyle: 0x10000
285 //--FixedSingle--
286 //Style: 0x16c80000
287 //ExStyle: 0x10100
288 //--Sizable--
289 //Style: 0x16cc0000
290 //ExStyle: 0x10100
291 //}}}
292 }
293 //}}}
294
295 //{{{ Ux class
296 class Ux {
297 static:
298 private:
299 HMODULE uxLib = null;
300 // TODO: these are the wrong calling convention!!
301 extern(Windows) {
302 BOOL function() _IsAppThemed;
303 BOOL function() _IsThemeActive;
304 HTHEME function(HWND hwnd, LPCWSTR pszClassList) _OpenThemeData;
305 HRESULT function(HTHEME hTheme) _CloseThemeData;
306 HRESULT function(HTHEME hTheme, HDC hdc, int iPartId, int iStateId,
307 RECT* pRect, RECT* pClipRect) _DrawThemeBackground;
308 }
309 static this() {
310 uxLib = LoadLibrary("uxtheme");
311 if(uxLib) {
312 _IsAppThemed = cast(typeof(_IsAppThemed))
313 GetProcAddress(uxLib, "IsAppThemed");
314 _IsThemeActive = cast(typeof(_IsThemeActive))
315 GetProcAddress(uxLib, "IsThemeActive");
316 _OpenThemeData = cast(typeof(_OpenThemeData))
317 GetProcAddress(uxLib, "OpenThemeData");
318 _CloseThemeData = cast(typeof(_CloseThemeData))
319 GetProcAddress(uxLib, "CloseThemeData");
320 _DrawThemeBackground = cast(typeof(_DrawThemeBackground))
321 GetProcAddress(uxLib, "DrawThemeBackground");
322 }
323 }
324 HTHEME[string] cache;
325 // opens an HTHEME for the specified controlName and caches it
326 // next time, just returns the HTHEME from the cache
327 HTHEME getHTHEME(string controlName) {
328 HTHEME* hthemePtr = controlName in cache;
329 HTHEME htheme = controlName in cache;
330 if(hthemePtr) {
331 htheme = *hthemePtr;
332 } else {
333 htheme = _OpenThemeData(null, controlName.toWcharPtr());
334 if(!htheme) {
335 if(_IsThemeActive())
336 throw new Exception("invalid uxtheme controlName");
337 else
338 throw new Exception("no theme active");
339 }
340 cache[controlName] = htheme;
341 }
342 return htheme;
343 }
344 // This is called when the WM_THEMECHANGED message is sent
345 package void themeChanged() {
346 foreach(htheme; cache.values)
347 _CloseThemeData(htheme);
348 cache = null;
349 updateThemeActive = true;
350 }
351 bool themeActive;
352 bool updateThemeActive = true;
353 public:
354 // cache this value, as this function was showing up in profiles
355 bool isThemeActive() {
356 if(updateThemeActive) {
357 themeActive = uxLib && _IsThemeActive() && _IsAppThemed();
358 updateThemeActive = false;
359 }
360 return themeActive;
361 }
362 // draw directly onto the HDC with the uxTheme API if the following
363 // three conditions are met:
364 // - the Graphics must be drawing to an HDC (duh)
365 // - there cannot be a scale or rotation...translation can be handled
366 // - the clip must be a pixel-aligned rectangle
367 bool drawBackground(Graphics g, Rect rect, string controlName, int part, int state) {
368 if(!uxLib)
369 throw new Exception("UxPaintBackground(): uxtheme library not found!");
370 HTHEME htheme = getHTHEME(controlName);
371 static if(true) {
372
373 HDC hdc = cairo_win32_surface_get_dc(cairo_get_target(g.handle));
374 //HDC hdc = null;
375 bool isMatrixTranslationOnly() {
376 cairo_matrix_t matrix;
377 cairo_get_matrix(g.handle, &matrix);
378 return matrix.xx == 1 && matrix.xy == 0 &&
379 matrix.xy == 0 && matrix.yy == 1;
380 }
381 bool isClipIntegerRect(cairo_rectangle_list_t* list) {
382 return list.status != CAIRO_STATUS_CLIP_NOT_REPRESENTABLE;
383 }
384 RECT locRect;
385 auto list = cairo_copy_clip_rectangle_list(g.handle);
386 scope(exit) cairo_rectangle_list_destroy(list);
387 if(hdc && isMatrixTranslationOnly() && isClipIntegerRect(list)) {
388 double x = rect.x, y = rect.y;
389 double right = rect.right, bottom = rect.bottom;
390 cairo_user_to_device(g.handle, &x, &y);
391 cairo_user_to_device(g.handle, &right, &bottom);
392 locRect.left = cast(int)x;
393 locRect.top = cast(int)y;
394 locRect.right = cast(int)right;
395 locRect.bottom = cast(int)bottom;
396 cairo_surface_flush(cairo_get_target(g.handle));
397
398 RECT clip;
399 for(int i = 0; i < list.num_rectangles; ++i) {
400 cairo_user_to_device(g.handle, &(list.rectangles[i].x), &(list.rectangles[i].y));
401 clip.left = cast(int)list.rectangles[i].x;
402 clip.top = cast(int)list.rectangles[i].y;
403 clip.right = clip.left + cast(int)list.rectangles[i].width;
404 clip.bottom = clip.top + cast(int)list.rectangles[i].height;
405 _DrawThemeBackground(htheme, hdc, part, state, &locRect, &clip);
406 }
407
408 cairo_surface_mark_dirty(cairo_get_target(g.handle));
409 } else {
410 assert(0, "So far, visual styles are supported only with no rotation, no scaling, a rectangular clip, and HDC surfaces.");
411 static if(0) {
412 BITMAPINFO bmi;
413 bmi.bmiHeader.biSize = sizeof(BITMAPINFOHEADER);
414 bmi.bmiHeader.biWidth = width;
415 bmi.bmiHeader.biHeight = -height; // top-down DIB
416 bmi.bmiHeader.biPlanes = 1;
417 bmi.bmiHeader.biBitCount = 32;
418 bmi.bmiHeader.biCompression = BI_RGB;
419 //bmi.bmiHeader.biSizeImage = width * height * 4;
420 hbmpBlack = CreateDIBSection(hdc, &bmi, DIB_RGB_COLORS,
421 &bmpBits, NULL, 0);
422 hbmpWhite = CreateDIBSection(hdc, &bmi, DIB_RGB_COLORS,
423 &bmpBits, NULL, 0);
424 //draw on a black background and on a white background
425 //calculate alpha and colors and draw that image with cairo
426 // DO TESTS WITH ROTATION AND SCALING
427 _DrawThemeBackground(htheme, hdc, part, state, null/*change*/, null);
428 DeleteObject(hbmpBlack);
429 DeleteObject(hbmpWhite);
430 }
431 }
432
433 }
434 return false;
435 }
436 }
437 //}}}
438
439 //{{{ module constructor and destructor
440 HWND msgWnd;
441 static this() {
442 assert(cairo_version() >= CAIRO_VERSION_ENCODE(1, 4, 0),
443 "cairo version 1.4.0 or newer is required");
444
445 /* create window classes */
446 WNDCLASSEX wc;
447 wc.cbSize = wc.sizeof;
448 wc.style = 0;
449 wc.hInstance = GetModuleHandle(null);
450
451 wc.lpfnWndProc = &dynaminMsgWindowProc;
452 wc.lpszClassName = "DynaminMsgWindow";
453 if(!RegisterClassExW(&wc))
454 Stdout("RegisterClassEx() failed registering class 'DynaminMsgWindow'").newline;
455
456 wc.lpfnWndProc = &dynaminWindowProc;
457 //wc.hbrBackground = cast(HBRUSH)16;
458
459 wc.lpszClassName = "DynaminWindow";
460 if(!RegisterClassEx(&wc))
461 Stdout("RegisterClassEx() failed registering class 'DynaminWindow'").newline;
462
463 wc.style = CS_SAVEBITS;
464 wc.lpszClassName = "DynaminPopup";
465 if(!RegisterClassExW(&wc))
466 Stdout("RegisterClassEx() failed registering class 'DynaminPopup'").newline;
467
468
469 msgWnd = CreateWindowEx(0, "DynaminMsgWindow", "",
470 0,
471 0, 0, 0, 0,
472 null, null, GetModuleHandle(null), null);
473 if(!msgWnd)
474 Stdout("CreateWindowEx() failed").newline;
475
476 /* initialize COM */
477 auto ret = CoInitializeEx(null, COINIT_APARTMENTTHREADED);
478 if(ret != S_OK && ret != S_FALSE)
479 Stdout("Failed to initialize COM").newline;
480
481 }
482 static ~this() {
483 CoUninitialize();
484 }
485 //}}}
486
487 //{{{ dynaminWindowProc()
488 extern(Windows)
489 LRESULT dynaminWindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam) {
490 //used in WM_MOVING
491 static int dragX, dragY; //the cursor location in window coordinates when the drag was started
492 static bool trackingMouseLeave = false;
493 auto c = getControl(hwnd);
494 //{{{ helper functions
495 void createMouseEvent(MouseButton button, void delegate(MouseEventArgs args) func) {
496 scope args = new MouseEventArgs(
497 cast(short)LOWORD(lParam)+c.borderSize.left,
498 cast(short)HIWORD(lParam)+c.borderSize.top, button);
499 func(args);
500 }
501 void snapSide(inout int sideToSnap, float side1, float side2) {
502 if(sideToSnap >= side1-c.snapDistance && sideToSnap <= side1+c.snapDistance)
503 sideToSnap = cast(int)side1;
504 if(sideToSnap >= side2-c.snapDistance && sideToSnap <= side2+c.snapDistance)
505 sideToSnap = cast(int)side2;
506 }
507 auto emptyFunc = (Rect snapRect) {};
508 // used to snap vertical sides, left and right
509 void snapVSide(inout int side, RECT* rect, void delegate(Rect snapRect) func = emptyFunc) {
510 if(c.snapRects is null)
511 return;
512 foreach(snapRect; c.snapRects) {
513 if(rect.bottom >= snapRect.y && rect.top <= snapRect.bottom) {
514 snapSide(side, snapRect.x, snapRect.right);
515 func(snapRect);
516 }
517 }
518 }
519 // used to snap horizontal sides, top and bottom
520 void snapHSide(inout int side, RECT* rect, void delegate(Rect snapRect) func = emptyFunc) {
521 if(c.snapRects is null)
522 return;
523 foreach(snapRect; c.snapRects) {
524 if(rect.right >= snapRect.x && rect.left <= snapRect.right) {
525 snapSide(side, snapRect.y, snapRect.bottom);
526 func(snapRect);
527 }
528 }
529 }
530 MouseButton getFromXBUTTONX() {
531 switch(HIWORD(wParam)) {
532 case XBUTTON1: return MouseButton.XButton1;
533 case XBUTTON2: return MouseButton.XButton2;
534 default: return MouseButton.None;
535 }
536 }
537 //}}}
538 switch(uMsg) {
539 case WM_ENTERSIZEMOVE: //when the user starts moving or resizing the window
540 //{{{
541 POINT cursor;
542 GetCursorPos(&cursor);
543 RECT rect;
544 GetWindowRect(hwnd, &rect);
545 dragX = cursor.x-rect.left;
546 dragY = cursor.y-rect.top;
547 return 0;
548 //}}}
549 case WM_MOVING:
550 //{{{
551 if(c.snapRects is null || c.snapRects.length == 0)
552 break;
553 RECT* rect = cast(RECT*)lParam;
554 int rectWidth = rect.right-rect.left;
555 int rectHeight = rect.bottom-rect.top;
556 POINT cursor;
557 GetCursorPos(&cursor);
558 rect.left = cursor.x-dragX;
559 rect.top = cursor.y-dragY;
560 void updateRightAndBottom() {
561 rect.right = rect.left+rectWidth;
562 rect.bottom = rect.top+rectHeight;
563 }
564 updateRightAndBottom();
565 snapVSide(rect.left, rect, (Rect snapRect) {
566 snapSide(rect.left, snapRect.x-rectWidth, snapRect.right-rectWidth);
567 });
568 updateRightAndBottom();
569 snapHSide(rect.top, rect, (Rect snapRect) {
570 snapSide(rect.top, snapRect.y-rectHeight, snapRect.bottom-rectHeight);
571 });
572 updateRightAndBottom();
573 snapVSide(rect.left, rect, (Rect snapRect) {
574 snapSide(rect.left, snapRect.x-rectWidth, snapRect.right-rectWidth);
575 });
576 updateRightAndBottom();
577 return true;
578 //}}}
579 case WM_SIZING:
580 //{{{
581 Size minSize = c.content.minSize+c.borderSize;
582 Size maxSize = c.content.maxSize+c.borderSize;
583 RECT* rect = cast(RECT*)lParam;
584 switch(wParam) {
585 case WMSZ_TOPLEFT:
586 snapHSide(rect.top, rect);
587 goto case WMSZ_LEFT;
588 case WMSZ_BOTTOMLEFT:
589 snapHSide(rect.bottom, rect);
590 case WMSZ_LEFT:
591 snapVSide(rect.left, rect);
592 // adjust left according to min and max
593 if(c.content.maxWidth != 0)
594 rect.left = max(rect.left, rect.right-cast(int)maxSize.width);
595 if(c.content.minWidth != 0)
596 rect.left = min(rect.left, rect.right-cast(int)minSize.width);
597 break;
598 case WMSZ_TOPRIGHT:
599 snapHSide(rect.top, rect);
600 goto case WMSZ_RIGHT;
601 case WMSZ_BOTTOMRIGHT:
602 snapHSide(rect.bottom, rect);
603 case WMSZ_RIGHT:
604 snapVSide(rect.right, rect);
605 // adjust right according to min and max
606 if(c.content.maxWidth != 0)
607 rect.right = min(rect.right, rect.left+cast(int)maxSize.width);
608 if(c.content.minWidth != 0)
609 rect.right = max(rect.right, rect.left+cast(int)minSize.width);
610 break;
611 default: break;
612 }
613 switch(wParam) {
614 case WMSZ_TOPLEFT: //already snapped left above
615 case WMSZ_TOPRIGHT: //already snapped right above
616 case WMSZ_TOP:
617 snapHSide(rect.top, rect);
618 // adjust top according to min and max
619 if(c.content.maxHeight != 0)
620 rect.top = max(rect.top, rect.bottom-cast(int)maxSize.height);
621 if(c.content.minHeight != 0)
622 rect.top = min(rect.top, rect.bottom-cast(int)minSize.height);
623 break;
624 case WMSZ_BOTTOMLEFT: //already snapped left above
625 case WMSZ_BOTTOMRIGHT: //already snapped right above
626 case WMSZ_BOTTOM:
627 snapHSide(rect.bottom, rect);
628 // adjust bottom according to min and max
629 if(c.content.maxHeight != 0)
630 rect.bottom = min(rect.bottom, rect.top+cast(int)maxSize.height);
631 if(c.content.minHeight != 0)
632 rect.bottom = max(rect.bottom, rect.top+cast(int)minSize.height);
633 break;
634 default: break;
635 }
636 return true;
637 //}}}
638 case WM_MOVE:
639 RECT rect;
640 GetWindowRect(hwnd, &rect);
641 c._location = Point(rect.left, rect.top);
642 scope args = new EventArgs();
643 c.moved(args);
644 return 0;
645 case WM_SIZE:
646 if(wParam == SIZE_MINIMIZED)
647 break;
648 RECT rect;
649 GetWindowRect(hwnd, &rect);
650 c._size = Size(rect.right-rect.left, rect.bottom-rect.top);
651 c.backend_nativeToBorderSize();
652 scope args = new EventArgs();
653 c.resized(args);
654 return 0;
655 case WM_MOUSEMOVE:
656 if(!trackingMouseLeave) {
657 TRACKMOUSEEVENT tme;
658 tme.cbSize = TRACKMOUSEEVENT.sizeof;
659 tme.dwFlags = TME_LEAVE;
660 tme.hWndTrack = hwnd;
661 tme.dwHoverTime = 0;
662 TrackMouseEvent(&tme);
663 trackingMouseLeave = true;
664 }
665 auto pt = Point(cast(short)LOWORD(lParam)+c.borderSize.left, cast(short)HIWORD(lParam)+c.borderSize.top);
666 Control captor = getCaptorControl();
667 if(captor)
668 pt = c.contentToContent(pt, captor);
669 else
670 captor = c;
671 scope event = new MouseEventArgs(
672 pt.x, pt.y, MouseButton.None);
673 if(wParam &
674 (MK_LBUTTON | MK_MBUTTON | MK_RBUTTON |
675 MK_XBUTTON1 | MK_XBUTTON2)) {
676 captor.mouseDragged(event);
677 } else {
678 captor.mouseMoved(event);
679 }
680 return 0;
681 case WM_MOUSELEAVE:
682 trackingMouseLeave = false;
683 setHotControl(null);
684 return 0;
685 case WM_LBUTTONDOWN:
686 SetCapture(hwnd);
687 createMouseEvent(MouseButton.Left, (MouseEventArgs args) {
688 c.mouseDown(args);
689 });
690 return 0;
691 case WM_MBUTTONDOWN:
692 SetCapture(hwnd);
693 createMouseEvent(MouseButton.Middle, (MouseEventArgs args) {
694 c.mouseDown(args);
695 });
696 return 0;
697 case WM_RBUTTONDOWN:
698 SetCapture(hwnd);
699 createMouseEvent(MouseButton.Right, (MouseEventArgs args) {
700 c.mouseDown(args);
701 });
702 return 0;
703 case WM_XBUTTONDOWN:
704 SetCapture(hwnd);
705 auto button = getFromXBUTTONX();
706 if(!button) break;
707 createMouseEvent(button, (MouseEventArgs args) {
708 c.mouseDown(args);
709 });
710 return true;
711 case WM_LBUTTONUP:
712 ReleaseCapture();
713 createMouseEvent(MouseButton.Left, (MouseEventArgs args) {
714 c.mouseUp(args);
715 });
716 return 0;
717 case WM_MBUTTONUP:
718 ReleaseCapture();
719 createMouseEvent(MouseButton.Middle, (MouseEventArgs args) {
720 c.mouseUp(args);
721 });
722 return 0;
723 case WM_RBUTTONUP:
724 ReleaseCapture();
725 createMouseEvent(MouseButton.Right, (MouseEventArgs args) {
726 c.mouseUp(args);
727 });
728 return 0;
729 case WM_XBUTTONUP:
730 ReleaseCapture();
731 auto button = getFromXBUTTONX();
732 if(!button) break;
733 createMouseEvent(button, (MouseEventArgs args) {
734 c.mouseUp(args);
735 });
736 return true;
737 case WM_MOUSEWHEEL:
738 int scrollLines;
739 SystemParametersInfo(SPI_GETWHEELSCROLLLINES, 0, &scrollLines, 0);
740 if(scrollLines == 0xFFFFFFFF)
741 scrollLines = 3;
742 int delta = -cast(short)HIWORD(wParam);
743 auto screenPt = Point(LOWORD(lParam), HIWORD(lParam));
744 auto des = c.getDescendantAtPoint(c.screenToContent(screenPt));
745 des.mouseTurned(
746 new MouseTurnedEventArgs(delta, delta*scrollLines/120.0) );
747 return 0;
748 case WM_SYSKEYDOWN:
749 //Stdout.format("WM_SYSKEYDOWN: {:x}", cast(int)wParam).newline;
750 if(wParam == 0x79) return 0;
751 break;
752 case WM_KEYDOWN:
753 //Stdout.format("WM_KEYDOWN: {:x}", cast(int)wParam).newline;
754 Control focused = c.focusedControl ? c.focusedControl : c;
755 focused.keyDown(new KeyEventArgs(
756 VKToKey(wParam), cast(bool)(lParam & (1 << 30)) ));
757 return 0;
758 case WM_SYSKEYUP:
759 //Stdout.format("WM_SYSKEYUP: {:x}", cast(int)wParam).newline;
760 if(wParam == 0x79) return 0;
761 break;
762 case WM_KEYUP:
763 //Stdout.format("WM_KEYUP: {:x}", cast(int)wParam).newline;
764 Control focused = c.focusedControl ? c.focusedControl : c;
765 focused.keyUp(new KeyEventArgs( VKToKey(wParam), false ));
766 return 0;
767 case WM_CHAR:
768 // DO NOT use the repeat count from the lParam to send multiple events
769 // I hate when programs do that
770
771 //stop backspace and escape and shift+enter
772 if(wParam == 0x08 || wParam == 0x1B || wParam == 0x0A)
773 break;
774 // don't process characters typed while control is down
775 if(HIBYTE(GetKeyState(VK_CONTROL)))
776 break;
777 if(wParam == 0x0D) // change \r to \n
778 wParam = 0x0A;
779 bool repeat;
780 repeat = cast(bool)(lParam & (1 << 30));
781 Control focused = c.focusedControl ? c.focusedControl : c;
782 focused.keyTyped(new KeyTypedEventArgs(cast(dchar)wParam, repeat));
783 return 0;
784 case WM_PRINT:
785 paintToHDC(cast(HDC)wParam, getControl(hwnd), null);
786 return 0;
787 case WM_PAINT:
788 PAINTSTRUCT ps;
789 BeginPaint(hwnd, &ps);
790 RECT* clip = &ps.rcPaint;
791
792 HDC hdcBuffer = CreateCompatibleDC(ps.hdc);
793 HBITMAP hbmpBuffer = CreateCompatibleBitmap(ps.hdc,
794 clip.right-clip.left, clip.bottom-clip.top);
795 HBITMAP hbmpDefault = SelectObject(hdcBuffer, hbmpBuffer);
796
797 paintToHDC(hdcBuffer, getControl(hwnd), clip);
798
799 BitBlt(ps.hdc, clip.left, clip.top, clip.right-clip.left, clip.bottom-clip.top,
800 hdcBuffer, 0, 0, SRCCOPY);
801 SelectObject(hdcBuffer, hbmpDefault);
802 DeleteDC(hdcBuffer);
803 DeleteObject(hbmpBuffer);
804
805 EndPaint(hwnd, &ps);
806 return 0;
807 case WM_CLOSE:
808 c.visible = false;
809 //DestroyWindow(hwnd);
810 //PostQuitMessage(0);
811 return 0;
812 case WM_DESTROY:
813 return 0;
814 default:
815 break;
816 }
817 return DefWindowProc(hwnd, uMsg, wParam, lParam);
818 }
819 //}}}
820
821 //{{{ paintToHDC()
822 void paintToHDC(HDC hdc, Window w, RECT* clip) {
823 cairo_surface_t* surface = cairo_win32_surface_create(hdc);
824 cairo_t* cr = cairo_create(surface);
825 cairo_surface_destroy(surface);
826
827 if(clip) {
828 cairo_translate(cr,
829 -clip.left-w.borderSize.left, -clip.top-w.borderSize.top);
830
831 //cairo_rectangle(cr, clip.left, clip.top, clip.right-clip.left, clip.bottom-clip.top);
832 }
833 //if(w.Opaque) {
834 //cairo_set_source_rgb(cr, w.content.backColor.R/255.0, w.content.backColor.G/255.0, w.content.backColor.B/255.0);
835 //cairo_paint(cr);
836 //}
837
838 //cairo_set_source_rgb(cr, .3, .3, .3);
839 //cairo_paint(cr);
840
841 //cairo_set_operator(cr, CAIRO_OPERATOR_CLEAR);
842 //cairo_paint(cr);
843 //cairo_set_operator(cr, CAIRO_OPERATOR_OVER);
844
845 cairo_set_source_rgb(cr, 0, 0, 0);
846 cairo_set_line_width(cr, 1.0);
847
848 //HBITMAP hbmp = LoadImage(null, cast(wchar*)cast(ushort)OIC_WARNING,
849 // IMAGE_ICON, 0, 0, LR_SHARED);
850 //if(hbmp == null)
851 // Stdout.format("LoadImage failed. GetLastError()={}", GetLastError()).newline;
852
853 //auto imgSurface = cairo_image_surface_create_for_data();
854 //cairo_set_source_surface(cr, imgSurface, 50, 50);
855 //cairo_paint(cr);
856 //cairo_surface_destroy(imgSurface);
857
858 auto g = new Graphics(cr);
859 scope args = new PaintingEventArgs(g);
860 w.painting(args);
861 delete g;
862 cairo_destroy(cr);
863 }
864 //}}}
865
866 //{{{ VKToKey()
867 Key VKToKey(int code) {
868 switch(code) {
869 case VK_F1: return Key.F1;
870 case VK_F2: return Key.F2;
871 case VK_F3: return Key.F3;
872 case VK_F4: return Key.F4;
873 case VK_F5: return Key.F5;
874 case VK_F6: return Key.F6;
875 case VK_F7: return Key.F7;
876 case VK_F8: return Key.F8;
877 case VK_F9: return Key.F9;
878 case VK_F10: return Key.F10;
879 case VK_F11: return Key.F11;
880 case VK_F12: return Key.F12;
881
882 case VK_ESCAPE: return Key.Escape;
883 case VK_TAB: return Key.Tab;
884 case VK_BACK: return Key.Backspace;
885 case VK_RETURN: return Key.Enter;
886 case VK_SPACE: return Key.Space;
887
888 case VK_LEFT: return Key.Left;
889 case VK_RIGHT: return Key.Right;
890 case VK_UP: return Key.Up;
891 case VK_DOWN: return Key.Down;
892
893 case VK_INSERT: return Key.Insert;
894 case VK_DELETE: return Key.Delete;
895 case VK_HOME: return Key.Home;
896 case VK_END: return Key.End;
897 case VK_PRIOR: return Key.PageUp;
898 case VK_NEXT: return Key.PageDown;
899
900 case VK_SNAPSHOT: return Key.PrintScreen;
901 case VK_PAUSE: return Key.Pause;
902
903 case VK_CAPITAL: return Key.CapsLock;
904 case VK_NUMLOCK: return Key.NumLock;
905 case VK_SCROLL: return Key.ScrollLock;
906
907 case VK_NUMPAD0: return Key.NumPad0;
908 case VK_NUMPAD1: return Key.NumPad1;
909 case VK_NUMPAD2: return Key.NumPad2;
910 case VK_NUMPAD3: return Key.NumPad3;
911 case VK_NUMPAD4: return Key.NumPad4;
912 case VK_NUMPAD5: return Key.NumPad5;
913 case VK_NUMPAD6: return Key.NumPad6;
914 case VK_NUMPAD7: return Key.NumPad7;
915 case VK_NUMPAD8: return Key.NumPad8;
916 case VK_NUMPAD9: return Key.NumPad9;
917 case VK_DIVIDE: return Key.NumPadDivide;
918 case VK_MULTIPLY: return Key.NumPadMultiply;
919 case VK_SUBTRACT: return Key.NumPadSubtract;
920 case VK_ADD: return Key.NumPadAdd;
921 case VK_DECIMAL: return Key.NumPadDecimal;
922
923 case VK_OEM_3: return Key.Backquote;
924 case VK_OEM_MINUS: return Key.Minus;
925 case VK_OEM_PLUS: return Key.Equals;
926 case VK_OEM_4: return Key.OpenBracket;
927 case VK_OEM_6: return Key.CloseBracket;
928 case VK_OEM_5: return Key.Backslash;
929 case VK_OEM_1: return Key.Semicolon;
930 case VK_OEM_7: return Key.Quote;
931 case VK_OEM_COMMA: return Key.Comma;
932 case VK_OEM_PERIOD: return Key.Period;
933 case VK_OEM_2: return Key.Slash;
934
935 //case VK_APPS: return Key.Menu;
936
937 case VK_SHIFT: return Key.Shift;
938 case VK_CONTROL: return Key.Control;
939 case VK_MENU: return Key.Alt;
940
941 //case VK_: return Key.;
942 default:
943 if(code >= 0x30 && code <= 0x39) // Key.D0 - Key.D9
944 return cast(Key)code;
945 if(code >= 0x41 && code <= 0x5A) // Key.A - Key.Z
946 return cast(Key)code;
947 return cast(Key)0;
948 }
949 }
950 //}}}
951
952 // Use the msgWnd for the following:
953 // Timers
954 // Clipboard.DataChanged event
955 // Clipboard?
956 // Taskbar created event using RegisterWindowMessage("TaskbarCreated")
957 // different settings changed events?
958 // screen saver enabled?
959 extern(Windows)
960 LRESULT dynaminMsgWindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam) {
961 switch(uMsg) {
962 case WM_THEMECHANGED:
963 Ux.themeChanged();
964 return 0;
965 case WM_POWERBROADCAST:
966 if(wParam == PBT_APMRESUMESUSPEND || wParam == PBT_APMRESUMECRITICAL)
967 Environment.backend_increaseTimerRes();
968 return 0;
969 case WM_TIMER:
970 case WM_CHANGECBCHAIN:
971 case WM_DRAWCLIPBOARD:
972 case WM_TIMECHANGE: //??
973 default:
974 break;
975 }
976 return DefWindowProc(hwnd, uMsg, wParam, lParam);
977 }
978