/win32_cairo_clock.d

http://github.com/AndrejMitrovic/cairoDSamples · D · 709 lines · 552 code · 114 blank · 43 comment · 12 complexity · 03536d63159470c6c32c608c6dcec335 MD5 · raw file

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