/win32_cairo_clock.d
D | 709 lines | 552 code | 114 blank | 43 comment | 12 complexity | 03536d63159470c6c32c608c6dcec335 MD5 | raw file
1module win32_cairo_clock; 2 3/+ 4 + Original author: Brad Elliott (20 Jan 2008) 5 + Derived from http://code.google.com/p/wxcairo 6 + 7 + Ported to D2 by Andrej Mitrovic, 2011. 8 + Using CairoD and win32. 9 +/ 10 11import core.memory; 12import core.runtime; 13import core.thread; 14import core.stdc.config; 15 16import std.algorithm; 17import std.array; 18import std.conv; 19import std.datetime; 20import std.exception; 21import std.functional; 22import std.math; 23import std.random; 24import std.range; 25import std.stdio; 26import std.string; 27import std.traits; 28import std.utf; 29 30pragma(lib, "gdi32.lib"); 31 32import win32.windef; 33import win32.winuser; 34import win32.wingdi; 35 36alias std.algorithm.min min; // conflict resolution 37alias std.algorithm.max max; // conflict resolution 38 39import cairo.cairo; 40import cairo.win32; 41 42alias cairo.cairo.RGB RGB; // conflict resolution 43 44struct StateContext 45{ 46 Context ctx; 47 48 this(Context ctx) 49 { 50 this.ctx = ctx; 51 ctx.save(); 52 } 53 54 ~this() 55 { 56 ctx.restore(); 57 } 58 59 alias ctx this; 60} 61 62 63class PaintBuffer 64{ 65 this(HDC localHdc, int cxClient, int cyClient) 66 { 67 hdc = localHdc; 68 width = cxClient; 69 height = cyClient; 70 71 hBuffer = CreateCompatibleDC(localHdc); 72 hBitmap = CreateCompatibleBitmap(localHdc, cxClient, cyClient); 73 hOldBitmap = SelectObject(hBuffer, hBitmap); 74 75 surf = new Win32Surface(hBuffer); 76 ctx = Context(surf); 77 initialized = true; 78 } 79 80 ~this() 81 { 82 if (initialized) 83 { 84 clear(); 85 } 86 } 87 88 void clear() 89 { 90 ctx.dispose(); 91 surf.finish(); 92 surf.dispose(); 93 94 SelectObject(hBuffer, hOldBitmap); 95 DeleteObject(hBitmap); 96 DeleteDC(hBuffer); 97 initialized = false; 98 } 99 100 HDC hdc; 101 bool initialized; 102 int width, height; 103 HDC hBuffer; 104 HBITMAP hBitmap; 105 HBITMAP hOldBitmap; 106 Context ctx; 107 Surface surf; 108} 109 110abstract class Widget 111{ 112 Widget parent; 113 PAINTSTRUCT ps; 114 PaintBuffer mainPaintBuff; 115 PaintBuffer paintBuffer; 116 HWND hwnd; 117 int width, height; 118 int xOffset, yOffset; 119 bool needsRedraw = true; 120 121 this(HWND hwnd, int width, int height) 122 { 123 this.hwnd = hwnd; 124 this.width = width; 125 this.height = height; 126 127 //~ SetTimer(hwnd, 100, 1, NULL); 128 } 129 130 @property Size!int size() 131 { 132 return Size!int (width, height); 133 } 134 135 void blit() 136 { 137 InvalidateRect(hwnd, null, true); 138 } 139 140 abstract LRESULT process(UINT message, WPARAM wParam, LPARAM lParam) 141 { 142 switch (message) 143 { 144 case WM_ERASEBKGND: 145 { 146 return 1; 147 } 148 149 case WM_PAINT: 150 { 151 OnPaint(hwnd, message, wParam, lParam); 152 return 0; 153 } 154 155 case WM_SIZE: 156 { 157 width = LOWORD(lParam); 158 height = HIWORD(lParam); 159 160 auto localHdc = GetDC(hwnd); 161 162 if (paintBuffer !is null) 163 { 164 paintBuffer.clear(); 165 } 166 167 paintBuffer = new PaintBuffer(localHdc, width, height); 168 ReleaseDC(hwnd, localHdc); 169 170 needsRedraw = true; 171 blit(); 172 return 0; 173 } 174 175 case WM_TIMER: 176 { 177 OnTimer(); 178 return 0; 179 } 180 181 case WM_MOVE: 182 { 183 xOffset = LOWORD(lParam); 184 yOffset = HIWORD(lParam); 185 return 0; 186 } 187 188 default: 189 } 190 191 return DefWindowProc(hwnd, message, wParam, lParam); 192 } 193 194 void OnTimer(); 195 abstract void OnPaint(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam); 196 abstract void draw(StateContext ctx); 197} 198 199class CairoClock : Widget 200{ 201 enum TimerID = 100; 202 203 this(HWND hwnd, int width, int height) 204 { 205 super(hwnd, width, height); 206 SetTimer(hwnd, TimerID, 1000, NULL); 207 208 // Grab the current time 209 GrabCurrentTime(); 210 } 211 212 override LRESULT process(UINT message, WPARAM wParam, LPARAM lParam) 213 { 214 return super.process(message, wParam, lParam); 215 } 216 217 override void OnPaint(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam) 218 { 219 auto ctx = paintBuffer.ctx; 220 auto hBuffer = paintBuffer.hBuffer; 221 auto hdc = BeginPaint(hwnd, &ps); 222 auto boundRect = ps.rcPaint; 223 224 if (needsRedraw) 225 { 226 //~ writeln("drawing"); 227 draw(StateContext(ctx)); 228 needsRedraw = false; 229 } 230 231 with (boundRect) 232 { 233 //~ writeln("blitting"); 234 BitBlt(hdc, left, top, right - left, bottom - top, paintBuffer.hBuffer, left, top, SRCCOPY); 235 } 236 237 EndPaint(hwnd, &ps); 238 } 239 240 void GrabCurrentTime() 241 { 242 auto currentTime = Clock.currTime(); 243 244 if (currentTime.hour >= 12) 245 { 246 m_hour_angle = (currentTime.hour - 12) * PI / 15 + PI / 2; 247 } 248 else 249 { 250 m_hour_angle = currentTime.hour * PI / 15 + PI / 2; 251 } 252 253 m_minute_angle = (currentTime.minute) * PI / 30 - PI / 2; 254 m_second_angle = (currentTime.second) * PI / 30 - PI / 2 + PI / 30; 255 } 256 257 override void OnTimer() 258 { 259 GrabCurrentTime(); 260 needsRedraw = true; 261 blit(); 262 } 263 264 override void draw(StateContext ctx) 265 { 266 double cx = width / 2; 267 double cy = height / 2; 268 double radius = height / 2 - 60; 269 270 ctx.setLineWidth(0.7); 271 272 double sin_of_hour_angle = sin(m_hour_angle); 273 double cos_of_hour_angle = cos(m_hour_angle); 274 double sin_of_minute_angle = sin(m_minute_angle); 275 double cos_of_minute_angle = cos(m_minute_angle); 276 double sin_of_second_angle = sin(m_second_angle); 277 double cos_of_second_angle = cos(m_second_angle); 278 279 // Draw a white background for the clock 280 ctx.setSourceRGB(1, 1, 1); 281 ctx.rectangle(0, 0, width, height); 282 ctx.fill(); 283 ctx.stroke(); 284 285 // Draw the outermost circle which forms the 286 // black radius of the clock. 287 ctx.setSourceRGB(0, 0, 0); 288 ctx.arc(cx, 289 cy, 290 radius + 30, 291 0 * PI, 292 2 * PI); 293 ctx.fill(); 294 295 ctx.setSourceRGB(1, 1, 1); 296 ctx.arc(cx, 297 cy, 298 radius + 25, 299 0 * PI, 300 2 * PI); 301 ctx.fill(); 302 303 ctx.setSourceRGB(0xC0 / 256.0, 0xC0 / 256.0, 0xC0 / 256.0); 304 ctx.arc(cx, 305 cy, 306 radius, 307 0 * PI, 308 2 * PI); 309 ctx.fill(); 310 311 ctx.setSourceRGB(0xE0 / 256.0, 0xE0 / 256.0, 0xE0 / 256.0); 312 ctx.arc(cx, 313 cy, 314 radius - 10, 315 0 * PI, 316 2 * PI); 317 ctx.fill(); 318 319 // Finally draw the border in black 320 ctx.setLineWidth(0.7); 321 ctx.setSourceRGB(0, 0, 0); 322 ctx.arc(cx, 323 cy, 324 radius, 325 0 * PI, 326 2 * PI); 327 ctx.stroke(); 328 329 // Now draw the hour arrow 330 ctx.setSourceRGB(0, 0, 0); 331 ctx.newPath(); 332 ctx.moveTo(cx, cy); 333 ctx.lineTo(cx - radius * 0.05 * sin_of_hour_angle, 334 cy + radius * 0.05 * cos_of_hour_angle); 335 ctx.lineTo(cx + radius * 0.55 * cos_of_hour_angle, 336 cy + radius * 0.55 * sin_of_hour_angle); 337 ctx.lineTo(cx + radius * 0.05 * sin_of_hour_angle, 338 cy - radius * 0.05 * cos_of_hour_angle); 339 ctx.lineTo(cx - radius * 0.05 * cos_of_hour_angle, 340 cy - radius * 0.05 * sin_of_hour_angle); 341 ctx.lineTo(cx - radius * 0.05 * sin_of_hour_angle, 342 cy + radius * 0.05 * cos_of_hour_angle); 343 ctx.closePath(); 344 ctx.fill(); 345 346 // Minute arrow 347 ctx.setSourceRGB(0, 0, 0); 348 ctx.newPath(); 349 ctx.moveTo(cx, cy); 350 ctx.lineTo(cx - radius * 0.04 * sin_of_minute_angle, 351 cy + radius * 0.04 * cos_of_minute_angle); 352 ctx.lineTo(cx + radius * 0.95 * cos_of_minute_angle, 353 cy + radius * 0.95 * sin_of_minute_angle); 354 ctx.lineTo(cx + radius * 0.04 * sin_of_minute_angle, 355 cy - radius * 0.04 * cos_of_minute_angle); 356 ctx.lineTo(cx - radius * 0.04 * cos_of_minute_angle, 357 cy - radius * 0.04 * sin_of_minute_angle); 358 ctx.lineTo(cx - radius * 0.04 * sin_of_minute_angle, 359 cy + radius * 0.04 * cos_of_minute_angle); 360 ctx.closePath(); 361 ctx.fill(); 362 363 // Draw the second hand in red 364 ctx.setSourceRGB(0x70 / 256.0, 0, 0); 365 ctx.newPath(); 366 ctx.moveTo(cx, cy); 367 ctx.lineTo(cx - radius * 0.02 * sin_of_second_angle, 368 cy + radius * 0.02 * cos_of_second_angle); 369 ctx.lineTo(cx + radius * 0.98 * cos_of_second_angle, 370 cy + radius * 0.98 * sin_of_second_angle); 371 ctx.lineTo(cx + radius * 0.02 * sin_of_second_angle, 372 cy - radius * 0.02 * cos_of_second_angle); 373 ctx.lineTo(cx - radius * 0.02 * cos_of_second_angle, 374 cy - radius * 0.02 * sin_of_second_angle); 375 ctx.lineTo(cx - radius * 0.02 * sin_of_second_angle, 376 cy + radius * 0.02 * cos_of_second_angle); 377 ctx.closePath(); 378 ctx.fill(); 379 380 // now draw the circle inside the arrow 381 ctx.setSourceRGB(1, 1, 1); 382 ctx.arc(cx, 383 cy, 384 radius * 0.02, 385 0 * PI, 386 2.0 * PI); 387 ctx.fill(); 388 389 // now draw the small minute markers 390 ctx.setLineWidth(1.2); 391 ctx.setSourceRGB(0, 0, 0); 392 393 for (double index = 0; index < PI / 2; index += (PI / 30)) 394 { 395 double start = 0.94; 396 397 // draw the markers at the bottom right half of the clock 398 ctx.newPath(); 399 ctx.moveTo(cx + radius * start * cos(index), 400 cy + radius * start * sin(index)); 401 ctx.lineTo(cx + radius * cos(index - PI / 240), 402 cy + radius * sin(index - PI / 240)); 403 ctx.lineTo(cx + radius * cos(index + PI / 240), 404 cy + radius * sin(index + PI / 240)); 405 ctx.closePath(); 406 ctx.fill(); 407 408 // draw the markers at the bottom left half of the clock 409 ctx.newPath(); 410 ctx.moveTo(cx - radius * start * cos(index), 411 cy + radius * start * sin(index)); 412 ctx.lineTo(cx - radius * cos(index - PI / 240), 413 cy + radius * sin(index - PI / 240)); 414 ctx.lineTo(cx - radius * cos(index + PI / 240), 415 cy + radius * sin(index + PI / 240)); 416 ctx.closePath(); 417 ctx.fill(); 418 419 // draw the markers at the top left half of the clock 420 ctx.newPath(); 421 ctx.moveTo(cx - radius * start * cos(index), 422 cy - radius * start * sin(index)); 423 ctx.lineTo(cx - radius * cos(index - PI / 240), 424 cy - radius * sin(index - PI / 240)); 425 ctx.lineTo(cx - radius * cos(index + PI / 240), 426 cy - radius * sin(index + PI / 240)); 427 ctx.closePath(); 428 ctx.fill(); 429 430 // draw the markers at the top right half of the clock 431 ctx.newPath(); 432 ctx.moveTo(cx + radius * start * cos(index), 433 cy - radius * start * sin(index)); 434 ctx.lineTo(cx + radius * cos(index - PI / 240), 435 cy - radius * sin(index - PI / 240)); 436 ctx.lineTo(cx + radius * cos(index + PI / 240), 437 cy - radius * sin(index + PI / 240)); 438 ctx.closePath(); 439 ctx.fill(); 440 } 441 442 // now draw the markers 443 ctx.setLineWidth(1.2); 444 ctx.setSourceRGB(0.5, 0.5, 0.5); 445 446 for (double index = 0; index <= PI / 2; index += (PI / 6)) 447 { 448 double start = 0.86; 449 450 // draw the markers at the bottom right half of the clock 451 ctx.newPath(); 452 ctx.moveTo(cx + radius * start * cos(index), 453 cy + radius * start * sin(index)); 454 ctx.lineTo(cx + radius * cos(index - PI / 200), 455 cy + radius * sin(index - PI / 200)); 456 ctx.lineTo(cx + radius * cos(index + PI / 200), 457 cy + radius * sin(index + PI / 200)); 458 ctx.closePath(); 459 ctx.fill(); 460 461 // draw the markers at the bottom left half of the clock 462 ctx.newPath(); 463 ctx.moveTo(cx - radius * start * cos(index), 464 cy + radius * start * sin(index)); 465 ctx.lineTo(cx - radius * cos(index - PI / 200), 466 cy + radius * sin(index - PI / 200)); 467 ctx.lineTo(cx - radius * cos(index + PI / 200), 468 cy + radius * sin(index + PI / 200)); 469 ctx.closePath(); 470 ctx.fill(); 471 472 // draw the markers at the top left half of the clock 473 ctx.newPath(); 474 ctx.moveTo(cx - radius * start * cos(index), 475 cy - radius * start * sin(index)); 476 ctx.lineTo(cx - radius * cos(index - PI / 200), 477 cy - radius * sin(index - PI / 200)); 478 ctx.lineTo(cx - radius * cos(index + PI / 200), 479 cy - radius * sin(index + PI / 200)); 480 ctx.closePath(); 481 ctx.fill(); 482 483 // draw the markers at the top right half of the clock 484 ctx.newPath(); 485 ctx.moveTo(cx + radius * start * cos(index), 486 cy - radius * start * sin(index)); 487 ctx.lineTo(cx + radius * cos(index - PI / 200), 488 cy - radius * sin(index - PI / 200)); 489 ctx.lineTo(cx + radius * cos(index + PI / 200), 490 cy - radius * sin(index + PI / 200)); 491 ctx.closePath(); 492 ctx.fill(); 493 } 494 } 495 496 // The angle of each of each clock hand. 497 double m_hour_angle; 498 double m_minute_angle; 499 double m_second_angle; 500} 501 502/* A place to hold Widget objects. Since each window has a unique HWND, 503 * we can use this hash type to store references to Widgets and call 504 * their window processing methods. 505 */ 506Widget[HWND] WidgetHandles; 507 508/* 509 * All Widget windows have this window procedure registered via RegisterClass(), 510 * we use it to dispatch to the appropriate Widget window processing method. 511 * 512 * A similar technique is used in the DFL and DGUI libraries for all of its 513 * windows and widgets. 514 */ 515extern (Windows) 516LRESULT winDispatch(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam) 517{ 518 auto widget = hwnd in WidgetHandles; 519 520 if (widget !is null) 521 { 522 return widget.process(message, wParam, lParam); 523 } 524 525 return DefWindowProc(hwnd, message, wParam, lParam); 526} 527 528extern (Windows) 529LRESULT mainWinProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam) 530{ 531 static PaintBuffer paintBuffer; 532 static int width, height; 533 534 static HMENU widgetID = cast(HMENU)0; 535 536 void draw(StateContext ctx) 537 { 538 ctx.setSourceRGB(1, 1, 1); 539 ctx.paint(); 540 } 541 542 switch (message) 543 { 544 case WM_CREATE: 545 { 546 auto hDesk = GetDesktopWindow(); 547 RECT rc; 548 GetClientRect(hDesk, &rc); 549 550 auto localHdc = GetDC(hwnd); 551 paintBuffer = new PaintBuffer(localHdc, rc.right, rc.bottom); 552 553 auto hWindow = CreateWindow(WidgetClass.toUTF16z, NULL, 554 WS_CHILDWINDOW | WS_VISIBLE | WS_CLIPCHILDREN, // WS_CLIPCHILDREN is necessary 555 0, 0, 0, 0, 556 hwnd, widgetID, // child ID 557 cast(HINSTANCE)GetWindowLongPtr(hwnd, GWL_HINSTANCE), // hInstance 558 NULL); 559 560 GetClientRect(hwnd, &rc); 561 auto widget = new CairoClock(hWindow, 400, 400); 562 WidgetHandles[hWindow] = widget; 563 564 auto size = widget.size; 565 MoveWindow(hWindow, 0, 0, size.width, size.height, true); 566 567 return 0; 568 } 569 570 case WM_LBUTTONDOWN: 571 { 572 SetFocus(hwnd); 573 return 0; 574 } 575 576 case WM_SIZE: 577 { 578 width = LOWORD(lParam); 579 height = HIWORD(lParam); 580 return 0; 581 } 582 583 case WM_PAINT: 584 { 585 auto ctx = paintBuffer.ctx; 586 auto hBuffer = paintBuffer.hBuffer; 587 PAINTSTRUCT ps; 588 auto hdc = BeginPaint(hwnd, &ps); 589 auto boundRect = ps.rcPaint; 590 591 draw(StateContext(paintBuffer.ctx)); 592 593 with (boundRect) 594 { 595 BitBlt(hdc, left, top, right - left, bottom - top, paintBuffer.hBuffer, left, top, SRCCOPY); 596 } 597 598 EndPaint(hwnd, &ps); 599 return 0; 600 } 601 602 case WM_TIMER: 603 { 604 InvalidateRect(hwnd, null, true); 605 return 0; 606 } 607 608 case WM_MOUSEWHEEL: 609 { 610 return 0; 611 } 612 613 case WM_DESTROY: 614 { 615 PostQuitMessage(0); 616 return 0; 617 } 618 619 default: 620 } 621 622 return DefWindowProc(hwnd, message, wParam, lParam); 623} 624 625string WidgetClass = "WidgetClass"; 626 627int myWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int iCmdShow) 628{ 629 string appName = "CairoClock"; 630 631 HWND hwnd; 632 MSG msg; 633 WNDCLASS wndclass; 634 635 /* One class for the main window */ 636 wndclass.lpfnWndProc = &mainWinProc; 637 wndclass.cbClsExtra = 0; 638 wndclass.cbWndExtra = 0; 639 wndclass.hInstance = hInstance; 640 wndclass.hIcon = LoadIcon(NULL, IDI_APPLICATION); 641 wndclass.hCursor = LoadCursor(NULL, IDC_ARROW); 642 wndclass.hbrBackground = null; 643 wndclass.lpszMenuName = NULL; 644 wndclass.lpszClassName = appName.toUTF16z; 645 646 if (!RegisterClass(&wndclass)) 647 { 648 MessageBox(NULL, "This program requires Windows NT!", appName.toUTF16z, MB_ICONERROR); 649 return 0; 650 } 651 652 /* Separate window class for Widgets. */ 653 wndclass.hbrBackground = null; 654 wndclass.lpfnWndProc = &winDispatch; 655 wndclass.cbWndExtra = 0; 656 wndclass.hIcon = NULL; 657 wndclass.lpszClassName = WidgetClass.toUTF16z; 658 659 if (!RegisterClass(&wndclass)) 660 { 661 MessageBox(NULL, "This program requires Windows NT!", appName.toUTF16z, MB_ICONERROR); 662 return 0; 663 } 664 665 hwnd = CreateWindow(appName.toUTF16z, "CairoD Clock", 666 WS_OVERLAPPEDWINDOW | WS_CLIPCHILDREN, // WS_CLIPCHILDREN is necessary 667 CW_USEDEFAULT, CW_USEDEFAULT, 668 420, 420, 669 NULL, NULL, hInstance, NULL); 670 671 auto hDesk = GetDesktopWindow(); 672 RECT rc; 673 GetClientRect(hDesk, &rc); 674 MoveWindow(hwnd, rc.right / 3, rc.bottom / 3, 420, 420, true); 675 676 ShowWindow(hwnd, iCmdShow); 677 UpdateWindow(hwnd); 678 679 while (GetMessage(&msg, NULL, 0, 0)) 680 { 681 TranslateMessage(&msg); 682 DispatchMessage(&msg); 683 } 684 685 return msg.wParam; 686} 687 688extern (Windows) 689int WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int iCmdShow) 690{ 691 int result; 692 void exceptionHandler(Throwable e) { 693 throw e; 694 } 695 696 try 697 { 698 Runtime.initialize(&exceptionHandler); 699 myWinMain(hInstance, hPrevInstance, lpCmdLine, iCmdShow); 700 Runtime.terminate(&exceptionHandler); 701 } 702 catch (Throwable o) 703 { 704 MessageBox(null, o.toString().toUTF16z, "Error", MB_OK | MB_ICONEXCLAMATION); 705 result = -1; 706 } 707 708 return result; 709}