--- /dev/null
+/*
+Copyright (C) 2002-2013 Nikolaus Gebhardt
+This file is part of the "Irrlicht Engine".
+For conditions of distribution and use, see copyright notice in irrlicht.h
+
+Modified 2019.05.01 by stujones11, Stuart Jones <stujones111@gmail.com>
+
+This is a heavily modified copy of the Irrlicht CGUIScrollBar class
+which includes automatic scaling of the thumb slider and hiding of
+the arrow buttons where there is insufficient space.
+*/
+
+#include "guiScrollBar.h"
+#include <IGUIButton.h>
+#include <IGUISkin.h>
+
+guiScrollBar::guiScrollBar(IGUIEnvironment *environment, IGUIElement *parent, s32 id,
+ core::rect<s32> rectangle, bool horizontal, bool auto_scale) :
+ IGUIElement(EGUIET_ELEMENT, environment, parent, id, rectangle),
+ up_button(nullptr), down_button(nullptr), is_dragging(false),
+ is_horizontal(horizontal), is_auto_scaling(auto_scale),
+ dragged_by_slider(false), tray_clicked(false), scroll_pos(0),
+ draw_center(0), thumb_size(0), min_pos(0), max_pos(100), small_step(10),
+ large_step(50), desired_pos(0), last_change(0), drag_offset(0),
+ page_size(100), border_size(0)
+{
+ refreshControls();
+ setNotClipped(false);
+ setTabStop(true);
+ setTabOrder(-1);
+ setPos(0);
+}
+
+bool guiScrollBar::OnEvent(const SEvent &event)
+{
+ if (isEnabled()) {
+ switch (event.EventType) {
+ case EET_KEY_INPUT_EVENT:
+ if (event.KeyInput.PressedDown) {
+ const s32 old_pos = scroll_pos;
+ bool absorb = true;
+ switch (event.KeyInput.Key) {
+ case KEY_LEFT:
+ case KEY_UP:
+ setPos(scroll_pos - small_step);
+ break;
+ case KEY_RIGHT:
+ case KEY_DOWN:
+ setPos(scroll_pos + small_step);
+ break;
+ case KEY_HOME:
+ setPos(min_pos);
+ break;
+ case KEY_PRIOR:
+ setPos(scroll_pos - large_step);
+ break;
+ case KEY_END:
+ setPos(max_pos);
+ break;
+ case KEY_NEXT:
+ setPos(scroll_pos + large_step);
+ break;
+ default:
+ absorb = false;
+ }
+ if (scroll_pos != old_pos) {
+ SEvent e;
+ e.EventType = EET_GUI_EVENT;
+ e.GUIEvent.Caller = this;
+ e.GUIEvent.Element = nullptr;
+ e.GUIEvent.EventType = EGET_SCROLL_BAR_CHANGED;
+ Parent->OnEvent(e);
+ }
+ if (absorb)
+ return true;
+ }
+ break;
+ case EET_GUI_EVENT:
+ if (event.GUIEvent.EventType == EGET_BUTTON_CLICKED) {
+ if (event.GUIEvent.Caller == up_button)
+ setPos(scroll_pos - small_step);
+ else if (event.GUIEvent.Caller == down_button)
+ setPos(scroll_pos + small_step);
+
+ SEvent e;
+ e.EventType = EET_GUI_EVENT;
+ e.GUIEvent.Caller = this;
+ e.GUIEvent.Element = nullptr;
+ e.GUIEvent.EventType = EGET_SCROLL_BAR_CHANGED;
+ Parent->OnEvent(e);
+ return true;
+ } else if (event.GUIEvent.EventType == EGET_ELEMENT_FOCUS_LOST)
+ if (event.GUIEvent.Caller == this)
+ is_dragging = false;
+ break;
+ case EET_MOUSE_INPUT_EVENT: {
+ const core::position2di p(event.MouseInput.X, event.MouseInput.Y);
+ bool is_inside = isPointInside(p);
+ switch (event.MouseInput.Event) {
+ case EMIE_MOUSE_WHEEL:
+ if (Environment->hasFocus(this)) {
+ s8 d = event.MouseInput.Wheel < 0 ? -1 : 1;
+ s8 h = is_horizontal ? 1 : -1;
+ setPos(getPos() + (d * small_step * h));
+
+ SEvent e;
+ e.EventType = EET_GUI_EVENT;
+ e.GUIEvent.Caller = this;
+ e.GUIEvent.Element = nullptr;
+ e.GUIEvent.EventType = EGET_SCROLL_BAR_CHANGED;
+ Parent->OnEvent(e);
+ return true;
+ }
+ break;
+ case EMIE_LMOUSE_PRESSED_DOWN: {
+ if (is_inside) {
+ is_dragging = true;
+ dragged_by_slider = slider_rect.isPointInside(p);
+ core::vector2di corner =
+ slider_rect.UpperLeftCorner;
+ drag_offset = is_horizontal ? p.X - corner.X
+ : p.Y - corner.Y;
+ tray_clicked = !dragged_by_slider;
+ desired_pos = getPosFromMousePos(p);
+ Environment->setFocus(this);
+ return true;
+ }
+ break;
+ }
+ case EMIE_LMOUSE_LEFT_UP:
+ case EMIE_MOUSE_MOVED: {
+ if (!event.MouseInput.isLeftPressed())
+ is_dragging = false;
+
+ if (!is_dragging) {
+ if (event.MouseInput.Event == EMIE_MOUSE_MOVED)
+ break;
+ return is_inside;
+ }
+
+ if (event.MouseInput.Event == EMIE_LMOUSE_LEFT_UP)
+ is_dragging = false;
+
+ // clang-format off
+ if (!dragged_by_slider) {
+ if (is_inside) {
+ dragged_by_slider = slider_rect.isPointInside(p);
+ tray_clicked = !dragged_by_slider;
+ }
+ if (!dragged_by_slider) {
+ tray_clicked = false;
+ if (event.MouseInput.Event == EMIE_MOUSE_MOVED)
+ return is_inside;
+ }
+ }
+ // clang-format on
+
+ const s32 new_pos = getPosFromMousePos(p);
+ const s32 old_pos = scroll_pos;
+
+ if (dragged_by_slider)
+ setPos(new_pos);
+ else
+ desired_pos = new_pos;
+
+ if (scroll_pos != old_pos && Parent) {
+ SEvent e;
+ e.EventType = EET_GUI_EVENT;
+ e.GUIEvent.Caller = this;
+ e.GUIEvent.Element = nullptr;
+ e.GUIEvent.EventType = EGET_SCROLL_BAR_CHANGED;
+ Parent->OnEvent(e);
+ }
+ return is_inside;
+ }
+ default:
+ break;
+ }
+ } break;
+ default:
+ break;
+ }
+ }
+ return IGUIElement::OnEvent(event);
+}
+
+void guiScrollBar::OnPostRender(u32 timeMs)
+{
+ if (is_dragging && !dragged_by_slider && tray_clicked &&
+ timeMs > last_change + 200) {
+ last_change = timeMs;
+ const s32 old_pos = scroll_pos;
+
+ if (desired_pos >= scroll_pos + large_step)
+ setPos(scroll_pos + large_step);
+ else if (desired_pos <= scroll_pos - large_step)
+ setPos(scroll_pos - large_step);
+ else if (desired_pos >= scroll_pos - large_step &&
+ desired_pos <= scroll_pos + large_step)
+ setPos(desired_pos);
+
+ if (scroll_pos != old_pos && Parent) {
+ SEvent e;
+ e.EventType = EET_GUI_EVENT;
+ e.GUIEvent.Caller = this;
+ e.GUIEvent.Element = nullptr;
+ e.GUIEvent.EventType = EGET_SCROLL_BAR_CHANGED;
+ Parent->OnEvent(e);
+ }
+ }
+}
+
+void guiScrollBar::draw()
+{
+ if (!IsVisible)
+ return;
+
+ IGUISkin *skin = Environment->getSkin();
+ if (!skin)
+ return;
+
+ video::SColor icon_color = skin->getColor(
+ isEnabled() ? EGDC_WINDOW_SYMBOL : EGDC_GRAY_WINDOW_SYMBOL);
+ if (icon_color != current_icon_color)
+ refreshControls();
+
+ slider_rect = AbsoluteRect;
+ skin->draw2DRectangle(this, skin->getColor(EGDC_SCROLLBAR), slider_rect,
+ &AbsoluteClippingRect);
+
+ if (core::isnotzero(range())) {
+ if (is_horizontal) {
+ slider_rect.UpperLeftCorner.X = AbsoluteRect.UpperLeftCorner.X +
+ draw_center - thumb_size / 2;
+ slider_rect.LowerRightCorner.X =
+ slider_rect.UpperLeftCorner.X + thumb_size;
+ } else {
+ slider_rect.UpperLeftCorner.Y = AbsoluteRect.UpperLeftCorner.Y +
+ draw_center - thumb_size / 2;
+ slider_rect.LowerRightCorner.Y =
+ slider_rect.UpperLeftCorner.Y + thumb_size;
+ }
+ skin->draw3DButtonPaneStandard(this, slider_rect, &AbsoluteClippingRect);
+ }
+ IGUIElement::draw();
+}
+
+void guiScrollBar::updateAbsolutePosition()
+{
+ IGUIElement::updateAbsolutePosition();
+ refreshControls();
+ setPos(scroll_pos);
+}
+
+s32 guiScrollBar::getPosFromMousePos(const core::position2di &pos) const
+{
+ s32 w, p;
+ s32 offset = dragged_by_slider ? drag_offset : thumb_size / 2;
+
+ if (is_horizontal) {
+ w = RelativeRect.getWidth() - border_size * 2 - thumb_size;
+ p = pos.X - AbsoluteRect.UpperLeftCorner.X - border_size - offset;
+ } else {
+ w = RelativeRect.getHeight() - border_size * 2 - thumb_size;
+ p = pos.Y - AbsoluteRect.UpperLeftCorner.Y - border_size - offset;
+ }
+ return core::isnotzero(range()) ? s32(f32(p) / f32(w) * range()) + min_pos : 0;
+}
+
+void guiScrollBar::setPos(const s32 &pos)
+{
+ s32 thumb_area = 0;
+ s32 thumb_min = 0;
+
+ if (is_horizontal) {
+ thumb_min = RelativeRect.getHeight();
+ thumb_area = RelativeRect.getWidth() - border_size * 2;
+ } else {
+ thumb_min = RelativeRect.getWidth();
+ thumb_area = RelativeRect.getHeight() - border_size * 2;
+ }
+
+ if (is_auto_scaling)
+ thumb_size = s32(thumb_area /
+ (f32(page_size) / f32(thumb_area + border_size * 2)));
+
+ thumb_size = core::s32_clamp(thumb_size, thumb_min, thumb_area);
+ scroll_pos = core::s32_clamp(pos, min_pos, max_pos);
+
+ f32 f = core::isnotzero(range()) ? (f32(thumb_area) - f32(thumb_size)) / range()
+ : 1.0f;
+ draw_center = s32((f32(scroll_pos) * f) + (f32(thumb_size) * 0.5f)) + border_size;
+}
+
+void guiScrollBar::setSmallStep(const s32 &step)
+{
+ small_step = step > 0 ? step : 10;
+}
+
+void guiScrollBar::setLargeStep(const s32 &step)
+{
+ large_step = step > 0 ? step : 50;
+}
+
+void guiScrollBar::setMax(const s32 &max)
+{
+ max_pos = max;
+ if (min_pos > max_pos)
+ min_pos = max_pos;
+
+ bool enable = core::isnotzero(range());
+ up_button->setEnabled(enable);
+ down_button->setEnabled(enable);
+ setPos(scroll_pos);
+}
+
+void guiScrollBar::setMin(const s32 &min)
+{
+ min_pos = min;
+ if (max_pos < min_pos)
+ max_pos = min_pos;
+
+ bool enable = core::isnotzero(range());
+ up_button->setEnabled(enable);
+ down_button->setEnabled(enable);
+ setPos(scroll_pos);
+}
+
+void guiScrollBar::setPageSize(const s32 &size)
+{
+ page_size = size;
+ setPos(scroll_pos);
+}
+
+s32 guiScrollBar::getPos() const
+{
+ return scroll_pos;
+}
+
+void guiScrollBar::refreshControls()
+{
+ IGUISkin *skin = Environment->getSkin();
+ IGUISpriteBank *sprites = nullptr;
+ current_icon_color = video::SColor(255, 255, 255, 255);
+
+ if (skin) {
+ sprites = skin->getSpriteBank();
+ current_icon_color =
+ skin->getColor(isEnabled() ? EGDC_WINDOW_SYMBOL
+ : EGDC_GRAY_WINDOW_SYMBOL);
+ }
+ if (is_horizontal) {
+ s32 h = RelativeRect.getHeight();
+ border_size = RelativeRect.getWidth() < h * 4 ? 0 : h;
+ if (!up_button) {
+ up_button = Environment->addButton(
+ core::rect<s32>(0, 0, h, h), this);
+ up_button->setSubElement(true);
+ up_button->setTabStop(false);
+ }
+ if (sprites) {
+ up_button->setSpriteBank(sprites);
+ up_button->setSprite(EGBS_BUTTON_UP,
+ s32(skin->getIcon(EGDI_CURSOR_LEFT)),
+ current_icon_color);
+ up_button->setSprite(EGBS_BUTTON_DOWN,
+ s32(skin->getIcon(EGDI_CURSOR_LEFT)),
+ current_icon_color);
+ }
+ up_button->setRelativePosition(core::rect<s32>(0, 0, h, h));
+ up_button->setAlignment(EGUIA_UPPERLEFT, EGUIA_UPPERLEFT, EGUIA_UPPERLEFT,
+ EGUIA_LOWERRIGHT);
+ if (!down_button) {
+ down_button = Environment->addButton(
+ core::rect<s32>(RelativeRect.getWidth() - h, 0,
+ RelativeRect.getWidth(), h),
+ this);
+ down_button->setSubElement(true);
+ down_button->setTabStop(false);
+ }
+ if (sprites) {
+ down_button->setSpriteBank(sprites);
+ down_button->setSprite(EGBS_BUTTON_UP,
+ s32(skin->getIcon(EGDI_CURSOR_RIGHT)),
+ current_icon_color);
+ down_button->setSprite(EGBS_BUTTON_DOWN,
+ s32(skin->getIcon(EGDI_CURSOR_RIGHT)),
+ current_icon_color);
+ }
+ down_button->setRelativePosition(
+ core::rect<s32>(RelativeRect.getWidth() - h, 0,
+ RelativeRect.getWidth(), h));
+ down_button->setAlignment(EGUIA_LOWERRIGHT, EGUIA_LOWERRIGHT,
+ EGUIA_UPPERLEFT, EGUIA_LOWERRIGHT);
+ } else {
+ s32 w = RelativeRect.getWidth();
+ border_size = RelativeRect.getHeight() < w * 4 ? 0 : w;
+ if (!up_button) {
+ up_button = Environment->addButton(
+ core::rect<s32>(0, 0, w, w), this);
+ up_button->setSubElement(true);
+ up_button->setTabStop(false);
+ }
+ if (sprites) {
+ up_button->setSpriteBank(sprites);
+ up_button->setSprite(EGBS_BUTTON_UP,
+ s32(skin->getIcon(EGDI_CURSOR_UP)),
+ current_icon_color);
+ up_button->setSprite(EGBS_BUTTON_DOWN,
+ s32(skin->getIcon(EGDI_CURSOR_UP)),
+ current_icon_color);
+ }
+ up_button->setRelativePosition(core::rect<s32>(0, 0, w, w));
+ up_button->setAlignment(EGUIA_UPPERLEFT, EGUIA_LOWERRIGHT,
+ EGUIA_UPPERLEFT, EGUIA_UPPERLEFT);
+ if (!down_button) {
+ down_button = Environment->addButton(
+ core::rect<s32>(0, RelativeRect.getHeight() - w,
+ w, RelativeRect.getHeight()),
+ this);
+ down_button->setSubElement(true);
+ down_button->setTabStop(false);
+ }
+ if (sprites) {
+ down_button->setSpriteBank(sprites);
+ down_button->setSprite(EGBS_BUTTON_UP,
+ s32(skin->getIcon(EGDI_CURSOR_DOWN)),
+ current_icon_color);
+ down_button->setSprite(EGBS_BUTTON_DOWN,
+ s32(skin->getIcon(EGDI_CURSOR_DOWN)),
+ current_icon_color);
+ }
+ down_button->setRelativePosition(
+ core::rect<s32>(0, RelativeRect.getHeight() - w, w,
+ RelativeRect.getHeight()));
+ down_button->setAlignment(EGUIA_UPPERLEFT, EGUIA_LOWERRIGHT,
+ EGUIA_LOWERRIGHT, EGUIA_LOWERRIGHT);
+ }
+ bool visible = (border_size != 0);
+ up_button->setVisible(visible);
+ down_button->setVisible(visible);
+}
\ No newline at end of file