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