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