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.knob;
26 
27 import core.all;
28 import render.all;
29 import common.all;
30 
31 import ui.widget;
32 
33 class Knob: Widget {
34 	protected {
35 		float _value = 0f, _step = 1f, _min = 0f, _max = 1f, _minAngle = 0f, _maxAngle = 360f, _angle = 0f, _lastValue = 0f;
36 		Sprite _baseSprite, _btnSprite;
37 		bool _isGrabbed = false;
38 		Vec2f _lastCursorPosition = Vec2f.zero;
39 	}
40 
41 	@property {
42 		float value01() const { return _value; }
43 		float value01(float newValue) { return _value = newValue; }
44 
45 		int ivalue() const { return cast(int)lerp(_min, _max, _value); }
46 		int ivalue(int newValue) { return cast(int)(_value = rlerp(_min, _max, newValue)); }
47 		float fvalue() const { return lerp(_min, _max, _value); }
48 		float fvalue(float newValue) { return _value = rlerp(_min, _max, newValue); }
49 
50 		uint step() const { return (_step > 0f) ? cast(uint)(1f / _step) : 0u; }
51 		uint step(uint newStep) {
52 			if(newStep < 1u)
53 				_step = 0f;
54 			else
55 				_step = 1f / newStep;
56 			return newStep;
57 		}
58 
59 		float min() const { return _min; }
60 		float min(float newMin) { return _min = newMin; }
61 
62 		float max() const { return _max; }
63 		float max(float newMax) { return _max = newMax; }
64 	}
65 
66 	this() {
67 		_btnSprite = fetch!Sprite("knob_btn");
68 		_size = _btnSprite.size;
69 	}
70 
71 	void setAngles(float minAngle, float maxAngle) {
72 		_minAngle = minAngle;
73 		_maxAngle = maxAngle;
74 	}
75 
76 	override void onEvent(Event event) {
77 		if(_step == 0f)
78 			return;
79 
80 		switch(event.type) with(EventType) {
81 		case MouseWheel:
82 			_value += event.position.y * _step;
83 			_value = clamp(_value, 0f, 1f);
84 			break;
85 		case MouseDown:
86 			_lastCursorPosition = event.position;
87 			break;
88 		case MouseUp:
89 		case MouseUpdate:
90 			if(!_isSelected)
91 				break;
92 			Vec2f delta = event.position - _position;
93 			Vec2f delta2 = event.position - _lastCursorPosition;
94 			if(delta2.lengthSquared() > 0f)
95 				delta2.normalize();
96 			else
97 				break;
98 			if(delta.lengthSquared() > 0f)
99 				delta.normalize();
100 			float direction = delta.rotated(90f).dot(delta2);
101 			direction = direction > .5f ? 1f : (direction < -.5f ? -1f : 0f);
102 			_value += direction * _step;
103 			_value = clamp(_value, 0f, 1f);
104 			_lastCursorPosition = event.position;
105 			break;
106 		default:
107 			break;
108 		}
109 	}
110 
111 	override void update(float deltaTime) {
112 		if(_step == 0f) {
113 			_value = 0f;
114 			return;
115 		}
116 		_angle = lerp(_minAngle, _maxAngle, _value);
117 		_btnSprite.angle = lerp(_btnSprite.angle, _angle, deltaTime * .5f);
118 
119 		if(_lastValue != _value) {
120 			triggerCallback();
121 			_lastValue = _value;
122 		}
123 	}
124 
125 	override void draw() {
126 		_btnSprite.draw(_position);
127 	}
128 
129 	override bool isInside(const Vec2f pos) const {
130 		float halfSize = _size.x / 2f;
131 		return (_position - pos).lengthSquared() < halfSize * halfSize;
132 	}
133 }