1 /** 2 Slider 3 4 Copyright: (c) Enalye 2017 5 License: Zlib 6 Authors: Enalye 7 */ 8 9 module atelier.ui.slider; 10 11 import std.math; 12 import std.algorithm.comparison; 13 import std.conv : to; 14 import atelier.core, atelier.render, atelier.common; 15 import atelier.ui.button, atelier.ui.gui_element; 16 17 /// Base abstract class for any vertical or horizontal slider/scrollbar. 18 abstract class Slider : GuiElement { 19 protected { 20 float _value = 0f, _offset = 0f, _step = 1f, _minValue = 0f, _maxValue = 1f, 21 _scrollLength = 1f, _minimalSliderSize = 25f, _scrollAngle = 0f; 22 bool _isGrabbed = false; 23 } 24 25 @property { 26 /// Slider's value between 0 and 1. 27 float value01() const { 28 return _value; 29 } 30 /// Ditto 31 float value01(float value_) { 32 return _value = _offset = value_; 33 } 34 35 /// Rounded value between the min and max values specified. 36 int ivalue() const { 37 return cast(int) lerp(_minValue, _maxValue, _value); 38 } 39 /// Ditto 40 int ivalue(int value_) { 41 return cast(int)(_value = _offset = rlerp(_minValue, _maxValue, value_)); 42 } 43 44 /// Value between the min and max values specified. 45 float fvalue() const { 46 return lerp(_minValue, _maxValue, _value); 47 } 48 /// Ditto 49 float fvalue(float value_) { 50 return _value = _offset = rlerp(_minValue, _maxValue, value_); 51 } 52 53 /// Value (from 0 to 1) before being processed/clamped/etc. \ 54 /// Useful for rendering, not for getting its value as data. 55 float offset() const { 56 return _offset; 57 } 58 59 /// The number of steps of the slider. \ 60 /// 1 = The slider jumps directly from start to finish. \ 61 /// More = The slider has more intermediate values. 62 uint steps() const { 63 return (_step > 0f) ? cast(uint)(1f / _step) : 0u; 64 } 65 /// Ditto 66 uint steps(uint steps_) { 67 if (steps_ < 1u) 68 _step = 0f; 69 else 70 _step = 1f / steps_; 71 return steps_; 72 } 73 74 /// Minimal value possible for the slider. \ 75 /// Used by ivalue() and fvalue(). 76 float minValue() const { 77 return _minValue; 78 } 79 /// Ditto 80 float minValue(float newMin) { 81 return _minValue = newMin; 82 } 83 84 /// Maximal value possible for the slider. \ 85 /// Used by ivalue() and fvalue(). 86 float maxValue() const { 87 return _maxValue; 88 } 89 /// Ditto 90 float maxValue(float newMax) { 91 return _maxValue = newMax; 92 } 93 } 94 95 /// Sets the angle of the slider. \ 96 /// 90 = Vertical. \ 97 /// 0 = Horizontal. 98 this(float scrollAngle) { 99 _scrollAngle = scrollAngle; 100 } 101 102 override void update(float deltaTime) { 103 if (!isSelected) { 104 _value = (_offset < 0f) ? 0f : ((_offset > 1f) ? 1f : _offset); //Clamp the value. 105 if (_step > 0f) 106 _value = std.math.round(_value / _step) * _step; //Snap the value. 107 if (!isClicked) 108 _offset = lerp(_offset, _value, deltaTime * 0.25f); 109 triggerCallback(); 110 } 111 } 112 113 override void onEvent(Event event) { 114 if (_step == 0f) 115 return; 116 117 switch (event.type) with (Event.Type) { 118 case mouseWheel: 119 _offset -= event.scroll.delta.y * _step; 120 _offset = (_offset < -_step) ? -_step : ((_offset > 1f + _step) ? 1f + _step : _offset); //Clamp the value. 121 break; 122 case mouseUpdate: 123 if (!isClicked) 124 break; 125 relocateSlider(event); 126 break; 127 case mouseDown: 128 relocateSlider(event); 129 break; 130 case mouseUp: 131 relocateSlider(event); 132 break; 133 default: 134 break; 135 } 136 if (isSelected) 137 relocateSlider(event); 138 } 139 140 /// Process the slider position. 141 protected void relocateSlider(Event event) { 142 if (_step == 0f) { 143 _offset = 0f; 144 _value = 0f; 145 return; 146 } 147 const Vec2f direction = Vec2f.angled(_scrollAngle); 148 const Vec2f startPos = center - direction * 0.5f * _scrollLength; 149 const float coef = direction.y / direction.x; 150 const float b = startPos.y - (coef * startPos.x); 151 152 const Vec2f closestPoint = Vec2f( 153 (coef * event.mouse.position.y + event.mouse.position.x - coef * b) / ( 154 coef * coef + 1f), 155 (coef * coef * event.mouse.position.y + coef * event.mouse.position.x + b) / ( 156 coef * coef + 1f)); 157 158 _offset = ((closestPoint.x - startPos.x) + (closestPoint.y - startPos.y)) / _scrollLength; 159 _offset = (_offset < 0f) ? 0f : ((_offset > 1f) ? 1f : _offset); //Clamp the value. 160 } 161 162 /// Current coordinate of the slider. 163 protected Vec2f getSliderPosition() { 164 if (_step == 0f) 165 return center; 166 Vec2f direction = Vec2f.angled(_scrollAngle); 167 return center + direction * (_scrollLength * (_offset - 0.5f)); 168 } 169 } 170 171 /// Simple vertical scrollbar with basic rendering. 172 class VScrollbar : Slider { 173 /// Ctor 174 this() { 175 super(90f); 176 } 177 178 override void draw() { 179 Vec2f sliderPosition = getSliderPosition(); 180 float sliderLength = std.algorithm.max((_step > 0f ? _step 181 : 1f) * _scrollLength, _minimalSliderSize); 182 183 //Resize the slider to fit in the rail. 184 if (sliderPosition.y - sliderLength / 2f < center.y - _scrollLength / 2f) { 185 const float startPos = center.y - _scrollLength / 2f; 186 const float destination = sliderPosition.y + sliderLength / 2f; 187 sliderLength = destination - startPos; 188 sliderPosition.y = startPos + sliderLength / 2f; 189 } 190 else if (sliderPosition.y + sliderLength / 2f > center.y + _scrollLength / 2f) { 191 const float startPos = sliderPosition.y - sliderLength / 2f; 192 const float destination = center.y + _scrollLength / 2f; 193 sliderLength = destination - startPos; 194 sliderPosition.y = startPos + sliderLength / 2f; 195 } 196 197 sliderPosition.y = clamp(sliderPosition.y, center.y - _scrollLength / 2f, 198 center.y + _scrollLength / 2f); 199 if (sliderLength < 0f) 200 sliderLength = 0f; 201 202 drawFilledRect(origin, size, Color.white * .25f); 203 const Vec2f sliderSize = Vec2f(size.x, sliderLength); 204 drawFilledRect(sliderPosition - sliderSize / 2f, sliderSize, Color.white); 205 } 206 207 override void onSize() { 208 _scrollLength = size.y; 209 } 210 } 211 212 /// Simple horizontal scrollbar with basic rendering. 213 class HScrollbar : Slider { 214 /// Ctor 215 this() { 216 super(0f); 217 } 218 219 override void draw() { 220 Vec2f sliderPosition = getSliderPosition(); 221 float sliderLength = std.algorithm.max((_step > 0f ? _step 222 : 1f) * _scrollLength, _minimalSliderSize); 223 224 //Resize the slider to fit in the rail. 225 if (sliderPosition.x - sliderLength / 2f < center.x - _scrollLength / 2f) { 226 const float startPos = center.x - _scrollLength / 2f; 227 const float destination = sliderPosition.x + sliderLength / 2f; 228 sliderLength = destination - startPos; 229 sliderPosition.x = startPos + sliderLength / 2f; 230 } 231 else if (sliderPosition.x + sliderLength / 2f > center.x + _scrollLength / 2f) { 232 const float startPos = sliderPosition.x - sliderLength / 2f; 233 const float destination = center.x + _scrollLength / 2f; 234 sliderLength = destination - startPos; 235 sliderPosition.x = startPos + sliderLength / 2f; 236 } 237 238 sliderPosition.x = clamp(sliderPosition.x, center.x - _scrollLength / 2f, 239 center.x + _scrollLength / 2f); 240 if (sliderLength < 0f) 241 sliderLength = 0f; 242 243 drawFilledRect(origin, size, Color.white * .25f); 244 const Vec2f sliderSize = Vec2f(sliderLength, size.y); 245 drawFilledRect(sliderPosition - sliderSize / 2f, sliderSize, Color.white); 246 } 247 248 override void onSize() { 249 _scrollLength = size.x; 250 } 251 } 252 253 /// Simple vertical slider with basic rendering. 254 class VSlider : Slider { 255 /// Ctor 256 this() { 257 super(90f); 258 } 259 260 override void draw() { 261 //Background 262 drawFilledRect(origin, size, Color.white * .25f); 263 264 //Gauge 265 const float sliderHeight = clamp(getSliderPosition().y - origin.y, 0f, size.y); 266 const Vec2f sliderSize = Vec2f(size.x, sliderHeight); 267 drawFilledRect(origin + Vec2f(0f, size.y - sliderHeight), sliderSize, color); 268 } 269 270 override void onSize() { 271 _scrollLength = size.y; 272 } 273 } 274 275 /// Simple horizontal slider with basic rendering. 276 class HSlider : Slider { 277 /// Ctor 278 this() { 279 super(0f); 280 } 281 282 override void draw() { 283 //Background 284 drawFilledRect(origin, size, Color.white * .25f); 285 286 //Gauge 287 const float sliderWidth = clamp(getSliderPosition().x - origin.x, 0f, size.x); 288 const Vec2f sliderSize = Vec2f(sliderWidth, size.y); 289 drawFilledRect(origin, sliderSize, color); 290 } 291 292 override void onSize() { 293 _scrollLength = size.x; 294 } 295 }