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 }