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;
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 
36     this(UIContext context, BoolParameter param)
37     {
38         super(context, flagAnimated | flagPBR);
39         _param = param;
40         _param.addListener(this);
41         _animation = 0.0f;
42     }
43 
44     ~this()
45     {
46         _param.removeListener(this);
47     }
48 
49     override void onAnimate(double dt, double time) nothrow @nogc
50     {
51         float target = _param.valueAtomic() ? 1 : 0;
52 
53         float newAnimation = lerp(_animation, target, 1.0 - exp(-dt * animationTimeConstant));
54 
55         if (abs(newAnimation - _animation) > 0.001f)
56         {
57             _animation = newAnimation;
58             setDirtyWhole();
59         }
60     }
61 
62     override void onDrawPBR(ImageRef!RGBA diffuseMap, ImageRef!L16 depthMap, ImageRef!RGBA materialMap, box2i[] dirtyRects) nothrow @nogc
63     {
64         // The switch is in a subrect
65         int width = _position.width;
66         int height = _position.height;
67         float border = 0.1f;
68         box2i switchRect = box2i( cast(int)(0.5f + width * border),
69                                   cast(int)(0.5f + height * border),
70                                   cast(int)(0.5f + width * (1 - border)),
71                                   cast(int)(0.5f + height * (1 - border)) );
72 
73         ubyte red    = cast(ubyte)(lerp!float(diffuseOff.r, diffuseOn.r, _animation));
74         ubyte green  = cast(ubyte)(lerp!float(diffuseOff.g, diffuseOn.g, _animation));
75         ubyte blue   = cast(ubyte)(lerp!float(diffuseOff.b, diffuseOn.b, _animation));
76         int emissive = cast(ubyte)(lerp!float(diffuseOff.a, diffuseOn.a, _animation));
77 
78         if (isMouseOver || isDragged)
79             emissive += 40;
80 
81         if (emissive > 255)
82             emissive = 255;
83 
84         // Workaround issue https://issues.dlang.org/show_bug.cgi?id=23076
85         // Regular should not be inlined here.
86         static float lerpfloat(float a, float b, float t) pure nothrow @nogc
87         {
88             pragma(inline, false);
89             return t * b + (1 - t) * a;
90         }
91 
92         L16 depthA = L16(cast(short)(lerpfloat(depthHigh, depthLow, _animation)));
93         L16 depthB = L16(cast(short)(lerpfloat(depthLow, depthHigh, _animation)));
94 
95         RGBA diffuseColor = RGBA(red, green, blue, cast(ubyte)emissive);
96 
97         foreach(dirtyRect; dirtyRects)
98         {
99             auto cDepth = depthMap.cropImageRef(dirtyRect);
100 
101             // Write a plain color in the diffuse and material map.
102             box2i validRect = dirtyRect.intersection(switchRect);
103             if (!validRect.empty)
104             {
105                 diffuseMap.cropImageRef(validRect).fillAll(diffuseColor);
106                 materialMap.cropImageRef(validRect).fillAll(material);
107             }       
108 
109             // dig a hole
110             cDepth.fillAll(L16(holeDepth));
111 
112             if (orientation == Orientation.vertical)
113                 verticalSlope(cDepth, switchRect, depthA, depthB);
114             else
115                 horizontalSlope(cDepth, switchRect, depthA, depthB);
116         }
117     }
118 
119     override Click onMouseClick(int x, int y, int button, bool isDoubleClick, MouseState mstate)
120     {
121         // ALT + click => set it to default
122         if (mstate.altPressed) // reset on ALT + click
123         {
124             _param.beginParamEdit();
125             _param.setFromGUI(_param.defaultValue());
126             _param.endParamEdit();
127         }
128         else
129         {
130             // Any click => invert
131             // Note: double-click doesn't reset to default, would be annoying
132             _param.beginParamEdit();
133             _param.setFromGUI(!_param.value());
134             _param.endParamEdit();
135         }
136         return Click.startDrag;
137     }
138 
139     override void onMouseEnter()
140     {
141         _param.beginParamHover();
142         setDirtyWhole();
143     }
144 
145     override void onMouseExit()
146     {
147          _param.endParamHover();
148         setDirtyWhole();
149     }
150 
151     override void onParameterChanged(Parameter sender) nothrow @nogc
152     {
153         setDirtyWhole();
154     }
155 
156     override void onBeginParameterEdit(Parameter sender)
157     {
158     }
159 
160     override void onEndParameterEdit(Parameter sender)
161     {
162     }
163 
164     override void onBeginParameterHover(Parameter sender)
165     {
166     }
167 
168     override void onEndParameterHover(Parameter sender)
169     {
170     }
171 
172 protected:
173 
174     /// The parameter this switch is linked with.
175     BoolParameter _param;
176 
177 private:
178     float _animation;
179 }