1 /**
2     Knob
3 
4     Copyright: (c) Enalye 2017
5     License: Zlib
6     Authors: Enalye
7 */
8 
9 module atelier.ui.knob;
10 
11 import atelier.core, atelier.render, atelier.common;
12 import atelier.ui.gui_element;
13 
14 /// A rotating knob. \
15 /// Behave a bit like slider.
16 class Knob : GuiElement {
17     protected {
18         float _value = 0f, _step = 1f, _min = 0f, _max = 1f, _minAngle = 0f,
19             _maxAngle = 360f, _knobAngle = 0f, _lastValue = 0f;
20         bool _isGrabbed = false;
21         Vec2f _lastCursorPosition = Vec2f.zero;
22     }
23 
24     @property {
25         /// Slider's value between 0 and 1.
26         float value01() const {
27             return _value;
28         }
29         /// Ditto
30         float value01(float newValue) {
31             return _value = newValue;
32         }
33 
34         /// Rounded value between the min and max values specified.
35         int ivalue() const {
36             return cast(int) lerp(_min, _max, _value);
37         }
38         /// Ditto
39         int ivalue(int newValue) {
40             return cast(int)(_value = rlerp(_min, _max, newValue));
41         }
42 
43         /// Value between the min and max values specified.
44         float fvalue() const {
45             return lerp(_min, _max, _value);
46         }
47         /// Ditto
48         float fvalue(float newValue) {
49             return _value = rlerp(_min, _max, newValue);
50         }
51 
52         /// The number of steps of the slider. \
53         /// 1 = The slider jumps directly from start to finish. \
54         /// More = The slider has more intermediate values.
55         uint step() const {
56             return (_step > 0f) ? cast(uint)(1f / _step) : 0u;
57         }
58         /// Ditto
59         uint step(uint newStep) {
60             if (newStep < 1u)
61                 _step = 0f;
62             else
63                 _step = 1f / newStep;
64             return newStep;
65         }
66 
67         /// Minimal value possible for the slider. \
68         /// Used by ivalue() and fvalue().
69         float min() const {
70             return _min;
71         }
72         /// Ditto
73         float min(float newMin) {
74             return _min = newMin;
75         }
76 
77         /// Maximal value possible for the slider. \
78         /// Used by ivalue() and fvalue().
79         float max() const {
80             return _max;
81         }
82         /// Ditto
83         float max(float newMax) {
84             return _max = newMax;
85         }
86 
87         /// Angle in degrees in which the rotator currently is.
88         float knobAngle() const {
89             return _knobAngle;
90         }
91     }
92 
93     /// Ctor
94     this() {
95         setEventHook(true);
96     }
97 
98     /// Sets the min and max angles that the knob can rotate.
99     void setAngles(float minAngle, float maxAngle) {
100         _minAngle = minAngle;
101         _maxAngle = maxAngle;
102     }
103 
104     override void onEvent(Event event) {
105         if (_step == 0f)
106             return;
107 
108         switch (event.type) with (Event.Type) {
109         case mouseWheel:
110             _value += event.scroll.delta.y * _step;
111             _value = clamp(_value, 0f, 1f);
112             break;
113         case mouseDown:
114             _lastCursorPosition = event.mouse.position;
115             break;
116         case mouseUp:
117         case mouseUpdate:
118             if (!isSelected)
119                 break;
120             Vec2f delta = event.mouse.position - center;
121             Vec2f delta2 = event.mouse.position - _lastCursorPosition;
122             if (delta2.lengthSquared() > 0f)
123                 delta2.normalize();
124             else
125                 break;
126             if (delta.lengthSquared() > 0f)
127                 delta.normalize();
128             float direction = delta.rotated(90f).dot(delta2);
129             direction = direction > .5f ? 1f : (direction < -.5f ? -1f : 0f);
130             _value += direction * _step;
131             _value = clamp(_value, 0f, 1f);
132             _lastCursorPosition = event.mouse.position;
133             break;
134         default:
135             break;
136         }
137     }
138 
139     override void update(float deltaTime) {
140         if (_step == 0f) {
141             _value = 0f;
142             return;
143         }
144         _knobAngle = lerp(_minAngle, _maxAngle, _value);
145 
146         if (_lastValue != _value) {
147             triggerCallback();
148             _lastValue = _value;
149         }
150     }
151 
152     override void draw() {
153         drawRect(origin, size, Color.white);
154         drawLine(center, center + Vec2f(1f, 0f).rotated(_knobAngle) * 10f, Color.white);
155     }
156 
157     override bool isInside(const Vec2f pos) const {
158         const float halfSize = size.x / 2f;
159         return (pos - center).lengthSquared() < halfSize * halfSize;
160     }
161 }