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