Mercurial > projects > dynamin
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 |