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