1 module double_buffer;
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 usage of a double-buffer using Cairo and win32.
12  + Also shows how to avoid re-blitting an area by simply using the
13  + ps.rcPaint bounding rectangle field, and removing redrawing of
14  + the entire window when it is resized.
15  +
16  + For more info on double-buffering and avoiding screen flicker, see:
17  + http://wiki.osdev.org/Double_Buffering
18  + http://www.catch22.net/tuts/flicker
19  +/
20 
21 import core.runtime;
22 import std.process;
23 import std.stdio;
24 import std.string;
25 import std.utf;
26 
27 pragma(lib, "gdi32.lib");
28 import windows.winbase;
29 import windows.windef;
30 import windows.winuser;
31 import windows.wingdi;
32 
33 string appName     = "CairoWindow";
34 string description = "A simple win32 window with Cairo drawing";
35 
36 import cairo.cairo;
37 import cairo.win32;
38 
39 alias cairo.cairo.RGB RGB;  // conflicts with win32.wingdi.RGB
40 
41 extern (Windows)
42 int WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int iCmdShow)
43 {
44     int result;
45 
46 
47     try
48     {
49         Runtime.initialize();
50         result = myWinMain(hInstance, hPrevInstance, lpCmdLine, iCmdShow);
51         Runtime.terminate();
52     }
53     catch (Throwable o)
54     {
55         MessageBox(null, o.toString().toUTF16z, "Error", MB_OK | MB_ICONEXCLAMATION);
56         result = 0;
57     }
58 
59     return result;
60 }
61 
62 int myWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int iCmdShow)
63 {
64     HACCEL hAccel;
65     HWND hwnd;
66     MSG  msg;
67     WNDCLASS wndclass;
68 
69     // commented out so we do not redraw the entire screen on resize
70     // wndclass.style         = CS_HREDRAW | CS_VREDRAW;
71     // wndclass.style         = WS_CLIPCHILDREN;
72 
73     wndclass.lpfnWndProc   = &WndProc;
74     wndclass.cbClsExtra    = 0;
75     wndclass.cbWndExtra    = 0;
76     wndclass.hInstance     = hInstance;
77     wndclass.hIcon         = LoadIcon(null, IDI_APPLICATION);
78     wndclass.hCursor       = LoadCursor(null, IDC_ARROW);
79     wndclass.hbrBackground = null;
80     wndclass.lpszMenuName  = null;
81     wndclass.lpszClassName = appName.toUTF16z;
82 
83     if (!RegisterClass(&wndclass))
84     {
85         auto lastErr = GetLastError();
86         auto lastError = toUTFz!(const(wchar)*)(format("Error: %s", lastErr));
87         MessageBox(null, lastError, appName.toUTF16z, MB_ICONERROR);
88         return 0;
89     }
90 
91     hwnd = CreateWindow(appName.toUTF16z,              // window class name
92                         description.toUTF16z,          // window caption
93                         WS_OVERLAPPEDWINDOW,           // window style
94                         CW_USEDEFAULT,                 // initial x position
95                         CW_USEDEFAULT,                 // initial y position
96                         200,                 // initial x size
97                         200,                 // initial y size
98                         null,                          // parent window handle
99                         null,                          // window menu handle
100                         hInstance,                     // program instance handle
101                         null);                         // creation parameters
102 
103     ShowWindow(hwnd, iCmdShow);
104     UpdateWindow(hwnd);
105 
106     while (GetMessage(&msg, null, 0, 0))
107     {
108         TranslateMessage(&msg);
109         DispatchMessage(&msg);
110     }
111 
112     return msg.wParam;
113 }
114 
115 void roundedRectangle(Context ctx, int x, int y, int w, int h, int radius_x = 5, int radius_y = 5)
116 {
117     enum ARC_TO_BEZIER = 0.55228475;
118 
119     if (radius_x > w - radius_x)
120         radius_x = w / 2;
121 
122     if (radius_y > h - radius_y)
123         radius_y = h / 2;
124 
125     // approximate (quite close) the arc using a bezier curve
126     auto c1 = ARC_TO_BEZIER * radius_x;
127     auto c2 = ARC_TO_BEZIER * radius_y;
128 
129     ctx.newPath();
130     ctx.moveTo(x + radius_x, y);
131     ctx.relLineTo(w - 2 * radius_x, 0.0);
132     ctx.relCurveTo(c1, 0.0, radius_x, c2, radius_x, radius_y);
133     ctx.relLineTo(0, h - 2 * radius_y);
134     ctx.relCurveTo(0.0, c2, c1 - radius_x, radius_y, -radius_x, radius_y);
135     ctx.relLineTo(-w + 2 * radius_x, 0);
136     ctx.relCurveTo(-c1, 0, -radius_x, -c2, -radius_x, -radius_y);
137     ctx.relLineTo(0, -h + 2 * radius_y);
138     ctx.relCurveTo(0.0, -c2, radius_x - c1, -radius_y, radius_x, -radius_y);
139     ctx.closePath();
140 }
141 
142 extern (Windows)
143 LRESULT WndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
144 {
145     switch (message)
146     {
147         case WM_CREATE:
148         {
149             window = new Window(hwnd);
150             return 0;
151         }
152 
153         default:
154     }
155 
156     if (window)
157         return window.process(hwnd, message, wParam, lParam);
158     else
159         return DefWindowProc(hwnd, message, wParam, lParam);
160 }
161 
162 Window window;
163 
164 struct PaintBuffer
165 {
166     int width, height;
167 
168     this(HDC localHdc, int cxClient, int cyClient)
169     {
170         width  = cxClient;
171         height = cyClient;
172 
173         hBuffer    = CreateCompatibleDC(localHdc);
174         hBitmap    = CreateCompatibleBitmap(localHdc, cxClient, cyClient);
175         hOldBitmap = SelectObject(hBuffer, hBitmap);
176 
177         surf = new Win32Surface(hBuffer);
178         ctx = Context(surf);
179         initialized = true;
180     }
181 
182     ~this()
183     {
184         if (initialized)  // struct dtors are still buggy sometimes
185         {
186             ctx.dispose();
187             surf.finish();
188             surf.dispose();
189 
190             SelectObject(hBuffer, hOldBitmap);
191             DeleteObject(hBitmap);
192             DeleteDC(hBuffer);
193             initialized = false;
194         }
195     }
196 
197     bool initialized;
198     HDC hBuffer;
199     HBITMAP hBitmap;
200     HBITMAP hOldBitmap;
201     Context ctx;
202     Surface surf;
203 }
204 
205 class Window
206 {
207     int width, height;
208     HWND hwnd;
209     PAINTSTRUCT ps;
210     PaintBuffer paintBuffer;
211     bool needsRedraw;
212 
213     this(HWND hwnd)
214     {
215         this.hwnd = hwnd;
216 
217         auto hDesk = GetDesktopWindow();
218         RECT rc;
219         GetClientRect(hDesk, &rc);
220 
221         auto localHdc = GetDC(hwnd);
222         paintBuffer = PaintBuffer(localHdc, rc.right, rc.bottom);
223         needsRedraw = true;
224     }
225 
226     LRESULT process(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
227     {
228         switch (message)
229         {
230             case WM_DESTROY:
231             {
232                 paintBuffer.clear();
233                 PostQuitMessage(0);
234                 return 0;
235             }
236 
237             case WM_PAINT:
238                 OnPaint(hwnd, message, wParam, lParam);
239                 return 0;
240 
241             case WM_ERASEBKGND:
242                 return 1;
243 
244             case WM_SIZE:
245             {
246                 width  = LOWORD(lParam);
247                 height = HIWORD(lParam);
248                 return 0;
249             }
250 
251             default:
252         }
253 
254         return DefWindowProc(hwnd, message, wParam, lParam);
255     }
256 
257     void OnPaint(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
258     {
259         static int blitCount;
260         auto hdc = BeginPaint(hwnd, &ps);
261         auto ctx = paintBuffer.ctx;
262         auto hBuffer = paintBuffer.hBuffer;
263 
264         auto boundRect = ps.rcPaint;
265 
266         if (needsRedraw)  // cairo needs to redraw
267         {
268             draw(ctx);
269             needsRedraw = false;
270         }
271 
272         with (boundRect)  // blit only required areas
273         {
274             BitBlt(hdc, left, top, right, bottom, hBuffer, left, top, SRCCOPY);
275         }
276 
277         EndPaint(hwnd, &ps);
278     }
279 
280     void draw(Context ctx)
281     {
282         ctx.setSourceRGB(1, 1, 1);
283         ctx.paint();
284 
285         ctx.rectangle(0, 0, 120, 90);
286         ctx.setSourceRGBA(0.7, 0, 0, 0.8);
287         ctx.fill();
288 
289         ctx.rectangle(40, 30, 120, 90);
290         ctx.setSourceRGBA(0, 0, 0.9, 0.4);
291         ctx.fill();
292     }
293 }