1 module pixelperfectengine.concrete.elements.scrollbar;
2 
3 public import pixelperfectengine.concrete.elements.base;
4 import std.math : isNaN, nearbyint;
5 import pixelperfectengine.system.timer;
6 
7 abstract class ScrollBar : WindowElement{
8 	protected static enum 	PLUS_PRESSED = 1<<9;
9 	protected static enum 	MINUS_PRESSED = 1<<10;
10 	protected static enum 	SCROLLMATIC = 1<<11;
11 	protected int _value, _maxValue, _barLength;
12 	public int				scrollSpeed = 1;			///Sets the scrollspeed for the given instance, can be useful for large number of items.
13 	//protected double largeVal;							///Set to double.nan if value is less than travellength, or the ratio between 
14 	protected double valRatio;							///Ratio between the travel length and the maximum value
15 	protected double barLength0;
16 	public void delegate(Event ev) onScrolling;			///Called shen the scrollbar's value is changed
17 
18 	/**
19 	 * Returns the slider position.
20 	 */
21 	public @property int value() @nogc @safe pure nothrow const {
22 		return _value;
23 	}
24 	/**
25 	 * Sets the slider position, and redraws the slider.
26 	 * Returns the new slider position.
27 	 */
28 	public @property int value(int val) {
29 		if (val < 0) _value = 0;
30 		else if (val < _maxValue) _value = val;
31 		else _value = _maxValue;
32 		draw();
33 		if (onScrolling !is null)
34 			onScrolling(new Event(this, EventType.MouseScroll, SourceType.WindowElement));
35 		return _value;
36 	}
37 	/**
38 	 * Returns the maximum value of the slider.
39 	 */
40 	public @property int maxValue() @nogc @safe pure nothrow const {
41 		return _maxValue;
42 	}
43 	/**
44 	 * Sets the new maximum value and bar lenght of the slider.
45 	 * Position is kept or lowered if maximum is reached.
46 	 */
47 	public @property int maxValue(int val) {
48 		assert(val >= 0, "Value must be positive!");
49 		const int iconSize = position.width < position.height ? position.width : position.height;
50 		const int length = position.width > position.height ? position.width : position.height;
51 		_maxValue = val;
52 		if (_value > _maxValue) _value = _maxValue;
53 		barLength0 = (length - iconSize * 2) / cast(double)(val + 1);
54 		_barLength = barLength0 < 1.0 ? 1 : cast(int)nearbyint(barLength0);
55 		//largeVal = barLength0 < 1.0 ? 1.0 / barLength0 : double.nan;
56 		valRatio = 1.0 / barLength0;
57 		return _maxValue;
58 	}
59 	/**
60 	 * Returns the length of the scrollbar in pixels.
61 	 *
62 	 * Automatically calculated every time maxValue is changed.
63 	 */
64 	public @property int barLength() @nogc @safe pure nothrow const {
65 		return _barLength;
66 	}
67 	protected void timerEvent(Duration jitter) nothrow {
68 		try {
69 			if (flags & PLUS_PRESSED) {
70 				value = value + 1;
71 				draw;
72 				flags |= SCROLLMATIC;
73 				registerTimer();
74 			} else if (flags & MINUS_PRESSED) {
75 				value = value - 1;
76 				draw;
77 				flags |= SCROLLMATIC;
78 				registerTimer();
79 			}
80 		} catch (Exception e) {
81 
82 		}
83 	}
84 	protected void registerTimer() nothrow {
85 		timer.register(&timerEvent, msecs(flags & SCROLLMATIC ? 50 : 1000));
86 	}
87 }
88 /**
89  * Vertical scroll bar.
90  */
91 public class VertScrollBar : ScrollBar {
92 	//public int[] brush;
93 
94 	//private int value, maxValue, barLength;
95 
96 	public this(int maxValue, string source, Box position){
97 		this.position = position;
98 		this.source = source;
99 		this.maxValue = maxValue;
100 	}
101 	public override void draw(){
102 		if (parent is null) return;
103 		StyleSheet ss = getStyleSheet();
104 		//draw background
105 		parent.drawFilledBox(position, ss.getColor("SliderBackground"));
106 		//draw slider
107 		//const int travelLength = position.height - (position.width * 2) - _barLength;
108 		Box slider;
109 		//const int value0 = valRatio < 1.0 ? value : cast(int)(value / valRatio);
110 		slider.left = position.left;
111 		slider.right = position.right;
112 		slider.top = position.top + position.width + cast(int)nearbyint(barLength0 * _value);
113 		slider.bottom = slider.top + _barLength;
114 		parent.drawFilledBox(slider, ss.getColor("SliderColor"));
115 		//draw buttons
116 		parent.bitBLT(position.cornerUL, flags & MINUS_PRESSED ? ss.getImage("upArrowB") : ss.getImage("upArrowA"));
117 		Point lower = position.cornerLL;
118 		lower.y -= position.width;
119 		parent.bitBLT(lower, flags & PLUS_PRESSED ? ss.getImage("downArrowB") : ss.getImage("downArrowA"));
120 		if (state == ElementState.Disabled) {
121 			parent.bitBLTPattern(position, ss.getImage("ElementDisabledPtrn"));
122 		}
123 		/+if (isFocused) {
124 			parent.drawBoxPattern(position, ss.pattern["blackDottedLine"]);
125 		}+/
126 		if (onDraw !is null) {
127 			onDraw();
128 		}
129 	}
130 	public override void passMCE(MouseEventCommons mec, MouseClickEvent mce) {
131 		if (state != ElementState.Enabled) return;
132 		mce.x -= position.left;
133 		mce.y -= position.top;
134 		if (mce.button == MouseButton.Left) {
135 			if (mce.y < position.width) {
136 				if (!(flags & MINUS_PRESSED) && mce.state == ButtonState.Pressed) {
137 					value = _value - 1;
138 					flags |= MINUS_PRESSED;
139 					registerTimer();
140 				} else if (flags & MINUS_PRESSED && mce.state == ButtonState.Released) {
141 					flags &= ~(MINUS_PRESSED | SCROLLMATIC);
142 				}
143 			} else if (mce.y >= position.height - position.width) {
144 				if (!(flags & PLUS_PRESSED) && mce.state == ButtonState.Pressed) {
145 					value = _value + 1;
146 					flags |= PLUS_PRESSED;
147 					registerTimer();
148 				} else if (flags & PLUS_PRESSED && mce.state == ButtonState.Released) {
149 					flags &= ~(PLUS_PRESSED | SCROLLMATIC);
150 				}
151 			} else {
152 				import std.math : nearbyint;
153 				const double newVal = mce.y - position.width - (_barLength / 2.0);
154 				if (newVal >= 0) {
155 					//const int travelLength = position.height - (position.width * 2) - _barLength;
156 					//const double valRatio = isNaN(largeVal) ? 1.0 : largeVal;
157 					//value = cast(int)nearbyint((travelLength / newVal) * valRatio);
158 					value = cast(int)nearbyint((newVal) * valRatio);
159 				}
160 			}
161 		} 
162 		super.passMCE(mec, mce);
163 	}
164 	public override void passMME(MouseEventCommons mec, MouseMotionEvent mme) {
165 		if (state != ElementState.Enabled) return;
166 		if (mme.buttonState == MouseButtonFlags.Left && mme.y > position.height && mme.y < position.width - position.height) {
167 			import std.math : nearbyint;
168 			const double newVal = mme.y - position.width - (_barLength / 2.0);
169 			if (newVal >= 0)
170 				value = cast(int)nearbyint((newVal) * valRatio);
171 			//value = _value = mme.relY;
172 			//draw();
173 		}
174 		super.passMME(mec, mme);
175 	}
176 	public override void passMWE(MouseEventCommons mec, MouseWheelEvent mwe) {
177 		if (state != ElementState.Enabled) return;
178 		value = _value - mwe.y * scrollSpeed;
179 		super.passMWE(mec, mwe);
180 	}
181 	
182 }
183 /**
184  * Horizontal scrollbar.
185  */
186 public class HorizScrollBar : ScrollBar {
187 	public this(int maxValue, string source, Box position){
188 		this.position = position;
189 		this.source = source;
190 		this.maxValue = maxValue;
191 	}
192 	public override void draw(){
193 		if (parent is null) return;
194 		StyleSheet ss = getStyleSheet();
195 		//draw background
196 		parent.drawFilledBox(position, ss.getColor("SliderBackground"));
197 		//draw slider
198 		//const int travelLength = position.width - position.height * 2;
199 		const int value0 = valRatio < 1.0 ? value : cast(int)(value / valRatio);
200 		Box slider;
201 		slider.top = position.top;
202 		slider.bottom = position.bottom;
203 		slider.left = position.left + position.height + cast(int)nearbyint(barLength0 * value0);
204 		slider.right = slider.left + _barLength;
205 		parent.drawFilledBox(slider, ss.getColor("SliderColor"));
206 		
207 		//draw buttons
208 		parent.bitBLT(position.cornerUL, flags & MINUS_PRESSED ? ss.getImage("leftArrowB") : ss.getImage("leftArrowA"));
209 		Point lower = position.cornerUR;
210 		lower.x -= position.height;
211 		parent.bitBLT(lower, flags & PLUS_PRESSED ? ss.getImage("rightArrowB") : ss.getImage("rightArrowA"));
212 		/+if (isFocused) {
213 			parent.drawBoxPattern(position, ss.pattern["blackDottedLine"]);
214 		}+/
215 		if (state == ElementState.Disabled) {
216 			parent.bitBLTPattern(position, ss.getImage("ElementDisabledPtrn"));
217 		}
218 	}
219 	public override void passMCE(MouseEventCommons mec, MouseClickEvent mce) {
220 		if (state != ElementState.Enabled) return;
221 		mce.x -= position.left;
222 		mce.y -= position.top;
223 		if (mce.button == MouseButton.Left) {
224 			if (mce.x < position.height) {
225 				if (!(flags & MINUS_PRESSED) && mce.state == ButtonState.Pressed) {
226 					value = _value - 1;
227 					flags |= MINUS_PRESSED;
228 					registerTimer();
229 				} else if (flags & MINUS_PRESSED && mce.state == ButtonState.Released) {
230 					flags &= ~MINUS_PRESSED;
231 				}
232 			} else if (mce.x >= position.width - position.height) {
233 				if (!(flags & PLUS_PRESSED) && mce.state == ButtonState.Pressed) {
234 					value = _value + 1;
235 					flags |= PLUS_PRESSED;
236 					registerTimer();
237 				} else if (flags & PLUS_PRESSED && mce.state == ButtonState.Released) {
238 					flags &= ~PLUS_PRESSED;
239 				}
240 			} else {
241 				import std.math : nearbyint;
242 				const double newVal = mce.x - position.height - (_barLength / 2.0);
243 				if (newVal >= 0) {
244 					//const int travelLength = position.width - position.height * 2 - _barLength;
245 					//const double valRatio = isNaN(largeVal) ? 1.0 : largeVal;
246 					//value = cast(int)nearbyint((travelLength / newVal) * valRatio);
247 					value = cast(int)nearbyint((newVal) * valRatio);
248 				}
249 			}
250 			flags &= ~SCROLLMATIC;
251 
252 		} 
253 		super.passMCE(mec, mce);
254 	}
255 	public override void passMME(MouseEventCommons mec, MouseMotionEvent mme) {
256 		if (state != ElementState.Enabled) return;
257 		if (mme.buttonState == MouseButtonFlags.Left && mme.x > position.width && mme.x < position.height) {
258 			/* value = _value + mme.relX;
259 			draw(); */
260 			import std.math : nearbyint;
261 			const double newVal = mme.x - position.height - (_barLength / 2.0);
262 			if (newVal >= 0)
263 				value = cast(int)nearbyint((newVal) * valRatio);
264 		}
265 		super.passMME(mec, mme);
266 	}
267 	public override void passMWE(MouseEventCommons mec, MouseWheelEvent mwe) {
268 		if (state != ElementState.Enabled) return;
269 		value = _value + mwe.x * scrollSpeed;
270 		super.passMWE(mec, mwe);
271 	}
272 }