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