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 		StyleSheet ss = getStyleSheet();
103 		//draw background
104 		parent.drawFilledBox(position, ss.getColor("SliderBackground"));
105 		//draw slider
106 		//const int travelLength = position.height - (position.width * 2) - _barLength;
107 		Box slider;
108 		//const int value0 = valRatio < 1.0 ? value : cast(int)(value / valRatio);
109 		slider.left = position.left;
110 		slider.right = position.right;
111 		slider.top = position.top + position.width + cast(int)nearbyint(barLength0 * _value);
112 		slider.bottom = slider.top + _barLength;
113 		parent.drawFilledBox(slider, ss.getColor("SliderColor"));
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 (isFocused) {
123 			parent.drawBoxPattern(position, ss.pattern["blackDottedLine"]);
124 		}+/
125 		if (onDraw !is null) {
126 			onDraw();
127 		}
128 	}
129 	public override void passMCE(MouseEventCommons mec, MouseClickEvent mce) {
130 		if (state != ElementState.Enabled) return;
131 		mce.x -= position.left;
132 		mce.y -= position.top;
133 		if (mce.button == MouseButton.Left) {
134 			if (mce.y < position.width) {
135 				if (!(flags & MINUS_PRESSED) && mce.state == ButtonState.Pressed) {
136 					value = _value - 1;
137 					flags |= MINUS_PRESSED;
138 					registerTimer();
139 				} else if (flags & MINUS_PRESSED && mce.state == ButtonState.Released) {
140 					flags &= ~(MINUS_PRESSED | SCROLLMATIC);
141 				}
142 			} else if (mce.y >= position.height - position.width) {
143 				if (!(flags & PLUS_PRESSED) && mce.state == ButtonState.Pressed) {
144 					value = _value + 1;
145 					flags |= PLUS_PRESSED;
146 					registerTimer();
147 				} else if (flags & PLUS_PRESSED && mce.state == ButtonState.Released) {
148 					flags &= ~(PLUS_PRESSED | SCROLLMATIC);
149 				}
150 			} else {
151 				import std.math : nearbyint;
152 				const double newVal = mce.y - position.width - (_barLength / 2.0);
153 				if (newVal >= 0) {
154 					//const int travelLength = position.height - (position.width * 2) - _barLength;
155 					//const double valRatio = isNaN(largeVal) ? 1.0 : largeVal;
156 					//value = cast(int)nearbyint((travelLength / newVal) * valRatio);
157 					value = cast(int)nearbyint((newVal) * valRatio);
158 				}
159 			}
160 		} 
161 		super.passMCE(mec, mce);
162 	}
163 	public override void passMME(MouseEventCommons mec, MouseMotionEvent mme) {
164 		if (state != ElementState.Enabled) return;
165 		if (mme.buttonState == MouseButtonFlags.Left && mme.y > position.height && mme.y < position.width - position.height) {
166 			value = _value = mme.relY;
167 			draw();
168 		}
169 		super.passMME(mec, mme);
170 	}
171 	public override void passMWE(MouseEventCommons mec, MouseWheelEvent mwe) {
172 		if (state != ElementState.Enabled) return;
173 		value = _value - mwe.y * scrollSpeed;
174 		super.passMWE(mec, mwe);
175 	}
176 	
177 }
178 /**
179  * Horizontal scrollbar.
180  */
181 public class HorizScrollBar : ScrollBar {
182 	public this(int maxValue, string source, Box position){
183 		this.position = position;
184 		this.source = source;
185 		this.maxValue = maxValue;
186 	}
187 	public override void draw(){
188 		StyleSheet ss = getStyleSheet();
189 		//draw background
190 		parent.drawFilledBox(position, ss.getColor("SliderBackground"));
191 		//draw slider
192 		//const int travelLength = position.width - position.height * 2;
193 		const int value0 = valRatio < 1.0 ? value : cast(int)(value / valRatio);
194 		Box slider;
195 		slider.top = position.top;
196 		slider.bottom = position.bottom;
197 		slider.left = position.left + position.height + cast(int)nearbyint(barLength0 * value0);
198 		slider.right = slider.left + _barLength;
199 		parent.drawFilledBox(slider, ss.getColor("SliderColor"));
200 		
201 		//draw buttons
202 		parent.bitBLT(position.cornerUL, flags & MINUS_PRESSED ? ss.getImage("leftArrowB") : ss.getImage("leftArrowA"));
203 		Point lower = position.cornerUR;
204 		lower.x -= position.height;
205 		parent.bitBLT(lower, flags & PLUS_PRESSED ? ss.getImage("rightArrowB") : ss.getImage("rightArrowA"));
206 		/+if (isFocused) {
207 			parent.drawBoxPattern(position, ss.pattern["blackDottedLine"]);
208 		}+/
209 		if (state == ElementState.Disabled) {
210 			parent.bitBLTPattern(position, ss.getImage("ElementDisabledPtrn"));
211 		}
212 	}
213 	public override void passMCE(MouseEventCommons mec, MouseClickEvent mce) {
214 		if (state != ElementState.Enabled) return;
215 		mce.x -= position.left;
216 		mce.y -= position.top;
217 		if (mce.button == MouseButton.Left) {
218 			if (mce.x < position.height) {
219 				if (!(flags & MINUS_PRESSED) && mce.state == ButtonState.Pressed) {
220 					value = _value - 1;
221 					flags |= MINUS_PRESSED;
222 					registerTimer();
223 				} else if (flags & MINUS_PRESSED && mce.state == ButtonState.Released) {
224 					flags &= ~MINUS_PRESSED;
225 				}
226 			} else if (mce.x >= position.width - position.height) {
227 				if (!(flags & PLUS_PRESSED) && mce.state == ButtonState.Pressed) {
228 					value = _value + 1;
229 					flags |= PLUS_PRESSED;
230 					registerTimer();
231 				} else if (flags & PLUS_PRESSED && mce.state == ButtonState.Released) {
232 					flags &= ~PLUS_PRESSED;
233 				}
234 			} else {
235 				import std.math : nearbyint;
236 				const double newVal = mce.x - position.height - (_barLength / 2.0);
237 				if (newVal >= 0) {
238 					//const int travelLength = position.width - position.height * 2 - _barLength;
239 					//const double valRatio = isNaN(largeVal) ? 1.0 : largeVal;
240 					//value = cast(int)nearbyint((travelLength / newVal) * valRatio);
241 					value = cast(int)nearbyint((newVal) * valRatio);
242 				}
243 			}
244 			flags &= ~SCROLLMATIC;
245 
246 		} 
247 		super.passMCE(mec, mce);
248 	}
249 	public override void passMME(MouseEventCommons mec, MouseMotionEvent mme) {
250 		if (state != ElementState.Enabled) return;
251 		if (mme.buttonState == MouseButtonFlags.Left && mme.x > position.width && mme.x < position.height) {
252 			value = _value + mme.relX;
253 			draw();
254 		}
255 		super.passMME(mec, mme);
256 	}
257 	public override void passMWE(MouseEventCommons mec, MouseWheelEvent mwe) {
258 		if (state != ElementState.Enabled) return;
259 		value = _value + mwe.x * scrollSpeed;
260 		super.passMWE(mec, mwe);
261 	}
262 }