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