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 }