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