PageRenderTime 147ms CodeModel.GetById 50ms app.highlight 74ms RepoModel.GetById 17ms app.codeStats 0ms

/win32_steps.d

http://github.com/AndrejMitrovic/cairoDSamples
D | 634 lines | 477 code | 116 blank | 41 comment | 19 complexity | fa06aef1bf4905382117d8096371c6b3 MD5 | raw file
  1module win32_steps;
  2
  3/+
  4 +           Copyright Andrej Mitrovic 2011.
  5 +  Distributed under the Boost Software License, Version 1.0.
  6 +     (See accompanying file LICENSE_1_0.txt or copy at
  7 +           http://www.boost.org/LICENSE_1_0.txt)
  8 +/
  9
 10/+
 11 + A prototype step-sequencer Widget example. You can press and hold
 12 + the left mouse button and drag the mouse to activate multiple steps.
 13 + If you first clicked on an active step, the mode is changed to
 14 + deactivating, so the steps you drag over will be deactivated.
 15 +/
 16
 17import core.memory;
 18import core.runtime;
 19import core.thread;
 20import core.stdc.config;
 21
 22import std.algorithm;
 23import std.array;
 24import std.conv;
 25import std.exception;
 26import std.functional;
 27import std.math;
 28import std.random;
 29import std.range;
 30import std.stdio;
 31import std.string;
 32import std.traits;
 33import std.utf;
 34
 35pragma(lib, "gdi32.lib");
 36
 37import win32.windef;
 38import win32.winuser;
 39import win32.wingdi;
 40
 41alias std.algorithm.min min;
 42alias std.algorithm.max max;
 43
 44import cairo.cairo;
 45import cairo.win32;
 46
 47alias cairo.cairo.RGB RGB;
 48
 49struct StateContext
 50{
 51    Context ctx;
 52
 53    this(Context ctx)
 54    {
 55        this.ctx = ctx;
 56        ctx.save();
 57    }
 58
 59    ~this()
 60    {
 61        ctx.restore();
 62    }
 63
 64    alias ctx this;
 65}
 66
 67/* Each allocation consumes 3 GDI objects. */
 68class PaintBuffer
 69{
 70    this(HDC localHdc, int cxClient, int cyClient)
 71    {
 72        width  = cxClient;
 73        height = cyClient;
 74
 75        hBuffer    = CreateCompatibleDC(localHdc);
 76        hBitmap    = CreateCompatibleBitmap(localHdc, cxClient, cyClient);
 77        hOldBitmap = SelectObject(hBuffer, hBitmap);
 78
 79        surf        = new Win32Surface(hBuffer);
 80        ctx         = Context(surf);
 81        initialized = true;
 82    }
 83
 84    ~this()
 85    {
 86        if (initialized)
 87        {
 88            clear();
 89        }
 90    }
 91
 92    void clear()
 93    {
 94        surf.dispose();
 95        ctx.dispose();
 96        surf.finish();
 97
 98        SelectObject(hBuffer, hOldBitmap);
 99        DeleteObject(hBitmap);
100        DeleteDC(hBuffer);
101
102        initialized = false;
103    }
104
105    bool initialized;
106    int  width, height;
107    HDC  hBuffer;
108    HBITMAP hBitmap;
109    HBITMAP hOldBitmap;
110    Context ctx;
111    Surface surf;
112}
113
114abstract class Widget
115{
116    Widget parent;
117    PaintBuffer paintBuffer;
118    PAINTSTRUCT ps;
119
120    HWND hwnd;
121    int  width, height;
122    bool needsRedraw = true;
123
124    this(Widget parent, HWND hwnd, int width, int height)
125    {
126        this.parent = parent;
127        this.hwnd   = hwnd;
128        this.width  = width;
129        this.height = height;
130    }
131
132    @property Size!int size()
133    {
134        return Size!int (width, height);
135    }
136
137    abstract LRESULT process(UINT message, WPARAM wParam, LPARAM lParam)
138    {
139        switch (message)
140        {
141            case WM_ERASEBKGND:
142            {
143                return 1;
144            }
145
146            case WM_PAINT:
147            {
148                OnPaint(hwnd, message, wParam, lParam);
149                return 0;
150            }
151
152            case WM_SIZE:
153            {
154                width  = LOWORD(lParam);
155                height = HIWORD(lParam);
156
157                auto localHdc = GetDC(hwnd);
158
159                if (paintBuffer !is null)
160                {
161                    paintBuffer.clear();
162                }
163
164                paintBuffer = new PaintBuffer(localHdc, width, height);
165                ReleaseDC(hwnd, localHdc);
166
167                needsRedraw = true;
168                blit();
169                return 0;
170            }
171
172            case WM_TIMER:
173            {
174                blit();
175                return 0;
176            }
177
178            case WM_DESTROY:
179            {
180                // @BUG@
181                // Not doing this here causes exceptions being thrown from within cairo
182                // when calling surface.dispose(). I'm not sure why yet.
183                paintBuffer.clear();
184                return 0;
185            }
186
187            default:
188        }
189
190        return DefWindowProc(hwnd, message, wParam, lParam);
191    }
192
193    void redraw()
194    {
195        needsRedraw = true;
196        blit();
197    }
198
199    void blit()
200    {
201        InvalidateRect(hwnd, null, true);
202    }
203
204    abstract void OnPaint(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam);
205    abstract void draw(StateContext ctx);
206}
207
208// Ported from http://cairographics.org/cookbook/roundedrectangles/
209void DrawRoundedRect(Context ctx, int x, int y, int width, int height, int radius = 10)
210{
211    // "Draw a rounded rectangle"
212    //   A****BQ
213    //  H      C
214    //  *      *
215    //  G      D
216    //   F****E
217
218    ctx.moveTo(x + radius, y);                                                                 // Move to A
219    ctx.lineTo(x + width - radius, y);                                                         // Straight line to B
220    ctx.curveTo(x + width, y, x + width, y, x + width, y + radius);                            // Curve to C, Control points are both at Q
221    ctx.lineTo(x + width, y + height - radius);                                                // Move to D
222    ctx.curveTo(x + width, y + height, x + width, y + height, x + width - radius, y + height); // Curve to E
223    ctx.lineTo(x + radius, y + height);                                                        // Line to F
224    ctx.curveTo(x, y + height, x, y + height, x, y + height - radius);                         // Curve to G
225    ctx.lineTo(x, y + radius);                                                                 // Line to H
226    ctx.curveTo(x, y, x, y, x + radius, y);                                                    // Curve to A
227}
228
229class Step : Widget
230{
231    static bool multiSelectState;
232    Steps steps;
233    bool  _selected;
234    RGB backColor;
235    size_t noteIndex;
236
237    @property void selected(bool state)
238    {
239        _selected = state;
240        redraw();
241    }
242
243    @property bool selected()
244    {
245        return _selected;
246    }
247
248    this(Widget parent, size_t noteIndex, HWND hwnd, int width, int height)
249    {
250        this.noteIndex = noteIndex;
251        super(parent, hwnd, width, height);
252        this.steps = cast(Steps)parent;
253        enforce(this.steps !is null);
254    }
255
256    void setStepState(bool state)
257    {
258        steps.setStepState(this, noteIndex, state);
259    }
260
261    override LRESULT process(UINT message, WPARAM wParam, LPARAM lParam)
262    {
263        switch (message)
264        {
265            case WM_LBUTTONDOWN:
266            {
267                selected = !selected;
268                setStepState(selected);
269                multiSelectState = selected;
270                break;
271            }
272
273            case WM_MOUSEMOVE:
274            {
275                if (wParam & MK_LBUTTON)
276                {
277                    setStepState(multiSelectState);
278                }
279
280                break;
281            }
282
283            default:
284        }
285
286        return super.process(message, wParam, lParam);
287    }
288
289    override void OnPaint(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
290    {
291        auto ctx       = paintBuffer.ctx;
292        auto hBuffer   = paintBuffer.hBuffer;
293        auto hdc       = BeginPaint(hwnd, &ps);
294        auto boundRect = ps.rcPaint;
295
296        if (needsRedraw)
297        {
298            //~ writeln("drawing");
299            draw(StateContext(ctx));
300            needsRedraw = false;
301        }
302
303        with (boundRect)
304        {
305            //~ writeln("blitting");
306            BitBlt(hdc, left, top, right - left, bottom - top, paintBuffer.hBuffer, left, top, SRCCOPY);
307        }
308
309        EndPaint(hwnd, &ps);
310    }
311
312    override void draw(StateContext ctx)
313    {
314        ctx.rectangle(1, 1, width - 2, height - 2);
315        ctx.setSourceRGB(0, 0, 0.8);
316        ctx.fill();
317
318        if (selected)
319        {
320            auto darkCyan = RGB(0, 0.6, 1);
321            ctx.setSourceRGB(darkCyan);
322            DrawRoundedRect(ctx, 5, 5, width - 10, height - 10, 15);
323            ctx.fill();
324
325            ctx.setSourceRGB(brightness(darkCyan, + 0.4));
326            DrawRoundedRect(ctx, 10, 10, width - 20, height - 20, 15);
327            ctx.fill();            
328        }
329    }
330}
331
332RGB brightness(RGB rgb, double amount)
333{
334    with (rgb)
335    {
336        if (red > 0)
337            red = max(0, min(1.0, red + amount));
338        
339        if (green > 0)
340            green = max(0, min(1.0, green + amount));
341        
342        if (blue > 0)
343            blue  = max(0, min(1.0, blue  + amount));
344    }
345    
346    return rgb;
347}
348
349class Steps : Widget
350{
351    RGB backColor;
352    Step[8] oldActiveStep;
353    ubyte[0][Step][8] steps;
354
355    void setStepState(Step step, size_t noteIndex, bool active)
356    {
357        if (active && (oldActiveStep[noteIndex] in steps[noteIndex]))
358        {
359            oldActiveStep[noteIndex].selected = false;
360        }
361
362        if (active)
363        {
364            oldActiveStep[noteIndex] = step;
365        }
366
367        step.selected = active;
368    }
369
370    // note: the main window is still not a Widget class, so parent is null
371    this(Widget parent, HWND hwnd, int width, int height)
372    {
373        super(parent, hwnd, width, height);
374
375        auto localHdc   = GetDC(hwnd);
376        auto stepWidth  = width / 5;
377        auto stepHeight = height / 10;
378
379        foreach (noteIndex; 0 .. steps.length)
380            foreach (vIndex; 0 .. 8)
381            {
382                auto hWindow = CreateWindow(WidgetClass.toUTF16z, NULL,
383                                            WS_CHILDWINDOW | WS_VISIBLE | WS_CLIPCHILDREN,        // WS_CLIPCHILDREN is necessary
384                                            0, 0, 0, 0,
385                                            hwnd, cast(HANDLE)1,                                  // child ID
386                                            cast(HINSTANCE)GetWindowLongPtr(hwnd, GWL_HINSTANCE), // hInstance
387                                            NULL);
388
389                auto widget = new Step(this, noteIndex, hWindow, stepWidth, stepHeight);
390                WidgetHandles[hWindow]   = widget;
391                steps[noteIndex][widget] = [];
392
393                auto size = widget.size;
394                MoveWindow(hWindow, noteIndex * stepWidth, vIndex * stepHeight, size.width, size.height, true);
395            }
396
397    }
398
399    override LRESULT process(UINT message, WPARAM wParam, LPARAM lParam)
400    {
401        return super.process(message, wParam, lParam);
402    }
403
404    override void OnPaint(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
405    {
406        auto ctx       = paintBuffer.ctx;
407        auto hBuffer   = paintBuffer.hBuffer;
408        auto hdc       = BeginPaint(hwnd, &ps);
409        auto boundRect = ps.rcPaint;
410
411        if (needsRedraw)
412        {
413            //~ writeln("drawing");
414            draw(StateContext(ctx));
415            needsRedraw = false;
416        }
417
418        with (boundRect)
419        {
420            //~ writeln("blitting");
421            BitBlt(hdc, left, top, right - left, bottom - top, paintBuffer.hBuffer, left, top, SRCCOPY);
422        }
423
424        EndPaint(hwnd, &ps);
425    }
426
427    override void draw(StateContext ctx)
428    {
429        ctx.setSourceRGB(1, 1, 1);
430        ctx.paint();
431    }
432}
433
434/* A place to hold Widget objects. Since each window has a unique HWND,
435 * we can use this hash type to store references to Widgets and call
436 * their window processing methods.
437 */
438Widget[HWND] WidgetHandles;
439
440/*
441 * All Widget windows have this window procedure registered via RegisterClass(),
442 * we use it to dispatch to the appropriate Widget window processing method.
443 *
444 * A similar technique is used in the DFL and DGUI libraries for all of its
445 * windows and widgets.
446 */
447extern (Windows)
448LRESULT winDispatch(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
449{
450    auto widget = hwnd in WidgetHandles;
451
452    if (widget !is null)
453    {
454        return widget.process(message, wParam, lParam);
455    }
456
457    return DefWindowProc(hwnd, message, wParam, lParam);
458}
459
460extern (Windows)
461LRESULT mainWinProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
462{
463    static PaintBuffer paintBuffer;
464    static int width, height;
465    static int TimerID = 16;
466
467    static HMENU widgetID = cast(HMENU)0;  // todo: each widget has its own HMENU ID
468
469    void draw(StateContext ctx)
470    {
471        ctx.setSourceRGB(1, 1, 1);
472        ctx.paint();
473    }
474
475    switch (message)
476    {
477        case WM_CREATE:
478        {
479            auto hDesk = GetDesktopWindow();
480            RECT rc;
481            GetClientRect(hDesk, &rc);
482
483            auto localHdc = GetDC(hwnd);
484            paintBuffer = new PaintBuffer(localHdc, rc.right, rc.bottom);
485
486            auto hWindow = CreateWindow(WidgetClass.toUTF16z, NULL,
487                                        WS_CHILDWINDOW | WS_VISIBLE | WS_CLIPCHILDREN,        // WS_CLIPCHILDREN is necessary
488                                        0, 0, 0, 0,
489                                        hwnd, widgetID,                                       // child ID
490                                        cast(HINSTANCE)GetWindowLongPtr(hwnd, GWL_HINSTANCE), // hInstance
491                                        NULL);
492
493            auto widget = new Steps(null, hWindow, 400, 400);
494            WidgetHandles[hWindow] = widget;
495
496            auto size = widget.size;
497            MoveWindow(hWindow, 0, 0, size.width, size.height, true);
498
499            return 0;
500        }
501
502        case WM_LBUTTONDOWN:
503        {
504            SetFocus(hwnd);
505            return 0;
506        }
507
508        case WM_SIZE:
509        {
510            width  = LOWORD(lParam);
511            height = HIWORD(lParam);
512            return 0;
513        }
514
515        case WM_PAINT:
516        {
517            auto ctx     = paintBuffer.ctx;
518            auto hBuffer = paintBuffer.hBuffer;
519            PAINTSTRUCT ps;
520            auto hdc       = BeginPaint(hwnd, &ps);
521            auto boundRect = ps.rcPaint;
522
523            draw(StateContext(paintBuffer.ctx));
524
525            with (boundRect)
526            {
527                BitBlt(hdc, left, top, right - left, bottom - top, paintBuffer.hBuffer, left, top, SRCCOPY);
528            }
529
530            EndPaint(hwnd, &ps);
531            return 0;
532        }
533
534        case WM_TIMER:
535        {
536            InvalidateRect(hwnd, null, true);
537            return 0;
538        }
539
540        case WM_MOUSEWHEEL:
541        {
542            return 0;
543        }
544
545        case WM_DESTROY:
546        {
547            PostQuitMessage(0);
548            return 0;
549        }
550
551        default:
552    }
553
554    return DefWindowProc(hwnd, message, wParam, lParam);
555}
556
557string WidgetClass = "WidgetClass";
558
559int myWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int iCmdShow)
560{
561    string appName = "Step Sequencer";
562
563    HWND hwnd;
564    MSG  msg;
565    WNDCLASS wndclass;
566
567    /* One class for the main window */
568    wndclass.lpfnWndProc   = &mainWinProc;
569    wndclass.cbClsExtra    = 0;
570    wndclass.cbWndExtra    = 0;
571    wndclass.hInstance     = hInstance;
572    wndclass.hIcon         = LoadIcon(NULL, IDI_APPLICATION);
573    wndclass.hCursor       = LoadCursor(NULL, IDC_ARROW);
574    wndclass.hbrBackground = null;
575    wndclass.lpszMenuName  = NULL;
576    wndclass.lpszClassName = appName.toUTF16z;
577
578    if (!RegisterClass(&wndclass))
579    {
580        MessageBox(NULL, "This program requires Windows NT!", appName.toUTF16z, MB_ICONERROR);
581        return 0;
582    }
583
584    /* Separate window class for Widgets. */
585    wndclass.hbrBackground = null;
586    wndclass.lpfnWndProc   = &winDispatch;
587    wndclass.cbWndExtra    = 0;
588    wndclass.hIcon         = NULL;
589    wndclass.lpszClassName = WidgetClass.toUTF16z;
590
591    if (!RegisterClass(&wndclass))
592    {
593        MessageBox(NULL, "This program requires Windows NT!", appName.toUTF16z, MB_ICONERROR);
594        return 0;
595    }
596
597    hwnd = CreateWindow(appName.toUTF16z, "step sequencer",
598                        WS_OVERLAPPEDWINDOW | WS_CLIPCHILDREN,  // WS_CLIPCHILDREN is necessary
599                        cast(int)(1680 / 3.3), 1050 / 3,
600                        400, 400,
601                        NULL, NULL, hInstance, NULL);
602
603    ShowWindow(hwnd, iCmdShow);
604    UpdateWindow(hwnd);
605
606    while (GetMessage(&msg, NULL, 0, 0))
607    {
608        TranslateMessage(&msg);
609        DispatchMessage(&msg);
610    }
611
612    return msg.wParam;
613}
614
615extern (Windows)
616int WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int iCmdShow)
617{
618    int result;
619    void exceptionHandler(Throwable e) { throw e; }
620
621    try
622    {
623        Runtime.initialize(&exceptionHandler);
624        myWinMain(hInstance, hPrevInstance, lpCmdLine, iCmdShow);
625        Runtime.terminate(&exceptionHandler);
626    }
627    catch (Throwable o)
628    {
629        MessageBox(null, o.toString().toUTF16z, "Error", MB_OK | MB_ICONEXCLAMATION);
630        result = -1;
631    }
632
633    return result;
634}