1 /** 2 Grimoire 3 Copyright (c) 2017 Enalye 4 5 This software is provided 'as-is', without any express or implied warranty. 6 In no event will the authors be held liable for any damages arising 7 from the use of this software. 8 9 Permission is granted to anyone to use this software for any purpose, 10 including commercial applications, and to alter it and redistribute 11 it freely, subject to the following restrictions: 12 13 1. The origin of this software must not be misrepresented; 14 you must not claim that you wrote the original software. 15 If you use this software in a product, an acknowledgment 16 in the product documentation would be appreciated but 17 is not required. 18 19 2. Altered source versions must be plainly marked as such, 20 and must not be misrepresented as being the original software. 21 22 3. This notice may not be removed or altered from any source distribution. 23 */ 24 25 module ui.slider; 26 27 import std.math; 28 import std.algorithm.comparison; 29 import std.conv: to; 30 31 import core.all; 32 import render.all; 33 import common.all; 34 35 import ui.button; 36 import ui.widget; 37 38 class Slider: Widget { 39 protected { 40 float _value = 0f, _offset = 0f, _step = 1f, _min = 0f, _max = 1f, _length = 1f, _minimalSliderSize = 25f; 41 bool _isGrabbed = false; 42 } 43 44 @property { 45 float value01() const { return _value; } 46 float value01(float newValue) { return _value = _offset = newValue; } 47 48 int ivalue() const { return cast(int)lerp(_min, _max, _value); } 49 int ivalue(int newValue) { return cast(int)(_value = _offset = rlerp(_min, _max, newValue)); } 50 float fvalue() const { return lerp(_min, _max, _value); } 51 float fvalue(float newValue) { return _value = _offset = rlerp(_min, _max, newValue); } 52 float offset() const { return _offset; } 53 54 uint step() const { return (_step > 0f) ? cast(uint)(1f / _step) : 0u; } 55 uint step(uint newStep) { 56 if(newStep < 1u) 57 _step = 0f; 58 else 59 _step = 1f / newStep; 60 return newStep; 61 } 62 63 float min() const { return _min; } 64 float min(float newMin) { return _min = newMin; } 65 66 float max() const { return _max; } 67 float max(float newMax) { return _max = newMax; } 68 69 float length() const { return _length; } 70 float length(float newLength) { return _length = newLength; } 71 } 72 73 this() {} 74 75 override void update(float deltaTime) { 76 if(!_isSelected) { 77 _value = (_offset < 0f) ? 0f : ((_offset > 1f) ? 1f : _offset); //Clamp the value. 78 if(_step > 0f) 79 _value = std.math.round(_value / _step) * _step; //Snap the value. 80 _offset = lerp(_offset, _value, deltaTime * 0.25f); 81 triggerCallback(); 82 } 83 } 84 85 override void onEvent(Event event) { 86 if(_step == 0f) 87 return; 88 89 switch(event.type) with(EventType) { 90 case MouseWheel: 91 _offset -= event.position.y * _step; 92 _offset = (_offset < -_step) ? -_step : ((_offset > 1f + _step) ? 1f + _step : _offset); //Clamp the value. 93 break; 94 case MouseDown: 95 case MouseUp: 96 if(_isSelected) 97 break; 98 relocateSlider(event); 99 break; 100 default: 101 break; 102 } 103 /+if(!_hasFocus) { 104 if(getKeyDown("left")) 105 _offset = clamp(_offset - _step, 0f, 1f); 106 if(getKeyDown("right")) 107 _offset = clamp(_offset + _step, 0f, 1f); 108 }+/ 109 if(_isSelected) 110 relocateSlider(event); 111 } 112 113 protected void relocateSlider(Event event) { 114 if(_step == 0f) { 115 _offset = 0f; 116 _value = 0f; 117 return; 118 } 119 Vec2f direction = Vec2f.angled(_angle); 120 Vec2f origin = _position - direction * 0.5f * _length; 121 float coef = direction.y / direction.x; 122 float b = origin.y - (coef * origin.x); 123 124 Vec2f closestPoint = Vec2f( 125 (coef * event.position.y + event.position.x - coef * b) / (coef * coef + 1f), 126 (coef * coef * event.position.y + coef * event.position.x + b) / (coef * coef + 1f)); 127 128 _offset = ((closestPoint.x - origin.x) + (closestPoint.y - origin.y)) / _length; 129 _offset = (_offset < 0f) ? 0f : ((_offset > 1f) ? 1f : _offset); //Clamp the value. 130 } 131 132 protected Vec2f getSliderPosition() { 133 if(_step == 0f) 134 return _position; 135 Vec2f direction = Vec2f.angled(_angle); 136 return _position + direction * (_length * (_offset - 0.5f)); 137 } 138 } 139 140 class VScrollbar: Slider { 141 private { 142 Sprite _circleSprite, _barSprite; 143 } 144 145 Color backColor, frontColor; 146 147 this() { 148 _angle = 90f; 149 _circleSprite = fetch!Sprite("gui_circle"); 150 _barSprite = fetch!Sprite("gui_texel"); 151 152 backColor = Color.white * .25f; 153 backColor.a = 1f; 154 frontColor = Color.white * .45f; 155 frontColor.a = 1f; 156 } 157 158 override void draw() { 159 Vec2f sliderPosition = getSliderPosition(); 160 float sliderLength = std.algorithm.max((_step > 0f ? _step : 1f) * _length, _minimalSliderSize); 161 162 //Resize the slider to fit in the rail. 163 if(sliderPosition.y - sliderLength / 2f < _position.y - _length / 2f) { 164 float origin = _position.y - _length / 2f; 165 float destination = sliderPosition.y + sliderLength / 2f; 166 sliderLength = destination - origin; 167 sliderPosition.y = origin + sliderLength / 2f; 168 } 169 else if(sliderPosition.y + sliderLength / 2f > _position.y + _length / 2f) { 170 float origin = sliderPosition.y - sliderLength / 2f; 171 float destination = _position.y + _length / 2f; 172 sliderLength = destination - origin; 173 sliderPosition.y = origin + sliderLength / 2f; 174 } 175 176 sliderPosition.y = clamp(sliderPosition.y, _position.y - _length / 2f, _position.y + _length / 2f); 177 if(sliderLength < 0f) 178 sliderLength = 0f; 179 180 _circleSprite.size = Vec2f(_size.x, _size.x); 181 _circleSprite.color = backColor; 182 _circleSprite.draw(_position - Vec2f(0f, _length / 2f)); 183 _circleSprite.draw(_position + Vec2f(0f, _length / 2f)); 184 185 _barSprite.color = backColor; 186 _barSprite.size = Vec2f(_size.x, _length); 187 _barSprite.draw(_position); 188 189 _circleSprite.color = frontColor; 190 _circleSprite.draw(sliderPosition - Vec2f(0f, sliderLength / 2f)); 191 _circleSprite.draw(sliderPosition + Vec2f(0f, sliderLength / 2f)); 192 193 _barSprite.color = frontColor; 194 _barSprite.size = Vec2f(_size.x, sliderLength); 195 _barSprite.draw(sliderPosition); 196 197 _circleSprite.size = Vec2f.one; 198 _barSprite.size = Vec2f.one; 199 } 200 201 override void onSize() { 202 _length = _size.y - _size.x; 203 } 204 } 205 206 class HScrollbar: Slider { 207 private { 208 Sprite _circleSprite, _barSprite; 209 } 210 211 Color backColor, frontColor; 212 213 this() { 214 _angle = 0f; 215 _circleSprite = fetch!Sprite("gui_circle"); 216 _barSprite = fetch!Sprite("gui_texel"); 217 218 backColor = Color.white * .25f; 219 backColor.a = 1f; 220 frontColor = Color.white * .45f; 221 frontColor.a = 1f; 222 } 223 224 override void draw() { 225 Vec2f sliderPosition = getSliderPosition(); 226 float sliderLength = std.algorithm.max((_step > 0f ? _step : 1f) * _length, _minimalSliderSize); 227 228 //Resize the slider to fit in the rail. 229 if(sliderPosition.x - sliderLength / 2f < _position.x - _length / 2f) { 230 float origin = _position.x - _length / 2f; 231 float destination = sliderPosition.x + sliderLength / 2f; 232 sliderLength = destination - origin; 233 sliderPosition.x = origin + sliderLength / 2f; 234 } 235 else if(sliderPosition.x + sliderLength / 2f > _position.x + _length / 2f) { 236 float origin = sliderPosition.x - sliderLength / 2f; 237 float destination = _position.x + _length / 2f; 238 sliderLength = destination - origin; 239 sliderPosition.x = origin + sliderLength / 2f; 240 } 241 242 sliderPosition.x = clamp(sliderPosition.x, _position.x - _length / 2f, _position.x + _length / 2f); 243 if(sliderLength < 0f) 244 sliderLength = 0f; 245 246 _circleSprite.size = Vec2f(_size.y, _size.y); 247 _circleSprite.color = backColor; 248 _circleSprite.draw(_position - Vec2f(_length / 2f, 0f)); 249 _circleSprite.draw(_position + Vec2f(_length / 2f, 0f)); 250 251 _barSprite.color = backColor; 252 _barSprite.size = Vec2f(_length, _size.y); 253 _barSprite.draw(_position); 254 255 _circleSprite.color = frontColor; 256 _circleSprite.draw(sliderPosition - Vec2f(sliderLength / 2f, 0f)); 257 _circleSprite.draw(sliderPosition + Vec2f(sliderLength / 2f, 0f)); 258 259 _barSprite.color = frontColor; 260 _barSprite.size = Vec2f(sliderLength, _size.y); 261 _barSprite.draw(sliderPosition); 262 263 _circleSprite.size = Vec2f.one; 264 _barSprite.size = Vec2f.one; 265 } 266 267 override void onSize() { 268 _length = _size.x - _size.y; 269 } 270 } 271 272 class VGauge: Slider { 273 private { 274 Sprite _barSprite, _endSprite; 275 int _clipSizeY, _clipSizeH; 276 } 277 278 this() { 279 _angle = 90f; 280 _barSprite = fetch!Sprite("gui_bar"); 281 _endSprite = fetch!Sprite("gui_bar_end"); 282 _clipSizeY = _endSprite.clip.y; 283 _clipSizeH = _endSprite.clip.w; 284 } 285 286 override void draw() { 287 Vec2f sliderPosition = getSliderPosition(); 288 289 //Base 290 _barSprite.size = _size - Vec2f(0f, 16f); 291 _endSprite.clip.y = _clipSizeY; 292 _endSprite.clip.w = _clipSizeH; 293 _endSprite.size = Vec2f(_size.x, _clipSizeH); 294 295 _barSprite.color = Color.white * .25f; 296 _endSprite.color = Color.white * .25f; 297 298 _barSprite.draw(_position); 299 _endSprite.flip = Flip.NoFlip; 300 _endSprite.draw(_position - Vec2f(0f, _length / 2f - _endSprite.size.y / 2f)); 301 _endSprite.flip = Flip.VerticalFlip; 302 _endSprite.draw(_position + Vec2f(0f, _length / 2f - _endSprite.size.y / 2f)); 303 304 //Gauge 305 _barSprite.color = Color.white; 306 _endSprite.color = Color.white; 307 if(sliderPosition.y > (_position.y +_length / 2f - _endSprite.size.y)) { 308 _endSprite.size.y = _clipSizeH + (((_position.y +_length / 2f - _endSprite.size.y) - sliderPosition.y) * 2f); 309 _endSprite.clip.w = to!int(_endSprite.size.y); 310 _endSprite.flip = Flip.VerticalFlip; 311 _endSprite.draw(_position + Vec2f(0f, _length / 2f - _endSprite.size.y / 2f)); 312 } 313 else if(sliderPosition.y < (_position.y - _length / 2f + _endSprite.size.y)) { 314 _endSprite.flip = Flip.VerticalFlip; 315 _endSprite.draw(_position + Vec2f(0f, _length / 2f - _endSprite.size.y / 2f)); 316 317 _barSprite.size = _size - Vec2f(0f, 16f); 318 _barSprite.draw(_position); 319 320 _endSprite.size.y = (((_position.y - _length / 2f + _endSprite.size.y) - sliderPosition.y) * 2f); 321 _endSprite.clip.y = to!int(_clipSizeY + _clipSizeH - _endSprite.size.y / 2f); 322 _endSprite.clip.w = to!int(_endSprite.size.y); 323 _endSprite.flip = Flip.NoFlip; 324 _endSprite.draw(_position - Vec2f(0f, _length / 2f - _clipSizeH / 2f - _clipSizeH / 2f)); 325 } 326 else { 327 _endSprite.flip = Flip.VerticalFlip; 328 _endSprite.draw(_position + Vec2f(0f, _length / 2f - _endSprite.size.y / 2f)); 329 330 float origin = sliderPosition.y; 331 float dest = _position.y + _barSprite.size.y / 2f; 332 _barSprite.size = Vec2f(_size.x, dest - origin + 1f); 333 _barSprite.draw(Vec2f(_position.x, origin + _barSprite.size.y / 2f)); 334 } 335 336 _endSprite.size = Vec2f.one; 337 _barSprite.size = Vec2f.one; 338 } 339 340 override void onSize() { 341 _length = _size.y; 342 } 343 } 344 345 class HGauge: Slider { 346 private { 347 Sprite _barSprite, _endSprite; 348 int _clipSizeY, _clipSizeH; 349 } 350 351 this() { 352 _angle = 0f; 353 _barSprite = fetch!Sprite("gui_bar"); 354 _endSprite = fetch!Sprite("gui_bar_end"); 355 _endSprite.angle = 90f; 356 _clipSizeY = _endSprite.clip.y; 357 _clipSizeH = _endSprite.clip.w; 358 } 359 360 override void draw() { 361 Vec2f sliderPosition = getSliderPosition(); 362 Vec2f leftAnchor = _position - Vec2f(_length / 2f - _clipSizeH, 0f); 363 Vec2f rightAnchor = _position + Vec2f(_length / 2f - _clipSizeH, 0f); 364 Vec2f leftEnd = _position - Vec2f(_length / 2f - _clipSizeH / 2f, 0f); 365 Vec2f rightEnd = _position + Vec2f(_length / 2f - _clipSizeH / 2f, 0f); 366 367 //Base 368 _barSprite.anchor = Vec2f(0f, .5f); 369 _barSprite.size = Vec2f((rightAnchor.x - leftAnchor.x), _size.y); 370 _barSprite.color = Color.white * .25f; 371 _barSprite.draw(leftAnchor); 372 373 _endSprite.clip.y = _clipSizeY; 374 _endSprite.clip.w = _clipSizeH; 375 _endSprite.size = Vec2f(_size.y, _clipSizeH); 376 _endSprite.color = Color.white * .25f; 377 378 _endSprite.flip = Flip.VerticalFlip; 379 _endSprite.draw(leftEnd); 380 _endSprite.flip = Flip.HorizontalFlip; 381 _endSprite.draw(rightEnd); 382 383 //Gauge 384 _barSprite.color = Color.white; 385 _endSprite.color = Color.white; 386 387 if(sliderPosition.x > rightAnchor.x) { 388 //Static left end 389 _endSprite.flip = Flip.VerticalFlip; 390 _endSprite.draw(leftEnd); 391 392 //Static bar 393 _barSprite.draw(leftAnchor); 394 395 //Resized right end 396 _endSprite.size.y = sliderPosition.x - rightAnchor.x; 397 _endSprite.clip.y = to!int(_clipSizeY + _clipSizeH - _endSprite.size.y); 398 _endSprite.clip.w = to!int(_endSprite.size.y); 399 _endSprite.flip = Flip.HorizontalFlip; 400 _endSprite.draw(rightAnchor + Vec2f(_endSprite.size.y / 2f, 0f)); 401 } 402 else if(sliderPosition.x < leftAnchor.x) { 403 //Resized left end 404 _endSprite.size.y = _clipSizeH - (leftAnchor.x - sliderPosition.x); 405 _endSprite.clip.w = to!int(_endSprite.size.y); 406 _endSprite.flip = Flip.VerticalFlip; 407 _endSprite.draw(leftAnchor + Vec2f(_endSprite.size.y / 2f - _clipSizeH, 0f)); 408 } 409 else { 410 //Static left end 411 _endSprite.flip = Flip.VerticalFlip; 412 _endSprite.draw(leftEnd); 413 414 //Resized bar 415 _barSprite.size = Vec2f(sliderPosition.x - leftAnchor.x, _size.y); 416 _barSprite.draw(leftAnchor); 417 } 418 419 _endSprite.size = Vec2f.one; 420 _barSprite.size = Vec2f.one; 421 } 422 423 override void onSize() { 424 _length = _size.x; 425 } 426 } 427 428 429 class VSlider: Slider { 430 private Sprite _circleSprite, _barSprite; 431 432 Color backColor, frontColor; 433 434 this() { 435 _angle = 90f; 436 _barSprite = fetch!Sprite("gui_bar"); 437 _circleSprite = fetch!Sprite("gui_circle"); 438 439 backColor = Color.white * .25f; 440 backColor.a = 1f; 441 frontColor = Color.cyan * .8f; 442 frontColor.a = 1f; 443 } 444 445 override void draw() { 446 Vec2f sliderPosition = getSliderPosition(); 447 448 Vec2f upPos = _position - Vec2f(0f, _length / 2f); 449 Vec2f downPos = _position + Vec2f(0f, _length / 2f); 450 451 _circleSprite.size = Vec2f(_size.x, _size.x); 452 _circleSprite.color = backColor; 453 _circleSprite.draw(upPos); 454 455 _barSprite.color = backColor; 456 _barSprite.size = Vec2f(_size.x, _length); 457 _barSprite.draw(_position); 458 459 _circleSprite.color = frontColor; 460 _circleSprite.draw(downPos); 461 _circleSprite.draw(sliderPosition); 462 463 _barSprite.color = frontColor; 464 _barSprite.size = Vec2f(_size.x, downPos.y - sliderPosition.y); 465 _barSprite.draw(sliderPosition + Vec2f(0f, (downPos.y - sliderPosition.y) / 2f)); 466 467 _circleSprite.size = Vec2f.one; 468 _barSprite.size = Vec2f.one; 469 } 470 471 override void onSize() { 472 _length = _size.y - _size.x; 473 } 474 } 475 476 class HSlider: Slider { 477 private Sprite _circleSprite, _barSprite; 478 479 Color backColor, frontColor; 480 481 this() { 482 _angle = 0f; 483 _barSprite = fetch!Sprite("gui_bar"); 484 _circleSprite = fetch!Sprite("gui_circle"); 485 486 backColor = Color.white * .25f; 487 backColor.a = 1f; 488 frontColor = Color.cyan * .8f; 489 frontColor.a = 1f; 490 } 491 492 override void draw() { 493 Vec2f sliderPosition = getSliderPosition(); 494 495 Vec2f leftPos = _position - Vec2f(_length / 2f, 0f); 496 Vec2f rightPos = _position + Vec2f(_length / 2f, 0f); 497 498 _circleSprite.size = Vec2f(_size.y, _size.y); 499 _circleSprite.color = backColor; 500 _circleSprite.draw(rightPos); 501 502 _barSprite.color = backColor; 503 _barSprite.size = Vec2f(_length, _size.y); 504 _barSprite.draw(_position); 505 506 _circleSprite.color = frontColor; 507 _circleSprite.draw(leftPos); 508 _circleSprite.draw(sliderPosition); 509 510 _barSprite.color = frontColor; 511 _barSprite.size = Vec2f(sliderPosition.x - leftPos.x, _size.y); 512 _barSprite.draw(leftPos + Vec2f((sliderPosition.x - leftPos.x) / 2f, 0f)); 513 514 _circleSprite.size = Vec2f.one; 515 _barSprite.size = Vec2f.one; 516 } 517 518 override void onSize() { 519 _length = _size.x - _size.y; 520 } 521 }