PageRenderTime 268ms CodeModel.GetById 60ms app.highlight 151ms RepoModel.GetById 50ms app.codeStats 1ms

/win32_steps_extended.d

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