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 }