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 }