1 /**
2 A widget to place at the bottom-right of your UI. It resizes the plugin, based upon valid sizes given by `SizeContstraints`.
3 
4 This is also an usage example for dplug:canvas.
5 
6 Copyright: Guillaume Piolat 2021.
7 License:   $(LINK2 http://www.boost.org/LICENSE_1_0.txt, Boost License 1.0)
8 */
9 module dplug.flatwidgets.windowresizer;
10 
11 // THIS IS A WORK IN PROGRESS, DOESN'T WORK YET.
12 
13 import dplug.gui.element;
14 import dplug.canvas;
15 
16 ///
17 class UIWindowResizer : UIElement
18 {
19 public:
20 nothrow:
21 @nogc:
22 
23     @ScriptProperty
24     {
25         /// Color of the stripes in nominal state.
26         RGBA color        = RGBA(255, 255, 255, 96);
27 
28         /// Color of the stripes when hovered by the mouse.
29         RGBA colorHovered = RGBA(255, 255, 255, 150);
30 
31         /// Color of the stripes when dragged / resizing.
32         RGBA colorDragged = RGBA(255, 255, 128, 200);
33 
34         /// Color of the stripes when a resize operation failed.
35         RGBA colorCannotResize = RGBA(255, 96, 96, 200);
36 
37         /// Time in seconds spent indicating failure to resize.
38         float failureDisplayTime = 1.2f;
39     }
40 
41     /// Construct a new `UIWindowResizer`.
42     /// Recommended size is around 20x20 whatever the UI size, and on the bottom-right.
43     this(UIContext context)
44     {
45         super(context, flagRaw | flagAnimated);
46         setCursorWhenMouseOver(MouseCursor.diagonalResize);
47         setCursorWhenDragged(MouseCursor.diagonalResize);
48     }
49 
50     override Click onMouseClick(int x, int y, int button, bool isDoubleClick, MouseState mstate)
51     {
52         // Initiate drag
53         setDirtyWhole();
54 
55         _sizeBeforeDrag = context.getUISizeInPixelsLogical();
56         _accumX = 0;
57         _accumY = 0;
58 
59         return Click.startDrag;
60     }
61 
62     override void onMouseDrag(int x, int y, int dx, int dy, MouseState mstate)
63     {
64         vec2i size = _sizeBeforeDrag;
65 
66         // Cumulative mouse movement since dragging started
67         _accumX += dx; // TODO: divide that by user scale
68         _accumY += dy;
69         size.x += _accumX;
70         size.y += _accumY;
71 
72         // Find nearest valid _logical_ size.
73         context.getUINearestValidSize(&size.x, &size.y);
74 
75         // Attempt to resize window with that size.
76         bool success = context.requestUIResize(size.x, size.y);
77 
78         if (!success)
79         {
80             _timeDisplayError = failureDisplayTime;
81         }
82     }
83 
84     override void onAnimate(double dt, double time) nothrow @nogc
85     {
86         if (_timeDisplayError > 0.0f)
87         {
88             _timeDisplayError = _timeDisplayError - dt;
89             if (_timeDisplayError < 0) _timeDisplayError = 0;
90             setDirtyWhole();
91         }
92     }
93 
94     override void onDrawRaw(ImageRef!RGBA rawMap, box2i[] dirtyRects)
95     {
96         float w = position.width;
97         float h = position.height;
98 
99         foreach(dirtyRect; dirtyRects)
100         {
101             auto cRaw = rawMap.cropImageRef(dirtyRect);
102             canvas.initialize(cRaw);
103             canvas.translate(-dirtyRect.min.x, -dirtyRect.min.y);
104 
105             // Makes a 3 lines hint like in JUCE or iZotope plugins.
106             // This seems to be easiest to understand for users.
107 
108             RGBA c = color;
109             if (isMouseOver) 
110                 c = colorHovered;
111             if (isDragged) 
112                 c = colorDragged;
113             if (_timeDisplayError > 0)
114                 c = colorCannotResize;
115 
116             canvas.fillStyle = c;
117 
118             canvas.beginPath;
119             canvas.moveTo(w*0/5, h*5/5);
120             canvas.lineTo(w*5/5, h*0/5);
121             canvas.lineTo(w*5/5, h*1/5);
122             canvas.lineTo(w*1/5, h*5/5);
123             canvas.lineTo(w*0/5, h*5/5);
124 
125             canvas.moveTo(w*2/5, h*5/5);
126             canvas.lineTo(w*5/5, h*2/5);
127             canvas.lineTo(w*5/5, h*3/5);
128             canvas.lineTo(w*3/5, h*5/5);
129             canvas.lineTo(w*2/5, h*5/5);
130 
131             canvas.moveTo(w*4/5, h*5/5);
132             canvas.lineTo(w*5/5, h*4/5);
133             canvas.lineTo(w*5/5, h*5/5);
134             canvas.lineTo(w*4/5, h*5/5);
135 
136             canvas.fill();
137         }
138     }
139 
140     override bool contains(int x, int y)
141     {
142         if (!context.isUIResizable())
143             return false; // not clickable if UI not resizeable
144 
145         return super.contains(x, y);
146     }
147 
148     // Account for color changes
149 
150     override void onBeginDrag()
151     {
152         setDirtyWhole();
153     }
154 
155     override void onStopDrag()
156     {
157         setDirtyWhole();
158     }
159 
160     override void onMouseEnter()
161     {
162         setDirtyWhole();
163     }
164 
165     override void onMouseExit()
166     {
167         setDirtyWhole();
168     }
169 
170 private:
171     Canvas canvas;
172     vec2i _sizeBeforeDrag;
173     int _accumX;
174     int _accumY;
175 
176     float _timeDisplayError = 0.0f;
177 }