1 /** 2 * Film-strip slider. 3 * Copyright: Copyright Auburn Sounds 2015 4 * Copyright: Copyright Cut Through Recordings 2017 5 * License: $(LINK2 http://www.boost.org/LICENSE_1_0.txt, Boost License 1.0) 6 * Authors: Guillaume Piolat, Ethan Reker 7 */ 8 9 module dplug.flatwidgets.flatslider; 10 11 import std.math; 12 import std.algorithm.comparison; 13 14 import dplug.core.math; 15 import dplug.graphics.drawex; 16 import dplug.gui.bufferedelement; 17 import dplug.client.params; 18 19 class UIFilmstripSlider : UIElement, IParameterListener 20 { 21 public: 22 nothrow: 23 @nogc: 24 25 this(UIContext context, FloatParameter param, OwnedImage!RGBA mipmap, int numFrames, float sensitivity = 0.25) 26 { 27 super(context); 28 _param = param; 29 _sensivity = sensitivity; 30 _filmstrip = mipmap; 31 _numFrames = numFrames; 32 _knobWidth = _filmstrip.w; 33 _knobHeight = _filmstrip.h / _numFrames; 34 _disabled = false; 35 } 36 37 ~this() 38 { 39 _param.removeListener(this); 40 } 41 42 /// Returns: sensivity. 43 float sensivity() 44 { 45 return _sensivity; 46 } 47 48 /// Sets sensivity. 49 float sensivity(float sensivity) 50 { 51 return _sensivity = sensivity; 52 } 53 54 override void onAnimate(double dt, double time) nothrow @nogc 55 { 56 float target = isDragged() ? 1 : 0; 57 float newAnimation = lerp(_pushedAnimation, target, 1.0 - exp(-dt * 30)); 58 if (abs(newAnimation - _pushedAnimation) > 0.001f) 59 { 60 _pushedAnimation = newAnimation; 61 setDirtyWhole(); 62 } 63 } 64 65 override void onDraw(ImageRef!RGBA diffuseMap, ImageRef!L16 depthMap, ImageRef!RGBA materialMap, box2i[] dirtyRects) nothrow @nogc 66 { 67 setCurrentImage(); 68 auto _currentImage = _filmstrip.crop(box2i(_imageX1, _imageY1, _imageX2, _imageY2)); 69 foreach(dirtyRect; dirtyRects){ 70 71 auto croppedDiffuseIn = _currentImage.crop(dirtyRect); 72 auto croppedDiffuseOut = diffuseMap.crop(dirtyRect); 73 74 int w = dirtyRect.width; 75 int h = dirtyRect.height; 76 77 for(int j = 0; j < h; ++j){ 78 79 RGBA[] input = croppedDiffuseIn.scanline(j); 80 RGBA[] output = croppedDiffuseOut.scanline(j); 81 82 83 for(int i = 0; i < w; ++i){ 84 ubyte alpha = input[i].a; 85 86 RGBA color = RGBA.op!q{.blend(a, b, c)} (input[i], output[i], alpha); 87 output[i] = color; 88 } 89 } 90 91 } 92 } 93 94 void setCurrentImage() 95 { 96 float value = _param.getNormalized(); 97 currentFrame = cast(int)(round(value * (_numFrames - 1))); 98 99 if(currentFrame < 0) currentFrame = 0; 100 if(currentFrame > 59) currentFrame = 59; 101 102 _imageX1 = 0; 103 _imageY1 = (_filmstrip.h / _numFrames) * currentFrame; 104 105 _imageX2 = _filmstrip.w; 106 _imageY2 = _imageY1 + (_filmstrip.h / _numFrames); 107 108 } 109 110 override bool onMouseClick(int x, int y, int button, bool isDoubleClick, MouseState mstate) 111 { 112 // double-click => set to default 113 if (isDoubleClick) 114 { 115 _param.beginParamEdit(); 116 if (auto p = cast(FloatParameter)_param) 117 p.setFromGUI(p.defaultValue()); 118 else if (auto p = cast(IntegerParameter)_param) 119 p.setFromGUI(p.defaultValue()); 120 else 121 assert(false); // only integer and float parameters supported 122 _param.endParamEdit(); 123 } 124 125 return true; // to initiate dragging 126 } 127 128 // Called when mouse drag this Element. 129 override void onMouseDrag(int x, int y, int dx, int dy, MouseState mstate) 130 { 131 // FUTURE: replace by actual trail height instead of total height 132 if(!_disabled) 133 { 134 float displacementInHeight = cast(float)(dy) / _position.height; 135 136 float modifier = 1.0f; 137 if (mstate.shiftPressed || mstate.ctrlPressed) 138 modifier *= 0.1f; 139 140 double oldParamValue = _param.getNormalized() + _draggingDebt; 141 double newParamValue = oldParamValue - displacementInHeight * modifier * _sensivity; 142 143 if (y > _mousePosOnLast0Cross) 144 return; 145 if (y < _mousePosOnLast1Cross) 146 return; 147 148 if (newParamValue <= 0 && oldParamValue > 0) 149 _mousePosOnLast0Cross = y; 150 151 if (newParamValue >= 1 && oldParamValue < 1) 152 _mousePosOnLast1Cross = y; 153 154 if (newParamValue < 0) 155 newParamValue = 0; 156 if (newParamValue > 1) 157 newParamValue = 1; 158 159 if (newParamValue > 0) 160 _mousePosOnLast0Cross = float.infinity; 161 162 if (newParamValue < 1) 163 _mousePosOnLast1Cross = -float.infinity; 164 165 if (newParamValue != oldParamValue) 166 { 167 if (auto p = cast(FloatParameter)_param) 168 { 169 p.setFromGUINormalized(newParamValue); 170 } 171 /*else if (auto p = cast(IntegerParameter)_param) 172 { 173 p.setFromGUINormalized(newParamValue); 174 _draggingDebt = newParamValue - p.getNormalized(); 175 }*/ 176 else 177 assert(false); // only integer and float parameters supported 178 } 179 setDirtyWhole(); 180 } 181 } 182 183 // For lazy updates 184 override void onBeginDrag() 185 { 186 _param.beginParamEdit(); 187 setDirtyWhole(); 188 } 189 190 override void onStopDrag() 191 { 192 _param.endParamEdit(); 193 setDirtyWhole(); 194 _draggingDebt = 0.0f; 195 } 196 197 override void onMouseEnter() 198 { 199 } 200 201 override void onMouseExit() 202 { 203 } 204 205 override void onParameterChanged(Parameter sender) 206 { 207 setDirtyWhole(); 208 } 209 210 override void onBeginParameterEdit(Parameter sender) 211 { 212 } 213 214 override void onEndParameterEdit(Parameter sender) 215 { 216 } 217 218 void disable() 219 { 220 _disabled = true; 221 } 222 223 protected: 224 225 /// The parameter this switch is linked with. 226 Parameter _param; 227 228 OwnedImage!RGBA _filmstrip; 229 int _numFrames; 230 int _imageX1, _imageX2, _imageY1, _imageY2; 231 int currentFrame; 232 233 int _knobWidth; 234 int _knobHeight; 235 236 /// Sensivity: given a mouse movement in 100th of the height of the knob, 237 /// how much should the normalized parameter change. 238 float _sensivity; 239 240 float _pushedAnimation; 241 242 float _mousePosOnLast0Cross; 243 float _mousePosOnLast1Cross; 244 245 // Exists because small mouse drags for integer parameters may not 246 // lead to a parameter value change, hence a need to accumulate those drags. 247 float _draggingDebt = 0.0f; 248 249 bool _disabled; 250 251 void clearCrosspoints() 252 { 253 _mousePosOnLast0Cross = float.infinity; 254 _mousePosOnLast1Cross = -float.infinity; 255 } 256 }