1 /** 2 On/Off switch. 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.onoffswitch; 9 10 import std.math: exp, abs; 11 import dplug.core.math; 12 import dplug.gui.element; 13 import dplug.gui.bufferedelement; 14 import dplug.client.params; 15 16 class UIOnOffSwitch : UIElement, IParameterListener 17 { 18 public: 19 nothrow: 20 @nogc: 21 22 enum Orientation 23 { 24 vertical, 25 horizontal 26 } 27 @ScriptProperty RGBA diffuseOff = RGBA(230, 80, 43, 0); 28 @ScriptProperty RGBA diffuseOn = RGBA(230, 80, 43, 200); 29 @ScriptProperty RGBA material = RGBA(192, 10, 128, 255); 30 @ScriptProperty float animationTimeConstant = 10.0f; 31 @ScriptProperty ushort depthLow = 0; 32 @ScriptProperty ushort depthHigh = 30000; 33 @ScriptProperty ushort holeDepth = 0; 34 @ScriptProperty Orientation orientation = Orientation.vertical; 35 @ScriptProperty bool drawDepth = true; 36 @ScriptProperty bool drawDiffuse = true; 37 @ScriptProperty bool drawMaterial = true; 38 @ScriptProperty bool drawHole = true; // if drawDepth && drawHole, draw Z hole 39 @ScriptProperty bool drawEmissive = true; // if drawEmissive && !drawDiffuse, draw just the emissive channel 40 41 42 /// Left and right border, in fraction of the widget's width. 43 /// Cannot be > 0.5f 44 @ScriptProperty float borderHorz = 0.1f; 45 46 /// Top and bottom border, in fraction of the widget's width.* 47 /// Cannot be > 0.5f 48 @ScriptProperty float borderVert = 0.1f; 49 50 this(UIContext context, BoolParameter param) 51 { 52 super(context, flagAnimated | flagPBR); 53 _param = param; 54 _param.addListener(this); 55 _animation = 0.0f; 56 } 57 58 ~this() 59 { 60 _param.removeListener(this); 61 } 62 63 override void onAnimate(double dt, double time) nothrow @nogc 64 { 65 float target = _param.valueAtomic() ? 1 : 0; 66 67 float newAnimation = lerp(_animation, target, 1.0 - exp(-dt * animationTimeConstant)); 68 69 if (abs(newAnimation - _animation) > 0.001f) 70 { 71 _animation = newAnimation; 72 setDirtyWhole(); 73 } 74 } 75 76 override void onDrawPBR(ImageRef!RGBA diffuseMap, ImageRef!L16 depthMap, ImageRef!RGBA materialMap, box2i[] dirtyRects) nothrow @nogc 77 { 78 // The switch is in a subrect 79 int width = _position.width; 80 int height = _position.height; 81 82 box2i switchRect = box2i( cast(int)(0.5f + width * borderHorz), 83 cast(int)(0.5f + height * borderVert), 84 cast(int)(0.5f + width * (1 - borderHorz)), 85 cast(int)(0.5f + height * (1 - borderVert)) ); 86 87 ubyte red = cast(ubyte)(lerp!float(diffuseOff.r, diffuseOn.r, _animation)); 88 ubyte green = cast(ubyte)(lerp!float(diffuseOff.g, diffuseOn.g, _animation)); 89 ubyte blue = cast(ubyte)(lerp!float(diffuseOff.b, diffuseOn.b, _animation)); 90 int emissive = cast(ubyte)(lerp!float(diffuseOff.a, diffuseOn.a, _animation)); 91 92 if (isMouseOver || isDragged) 93 emissive += 40; 94 95 if (emissive > 255) 96 emissive = 255; 97 98 // Workaround issue https://issues.dlang.org/show_bug.cgi?id=23076 99 // Regular should not be inlined here. 100 static float lerpfloat(float a, float b, float t) pure nothrow @nogc 101 { 102 pragma(inline, false); 103 return t * b + (1 - t) * a; 104 } 105 106 L16 depthA = L16(cast(short)(lerpfloat(depthHigh, depthLow, _animation))); 107 L16 depthB = L16(cast(short)(lerpfloat(depthLow, depthHigh, _animation))); 108 109 RGBA diffuseColor = RGBA(red, green, blue, cast(ubyte)emissive); 110 111 foreach(dirtyRect; dirtyRects) 112 { 113 auto cDepth = depthMap.cropImageRef(dirtyRect); 114 115 // Write a plain color in the diffuse and material map. 116 box2i validRect = dirtyRect.intersection(switchRect); 117 if (!validRect.empty) 118 { 119 ImageRef!RGBA cDiffuse = diffuseMap.cropImageRef(validRect); 120 if (drawDiffuse) 121 { 122 cDiffuse.fillAll(diffuseColor); 123 } 124 else if (drawEmissive) 125 { 126 cDiffuse.fillRectAlpha(0, 0, cDiffuse.w, cDiffuse.h, diffuseColor.a); 127 } 128 if (drawMaterial) 129 materialMap.cropImageRef(validRect).fillAll(material); 130 } 131 132 // dig a hole 133 if (drawDepth) 134 { 135 if (drawHole) 136 cDepth.fillAll(L16(holeDepth)); 137 138 if (orientation == Orientation.vertical) 139 verticalSlope(cDepth, switchRect, depthA, depthB); 140 else 141 horizontalSlope(cDepth, switchRect, depthA, depthB); 142 } 143 } 144 } 145 146 override Click onMouseClick(int x, int y, int button, bool isDoubleClick, MouseState mstate) 147 { 148 if (!_canBeDragged) 149 { 150 // inside gesture, refuse new clicks that could call 151 // excess beginParamEdit()/endParamEdit() 152 return Click.unhandled; 153 } 154 155 // ALT + click => set it to default 156 if (mstate.altPressed) // reset on ALT + click 157 { 158 _param.beginParamEdit(); 159 _param.setFromGUI(_param.defaultValue()); 160 } 161 else 162 { 163 // Any click => invert 164 // Note: double-click doesn't reset to default, would be annoying 165 _param.beginParamEdit(); 166 _param.setFromGUI(!_param.value()); 167 } 168 _canBeDragged = false; 169 return Click.startDrag; 170 } 171 172 override void onMouseEnter() 173 { 174 _param.beginParamHover(); 175 setDirtyWhole(); 176 } 177 178 override void onMouseExit() 179 { 180 _param.endParamHover(); 181 setDirtyWhole(); 182 } 183 184 override void onStopDrag() 185 { 186 // End parameter edit at end of dragging, even if no parameter change happen, 187 // so that touch automation restore previous parameter value at the end of the mouse 188 // gesture. 189 _param.endParamEdit(); 190 _canBeDragged = true; 191 } 192 193 override void onParameterChanged(Parameter sender) nothrow @nogc 194 { 195 setDirtyWhole(); 196 } 197 198 override void onBeginParameterEdit(Parameter sender) 199 { 200 } 201 202 override void onEndParameterEdit(Parameter sender) 203 { 204 } 205 206 override void onBeginParameterHover(Parameter sender) 207 { 208 } 209 210 override void onEndParameterHover(Parameter sender) 211 { 212 } 213 214 protected: 215 216 /// The parameter this switch is linked with. 217 BoolParameter _param; 218 219 /// To prevent multiple-clicks having an adverse effect on automation. 220 bool _canBeDragged = true; 221 222 private: 223 float _animation; 224 } 225 226 private: 227 void fillRectAlpha(bool CHECKED=true, V)(auto ref V v, int x1, int y1, int x2, int y2, ubyte alpha) nothrow @nogc 228 if (isWritableView!V && is(RGBA : ViewColor!V)) 229 { 230 sort2(x1, x2); 231 sort2(y1, y2); 232 static if (CHECKED) 233 { 234 if (x1 >= v.w || y1 >= v.h || x2 <= 0 || y2 <= 0 || x1==x2 || y1==y2) return; 235 if (x1 < 0) x1 = 0; 236 if (y1 < 0) y1 = 0; 237 if (x2 >= v.w) x2 = v.w; 238 if (y2 >= v.h) y2 = v.h; 239 } 240 241 foreach (y; y1..y2) 242 { 243 RGBA[] scan = v.scanline(y); 244 foreach (x; x1..x2) 245 { 246 scan[x].a = alpha; 247 } 248 } 249 }