1 module clipped_draw;
2 
3 /+
4  +           Copyright Andrej Mitrovic 2011.
5  +  Distributed under the Boost Software License, Version 1.0.
6  +     (See accompanying file LICENSE_1_0.txt or copy at
7  +           http://www.boost.org/LICENSE_1_0.txt)
8  +/
9 
10 /+
11  + Demonstrates the use of WS_CLIPCHILDREN when calling CreateWindow().
12  + This clips the drawing of a parent window with any child windows,
13  + therefore it won't draw over the children's areas, avoiding flicker.
14  +
15  + I'm also using the ps.rcPaint from the BeginPaint call to limit
16  + blitting to only the areas that need to be updated. I'm also not
17  + re-drawing (with cairo) the areas of a widget that have already
18  + been drawn.
19  +
20  + These techniques give us good drawing and blitting performance.
21  + We could also dynamically create the backbuffer for the main
22  + window (atm. it creates a memory buffer the size of the screen).
23  +/
24 
25 import core.memory;
26 import core.runtime;
27 import core.thread;
28 import core.stdc.config;
29 
30 import std.algorithm;
31 import std.array;
32 import std.conv;
33 import std.exception;
34 import std.functional;
35 import std.math;
36 import std.random;
37 import std.range;
38 import std.stdio;
39 import std.string;
40 import std.traits;
41 import std.utf;
42 
43 pragma(lib, "gdi32.lib");
44 
45 import windows.windef;
46 import windows.winuser;
47 import windows.wingdi;
48 
49 alias std.algorithm.min min;  // conflict resolution
50 alias std.algorithm.max max;  // conflict resolution
51 
52 import cairo.cairo;
53 import cairo.win32;
54 
55 alias cairo.cairo.RGB RGB;   // conflict resolution
56 
57 struct StateContext
58 {
59     Context ctx;
60 
61     this(Context ctx)
62     {
63         this.ctx = ctx;
64         ctx.save();
65     }
66 
67     ~this()
68     {
69         ctx.restore();
70     }
71 
72     alias ctx this;
73 }
74 
75 class PaintBuffer
76 {
77     this(HDC localHdc, int cxClient, int cyClient)
78     {
79         hdc    = localHdc;
80         width  = cxClient;
81         height = cyClient;
82 
83         hBuffer    = CreateCompatibleDC(localHdc);
84         hBitmap    = CreateCompatibleBitmap(localHdc, cxClient, cyClient);
85         hOldBitmap = SelectObject(hBuffer, hBitmap);
86 
87         surf = new Win32Surface(hBuffer);
88         ctx = Context(surf);
89         initialized = true;
90     }
91 
92     ~this()
93     {
94         if (initialized)
95         {
96             clear();
97         }
98     }
99 
100     void clear()
101     {
102         ctx.dispose();
103         surf.finish();
104         surf.dispose();
105 
106         SelectObject(hBuffer, hOldBitmap);
107         DeleteObject(hBitmap);
108         DeleteDC(hBuffer);
109         initialized = false;
110     }
111 
112     HDC hdc;
113     bool initialized;
114     int width, height;
115     HDC hBuffer;
116     HBITMAP hBitmap;
117     HBITMAP hOldBitmap;
118     Context ctx;
119     Surface surf;
120 }
121 
122 abstract class Widget
123 {
124     Widget parent;
125     PAINTSTRUCT ps;
126     PaintBuffer mainPaintBuff;
127     PaintBuffer paintBuffer;
128     HWND hwnd;
129     int width, height;
130     int xOffset, yOffset;
131     bool needsRedraw = true;
132 
133     this(HWND hwnd, int width, int height)
134     {
135         this.hwnd = hwnd;
136         this.width = width;
137         this.height = height;
138         //~ SetTimer(hwnd, 100, 1, null);
139     }
140 
141     @property Size!int size()
142     {
143         return Size!int(width, height);
144     }
145 
146     abstract LRESULT process(UINT message, WPARAM wParam, LPARAM lParam)
147     {
148         switch (message)
149         {
150             case WM_ERASEBKGND:
151             {
152                 return 1;
153             }
154 
155             case WM_PAINT:
156             {
157                 OnPaint(hwnd, message, wParam, lParam);
158                 return 0;
159             }
160 
161             case WM_SIZE:
162             {
163                 width  = LOWORD(lParam);
164                 height = HIWORD(lParam);
165 
166                 auto localHdc = GetDC(hwnd);
167 
168                 if (paintBuffer !is null)
169                 {
170                     paintBuffer.clear();
171                 }
172 
173                 paintBuffer = new PaintBuffer(localHdc, width, height);
174                 ReleaseDC(hwnd, localHdc);
175 
176                 needsRedraw = true;
177                 InvalidateRect(hwnd, null, true);
178                 return 0;
179             }
180 
181             case WM_TIMER:
182             {
183                 InvalidateRect(hwnd, null, true);
184                 return 0;
185             }
186 
187             case WM_MOVE:
188             {
189                 xOffset = LOWORD(lParam);
190                 yOffset = HIWORD(lParam);
191                 return 0;
192             }
193 
194             case WM_DESTROY:
195             {
196                 paintBuffer.clear();
197                 PostQuitMessage(0);
198                 return 0;
199             }
200 
201             default:
202         }
203 
204         return DefWindowProc(hwnd, message, wParam, lParam);
205     }
206 
207     abstract void OnPaint(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam);
208     abstract void draw(StateContext ctx);
209 }
210 
211 class TestWidget2 : Widget
212 {
213     this(HWND hwnd, int width, int height)
214     {
215         super(hwnd, width, height);
216     }
217 
218     override LRESULT process(UINT message, WPARAM wParam, LPARAM lParam)
219     {
220         return super.process(message, wParam, lParam);
221     }
222 
223     override void OnPaint(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
224     {
225         auto ctx = paintBuffer.ctx;
226         auto hBuffer = paintBuffer.hBuffer;
227         auto hdc = BeginPaint(hwnd, &ps);
228         auto boundRect = ps.rcPaint;
229 
230         if (needsRedraw)
231         {
232             //~ writeln("drawing");
233             draw(StateContext(ctx));
234             needsRedraw = false;
235         }
236 
237         with (boundRect)
238         {
239             //~ writeln("blitting");
240             BitBlt(hdc, left, top, right - left, bottom - top, paintBuffer.hBuffer, left, top, SRCCOPY);
241         }
242 
243         EndPaint(hwnd, &ps);
244     }
245 
246     override void draw(StateContext ctx)
247     {
248         ctx.setSourceRGB(1, 1, 1);
249         ctx.paint();
250 
251         ctx.scale(width, height);
252         ctx.moveTo(0, 0);
253 
254         ctx.rectangle(0, 0, 1, 1);
255         ctx.setSourceRGBA(1, 1, 1, 0);
256         ctx.setOperator(Operator.CAIRO_OPERATOR_CLEAR);
257         ctx.fill();
258 
259         ctx.setSourceRGB(0, 0, 0);
260         ctx.setOperator(Operator.CAIRO_OPERATOR_OVER);
261 
262         auto linpat = new LinearGradient(0, 0, 1, 1);
263         linpat.addColorStopRGB(0, RGB(0, 0.3, 0.8));
264         linpat.addColorStopRGB(1, RGB(0, 0.8, 0.3));
265 
266         auto radpat = new RadialGradient(0.5, 0.5, 0.25, 0.5, 0.5, 0.75);
267         radpat.addColorStopRGBA(0,   RGBA(0, 0, 0, 1));
268         radpat.addColorStopRGBA(0.5, RGBA(0, 0, 0, 0));
269 
270         ctx.setSource(linpat);
271         ctx.mask(radpat);
272     }
273 }
274 
275 class TestWidget : Widget
276 {
277     RGB backColor;
278 
279     this(HWND hwnd, int width, int height)
280     {
281         super(hwnd, width, height);
282         this.backColor = RGB(1, 0, 0);
283 
284         auto localHdc = GetDC(hwnd);
285         auto hWindow = CreateWindow(WidgetClass.toUTF16z, null,
286                        WS_CHILDWINDOW | WS_VISIBLE | WS_CLIPCHILDREN,  // WS_CLIPCHILDREN is necessary
287                        0, 0, 0, 0,
288                        hwnd, cast(HANDLE)1,                                   // child ID
289                        cast(HINSTANCE)GetWindowLongPtr(hwnd, GWL_HINSTANCE),  // hInstance
290                        null);
291 
292         auto widget = new TestWidget2(hWindow, width / 2, width / 2);
293         WidgetHandles[hWindow] = widget;
294 
295         auto size = widget.size;
296         MoveWindow(hWindow, size.width / 2, size.height / 2, size.width, size.height, true);
297     }
298 
299     override LRESULT process(UINT message, WPARAM wParam, LPARAM lParam)
300     {
301         return super.process(message, wParam, lParam);
302     }
303 
304     override void OnPaint(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
305     {
306         auto ctx = paintBuffer.ctx;
307         auto hBuffer = paintBuffer.hBuffer;
308         auto hdc = BeginPaint(hwnd, &ps);
309         auto boundRect = ps.rcPaint;
310 
311         if (needsRedraw)
312         {
313             //~ writeln("drawing");
314             draw(StateContext(ctx));
315             needsRedraw = false;
316         }
317 
318         with (boundRect)
319         {
320             //~ writeln("blitting");
321             BitBlt(hdc, left, top, right - left, bottom - top, paintBuffer.hBuffer, left, top, SRCCOPY);
322         }
323 
324         EndPaint(hwnd, &ps);
325     }
326 
327     override void draw(StateContext ctx)
328     {
329         ctx.save();
330         ctx.scale(width, height);
331         ctx.moveTo(0, 0);
332 
333         ctx.rectangle(0, 0, 1, 1);
334         ctx.setSourceRGBA(1, 1, 1, 0);
335         ctx.setOperator(Operator.CAIRO_OPERATOR_CLEAR);
336         ctx.fill();
337 
338         ctx.setSourceRGB(0, 0, 0);
339         ctx.setOperator(Operator.CAIRO_OPERATOR_OVER);
340 
341         auto linpat = new LinearGradient(0, 0, 1, 1);
342         linpat.addColorStopRGB(0, RGB(0, 0.3, 0.8));
343         linpat.addColorStopRGB(1, RGB(0, 0.8, 0.3));
344 
345         auto radpat = new RadialGradient(0.5, 0.5, 0.25, 0.5, 0.5, 0.75);
346         radpat.addColorStopRGBA(0,   RGBA(0, 0, 0, 1));
347         radpat.addColorStopRGBA(0.5, RGBA(0, 0, 0, 0));
348 
349         ctx.setSource(linpat);
350         ctx.mask(radpat);
351 
352         ctx.moveTo(0.1, 0.5);
353         ctx.restore();
354 
355         ctx.setSourceRGB(1, 1, 1);
356         ctx.selectFontFace("Tahoma", FontSlant.CAIRO_FONT_SLANT_NORMAL, FontWeight.CAIRO_FONT_WEIGHT_NORMAL);
357         ctx.setFontSize(20);
358         ctx.showText("weeeeeeeeeeeeeeeeeeeeeeeeeee");
359     }
360 }
361 
362 /* A place to hold Widget objects. Since each window has a unique HWND,
363  * we can use this hash type to store references to Widgets and call
364  * their window processing methods.
365  */
366 Widget[HWND] WidgetHandles;
367 
368 /*
369  * All Widget windows have this window procedure registered via RegisterClass(),
370  * we use it to dispatch to the appropriate Widget window processing method.
371  *
372  * A similar technique is used in the DFL and DGUI libraries for all of its
373  * windows and widgets.
374  */
375 extern (Windows)
376 LRESULT winDispatch(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
377 {
378     auto widget = hwnd in WidgetHandles;
379 
380     if (widget !is null)
381     {
382         return widget.process(message, wParam, lParam);
383     }
384 
385     return DefWindowProc(hwnd, message, wParam, lParam);
386 }
387 
388 extern (Windows)
389 LRESULT mainWinProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
390 {
391     static PaintBuffer paintBuffer;
392     static int width, height;
393     static int TimerID = 16;
394 
395     static HMENU widgetID = cast(HMENU)0;  // todo: each widget has its own HMENU ID
396 
397     void draw(StateContext ctx)
398     {
399         ctx.setSourceRGB(0.3, 0.3, 0.3);
400         ctx.paint();
401     }
402 
403     switch (message)
404     {
405         case WM_CREATE:
406         {
407             auto hDesk = GetDesktopWindow();
408             RECT rc;
409             GetClientRect(hDesk, &rc);
410 
411             auto localHdc = GetDC(hwnd);
412             paintBuffer = new PaintBuffer(localHdc, rc.right, rc.bottom);
413 
414             auto hWindow = CreateWindow(WidgetClass.toUTF16z, null,
415                            WS_CHILDWINDOW | WS_VISIBLE | WS_CLIPCHILDREN,  // WS_CLIPCHILDREN is necessary
416                            0, 0, 0, 0,
417                            hwnd, widgetID,                                        // child ID
418                            cast(HINSTANCE)GetWindowLongPtr(hwnd, GWL_HINSTANCE),  // hInstance
419                            null);
420 
421             auto widget = new TestWidget(hWindow, 400, 400);
422             WidgetHandles[hWindow] = widget;
423 
424             auto size = widget.size;
425             MoveWindow(hWindow, size.width / 3, size.width / 3, size.width, size.height, true);
426 
427             //~ SetTimer(hwnd, TimerID, 1, null);
428 
429             return 0;
430         }
431 
432         case WM_LBUTTONDOWN:
433         {
434             SetFocus(hwnd);
435             return 0;
436         }
437 
438         case WM_SIZE:
439         {
440             width = LOWORD(lParam);
441             height = HIWORD(lParam);
442             return 0;
443         }
444 
445         case WM_PAINT:
446         {
447             auto ctx = paintBuffer.ctx;
448             auto hBuffer = paintBuffer.hBuffer;
449             PAINTSTRUCT ps;
450             auto hdc = BeginPaint(hwnd, &ps);
451             auto boundRect = ps.rcPaint;
452 
453             draw(StateContext(paintBuffer.ctx));
454 
455             with (boundRect)
456             {
457                 BitBlt(hdc, left, top, right - left, bottom - top, paintBuffer.hBuffer, left, top, SRCCOPY);
458             }
459 
460             EndPaint(hwnd, &ps);
461             return 0;
462         }
463 
464         case WM_TIMER:
465         {
466             InvalidateRect(hwnd, null, true);
467             return 0;
468         }
469 
470         case WM_MOUSEWHEEL:
471         {
472             return 0;
473         }
474 
475         case WM_DESTROY:
476         {
477             paintBuffer.clear();
478             PostQuitMessage(0);
479             return 0;
480         }
481 
482         default:
483     }
484 
485     return DefWindowProc(hwnd, message, wParam, lParam);
486 }
487 
488 string WidgetClass = "WidgetClass";
489 
490 int myWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int iCmdShow)
491 {
492     string appName = "layered drawing";
493 
494     HWND hwnd;
495     MSG  msg;
496     WNDCLASS wndclass;
497 
498     /* One class for the main window */
499     wndclass.lpfnWndProc = &mainWinProc;
500     wndclass.cbClsExtra  = 0;
501     wndclass.cbWndExtra  = 0;
502     wndclass.hInstance   = hInstance;
503     wndclass.hIcon       = LoadIcon(null, IDI_APPLICATION);
504     wndclass.hCursor     = LoadCursor(null, IDC_ARROW);
505     wndclass.hbrBackground = null;
506     wndclass.lpszMenuName  = null;
507     wndclass.lpszClassName = appName.toUTF16z;
508 
509     if (!RegisterClass(&wndclass))
510     {
511         MessageBox(null, "This program requires Windows NT!", appName.toUTF16z, MB_ICONERROR);
512         return 0;
513     }
514 
515     /* Separate window class for Widgets. */
516     wndclass.hbrBackground = null;
517     wndclass.lpfnWndProc   = &winDispatch;
518     wndclass.cbWndExtra    = 0;
519     wndclass.hIcon         = null;
520     wndclass.lpszClassName = WidgetClass.toUTF16z;
521 
522     if (!RegisterClass(&wndclass))
523     {
524         MessageBox(null, "This program requires Windows NT!", appName.toUTF16z, MB_ICONERROR);
525         return 0;
526     }
527 
528     hwnd = CreateWindow(appName.toUTF16z, "layered drawing",
529                         WS_OVERLAPPEDWINDOW | WS_CLIPCHILDREN,  // WS_CLIPCHILDREN is necessary
530                         CW_USEDEFAULT, CW_USEDEFAULT,
531                         CW_USEDEFAULT, CW_USEDEFAULT,
532                         null, null, hInstance, null);
533 
534     ShowWindow(hwnd, iCmdShow);
535     UpdateWindow(hwnd);
536 
537     while (GetMessage(&msg, null, 0, 0))
538     {
539         TranslateMessage(&msg);
540         DispatchMessage(&msg);
541     }
542 
543     return msg.wParam;
544 }
545 
546 extern (Windows)
547 int WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int iCmdShow)
548 {
549     int result;
550 
551     try
552     {
553         Runtime.initialize();
554         myWinMain(hInstance, hPrevInstance, lpCmdLine, iCmdShow);
555         Runtime.terminate();
556     }
557     catch (Throwable o)
558     {
559         MessageBox(null, o.toString().toUTF16z, "Error", MB_OK | MB_ICONEXCLAMATION);
560         result = -1;
561     }
562 
563     return result;
564 }