1 /*
2 Copyright (C) 2002-2013 Nikolaus Gebhardt
3 This file is part of the "Irrlicht Engine".
4 For conditions of distribution and use, see copyright notice in irrlicht.h
5 
6 Modified 2019.05.01 by stujones11, Stuart Jones <stujones111@gmail.com>
7 
8 This is a heavily modified copy of the Irrlicht CGUIScrollBar class
9 which includes automatic scaling of the thumb slider and hiding of
10 the arrow buttons where there is insufficient space.
11 */
12 
13 #include "guiScrollBar.h"
14 #include <IGUIButton.h>
15 #include <IGUISkin.h>
16 
GUIScrollBar(IGUIEnvironment * environment,IGUIElement * parent,s32 id,core::rect<s32> rectangle,bool horizontal,bool auto_scale)17 GUIScrollBar::GUIScrollBar(IGUIEnvironment *environment, IGUIElement *parent, s32 id,
18 		core::rect<s32> rectangle, bool horizontal, bool auto_scale) :
19 		IGUIElement(EGUIET_ELEMENT, environment, parent, id, rectangle),
20 		up_button(nullptr), down_button(nullptr), is_dragging(false),
21 		is_horizontal(horizontal), is_auto_scaling(auto_scale),
22 		dragged_by_slider(false), tray_clicked(false), scroll_pos(0),
23 		draw_center(0), thumb_size(0), min_pos(0), max_pos(100), small_step(10),
24 		large_step(50), drag_offset(0), page_size(100), border_size(0)
25 {
26 	refreshControls();
27 	setNotClipped(false);
28 	setTabStop(true);
29 	setTabOrder(-1);
30 	setPos(0);
31 }
32 
OnEvent(const SEvent & event)33 bool GUIScrollBar::OnEvent(const SEvent &event)
34 {
35 	if (isEnabled()) {
36 		switch (event.EventType) {
37 		case EET_KEY_INPUT_EVENT:
38 			if (event.KeyInput.PressedDown) {
39 				const s32 old_pos = scroll_pos;
40 				bool absorb = true;
41 				switch (event.KeyInput.Key) {
42 				case KEY_LEFT:
43 				case KEY_UP:
44 					setPos(scroll_pos - small_step);
45 					break;
46 				case KEY_RIGHT:
47 				case KEY_DOWN:
48 					setPos(scroll_pos + small_step);
49 					break;
50 				case KEY_HOME:
51 					setPos(min_pos);
52 					break;
53 				case KEY_PRIOR:
54 					setPos(scroll_pos - large_step);
55 					break;
56 				case KEY_END:
57 					setPos(max_pos);
58 					break;
59 				case KEY_NEXT:
60 					setPos(scroll_pos + large_step);
61 					break;
62 				default:
63 					absorb = false;
64 				}
65 				if (scroll_pos != old_pos) {
66 					SEvent e;
67 					e.EventType = EET_GUI_EVENT;
68 					e.GUIEvent.Caller = this;
69 					e.GUIEvent.Element = nullptr;
70 					e.GUIEvent.EventType = EGET_SCROLL_BAR_CHANGED;
71 					Parent->OnEvent(e);
72 				}
73 				if (absorb)
74 					return true;
75 			}
76 			break;
77 		case EET_GUI_EVENT:
78 			if (event.GUIEvent.EventType == EGET_BUTTON_CLICKED) {
79 				if (event.GUIEvent.Caller == up_button)
80 					setPos(scroll_pos - small_step);
81 				else if (event.GUIEvent.Caller == down_button)
82 					setPos(scroll_pos + small_step);
83 
84 				SEvent e;
85 				e.EventType = EET_GUI_EVENT;
86 				e.GUIEvent.Caller = this;
87 				e.GUIEvent.Element = nullptr;
88 				e.GUIEvent.EventType = EGET_SCROLL_BAR_CHANGED;
89 				Parent->OnEvent(e);
90 				return true;
91 			} else if (event.GUIEvent.EventType == EGET_ELEMENT_FOCUS_LOST)
92 				if (event.GUIEvent.Caller == this)
93 					is_dragging = false;
94 			break;
95 		case EET_MOUSE_INPUT_EVENT: {
96 			const core::position2di p(event.MouseInput.X, event.MouseInput.Y);
97 			bool is_inside = isPointInside(p);
98 			switch (event.MouseInput.Event) {
99 			case EMIE_MOUSE_WHEEL:
100 				if (Environment->hasFocus(this)) {
101 					s8 d = event.MouseInput.Wheel < 0 ? -1 : 1;
102 					s8 h = is_horizontal ? 1 : -1;
103 					setPos(getPos() + (d * small_step * h));
104 
105 					SEvent e;
106 					e.EventType = EET_GUI_EVENT;
107 					e.GUIEvent.Caller = this;
108 					e.GUIEvent.Element = nullptr;
109 					e.GUIEvent.EventType = EGET_SCROLL_BAR_CHANGED;
110 					Parent->OnEvent(e);
111 					return true;
112 				}
113 				break;
114 			case EMIE_LMOUSE_PRESSED_DOWN: {
115 				if (is_inside) {
116 					is_dragging = true;
117 					dragged_by_slider = slider_rect.isPointInside(p);
118 					core::vector2di corner = slider_rect.UpperLeftCorner;
119 					drag_offset = is_horizontal ? p.X - corner.X : p.Y - corner.Y;
120 					tray_clicked = !dragged_by_slider;
121 					if (tray_clicked) {
122 						const s32 new_pos = getPosFromMousePos(p);
123 						const s32 old_pos = scroll_pos;
124 						setPos(new_pos);
125 						// drag in the middle
126 						drag_offset = thumb_size / 2;
127 						// report the scroll event
128 						if (scroll_pos != old_pos && Parent) {
129 							SEvent e;
130 							e.EventType = EET_GUI_EVENT;
131 							e.GUIEvent.Caller = this;
132 							e.GUIEvent.Element = nullptr;
133 							e.GUIEvent.EventType = EGET_SCROLL_BAR_CHANGED;
134 							Parent->OnEvent(e);
135 						}
136 					}
137 					Environment->setFocus(this);
138 					return true;
139 				}
140 				break;
141 			}
142 			case EMIE_LMOUSE_LEFT_UP:
143 			case EMIE_MOUSE_MOVED: {
144 				if (!event.MouseInput.isLeftPressed())
145 					is_dragging = false;
146 
147 				if (!is_dragging) {
148 					if (event.MouseInput.Event == EMIE_MOUSE_MOVED)
149 						break;
150 					return is_inside;
151 				}
152 
153 				if (event.MouseInput.Event == EMIE_LMOUSE_LEFT_UP)
154 					is_dragging = false;
155 
156 				// clang-format off
157 				if (!dragged_by_slider) {
158 					if (is_inside) {
159 						dragged_by_slider = slider_rect.isPointInside(p);
160 						tray_clicked = !dragged_by_slider;
161 					}
162 					if (!dragged_by_slider) {
163 						tray_clicked = false;
164 						if (event.MouseInput.Event == EMIE_MOUSE_MOVED)
165 							return is_inside;
166 					}
167 				}
168 				// clang-format on
169 
170 				const s32 new_pos = getPosFromMousePos(p);
171 				const s32 old_pos = scroll_pos;
172 
173 				setPos(new_pos);
174 
175 				if (scroll_pos != old_pos && Parent) {
176 					SEvent e;
177 					e.EventType = EET_GUI_EVENT;
178 					e.GUIEvent.Caller = this;
179 					e.GUIEvent.Element = nullptr;
180 					e.GUIEvent.EventType = EGET_SCROLL_BAR_CHANGED;
181 					Parent->OnEvent(e);
182 				}
183 				return is_inside;
184 			}
185 			default:
186 				break;
187 			}
188 		} break;
189 		default:
190 			break;
191 		}
192 	}
193 	return IGUIElement::OnEvent(event);
194 }
195 
draw()196 void GUIScrollBar::draw()
197 {
198 	if (!IsVisible)
199 		return;
200 
201 	IGUISkin *skin = Environment->getSkin();
202 	if (!skin)
203 		return;
204 
205 	video::SColor icon_color = skin->getColor(
206 			isEnabled() ? EGDC_WINDOW_SYMBOL : EGDC_GRAY_WINDOW_SYMBOL);
207 	if (icon_color != current_icon_color)
208 		refreshControls();
209 
210 	slider_rect = AbsoluteRect;
211 	skin->draw2DRectangle(this, skin->getColor(EGDC_SCROLLBAR), slider_rect,
212 			&AbsoluteClippingRect);
213 
214 	if (core::isnotzero(range())) {
215 		if (is_horizontal) {
216 			slider_rect.UpperLeftCorner.X = AbsoluteRect.UpperLeftCorner.X +
217 							draw_center - thumb_size / 2;
218 			slider_rect.LowerRightCorner.X =
219 					slider_rect.UpperLeftCorner.X + thumb_size;
220 		} else {
221 			slider_rect.UpperLeftCorner.Y = AbsoluteRect.UpperLeftCorner.Y +
222 							draw_center - thumb_size / 2;
223 			slider_rect.LowerRightCorner.Y =
224 					slider_rect.UpperLeftCorner.Y + thumb_size;
225 		}
226 		skin->draw3DButtonPaneStandard(this, slider_rect, &AbsoluteClippingRect);
227 	}
228 	IGUIElement::draw();
229 }
230 
updateAbsolutePosition()231 void GUIScrollBar::updateAbsolutePosition()
232 {
233 	IGUIElement::updateAbsolutePosition();
234 	refreshControls();
235 	setPos(scroll_pos);
236 }
237 
getPosFromMousePos(const core::position2di & pos) const238 s32 GUIScrollBar::getPosFromMousePos(const core::position2di &pos) const
239 {
240 	s32 w, p;
241 	s32 offset = dragged_by_slider ? drag_offset : thumb_size / 2;
242 
243 	if (is_horizontal) {
244 		w = RelativeRect.getWidth() - border_size * 2 - thumb_size;
245 		p = pos.X - AbsoluteRect.UpperLeftCorner.X - border_size - offset;
246 	} else {
247 		w = RelativeRect.getHeight() - border_size * 2 - thumb_size;
248 		p = pos.Y - AbsoluteRect.UpperLeftCorner.Y - border_size - offset;
249 	}
250 	return core::isnotzero(range()) ? s32(f32(p) / f32(w) * range() + 0.5f) + min_pos : 0;
251 }
252 
setPos(const s32 & pos)253 void GUIScrollBar::setPos(const s32 &pos)
254 {
255 	s32 thumb_area = 0;
256 	s32 thumb_min = 0;
257 
258 	if (is_horizontal) {
259 		thumb_min = RelativeRect.getHeight();
260 		thumb_area = RelativeRect.getWidth() - border_size * 2;
261 	} else {
262 		thumb_min = RelativeRect.getWidth();
263 		thumb_area = RelativeRect.getHeight() - border_size * 2;
264 	}
265 
266 	if (is_auto_scaling)
267 		thumb_size = s32(thumb_area /
268 				 (f32(page_size) / f32(thumb_area + border_size * 2)));
269 
270 	thumb_size = core::s32_clamp(thumb_size, thumb_min, thumb_area);
271 	scroll_pos = core::s32_clamp(pos, min_pos, max_pos);
272 
273 	f32 f = core::isnotzero(range()) ? (f32(thumb_area) - f32(thumb_size)) / range()
274 					 : 1.0f;
275 	draw_center = s32((f32(scroll_pos - min_pos) * f) + (f32(thumb_size) * 0.5f)) +
276 		border_size;
277 }
278 
setSmallStep(const s32 & step)279 void GUIScrollBar::setSmallStep(const s32 &step)
280 {
281 	small_step = step > 0 ? step : 10;
282 }
283 
setLargeStep(const s32 & step)284 void GUIScrollBar::setLargeStep(const s32 &step)
285 {
286 	large_step = step > 0 ? step : 50;
287 }
288 
setMax(const s32 & max)289 void GUIScrollBar::setMax(const s32 &max)
290 {
291 	max_pos = max;
292 	if (min_pos > max_pos)
293 		min_pos = max_pos;
294 
295 	bool enable = core::isnotzero(range());
296 	up_button->setEnabled(enable);
297 	down_button->setEnabled(enable);
298 	setPos(scroll_pos);
299 }
300 
setMin(const s32 & min)301 void GUIScrollBar::setMin(const s32 &min)
302 {
303 	min_pos = min;
304 	if (max_pos < min_pos)
305 		max_pos = min_pos;
306 
307 	bool enable = core::isnotzero(range());
308 	up_button->setEnabled(enable);
309 	down_button->setEnabled(enable);
310 	setPos(scroll_pos);
311 }
312 
setPageSize(const s32 & size)313 void GUIScrollBar::setPageSize(const s32 &size)
314 {
315 	page_size = size;
316 	setPos(scroll_pos);
317 }
318 
setArrowsVisible(ArrowVisibility visible)319 void GUIScrollBar::setArrowsVisible(ArrowVisibility visible)
320 {
321 	arrow_visibility = visible;
322 	refreshControls();
323 }
324 
getPos() const325 s32 GUIScrollBar::getPos() const
326 {
327 	return scroll_pos;
328 }
329 
refreshControls()330 void GUIScrollBar::refreshControls()
331 {
332 	IGUISkin *skin = Environment->getSkin();
333 	IGUISpriteBank *sprites = nullptr;
334 	current_icon_color = video::SColor(255, 255, 255, 255);
335 
336 	if (skin) {
337 		sprites = skin->getSpriteBank();
338 		current_icon_color =
339 				skin->getColor(isEnabled() ? EGDC_WINDOW_SYMBOL
340 							   : EGDC_GRAY_WINDOW_SYMBOL);
341 	}
342 	if (is_horizontal) {
343 		s32 h = RelativeRect.getHeight();
344 		border_size = RelativeRect.getWidth() < h * 4 ? 0 : h;
345 		if (!up_button) {
346 			up_button = Environment->addButton(
347 					core::rect<s32>(0, 0, h, h), this);
348 			up_button->setSubElement(true);
349 			up_button->setTabStop(false);
350 		}
351 		if (sprites) {
352 			up_button->setSpriteBank(sprites);
353 			up_button->setSprite(EGBS_BUTTON_UP,
354 					s32(skin->getIcon(EGDI_CURSOR_LEFT)),
355 					current_icon_color);
356 			up_button->setSprite(EGBS_BUTTON_DOWN,
357 					s32(skin->getIcon(EGDI_CURSOR_LEFT)),
358 					current_icon_color);
359 		}
360 		up_button->setRelativePosition(core::rect<s32>(0, 0, h, h));
361 		up_button->setAlignment(EGUIA_UPPERLEFT, EGUIA_UPPERLEFT, EGUIA_UPPERLEFT,
362 				EGUIA_LOWERRIGHT);
363 		if (!down_button) {
364 			down_button = Environment->addButton(
365 					core::rect<s32>(RelativeRect.getWidth() - h, 0,
366 							RelativeRect.getWidth(), h),
367 					this);
368 			down_button->setSubElement(true);
369 			down_button->setTabStop(false);
370 		}
371 		if (sprites) {
372 			down_button->setSpriteBank(sprites);
373 			down_button->setSprite(EGBS_BUTTON_UP,
374 					s32(skin->getIcon(EGDI_CURSOR_RIGHT)),
375 					current_icon_color);
376 			down_button->setSprite(EGBS_BUTTON_DOWN,
377 					s32(skin->getIcon(EGDI_CURSOR_RIGHT)),
378 					current_icon_color);
379 		}
380 		down_button->setRelativePosition(
381 				core::rect<s32>(RelativeRect.getWidth() - h, 0,
382 						RelativeRect.getWidth(), h));
383 		down_button->setAlignment(EGUIA_LOWERRIGHT, EGUIA_LOWERRIGHT,
384 				EGUIA_UPPERLEFT, EGUIA_LOWERRIGHT);
385 	} else {
386 		s32 w = RelativeRect.getWidth();
387 		border_size = RelativeRect.getHeight() < w * 4 ? 0 : w;
388 		if (!up_button) {
389 			up_button = Environment->addButton(
390 					core::rect<s32>(0, 0, w, w), this);
391 			up_button->setSubElement(true);
392 			up_button->setTabStop(false);
393 		}
394 		if (sprites) {
395 			up_button->setSpriteBank(sprites);
396 			up_button->setSprite(EGBS_BUTTON_UP,
397 					s32(skin->getIcon(EGDI_CURSOR_UP)),
398 					current_icon_color);
399 			up_button->setSprite(EGBS_BUTTON_DOWN,
400 					s32(skin->getIcon(EGDI_CURSOR_UP)),
401 					current_icon_color);
402 		}
403 		up_button->setRelativePosition(core::rect<s32>(0, 0, w, w));
404 		up_button->setAlignment(EGUIA_UPPERLEFT, EGUIA_LOWERRIGHT,
405 				EGUIA_UPPERLEFT, EGUIA_UPPERLEFT);
406 		if (!down_button) {
407 			down_button = Environment->addButton(
408 					core::rect<s32>(0, RelativeRect.getHeight() - w,
409 							w, RelativeRect.getHeight()),
410 					this);
411 			down_button->setSubElement(true);
412 			down_button->setTabStop(false);
413 		}
414 		if (sprites) {
415 			down_button->setSpriteBank(sprites);
416 			down_button->setSprite(EGBS_BUTTON_UP,
417 					s32(skin->getIcon(EGDI_CURSOR_DOWN)),
418 					current_icon_color);
419 			down_button->setSprite(EGBS_BUTTON_DOWN,
420 					s32(skin->getIcon(EGDI_CURSOR_DOWN)),
421 					current_icon_color);
422 		}
423 		down_button->setRelativePosition(
424 				core::rect<s32>(0, RelativeRect.getHeight() - w, w,
425 						RelativeRect.getHeight()));
426 		down_button->setAlignment(EGUIA_UPPERLEFT, EGUIA_LOWERRIGHT,
427 				EGUIA_LOWERRIGHT, EGUIA_LOWERRIGHT);
428 	}
429 
430 	bool visible;
431 	if (arrow_visibility == DEFAULT)
432 		visible = (border_size != 0);
433 	else if (arrow_visibility == HIDE) {
434 		visible = false;
435 		border_size = 0;
436 	} else {
437 		visible = true;
438 		if (is_horizontal)
439 			border_size = RelativeRect.getHeight();
440 		else
441 			border_size = RelativeRect.getWidth();
442 	}
443 
444 	up_button->setVisible(visible);
445 	down_button->setVisible(visible);
446 }
447