1 /**
2 * Copyright: Copyright Auburn Sounds 2015-2017.
3 * License:   $(LINK2 http://www.boost.org/LICENSE_1_0.txt, Boost License 1.0)
4 * Authors:   Guillaume Piolat
5 */
6 module dplug.gui.paramhint;
7 
8 import core.stdc.string;
9 import core.atomic;
10 
11 import std.math;
12 import std.conv;
13 import std.algorithm;
14 
15 import dplug.core;
16 import dplug.gui.element;
17 import dplug.gui.bufferedelement;
18 import dplug.client.params;
19 
20 
21 /// Widget that monitors the value of a parameter and
22 /// appears whenever it change to display its value.
23 class UIParamHint : UIBufferedElement, IParameterListener
24 {
25 public:
26 nothrow:
27 @nogc:
28 
29     double fadeinDuration = 0.15f;
30     double fadeoutDuration = 0.3f;
31     double holdDuration = 0.85f;
32 
33     float textSizePx = 9.5f;
34     RGBA holeDiffuse = RGBA(90, 90, 90, 0);
35     
36     RGBA textDiffuseLow = RGBA(90, 90, 90, 0);
37     RGBA textDiffuseHigh = RGBA(42, 42, 42, 0);
38 
39     RGBA diffuseLow = RGBA(90, 90, 90, 0);
40     RGBA diffuseHigh = RGBA(245, 245, 245, 30);
41     RGBA material = RGBA(0, 255, 192, 255);
42 
43     RGBA plasticMaterial = RGBA(155, 240, 69, 255);
44     float plasticAlpha = 0.05f;
45 
46     ushort depthLow = 0;
47     ushort depthHigh = 50000;
48 
49     this(UIContext context, Parameter param, Font font)
50     {
51         super(context);
52         _param = param;
53         _param.addListener(this);
54         _font = font;
55     }
56 
57     ~this()
58     {
59         _param.removeListener(this);
60     }
61 
62     const(char)[] paramString() nothrow @nogc
63     {
64         _param.toDisplayN(_pParamStringBuffer.ptr, 128);
65         size_t len = strlen(_pParamStringBuffer.ptr);
66         string label = _param.label();
67         assert(label.length < 127);
68         _pParamStringBuffer[len] = ' ';
69         size_t totalLength = len + 1 + label.length;
70         _pParamStringBuffer[len+1..totalLength] = label[];
71         return _pParamStringBuffer[0..totalLength];
72     }
73 
74     override void onDrawBuffered(ImageRef!RGBA diffuseMap, 
75                                  ImageRef!L16 depthMap, 
76                                  ImageRef!RGBA materialMap, 
77                                  ImageRef!L8 diffuseOpacity,
78                                  ImageRef!L8 depthOpacity,
79                                  ImageRef!L8 materialOpacity) nothrow @nogc
80     {
81         int W = diffuseMap.w;
82         int H = diffuseMap.h;
83 
84         assert(_upAnimation >= 0 && _upAnimation <= 1);
85 
86         float openAnimation = std.algorithm.min(1.0f,  _upAnimation * 2.0f);
87         float moveUpAnimation = std.algorithm.max(0.0f,  _upAnimation * 2.0f - 1.0f);
88 
89         box2i fullRect = box2i(0, 0, W, H);
90 
91         float holeHeight = H * openAnimation;
92 
93         box2f plasticRect = box2f(0, 0, W, H - holeHeight);
94         materialMap.aaFillRectFloat(plasticRect.min.x, plasticRect.min.y, plasticRect.max.x, plasticRect.max.y, plasticMaterial, plasticAlpha);
95 
96 
97         box2f holeRect = box2f(0, H - holeHeight, W, H);
98 
99         if (holeRect.empty)
100             return;
101 
102         depthMap.aaFillRectFloat(holeRect.min.x, holeRect.min.y, holeRect.max.x, holeRect.max.y, L16(0));
103         diffuseMap.aaFillRectFloat(holeRect.min.x, holeRect.min.y, holeRect.max.x, holeRect.max.y, holeDiffuse);        
104 
105         ushort labelDepth = cast(ushort)(depthLow + moveUpAnimation * (depthHigh - depthLow));
106 
107         float perspectiveAnimation = 0.8f + 0.2f * moveUpAnimation;
108 
109         float labelW = W * perspectiveAnimation;
110         float labelH = H * perspectiveAnimation;
111         float labelMarginW = W - labelW;
112         float labelMarginH = H - labelH;
113 
114         box2f labelRect = box2f(labelMarginW * 0.5f, labelMarginH  * 0.5f, W - labelMarginW * 0.5f, H - labelMarginH * 0.5f);
115         box2f labelRectCrossHole = labelRect.intersection(holeRect);
116 
117         box2i ilabelRectCrossHole = box2i(cast(int)(0.5f + labelRectCrossHole.min.x), 
118                                           cast(int)(0.5f + labelRectCrossHole.min.y), 
119                                           cast(int)(0.5f + labelRectCrossHole.max.x),
120                                           cast(int)(0.5f + labelRectCrossHole.max.y));
121 
122         if (labelRect.empty)
123             return;
124 
125         // Cheating :( because depth animation isn't sufficient
126         RGBA diffuse = RGBA(cast(ubyte)(0.5f + lerp!float(diffuseLow.r, diffuseHigh.r, moveUpAnimation)),
127                             cast(ubyte)(0.5f + lerp!float(diffuseLow.g, diffuseHigh.g, moveUpAnimation)),
128                             cast(ubyte)(0.5f + lerp!float(diffuseLow.b, diffuseHigh.b, moveUpAnimation)),
129                             cast(ubyte)(0.5f + lerp!float(diffuseLow.a, diffuseHigh.a, moveUpAnimation)));
130 
131         depthMap.aaFillRectFloat(labelRectCrossHole.min.x, labelRectCrossHole.min.y, labelRectCrossHole.max.x, labelRectCrossHole.max.y, L16(labelDepth));
132         materialMap.aaFillRectFloat(labelRectCrossHole.min.x, labelRectCrossHole.min.y, labelRectCrossHole.max.x, labelRectCrossHole.max.y, material);
133         diffuseMap.aaFillRectFloat(labelRectCrossHole.min.x, labelRectCrossHole.min.y, labelRectCrossHole.max.x, labelRectCrossHole.max.y, diffuse);
134 
135         RGBA textDiffuse = RGBA(cast(ubyte)(0.5f + lerp!float(textDiffuseLow.r, textDiffuseHigh.r, moveUpAnimation)),
136                                 cast(ubyte)(0.5f + lerp!float(textDiffuseLow.g, textDiffuseHigh.g, moveUpAnimation)),
137                                 cast(ubyte)(0.5f + lerp!float(textDiffuseLow.b, textDiffuseHigh.b, moveUpAnimation)),
138                                 cast(ubyte)(0.5f + lerp!float(textDiffuseLow.a, textDiffuseHigh.a, moveUpAnimation)));
139 
140         diffuseMap.crop(ilabelRectCrossHole).fillText(_font, _lastParamString, textSizePx * perspectiveAnimation, 0, textDiffuse, 
141                                                       ilabelRectCrossHole.width - labelRectCrossHole.center.x, 
142                                                       ilabelRectCrossHole.height - labelRect.height * 0.5f);
143 
144         // Fill opacity
145         {
146             float alpha = openAnimation;
147             ubyte balpha = cast(ubyte)(255.0f*openAnimation + 0.5f);
148             depthOpacity.fill(L8(balpha));
149             materialOpacity.fill(L8(balpha));
150             diffuseOpacity.fill(L8(balpha));
151         }
152     }
153 
154     override void onAnimate(double dt, double time) nothrow @nogc
155     {
156         bool isBeingEdited = atomicLoad(_parameterIsEdited);
157         bool wasJustChanged = cas(&_parameterChanged, true, false);
158 
159         if (isBeingEdited)
160             _timeSinceEdit = 0;
161 
162         if (isBeingEdited && wasJustChanged)
163             _lastParamString = paramString();
164 
165         float targetAnimation = _timeSinceEdit < holdDuration ? 1 : 0;
166 
167         bool animationMoved = void;
168 
169         if (_upAnimation < targetAnimation)
170         {
171             _upAnimation += dt / fadeinDuration;
172             if (_upAnimation > targetAnimation)
173                 _upAnimation = targetAnimation;
174             animationMoved = true;
175         }
176         else if (_upAnimation > targetAnimation)
177         {
178             _upAnimation -= dt / fadeoutDuration;
179             if (_upAnimation < targetAnimation)
180                 _upAnimation = targetAnimation;
181             animationMoved = true;
182         }
183         else
184             animationMoved = false;
185         
186         // redraw if:
187         // - parameter changed
188         // - animation changed
189         if ((wasJustChanged && isBeingEdited) || animationMoved)
190             setDirtyWhole();
191 
192         _timeSinceEdit += dt;
193     }
194 
195     override void onParameterChanged(Parameter sender) nothrow @nogc
196     {
197         atomicStore(_parameterChanged, true);
198     }
199 
200     override void onBeginParameterEdit(Parameter sender)
201     {
202         atomicStore(_parameterIsEdited, true);
203         atomicStore(_parameterChanged, true);
204     }
205 
206     override void onEndParameterEdit(Parameter sender)
207     {
208         atomicStore(_parameterIsEdited, false);
209     }
210 
211 private:
212     Parameter _param;
213 
214     shared(bool) _parameterIsEdited = false; // access to this is through atomic ops
215     shared(bool) _parameterChanged = false;  // access to this is through atomic ops
216 
217     float _upAnimation = 0;
218 
219     double _timeSinceEdit = double.infinity;
220     
221     const(char)[] _lastParamString;
222 
223     Font _font;
224 
225     char[256] _pParamStringBuffer;
226 }
227