PageRenderTime 1294ms CodeModel.GetById 121ms app.highlight 1038ms RepoModel.GetById 79ms app.codeStats 1ms

/win32_cairo_clock.d

http://github.com/AndrejMitrovic/cairoDSamples
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}