1 /** 2 * PBR widget: parameter value hint. 3 * Copyright: Copyright Auburn Sounds 2015-2017. 4 * License: $(LINK2 http://www.boost.org/LICENSE_1_0.txt, Boost License 1.0) 5 * Authors: Guillaume Piolat 6 */ 7 module dplug.pbrwidgets.paramhint; 8 9 import core.stdc..string; 10 import core.atomic; 11 12 import std.math; 13 import std.conv; 14 import std.algorithm; 15 16 import dplug.core; 17 import dplug.gui.element; 18 import dplug.gui.bufferedelement; 19 import dplug.client.params; 20 21 22 /// Widget that monitors the value of a parameter and 23 /// appears whenever it change to display its value. 24 class UIParamHint : UIBufferedElement, IParameterListener 25 { 26 public: 27 nothrow: 28 @nogc: 29 30 double fadeinDuration = 0.15f; 31 double fadeoutDuration = 0.3f; 32 double holdDuration = 0.85f; 33 34 float textSizePx = 9.5f; 35 RGBA holeDiffuse = RGBA(90, 90, 90, 0); 36 37 RGBA textDiffuseLow = RGBA(90, 90, 90, 0); 38 RGBA textDiffuseHigh = RGBA(42, 42, 42, 0); 39 40 RGBA diffuseLow = RGBA(90, 90, 90, 0); 41 RGBA diffuseHigh = RGBA(245, 245, 245, 30); 42 RGBA material = RGBA(0, 255, 192, 255); 43 44 RGBA plasticMaterial = RGBA(155, 240, 69, 255); 45 float plasticAlpha = 0.05f; 46 47 ushort depthLow = 0; 48 ushort depthHigh = 50000; 49 50 this(UIContext context, Parameter param, Font font) 51 { 52 super(context); 53 _param = param; 54 _param.addListener(this); 55 _font = font; 56 } 57 58 ~this() 59 { 60 _param.removeListener(this); 61 } 62 63 const(char)[] paramString() nothrow @nogc 64 { 65 _param.toDisplayN(_pParamStringBuffer.ptr, 128); 66 size_t len = strlen(_pParamStringBuffer.ptr); 67 string label = _param.label(); 68 assert(label.length < 127); 69 _pParamStringBuffer[len] = ' '; 70 size_t totalLength = len + 1 + label.length; 71 _pParamStringBuffer[len+1..totalLength] = label[]; 72 return _pParamStringBuffer[0..totalLength]; 73 } 74 75 override void onDrawBuffered(ImageRef!RGBA diffuseMap, 76 ImageRef!L16 depthMap, 77 ImageRef!RGBA materialMap, 78 ImageRef!L8 diffuseOpacity, 79 ImageRef!L8 depthOpacity, 80 ImageRef!L8 materialOpacity) nothrow @nogc 81 { 82 int W = diffuseMap.w; 83 int H = diffuseMap.h; 84 85 assert(_upAnimation >= 0 && _upAnimation <= 1); 86 87 float openAnimation = std.algorithm.min(1.0f, _upAnimation * 2.0f); 88 float moveUpAnimation = std.algorithm.max(0.0f, _upAnimation * 2.0f - 1.0f); 89 90 box2i fullRect = box2i(0, 0, W, H); 91 92 float holeHeight = H * openAnimation; 93 94 box2f plasticRect = box2f(0, 0, W, H - holeHeight); 95 materialMap.aaFillRectFloat(plasticRect.min.x, plasticRect.min.y, plasticRect.max.x, plasticRect.max.y, plasticMaterial, plasticAlpha); 96 97 98 box2f holeRect = box2f(0, H - holeHeight, W, H); 99 100 if (holeRect.empty) 101 return; 102 103 depthMap.aaFillRectFloat(holeRect.min.x, holeRect.min.y, holeRect.max.x, holeRect.max.y, L16(0)); 104 diffuseMap.aaFillRectFloat(holeRect.min.x, holeRect.min.y, holeRect.max.x, holeRect.max.y, holeDiffuse); 105 106 ushort labelDepth = cast(ushort)(depthLow + moveUpAnimation * (depthHigh - depthLow)); 107 108 float perspectiveAnimation = 0.8f + 0.2f * moveUpAnimation; 109 110 float labelW = W * perspectiveAnimation; 111 float labelH = H * perspectiveAnimation; 112 float labelMarginW = W - labelW; 113 float labelMarginH = H - labelH; 114 115 box2f labelRect = box2f(labelMarginW * 0.5f, labelMarginH * 0.5f, W - labelMarginW * 0.5f, H - labelMarginH * 0.5f); 116 box2f labelRectCrossHole = labelRect.intersection(holeRect); 117 118 box2i ilabelRectCrossHole = box2i(cast(int)(0.5f + labelRectCrossHole.min.x), 119 cast(int)(0.5f + labelRectCrossHole.min.y), 120 cast(int)(0.5f + labelRectCrossHole.max.x), 121 cast(int)(0.5f + labelRectCrossHole.max.y)); 122 123 if (labelRect.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.crop(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.fill(L8(balpha)); 156 materialOpacity.fill(L8(balpha)); 157 diffuseOpacity.fill(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 private: 219 Parameter _param; 220 221 shared(bool) _parameterIsEdited = false; // access to this is through atomic ops 222 shared(bool) _parameterChanged = false; // access to this is through atomic ops 223 224 float _upAnimation = 0; 225 226 double _timeSinceEdit = double.infinity; 227 228 const(char)[] _lastParamString; 229 230 Font _font; 231 232 char[256] _pParamStringBuffer; 233 } 234