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 }