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