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