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, _min = 0f, _max = 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 newValue) { 32 return _value = _offset = newValue; 33 } 34 35 /// Rounded value between the min and max values specified. 36 int ivalue() const { 37 return cast(int) lerp(_min, _max, _value); 38 } 39 /// Ditto 40 int ivalue(int newValue) { 41 return cast(int)(_value = _offset = rlerp(_min, _max, newValue)); 42 } 43 44 /// Value between the min and max values specified. 45 float fvalue() const { 46 return lerp(_min, _max, _value); 47 } 48 /// Ditto 49 float fvalue(float newValue) { 50 return _value = _offset = rlerp(_min, _max, newValue); 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 step() const { 63 return (_step > 0f) ? cast(uint)(1f / _step) : 0u; 64 } 65 /// Ditto 66 uint step(uint newStep) { 67 if (newStep < 1u) 68 _step = 0f; 69 else 70 _step = 1f / newStep; 71 return newStep; 72 } 73 74 /// Minimal value possible for the slider. \ 75 /// Used by ivalue() and fvalue(). 76 float min() const { 77 return _min; 78 } 79 /// Ditto 80 float min(float newMin) { 81 return _min = newMin; 82 } 83 84 /// Maximal value possible for the slider. \ 85 /// Used by ivalue() and fvalue(). 86 float max() const { 87 return _max; 88 } 89 /// Ditto 90 float max(float newMax) { 91 return _max = 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 _offset = lerp(_offset, _value, deltaTime * 0.25f); 108 triggerCallback(); 109 } 110 } 111 112 override void onEvent(Event event) { 113 if (_step == 0f) 114 return; 115 116 switch (event.type) with (Event.Type) { 117 case mouseWheel: 118 _offset -= event.scroll.delta.y * _step; 119 _offset = (_offset < -_step) ? -_step : ((_offset > 1f + _step) ? 1f + _step : _offset); //Clamp the value. 120 break; 121 case mouseUpdate: 122 if (!isClicked) 123 break; 124 relocateSlider(event); 125 break; 126 case mouseDown: 127 relocateSlider(event); 128 break; 129 case mouseUp: 130 relocateSlider(event); 131 break; 132 default: 133 break; 134 } 135 if (isSelected) 136 relocateSlider(event); 137 } 138 139 /// Process the slider position. 140 protected void relocateSlider(Event event) { 141 if (_step == 0f) { 142 _offset = 0f; 143 _value = 0f; 144 return; 145 } 146 const Vec2f direction = Vec2f.angled(_scrollAngle); 147 const Vec2f startPos = center - direction * 0.5f * _scrollLength; 148 const float coef = direction.y / direction.x; 149 const float b = startPos.y - (coef * startPos.x); 150 151 const Vec2f closestPoint = Vec2f( 152 (coef * event.mouse.position.y + event.mouse.position.x - coef * b) / ( 153 coef * coef + 1f), 154 (coef * coef * event.mouse.position.y + coef * event.mouse.position.x + b) / ( 155 coef * coef + 1f)); 156 157 _offset = ((closestPoint.x - startPos.x) + (closestPoint.y - startPos.y)) / _scrollLength; 158 _offset = (_offset < 0f) ? 0f : ((_offset > 1f) ? 1f : _offset); //Clamp the value. 159 } 160 161 /// Current coordinate of the slider. 162 protected Vec2f getSliderPosition() { 163 if (_step == 0f) 164 return center; 165 Vec2f direction = Vec2f.angled(_scrollAngle); 166 return center + direction * (_scrollLength * (_offset - 0.5f)); 167 } 168 } 169 170 /// Simple vertical scrollbar with basic rendering. 171 class VScrollbar : Slider { 172 /// Ctor 173 this() { 174 super(90f); 175 } 176 177 override void draw() { 178 Vec2f sliderPosition = getSliderPosition(); 179 float sliderLength = std.algorithm.max((_step > 0f ? _step 180 : 1f) * _scrollLength, _minimalSliderSize); 181 182 //Resize the slider to fit in the rail. 183 if (sliderPosition.y - sliderLength / 2f < center.y - _scrollLength / 2f) { 184 const float startPos = center.y - _scrollLength / 2f; 185 const float destination = sliderPosition.y + sliderLength / 2f; 186 sliderLength = destination - startPos; 187 sliderPosition.y = startPos + sliderLength / 2f; 188 } 189 else if (sliderPosition.y + sliderLength / 2f > center.y + _scrollLength / 2f) { 190 const float startPos = sliderPosition.y - sliderLength / 2f; 191 const float destination = center.y + _scrollLength / 2f; 192 sliderLength = destination - startPos; 193 sliderPosition.y = startPos + sliderLength / 2f; 194 } 195 196 sliderPosition.y = clamp(sliderPosition.y, center.y - _scrollLength / 2f, 197 center.y + _scrollLength / 2f); 198 if (sliderLength < 0f) 199 sliderLength = 0f; 200 201 drawFilledRect(origin, size, Color.white * .25f); 202 const Vec2f sliderSize = Vec2f(size.x, sliderLength); 203 drawFilledRect(sliderPosition - sliderSize / 2f, sliderSize, Color.white); 204 } 205 206 override void onSize() { 207 _scrollLength = size.y; 208 } 209 } 210 211 /// Simple horizontal scrollbar with basic rendering. 212 class HScrollbar : Slider { 213 /// Ctor 214 this() { 215 super(0f); 216 } 217 218 override void draw() { 219 Vec2f sliderPosition = getSliderPosition(); 220 float sliderLength = std.algorithm.max((_step > 0f ? _step 221 : 1f) * _scrollLength, _minimalSliderSize); 222 223 //Resize the slider to fit in the rail. 224 if (sliderPosition.x - sliderLength / 2f < center.x - _scrollLength / 2f) { 225 const float startPos = center.x - _scrollLength / 2f; 226 const float destination = sliderPosition.x + sliderLength / 2f; 227 sliderLength = destination - startPos; 228 sliderPosition.x = startPos + sliderLength / 2f; 229 } 230 else if (sliderPosition.x + sliderLength / 2f > center.x + _scrollLength / 2f) { 231 const float startPos = sliderPosition.x - sliderLength / 2f; 232 const float destination = center.x + _scrollLength / 2f; 233 sliderLength = destination - startPos; 234 sliderPosition.x = startPos + sliderLength / 2f; 235 } 236 237 sliderPosition.x = clamp(sliderPosition.x, center.x - _scrollLength / 2f, 238 center.x + _scrollLength / 2f); 239 if (sliderLength < 0f) 240 sliderLength = 0f; 241 242 drawFilledRect(origin, size, Color.white * .25f); 243 const Vec2f sliderSize = Vec2f(sliderLength, size.y); 244 drawFilledRect(sliderPosition - sliderSize / 2f, sliderSize, Color.white); 245 } 246 247 override void onSize() { 248 _scrollLength = size.x; 249 } 250 } 251 252 /// Simple vertical slider with basic rendering. 253 class VSlider : Slider { 254 /// Ctor 255 this() { 256 super(90f); 257 } 258 259 override void draw() { 260 //Background 261 drawFilledRect(origin, size, Color.white * .25f); 262 263 //Gauge 264 const float sliderHeight = clamp(getSliderPosition().y - origin.y, 0f, size.y); 265 const Vec2f sliderSize = Vec2f(size.x, sliderHeight); 266 drawFilledRect(origin + Vec2f(0f, size.y - sliderHeight), sliderSize, color); 267 } 268 269 override void onSize() { 270 _scrollLength = size.y; 271 } 272 } 273 274 /// Simple horizontal slider with basic rendering. 275 class HSlider : Slider { 276 /// Ctor 277 this() { 278 super(0f); 279 } 280 281 override void draw() { 282 //Background 283 drawFilledRect(origin, size, Color.white * .25f); 284 285 //Gauge 286 const float sliderWidth = clamp(getSliderPosition().x - origin.x, 0f, size.x); 287 const Vec2f sliderSize = Vec2f(sliderWidth, size.y); 288 drawFilledRect(origin, sliderSize, color); 289 } 290 291 override void onSize() { 292 _scrollLength = size.x; 293 } 294 }