1 /** 2 Slider. 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.slider; 9 10 import std.math; 11 import std.algorithm.comparison; 12 13 import dplug.core.math; 14 import dplug.gui.bufferedelement; 15 import dplug.client.params; 16 17 18 enum HandleStyle 19 { 20 shapeW, 21 shapeV, 22 shapeA, 23 shapeBlock 24 } 25 26 class UISlider : UIBufferedElementPBR, IParameterListener 27 { 28 public: 29 nothrow: 30 @nogc: 31 32 // Trail customization 33 @ScriptProperty L16 trailDepth = L16(30000); 34 @ScriptProperty RGBA unlitTrailDiffuse = RGBA(130, 90, 45, 5); 35 @ScriptProperty RGBA litTrailDiffuse = RGBA(240, 165, 102, 130); 36 @ScriptProperty float trailWidth = 0.2f; 37 38 @ScriptProperty RGBA litTrailDiffuseAlt = RGBA(240, 165, 102, 130); 39 @ScriptProperty bool hasAlternateTrail = false; 40 @ScriptProperty float trailBase = 0.0f; // trail is from trailBase to parameter value 41 42 // Handle customization 43 @ScriptProperty HandleStyle handleStyle = HandleStyle.shapeW; 44 @ScriptProperty float handleHeightRatio = 0.25f; 45 @ScriptProperty float handleWidthRatio = 0.7f; 46 @ScriptProperty RGBA handleDiffuse = RGBA(248, 245, 233, 16); 47 @ScriptProperty RGBA handleMaterial = RGBA(0, 255, 128, 255); 48 49 this(UIContext context, Parameter param) 50 { 51 super(context, flagAnimated | flagPBR); 52 _param = param; 53 _param.addListener(this); 54 _sensivity = 1.0f; 55 _pushedAnimation = 0; 56 clearCrosspoints(); 57 setCursorWhenDragged(MouseCursor.drag); 58 setCursorWhenMouseOver(MouseCursor.move); 59 } 60 61 ~this() 62 { 63 _param.removeListener(this); 64 } 65 66 /// Returns: sensivity. 67 float sensivity() 68 { 69 return _sensivity; 70 } 71 72 /// Sets sensivity. 73 float sensivity(float sensivity) 74 { 75 return _sensivity = sensivity; 76 } 77 78 override void onAnimate(double dt, double time) nothrow @nogc 79 { 80 float target = isDragged() ? 1 : 0; 81 float newAnimation = lerp(_pushedAnimation, target, 1.0 - exp(-dt * 30)); 82 if (abs(newAnimation - _pushedAnimation) > 0.001f) 83 { 84 _pushedAnimation = newAnimation; 85 setDirtyWhole(); 86 } 87 } 88 89 override void onDrawBufferedPBR(ImageRef!RGBA diffuseMap, 90 ImageRef!L16 depthMap, 91 ImageRef!RGBA materialMap, 92 ImageRef!L8 diffuseOpacity, 93 ImageRef!L8 depthOpacity, 94 ImageRef!L8 materialOpacity) nothrow @nogc 95 { 96 int width = _position.width; 97 int height = _position.height; 98 int handleHeight = cast(int)(0.5f + this.handleHeightRatio * height * (1 - 0.12f * _pushedAnimation)); 99 int handleWidth = cast(int)(0.5f + this.handleWidthRatio * width * (1 - 0.06f * _pushedAnimation)); 100 int trailWidth = cast(int)(0.5f + width * this.trailWidth); 101 102 int handleHeightUnpushed = cast(int)(0.5f + this.handleHeightRatio * height); 103 int trailMargin = cast(int)(0.5f + (handleHeightUnpushed - trailWidth) * 0.5f); 104 if (trailMargin < 0) 105 trailMargin = 0; 106 107 int trailX = cast(int)(0.5 + (width - trailWidth) * 0.5f); 108 int trailHeight = height - 2 * trailMargin; 109 110 // The switch is in a subrect 111 112 box2i holeRect = box2i(trailX, trailMargin, trailX + trailWidth, trailMargin + trailHeight); 113 114 float value = _param.getNormalized(); 115 116 int posX = cast(int)(0.5f + (width - handleWidth) * 0.5f); 117 int posY = cast(int)(0.5f + (1 - value) * (height - handleHeight)); 118 assert(posX >= 0); 119 assert(posY >= 0); 120 121 box2i handleRect = box2i(posX, posY, posX + handleWidth, posY + handleHeight); 122 123 124 // Dig hole and paint trail deeper hole 125 { 126 127 128 depthMap.cropImageRef(holeRect).fillAll(trailDepth); 129 130 // Fill opacity for hole 131 diffuseOpacity.cropImageRef(holeRect).fillAll(opacityFullyOpaque); 132 depthOpacity.cropImageRef(holeRect).fillAll(opacityFullyOpaque); 133 134 int valueToTrail(float value) nothrow @nogc 135 { 136 return cast(int)(0.5f + (1 - value) * (height+4 - handleHeight) + handleHeight*0.5f - 2); 137 } 138 139 void paintTrail(float from, float to, RGBA diffuse) nothrow @nogc 140 { 141 int ymin = valueToTrail(from); 142 int ymax = valueToTrail(to); 143 if (ymin > ymax) 144 { 145 int temp = ymin; 146 ymin = ymax; 147 ymax = temp; 148 } 149 box2i b = box2i(holeRect.min.x, ymin, holeRect.max.x, ymax); 150 diffuseMap.cropImageRef(b).fillAll(diffuse); 151 } 152 153 154 155 RGBA litTrail = (value >= trailBase) ? litTrailDiffuse : litTrailDiffuseAlt; 156 if (isDragged) 157 { 158 // lit trail is 50% brighter when dragged 159 litTrail.a = cast(ubyte) min(255, 3 * litTrail.a / 2); 160 } 161 162 paintTrail(0, 1, unlitTrailDiffuse); 163 paintTrail(trailBase, value, litTrail); 164 } 165 166 // Paint handle of slider 167 int emissive = handleDiffuse.a; 168 if (isMouseOver && !isDragged) 169 emissive += 50; 170 if (emissive > 255) 171 emissive = 255; 172 173 RGBA handleDiffuseLit = RGBA(handleDiffuse.r, handleDiffuse.g, handleDiffuse.b, cast(ubyte)emissive); 174 175 diffuseMap.cropImageRef(handleRect).fillAll(handleDiffuseLit); 176 177 if (handleStyle == HandleStyle.shapeV) 178 { 179 auto c0 = L16(20000); 180 auto c1 = L16(50000); 181 182 int h0 = handleRect.min.y; 183 int h1 = handleRect.center.y; 184 int h2 = handleRect.max.y; 185 186 verticalSlope(depthMap, box2i(handleRect.min.x, h0, handleRect.max.x, h1), c0, c1); 187 verticalSlope(depthMap, box2i(handleRect.min.x, h1, handleRect.max.x, h2), c1, c0); 188 } 189 else if (handleStyle == HandleStyle.shapeA) 190 { 191 auto c0 = L16(50000); 192 auto c1 = L16(20000); 193 194 int h0 = handleRect.min.y; 195 int h1 = handleRect.center.y; 196 int h2 = handleRect.max.y; 197 198 verticalSlope(depthMap, box2i(handleRect.min.x, h0, handleRect.max.x, h1), c0, c1); 199 verticalSlope(depthMap, box2i(handleRect.min.x, h1, handleRect.max.x, h2), c1, c0); 200 } 201 else if (handleStyle == HandleStyle.shapeW) 202 { 203 auto c0 = L16(15000); 204 auto c1 = L16(65535); 205 auto c2 = L16(51400); 206 207 int h0 = handleRect.min.y; 208 int h1 = (handleRect.min.y * 3 + handleRect.max.y + 2) / 4; 209 int h2 = handleRect.center.y; 210 int h3 = (handleRect.min.y + handleRect.max.y * 3 + 2) / 4; 211 int h4 = handleRect.max.y; 212 213 verticalSlope(depthMap, box2i(handleRect.min.x, h0, handleRect.max.x, h1), c0, c1); 214 verticalSlope(depthMap, box2i(handleRect.min.x, h1, handleRect.max.x, h2), c1, c2); 215 verticalSlope(depthMap, box2i(handleRect.min.x, h2, handleRect.max.x, h3), c2, c1); 216 verticalSlope(depthMap, box2i(handleRect.min.x, h3, handleRect.max.x, h4), c1, c0); 217 } 218 else if (handleStyle == HandleStyle.shapeBlock) 219 { 220 depthMap.cropImageRef(handleRect).fillAll(L16(50000)); 221 } 222 223 materialMap.cropImageRef(handleRect).fillAll(handleMaterial); 224 225 // Fill opacity for handle 226 diffuseOpacity.cropImageRef(handleRect).fillAll(opacityFullyOpaque); 227 depthOpacity.cropImageRef(handleRect).fillAll(opacityFullyOpaque); 228 materialOpacity.cropImageRef(handleRect).fillAll(opacityFullyOpaque); 229 } 230 231 override Click onMouseClick(int x, int y, int button, bool isDoubleClick, MouseState mstate) 232 { 233 // double-click => set to default 234 if (isDoubleClick || mstate.altPressed) 235 { 236 if (auto p = cast(FloatParameter)_param) 237 { 238 p.beginParamEdit(); 239 p.setFromGUI(p.defaultValue()); 240 p.endParamEdit(); 241 } 242 else if (auto p = cast(IntegerParameter)_param) 243 { 244 p.beginParamEdit(); 245 p.setFromGUI(p.defaultValue()); 246 p.endParamEdit(); 247 } 248 else 249 assert(false); // only integer and float parameters supported 250 } 251 252 return Click.startDrag; // to initiate dragging 253 } 254 255 // Called when mouse drag this Element. 256 override void onMouseDrag(int x, int y, int dx, int dy, MouseState mstate) 257 { 258 // FUTURE: replace by actual trail height instead of total height 259 float displacementInHeight = cast(float)(dy) / _position.height; 260 261 float modifier = 1.0f; 262 if (mstate.shiftPressed || mstate.ctrlPressed) 263 modifier *= 0.1f; 264 265 double oldParamValue = _param.getNormalized() + _draggingDebt; 266 double newParamValue = oldParamValue - displacementInHeight * modifier * _sensivity; 267 if (mstate.altPressed) 268 newParamValue = _param.getNormalizedDefault(); 269 270 if (y > _mousePosOnLast0Cross) 271 return; 272 if (y < _mousePosOnLast1Cross) 273 return; 274 275 if (newParamValue <= 0 && oldParamValue > 0) 276 _mousePosOnLast0Cross = y; 277 278 if (newParamValue >= 1 && oldParamValue < 1) 279 _mousePosOnLast1Cross = y; 280 281 if (newParamValue < 0) 282 newParamValue = 0; 283 if (newParamValue > 1) 284 newParamValue = 1; 285 286 if (newParamValue > 0) 287 _mousePosOnLast0Cross = float.infinity; 288 289 if (newParamValue < 1) 290 _mousePosOnLast1Cross = -float.infinity; 291 292 if (newParamValue != oldParamValue) 293 { 294 if (auto p = cast(FloatParameter)_param) 295 { 296 p.setFromGUINormalized(newParamValue); 297 } 298 else if (auto p = cast(IntegerParameter)_param) 299 { 300 p.setFromGUINormalized(newParamValue); 301 _draggingDebt = newParamValue - p.getNormalized(); 302 } 303 else 304 assert(false); // only integer and float parameters supported 305 } 306 } 307 308 // For lazy updates 309 override void onBeginDrag() 310 { 311 _param.beginParamEdit(); 312 setDirtyWhole(); 313 } 314 315 override void onStopDrag() 316 { 317 _param.endParamEdit(); 318 setDirtyWhole(); 319 _draggingDebt = 0.0f; 320 } 321 322 override void onMouseEnter() 323 { 324 _param.beginParamHover(); 325 setDirtyWhole(); 326 } 327 328 override void onMouseExit() 329 { 330 _param.endParamHover(); 331 setDirtyWhole(); 332 } 333 334 override void onParameterChanged(Parameter sender) 335 { 336 setDirtyWhole(); 337 } 338 339 override void onBeginParameterEdit(Parameter sender) 340 { 341 } 342 343 override void onEndParameterEdit(Parameter sender) 344 { 345 } 346 347 override void onBeginParameterHover(Parameter sender) 348 { 349 } 350 351 override void onEndParameterHover(Parameter sender) 352 { 353 } 354 355 356 protected: 357 358 /// The parameter this switch is linked with. 359 Parameter _param; 360 361 /// Sensivity: given a mouse movement in 100th of the height of the knob, 362 /// how much should the normalized parameter change. 363 float _sensivity; 364 365 float _pushedAnimation; 366 367 float _mousePosOnLast0Cross; 368 float _mousePosOnLast1Cross; 369 370 // Exists because small mouse drags for integer parameters may not 371 // lead to a parameter value change, hence a need to accumulate those drags. 372 float _draggingDebt = 0.0f; 373 374 void clearCrosspoints() 375 { 376 _mousePosOnLast0Cross = float.infinity; 377 _mousePosOnLast1Cross = -float.infinity; 378 } 379 }