PageRenderTime 104ms CodeModel.GetById 19ms app.highlight 76ms RepoModel.GetById 1ms app.codeStats 1ms

/win32_scrolling.d

http://github.com/AndrejMitrovic/cairoDSamples
D | 752 lines | 560 code | 146 blank | 46 comment | 23 complexity | 7179a80fe0ee68658098e63eae852a33 MD5 | raw file
  1module win32_clipped_draw;
  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
 10import core.memory;
 11import core.runtime;
 12import core.thread;
 13import core.stdc.config;
 14
 15import std.algorithm;
 16import std.array;
 17import std.conv;
 18import std.exception;
 19import std.functional;
 20import std.math;
 21import std.random;
 22import std.range;
 23import std.stdio;
 24import std.string;
 25import std.traits;
 26import std.utf;
 27
 28pragma(lib, "gdi32.lib");
 29
 30import win32.windef;
 31import win32.winuser;
 32import win32.wingdi;
 33
 34alias std.algorithm.min min;  // conflict resolution
 35alias std.algorithm.max max;  // conflict resolution
 36
 37import cairo.cairo;
 38import cairo.win32;
 39
 40alias cairo.cairo.RGB RGB;   // conflict resolution
 41
 42struct StateContext
 43{
 44    Context ctx;
 45
 46    this(Context ctx)
 47    {
 48        this.ctx = ctx;
 49        ctx.save();
 50    }
 51
 52    ~this()
 53    {
 54        ctx.restore();
 55    }
 56
 57    alias ctx this;
 58}
 59
 60class PaintBuffer
 61{
 62    this(HDC localHdc, int cxClient, int cyClient)
 63    {
 64        hdc    = localHdc;
 65        width  = cxClient;
 66        height = cyClient;
 67
 68        hBuffer    = CreateCompatibleDC(localHdc);
 69        hBitmap    = CreateCompatibleBitmap(localHdc, cxClient, cyClient);
 70        hOldBitmap = SelectObject(hBuffer, hBitmap);
 71
 72        surf = new Win32Surface(hBuffer);
 73        ctx = Context(surf);
 74        initialized = true;
 75    }
 76
 77    ~this()
 78    {
 79        if (initialized)
 80        {
 81            clear();
 82        }
 83    }
 84    
 85    void clear()
 86    {
 87        ctx.dispose();
 88        surf.finish();
 89        surf.dispose();
 90
 91        SelectObject(hBuffer, hOldBitmap);
 92        DeleteObject(hBitmap);
 93        DeleteDC(hBuffer);
 94        initialized = false;        
 95    }
 96
 97    HDC hdc;
 98    bool initialized;
 99    int width, height;
100    HDC hBuffer;
101    HBITMAP hBitmap;
102    HBITMAP hOldBitmap;
103    Context ctx;
104    Surface surf;
105}
106
107abstract class Widget
108{
109    Widget parent;
110    PAINTSTRUCT ps;
111    PaintBuffer mainPaintBuff;
112    PaintBuffer paintBuffer;
113    HWND hwnd;
114    int width, height;
115    int xOffset, yOffset;
116    bool needsRedraw = true;
117    bool selected;
118    
119    this(HWND hwnd, int width, int height)
120    {
121        this.hwnd = hwnd;
122        this.width = width;
123        this.height = height;
124        //~ SetTimer(hwnd, 100, 1, NULL);
125    }
126
127    @property Size!int size()
128    {
129        return Size!int(width, height);
130    }
131
132    abstract LRESULT process(UINT message, WPARAM wParam, LPARAM lParam)
133    {
134        switch (message)
135        {
136            case WM_ERASEBKGND:
137            {
138                return 1;
139            }
140
141            case WM_PAINT:
142            {
143                OnPaint(hwnd, message, wParam, lParam);
144                return 0;
145            }
146
147            case WM_SIZE:
148            {
149                width  = cast(short)LOWORD(lParam);
150                height = cast(short)HIWORD(lParam);
151                
152                auto localHdc = GetDC(hwnd);
153                
154                if (paintBuffer !is null)
155                {
156                    paintBuffer.clear();
157                }
158                
159                paintBuffer = new PaintBuffer(localHdc, width, height);
160                ReleaseDC(hwnd, localHdc);
161
162                needsRedraw = true;
163                InvalidateRect(hwnd, null, true);
164                return 0;
165            }
166            
167            case WM_TIMER:
168            {
169                InvalidateRect(hwnd, null, true);
170                return 0;
171            }                
172            
173            case WM_MOVE:
174            {
175                xOffset = cast(short)LOWORD(lParam);
176                yOffset = cast(short)HIWORD(lParam);
177                writeln(yOffset);
178                return 0;
179            }
180
181            case WM_LBUTTONDOWN:
182            {
183                selected ^= 1;  // flip
184                needsRedraw = true;
185                InvalidateRect(hwnd, NULL, false);
186                
187                //~ auto xPos = cast(short)LOWORD(lParam); 
188                //~ auto yPos = cast(short)HIWORD(lParam); 
189                //~ writefln("xPos: %s, yPos: %s", xPos, yPos);
190                return 0;
191            }   
192            
193            default:
194        }
195
196        return DefWindowProc(hwnd, message, wParam, lParam);
197    }
198
199    abstract void OnPaint(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam);
200    abstract void draw(StateContext ctx);
201}
202
203class TestWidget2 : Widget
204{
205    this(HWND hwnd, int width, int height)
206    {
207        super(hwnd, width, height);
208    }
209    
210    override LRESULT process(UINT message, WPARAM wParam, LPARAM lParam)
211    {
212        return super.process(message, wParam, lParam);
213    }
214
215    override void OnPaint(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
216    {
217        auto ctx = paintBuffer.ctx;
218        auto hBuffer = paintBuffer.hBuffer;
219        auto hdc = BeginPaint(hwnd, &ps);
220        auto boundRect = ps.rcPaint;
221
222        if (needsRedraw)
223        {
224            //~ writeln("drawing");
225            draw(StateContext(ctx));
226            needsRedraw = false;
227        }
228        
229        with (boundRect)
230        {
231            //~ writeln("blitting");
232            BitBlt(hdc, left, top, right - left, bottom - top, paintBuffer.hBuffer, left, top, SRCCOPY);
233        }
234
235        EndPaint(hwnd, &ps);
236    }
237
238    override void draw(StateContext ctx)
239    {
240        ctx.setSourceRGB(1, 1, 1);
241        ctx.paint();
242
243        ctx.save();
244        ctx.scale(width, height);
245        ctx.moveTo(0, 0);
246
247        ctx.rectangle(0, 0, 1, 1);
248        ctx.setSourceRGBA(1, 1, 1, 0);
249        ctx.setOperator(Operator.CAIRO_OPERATOR_CLEAR);
250        ctx.fill();
251        
252        ctx.setSourceRGB(0, 0, 0);
253        ctx.setOperator(Operator.CAIRO_OPERATOR_OVER);        
254        
255        auto linpat = new LinearGradient(0, 0, 1, 1);
256        linpat.addColorStopRGB(0, RGB(0, 0.3, 0.8));
257        linpat.addColorStopRGB(1, RGB(0, 0.8, 0.3));
258
259        auto radpat = new RadialGradient(0.5, 0.5, 0.25, 0.5, 0.5, 0.75);
260        radpat.addColorStopRGBA(0,   RGBA(0, 0, 0, 1));
261        radpat.addColorStopRGBA(0.5, RGBA(0, 0, 0, 0));    
262
263        ctx.setSource(linpat);
264        ctx.mask(radpat);
265        ctx.restore();
266        
267        if (selected)
268        {
269            ctx.moveTo(0, 0);
270            ctx.setLineWidth(3);
271            ctx.setSourceRGB(1, 0, 0);
272            ctx.rectangle(0, 0, width, height);
273            ctx.stroke();
274        }       
275    }    
276}
277
278class TestWidget : Widget
279{
280    RGB backColor;
281    
282    this(HWND hwnd, int width, int height)
283    {
284        super(hwnd, width, height);
285        this.backColor = RGB(1, 0, 0);
286    
287        auto localHdc = GetDC(hwnd);
288        auto hWindow = CreateWindow(WidgetClass.toUTF16z, NULL,
289                       WS_CHILDWINDOW | WS_VISIBLE | WS_CLIPCHILDREN,  // WS_CLIPCHILDREN is necessary
290                       0, 0, 0, 0,
291                       hwnd, cast(HANDLE)1,                                   // child ID
292                       cast(HINSTANCE)GetWindowLongPtr(hwnd, GWL_HINSTANCE),  // hInstance
293                       NULL);
294
295        auto widget = new TestWidget2(hWindow, width / 2, width / 2);
296        WidgetHandles[hWindow] = widget;
297
298        auto size = widget.size;
299        MoveWindow(hWindow, size.width / 2, size.height / 2, size.width, size.height, true);        
300    }
301
302    override LRESULT process(UINT message, WPARAM wParam, LPARAM lParam)
303    {
304        return super.process(message, wParam, lParam);
305    }
306
307    override void OnPaint(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
308    {
309        auto ctx = paintBuffer.ctx;
310        auto hBuffer = paintBuffer.hBuffer;
311        auto hdc = BeginPaint(hwnd, &ps);
312        auto boundRect = ps.rcPaint;
313
314        if (needsRedraw)
315        {
316            //~ writeln("drawing");
317            draw(StateContext(ctx));
318            needsRedraw = false;
319        }
320        
321        with (boundRect)
322        {
323            //~ writeln("blitting");
324            BitBlt(hdc, left, top, right - left, bottom - top, paintBuffer.hBuffer, left, top, SRCCOPY);
325        }
326
327        EndPaint(hwnd, &ps);
328    }
329
330    override void draw(StateContext ctx)
331    {
332        ctx.save();
333        ctx.scale(width, height);
334        ctx.moveTo(0, 0);
335
336        ctx.rectangle(0, 0, 1, 1);
337        ctx.setSourceRGBA(1, 1, 1, 0);
338        ctx.setOperator(Operator.CAIRO_OPERATOR_CLEAR);
339        ctx.fill();
340
341        ctx.setSourceRGB(0, 0, 0);
342        ctx.setOperator(Operator.CAIRO_OPERATOR_OVER);        
343        
344        auto linpat = new LinearGradient(0, 0, 1, 1);
345        linpat.addColorStopRGB(0, RGB(0, 0.3, 0.8));
346        linpat.addColorStopRGB(1, RGB(0, 0.8, 0.3));
347
348        auto radpat = new RadialGradient(0.5, 0.5, 0.25, 0.5, 0.5, 0.75);
349        radpat.addColorStopRGBA(0,   RGBA(0, 0, 0, 1));
350        radpat.addColorStopRGBA(0.5, RGBA(0, 0, 0, 0));    
351
352        ctx.setSource(linpat);
353        ctx.mask(radpat);
354        
355        ctx.moveTo(0.1, 0.5);
356        ctx.restore();
357        
358        ctx.setSourceRGB(1, 1, 1);
359        ctx.selectFontFace("Tahoma", FontSlant.CAIRO_FONT_SLANT_NORMAL, FontWeight.CAIRO_FONT_WEIGHT_NORMAL);
360        ctx.setFontSize(20);
361        ctx.showText("weeeeeeeeeeeeeeeeeeeeeeeeeee");        
362        
363        if (selected)
364        {
365            ctx.moveTo(0, 0);
366            ctx.setLineWidth(3);
367            ctx.setSourceRGB(1, 0, 0);
368            ctx.rectangle(0, 0, width, height);
369            ctx.stroke();
370        }        
371    }
372}
373
374/* A place to hold Widget objects. Since each window has a unique HWND,
375 * we can use this hash type to store references to Widgets and call
376 * their window processing methods.
377 */
378Widget[HWND] WidgetHandles;
379
380/*
381 * All Widget windows have this window procedure registered via RegisterClass(),
382 * we use it to dispatch to the appropriate Widget window processing method.
383 *
384 * A similar technique is used in the DFL and DGUI libraries for all of its
385 * windows and widgets.
386 */
387extern (Windows)
388LRESULT winDispatch(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
389{
390    auto widget = hwnd in WidgetHandles;
391
392    if (widget !is null)
393    {
394        return widget.process(message, wParam, lParam);
395    }
396
397    return DefWindowProc(hwnd, message, wParam, lParam);
398}
399
400extern (Windows)
401LRESULT mainWinProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
402{
403    static PaintBuffer paintBuffer;
404    static int width, height;
405    static int TimerID = 16;
406    
407    // Note: this was copied from another example, most of these are not required here
408    static int cxChar, cxCaps, cyChar, cxClient, cyClient, iMaxWidth;
409    static int iDeltaPerLine, iAccumDelta;  // for mouse wheel logic
410    int i, x, y, iVertPos, iHorzPos, iPaintStart, iPaintEnd;
411    SCROLLINFO  si;
412    TEXTMETRIC tm;
413    ULONG ulScrollLines;  // for mouse wheel logic    
414    
415    static HMENU widgetID = cast(HMENU)0;  // todo: each widget has its own HMENU ID
416
417    void draw(StateContext ctx)
418    {
419        ctx.setSourceRGB(0.2, 0.2, 0.2);
420        ctx.paint();
421    }    
422    
423    switch (message)
424    {
425        case WM_CREATE:
426            auto hdc = GetDC(hwnd);
427
428            GetTextMetrics(hdc, &tm);
429            cxChar = tm.tmAveCharWidth;
430            cxCaps = (tm.tmPitchAndFamily & 1 ? 3 : 2) * cxChar / 2;
431            cyChar = tm.tmHeight + tm.tmExternalLeading;
432
433            ReleaseDC(hwnd, hdc);
434
435            // Save the width of the three columns
436            iMaxWidth = 40 * cxChar + 22 * cxCaps;
437        
438            auto hDesk = GetDesktopWindow();
439            RECT rc;
440            GetClientRect(hDesk, &rc);
441
442            auto localHdc = GetDC(hwnd);
443            paintBuffer = new PaintBuffer(localHdc, rc.right, rc.bottom);
444
445            auto hWindow = CreateWindow(WidgetClass.toUTF16z, NULL,
446                           WS_CHILDWINDOW | WS_VISIBLE | WS_CLIPCHILDREN,  // WS_CLIPCHILDREN is necessary
447                           0, 0, 0, 0,
448                           hwnd, widgetID,                                        // child ID
449                           cast(HINSTANCE)GetWindowLongPtr(hwnd, GWL_HINSTANCE),  // hInstance
450                           NULL);
451
452            auto widget = new TestWidget(hWindow, 400, 400);
453            WidgetHandles[hWindow] = widget;
454
455            auto size = widget.size;
456            MoveWindow(hWindow, size.width / 3, size.width / 3, size.width, size.height, true);
457        
458            goto case;
459
460        // Fall through for mouse wheel information
461        case WM_SETTINGCHANGE:
462        {
463            SystemParametersInfo(SPI_GETWHEELSCROLLLINES, 0, &ulScrollLines, 0);
464
465            // ulScrollLines usually equals 3 or 0 (for no scrolling)
466            // WHEEL_DELTA equals 120, so iDeltaPerLine will be 40
467            if (ulScrollLines)
468                iDeltaPerLine = WHEEL_DELTA / ulScrollLines;
469            else
470                iDeltaPerLine = 0;
471
472            return 0;
473        }        
474
475        case WM_LBUTTONDOWN:
476        {
477            SetFocus(hwnd);
478            return 0;
479        }
480
481        case WM_SIZE:
482        {
483            width  = LOWORD(lParam);
484            height = HIWORD(lParam);
485            
486            enum windowLength = 100;
487            
488            // Set vertical scroll bar range and page size
489            si.fMask = SIF_RANGE | SIF_PAGE;
490            si.nMin  = 0;
491            si.nMax  = windowLength;
492            si.nPage = height / cyChar;
493            SetScrollInfo(hwnd, SB_VERT, &si, TRUE);
494
495            // Set horizontal scroll bar range and page size
496            si.fMask = SIF_RANGE | SIF_PAGE;
497            si.nMin  = 0;
498            si.nMax  = 2 + iMaxWidth / cxChar;
499            si.nPage = width / cxChar;
500            SetScrollInfo(hwnd, SB_HORZ, &si, TRUE);
501            return 0;            
502        }
503
504        case WM_VSCROLL:
505        {
506            // Get all the vertical scroll bar information
507            si.fMask = SIF_ALL;
508            GetScrollInfo(hwnd, SB_VERT, &si);
509
510            // Save the position for comparison later on
511            iVertPos = si.nPos;
512
513            switch (LOWORD(wParam))
514            {
515                case SB_TOP:
516                    si.nPos = si.nMin;
517                    break;
518
519                case SB_BOTTOM:
520                    si.nPos = si.nMax;
521                    break;
522
523                case SB_LINEUP:
524                    si.nPos -= 1;
525                    break;
526
527                case SB_LINEDOWN:
528                    si.nPos += 1;
529                    break;
530
531                case SB_PAGEUP:
532                    si.nPos -= si.nPage;
533                    break;
534
535                case SB_PAGEDOWN:
536                    si.nPos += si.nPage;
537                    break;
538
539                case SB_THUMBTRACK:
540                    si.nPos = si.nTrackPos;
541                    break;
542
543                default:
544                    break;
545            }
546
547            // Set the position and then retrieve it.  Due to adjustments
548            // by Windows it may not be the same as the value set.
549            si.fMask = SIF_POS;
550            SetScrollInfo(hwnd, SB_VERT, &si, TRUE);
551            GetScrollInfo(hwnd, SB_VERT, &si);
552
553            // If the position has changed, scroll the window and update it
554            if (si.nPos != iVertPos)
555            {
556                auto rect = RECT(0, 0, width, 400);
557                // second to last argument is area that is scrolled
558                //~ ScrollWindow(hwnd, 0, cyChar * (iVertPos - si.nPos), NULL, &rect);  // last arg is boundary
559                ScrollWindow(hwnd, 0, cyChar * (iVertPos - si.nPos), NULL, NULL);
560                UpdateWindow(hwnd);
561            }
562
563            return 0;
564        }
565
566        case WM_HSCROLL:
567        {
568            // Get all the vertical scroll bar information
569            si.fMask = SIF_ALL;
570
571            // Save the position for comparison later on
572            GetScrollInfo(hwnd, SB_HORZ, &si);
573            iHorzPos = si.nPos;
574
575            switch (LOWORD(wParam))
576            {
577                case SB_LINELEFT:
578                    si.nPos -= 1;
579                    break;
580
581                case SB_LINERIGHT:
582                    si.nPos += 1;
583                    break;
584
585                case SB_PAGELEFT:
586                    si.nPos -= si.nPage;
587                    break;
588
589                case SB_PAGERIGHT:
590                    si.nPos += si.nPage;
591                    break;
592
593                case SB_THUMBPOSITION:
594                    si.nPos = si.nTrackPos;
595                    break;
596
597                default:
598                    break;
599            }
600
601            // Set the position and then retrieve it.  Due to adjustments
602            //   by Windows it may not be the same as the value set.
603            si.fMask = SIF_POS;
604            SetScrollInfo(hwnd, SB_HORZ, &si, TRUE);
605            GetScrollInfo(hwnd, SB_HORZ, &si);
606
607            // If the position has changed, scroll the window
608            if (si.nPos != iHorzPos)
609            {
610                ScrollWindow(hwnd, cxChar * (iHorzPos - si.nPos), 0, NULL, NULL);
611            }
612
613            return 0;
614        }        
615        
616        case WM_MOUSEWHEEL:
617        {
618            if (iDeltaPerLine == 0)  // no scroll
619                break;
620
621            iAccumDelta += cast(short) HIWORD(wParam);     // 120 or -120
622
623            while (iAccumDelta >= iDeltaPerLine)
624            {
625                SendMessage(hwnd, WM_VSCROLL, SB_LINEUP, 0);
626                iAccumDelta -= iDeltaPerLine;
627            }
628
629            while (iAccumDelta <= -iDeltaPerLine)
630            {
631                SendMessage(hwnd, WM_VSCROLL, SB_LINEDOWN, 0);
632                iAccumDelta += iDeltaPerLine;
633            }
634
635            return 0;
636        }        
637        
638        case WM_PAINT:
639        {
640            auto ctx = paintBuffer.ctx;
641            auto hBuffer = paintBuffer.hBuffer;
642            PAINTSTRUCT ps;
643            auto hdc = BeginPaint(hwnd, &ps);
644            auto boundRect = ps.rcPaint;
645
646            draw(StateContext(paintBuffer.ctx));
647
648            with (boundRect)
649            {
650                BitBlt(hdc, left, top, right - left, bottom - top, paintBuffer.hBuffer, left, top, SRCCOPY);
651            }
652
653            EndPaint(hwnd, &ps);            
654            return 0;
655        }
656        
657        case WM_TIMER:
658        {
659            InvalidateRect(hwnd, null, true);
660            return 0;
661        }        
662        
663        case WM_DESTROY:
664        {
665            PostQuitMessage(0);
666            return 0;
667        }
668
669        default:
670    }
671    
672    return DefWindowProc(hwnd, message, wParam, lParam);
673}
674
675string WidgetClass = "WidgetClass";
676
677int myWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int iCmdShow)
678{
679    string appName = "layered drawing";
680
681    HWND hwnd;
682    MSG  msg;
683    WNDCLASS wndclass;
684
685    /* One class for the main window */
686    wndclass.lpfnWndProc = &mainWinProc;
687    wndclass.cbClsExtra  = 0;
688    wndclass.cbWndExtra  = 0;
689    wndclass.hInstance   = hInstance;
690    wndclass.hIcon       = LoadIcon(NULL, IDI_APPLICATION);
691    wndclass.hCursor     = LoadCursor(NULL, IDC_ARROW);
692    wndclass.hbrBackground = null;
693    wndclass.lpszMenuName  = NULL;
694    wndclass.lpszClassName = appName.toUTF16z;
695
696    if (!RegisterClass(&wndclass))
697    {
698        MessageBox(NULL, "This program requires Windows NT!", appName.toUTF16z, MB_ICONERROR);
699        return 0;
700    }
701
702    /* Separate window class for Widgets. */
703    wndclass.hbrBackground = null;
704    wndclass.lpfnWndProc   = &winDispatch;
705    wndclass.cbWndExtra    = 0;
706    wndclass.hIcon         = NULL;
707    wndclass.lpszClassName = WidgetClass.toUTF16z;
708
709    if (!RegisterClass(&wndclass))
710    {
711        MessageBox(NULL, "This program requires Windows NT!", appName.toUTF16z, MB_ICONERROR);
712        return 0;
713    }
714
715    hwnd = CreateWindow(appName.toUTF16z, "layered drawing",
716                        WS_OVERLAPPEDWINDOW | WS_CLIPCHILDREN,  // WS_CLIPCHILDREN is necessary
717                        CW_USEDEFAULT, CW_USEDEFAULT,
718                        CW_USEDEFAULT, CW_USEDEFAULT,
719                        NULL, NULL, hInstance, NULL);
720
721    ShowWindow(hwnd, iCmdShow);
722    UpdateWindow(hwnd);
723
724    while (GetMessage(&msg, NULL, 0, 0))
725    {
726        TranslateMessage(&msg);
727        DispatchMessage(&msg);
728    }
729
730    return msg.wParam;
731}
732
733extern (Windows)
734int WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int iCmdShow)
735{
736    int result;
737    void exceptionHandler(Throwable e) { throw e; }
738
739    try
740    {
741        Runtime.initialize(&exceptionHandler);
742        myWinMain(hInstance, hPrevInstance, lpCmdLine, iCmdShow);
743        Runtime.terminate(&exceptionHandler);
744    }
745    catch (Throwable o)
746    {
747        MessageBox(null, o.toString().toUTF16z, "Error", MB_OK | MB_ICONEXCLAMATION);
748        result = -1;
749    }
750
751    return result;
752}