scrollbar with auto-sizing slider

Post those lines of code you feel like sharing or find what you require for your project here; or simply use them as tutorials.
Post Reply
Rusty Rooster
Posts: 11
Joined: Thu May 24, 2018 6:43 pm
Location: USA

scrollbar with auto-sizing slider

Post by Rusty Rooster »

Hi guys,
I modified CGUIScrollbar to resize the slider to account for the length of the scrolled form. So if the form is twice as long as the scrollbar, the slider will take up half the length of the scrollbar and so on. Note that I assume Min=0 in all cases and that Max represents the length of the entire scrolled form, and that these lengths are in pixels not arbitrary units.

Scrollbar2.h:

Code: Select all

#include <irrlicht.h>

using namespace irr;
using namespace gui;

	class CGUIScrollBar2 : public IGUIScrollBar
	{
	public:

		//! constructor
		CGUIScrollBar2(bool horizontal, IGUIEnvironment* environment,
			IGUIElement* parent, s32 id, core::rect<s32> rectangle,
			bool noclip=false);

		//! destructor
		virtual ~CGUIScrollBar2();

		//! called if an event happened.
		virtual bool OnEvent(const SEvent& event);

		//! draws the element and its children
		virtual void draw();

		//! gets the maximum value of the scrollbar.
		virtual s32 getMax() const;

		//! sets the maximum value of the scrollbar.
		virtual void setMax(s32 max);

		//! gets the minimum value of the scrollbar.
		virtual s32 getMin() const;

		//! sets the minimum value of the scrollbar.
		virtual void setMin(s32 min);

		//! gets the small step value
		virtual s32 getSmallStep() const;

		//! sets the small step value
		virtual void setSmallStep(s32 step);

		//! gets the large step value
		virtual s32 getLargeStep() const;

		//! sets the large step value
		virtual void setLargeStep(s32 step);

		//! gets the current position of the scrollbar
		virtual s32 getPos() const;

		//! sets the position of the scrollbar
		virtual void setPos(s32 pos);

		//! updates the rectangle
		virtual void updateAbsolutePosition();

		//! Writes attributes of the element.
		virtual void serializeAttributes(io::IAttributes* out, io::SAttributeReadWriteOptions* options) const;

		//! Reads attributes of the element
		virtual void deserializeAttributes(io::IAttributes* in, io::SAttributeReadWriteOptions* options);

	private:

		void refreshControls();
		s32 getPosFromMousePos(const core::position2di &p) const;

		IGUIButton* UpButton;
		IGUIButton* DownButton;

		core::rect<s32> SliderRect;

		bool Horizontal;
		bool DraggedBySlider;

		s32 Pos;
		s32 DrawPos;
		s32 DrawHeight;
		s32 Min;
		s32 Max;
		s32 SmallStep;
		s32 LargeStep;
		s32 DesiredPos;
		u32 LastChange;
		s32 DragOffset;

		video::SColor CurrentIconColor;

		f32 range () const { return (f32) ( Max - Min ); }
	};
Scrollbar2.cpp:

Code: Select all

// Copyright (C) 2002-2012 Nikolaus Gebhardt
// This file is part of the "Irrlicht Engine".
// For conditions of distribution and use, see copyright notice in irrlicht.h

#include "scrollbar2.h"
#include <irrlicht.h>
#include "os.h"
#include "CGUIButton.h"
#include <iostream>

using namespace irr;
using namespace gui;

//! constructor
CGUIScrollBar2::CGUIScrollBar2(bool horizontal, IGUIEnvironment* environment,
				IGUIElement* parent, s32 id,
				core::rect<s32> rectangle, bool noclip)
	: IGUIScrollBar(environment, parent, id, rectangle), UpButton(0),
	DownButton(0), Horizontal(horizontal),
	DraggedBySlider(false), Pos(0), DrawPos(0),
	DrawHeight(0), Min(0), Max(100), SmallStep(24), LargeStep(96), DesiredPos(0),
	LastChange(0)
{
	#ifdef _DEBUG
	setDebugName("CGUIScrollBar2");
	#endif

	refreshControls();

	setNotClipped(noclip);

	// this element can be tabbed to
	setTabStop(true);
	setTabOrder(-1);

	if(horizontal)
        DrawHeight = RelativeRect.getHeight();
    else
        DrawHeight = RelativeRect.getWidth();

	setPos(0);
}

//! destructor
CGUIScrollBar2::~CGUIScrollBar2()
{
	//if (UpButton)
	//	UpButton->drop();

	//if (DownButton)
	//	DownButton->drop();
}

//! called if an event happened.
bool CGUIScrollBar2::OnEvent(const SEvent& event)
{
	if (isEnabled())
	{

		switch(event.EventType)
		{
		case EET_KEY_INPUT_EVENT:
			if (event.KeyInput.PressedDown)
			{
				const s32 oldPos = getPos();
				bool absorb = true;
				switch (event.KeyInput.Key)
				{
				case KEY_LEFT:
				case KEY_UP:
					setPos(getPos()-SmallStep);
					break;
				case KEY_RIGHT:
				case KEY_DOWN:
					setPos(getPos() +SmallStep);
					break;
				case KEY_HOME:
					setPos(Min);
					break;
				case KEY_PRIOR:
					setPos(getPos() -LargeStep);
					break;
				case KEY_END:
					setPos(Max);
					break;
				case KEY_NEXT:
					setPos(getPos() +LargeStep);
					break;
				default:
					absorb = false;
				}

				if (Pos != oldPos)
				{
					SEvent newEvent;
					newEvent.EventType = EET_GUI_EVENT;
					newEvent.GUIEvent.Caller = this;
					newEvent.GUIEvent.Element = 0;
					newEvent.GUIEvent.EventType = EGET_SCROLL_BAR_CHANGED;
					Parent->OnEvent(newEvent);
				}
				if (absorb)
					return true;
			}
			break;
		case EET_GUI_EVENT:
			if (event.GUIEvent.EventType == EGET_BUTTON_CLICKED)
			{
				if (event.GUIEvent.Caller == UpButton)
					setPos(getPos() - SmallStep);
				else
				if (event.GUIEvent.Caller == DownButton)
					setPos(getPos() + SmallStep);

				SEvent newEvent;
				newEvent.EventType = EET_GUI_EVENT;
				newEvent.GUIEvent.Caller = this;
				newEvent.GUIEvent.Element = 0;
				newEvent.GUIEvent.EventType = EGET_SCROLL_BAR_CHANGED;
				Parent->OnEvent(newEvent);

				return true;
			}
			else
			if (event.GUIEvent.EventType == EGET_ELEMENT_FOCUS_LOST)
			{
				if (event.GUIEvent.Caller == this)
					DraggedBySlider = false;
			}
			break;
		case EET_MOUSE_INPUT_EVENT:
		{
			const core::position2di p(event.MouseInput.X, event.MouseInput.Y);
			bool isInside = isPointInside ( p );
			switch(event.MouseInput.Event)
			{
			case EMIE_MOUSE_WHEEL:
			{
				setPos(	getPos() +
						( (event.MouseInput.Wheel < 0 ? -1 : 1) * SmallStep * (Horizontal ? 1 : -1 ) )
						);

				SEvent newEvent;
				newEvent.EventType = EET_GUI_EVENT;
				newEvent.GUIEvent.Caller = this;
				newEvent.GUIEvent.Element = 0;
				newEvent.GUIEvent.EventType = EGET_SCROLL_BAR_CHANGED;
				Parent->OnEvent(newEvent);
				return true;
				break;
			}
			case EMIE_LMOUSE_PRESSED_DOWN:
			{
				if (isInside)
				{
					DraggedBySlider = SliderRect.isPointInside(p);

					DragOffset = getPosFromMousePos(p) - getPos();

					DesiredPos = getPosFromMousePos(p) - DragOffset;

					Environment->setFocus ( this );
					return true;
				}
				break;
			}
			case EMIE_LMOUSE_LEFT_UP:
			{
				if (!DraggedBySlider)
				{
					int oldPos = getPos();
					int pos = getPosFromMousePos(p);

					if (pos < getPos())
						setPos(getPos() - LargeStep);
					else
						setPos(getPos() + LargeStep);

					if (getPos() != oldPos && Parent)
					{
						SEvent newEvent;
						newEvent.EventType = EET_GUI_EVENT;
						newEvent.GUIEvent.Caller = this;
						newEvent.GUIEvent.Element = 0;
						newEvent.GUIEvent.EventType = EGET_SCROLL_BAR_CHANGED;
						Parent->OnEvent(newEvent);
					}
				}
			}break;
			case EMIE_MOUSE_MOVED:
			{
				
				if (!event.MouseInput.isLeftPressed())
				{
					DraggedBySlider = false;
				}

				if (!DraggedBySlider)
					return isInside;

				const s32 newPos = getPosFromMousePos(p) - DragOffset;
				const s32 oldPos = getPos();

				if (DraggedBySlider)
				{
					setPos(newPos);
				}

				if (getPos() != oldPos && Parent)
				{
					SEvent newEvent;
					newEvent.EventType = EET_GUI_EVENT;
					newEvent.GUIEvent.Caller = this;
					newEvent.GUIEvent.Element = 0;
					newEvent.GUIEvent.EventType = EGET_SCROLL_BAR_CHANGED;
					Parent->OnEvent(newEvent);
				}
				return isInside;
			} break;

			default:
				break;
			}
		} break;
		default:
			break;
		}
	}

	return IGUIElement::OnEvent(event);
}

//! draws the element and its children
void CGUIScrollBar2::draw()
{
	if (!IsVisible)
		return;

	IGUISkin* skin = Environment->getSkin();
	if (!skin)
		return;


	video::SColor iconColor = skin->getColor(isEnabled() ? EGDC_WINDOW_SYMBOL : EGDC_GRAY_WINDOW_SYMBOL);
	if ( iconColor != CurrentIconColor )
	{
		refreshControls();
	}

	SliderRect = AbsoluteRect;

	skin->draw2DRectangle(this, skin->getColor(EGDC_SCROLLBAR), SliderRect, &AbsoluteClippingRect);

	if ( core::isnotzero ( range() ) )
	{
		if (Horizontal)
		{
			SliderRect.UpperLeftCorner.X = AbsoluteRect.UpperLeftCorner.X + DrawPos + RelativeRect.getHeight() - DrawHeight/2;
			SliderRect.LowerRightCorner.X = SliderRect.UpperLeftCorner.X + DrawHeight;
		}
		else
		{
		    SliderRect.UpperLeftCorner.Y = AbsoluteRect.UpperLeftCorner.Y + DrawPos + RelativeRect.getWidth() - DrawHeight/2;
			SliderRect.LowerRightCorner.Y = SliderRect.UpperLeftCorner.Y + DrawHeight;
		}

		skin->draw3DButtonPaneStandard(this, SliderRect, &AbsoluteClippingRect);
	}

	IGUIElement::draw();
}


void CGUIScrollBar2::updateAbsolutePosition()
{
	IGUIElement::updateAbsolutePosition();
	// todo: properly resize
	refreshControls();
	setPos ( Pos );
}

//!
s32 CGUIScrollBar2::getPosFromMousePos(const core::position2di &pos) const
{
	f32 p;
	if (Horizontal)
	{
		p = (f32)(pos.X - RelativeRect.getHeight() - AbsoluteRect.UpperLeftCorner.X) / (RelativeRect.getWidth() - (DrawHeight + RelativeRect.getHeight() * 2.0f));
		p *= (Max - RelativeRect.getWidth());
	}
	else
	{
		p = (f32)(pos.Y - RelativeRect.getWidth() - AbsoluteRect.UpperLeftCorner.Y) / (RelativeRect.getHeight() - (DrawHeight + RelativeRect.getWidth() * 2.0f));
		p *= (Max - RelativeRect.getHeight());
	}
	
	return p;
}


//! sets the position of the scrollbar
void CGUIScrollBar2::setPos(s32 pos)
{
	if (Horizontal)
	{
		Pos = (f32)pos * Max / (f32)((Max - RelativeRect.getWidth()));
	}
	else
	{
		Pos = (f32)pos * Max / (f32)((Max - RelativeRect.getHeight()));
	}

	Pos = core::s32_clamp ( Pos, Min, Max );

	if (Horizontal)
	{
		f32 f = ((f32)RelativeRect.getWidth() - ((f32)DrawHeight + RelativeRect.getHeight()*2.0f)) / range();
		DrawPos = (s32)(f * ( Pos - Min ) + ((f32)DrawHeight * 0.5f));
	}
	else
	{
		f32 f = ((f32)RelativeRect.getHeight() - ((f32)DrawHeight + RelativeRect.getWidth()*2.0f)) / range();
		DrawPos = (s32)(f * ( Pos - Min ) + ((f32)DrawHeight * 0.5f));
	}
}


//! gets the small step value
s32 CGUIScrollBar2::getSmallStep() const
{
	return SmallStep;
}


//! sets the small step value
void CGUIScrollBar2::setSmallStep(s32 step)
{
	if (step > 0)
		SmallStep = step;
	else
		SmallStep = 10;
}


//! gets the small step value
s32 CGUIScrollBar2::getLargeStep() const
{
	return LargeStep;
}


//! sets the small step value
void CGUIScrollBar2::setLargeStep(s32 step)
{
	if (step > 0)
		LargeStep = step;
	else
		LargeStep = 50;
}


//! gets the maximum value of the scrollbar.
s32 CGUIScrollBar2::getMax() const
{
	return Max;
}


//! sets the maximum value of the scrollbar.
void CGUIScrollBar2::setMax(s32 max)
{
	Max = max;
	if ( Min > Max )
		Min = Max;

	s32 slider_len;
	f32 lr;
	
	if (Horizontal)
	{
		lr = (f32)RelativeRect.getWidth() / (f32)Max;
		slider_len = (RelativeRect.getWidth() - RelativeRect.getHeight() * 2) * lr;

		DrawHeight = std::max(slider_len, RelativeRect.getHeight());

		setLargeStep(RelativeRect.getWidth());
	}
	else
	{
		lr = (f32)RelativeRect.getHeight() / (f32)Max;
		slider_len = (RelativeRect.getHeight() - RelativeRect.getWidth() * 2) * lr;

		DrawHeight = std::max(slider_len, RelativeRect.getWidth());

		setLargeStep(RelativeRect.getHeight());
	}

	bool enable = core::isnotzero ( range() );
	UpButton->setEnabled(enable);
	DownButton->setEnabled(enable);
	setPos(Pos);
}

//! gets the minimum value of the scrollbar.
s32 CGUIScrollBar2::getMin() const
{
	return Min;
}

//! sets the minimum value of the scrollbar.
void CGUIScrollBar2::setMin(s32 min)
{
	Min = min;
	if ( Max < Min )
		Max = Min;

	bool enable = core::isnotzero ( range() );
	UpButton->setEnabled(enable);
	DownButton->setEnabled(enable);
	setPos(Pos);
}


//! gets the current position of the scrollbar
s32 CGUIScrollBar2::getPos() const
{
	if(Horizontal)
		return (range() - RelativeRect.getWidth()) * (f32)(Pos / range());
	else
		return (range() - RelativeRect.getHeight()) * (f32)(Pos / range());
}


//! refreshes the position and text on child buttons
void CGUIScrollBar2::refreshControls()
{
	CurrentIconColor = video::SColor(255,255,255,255);

	IGUISkin* skin = Environment->getSkin();
	IGUISpriteBank* sprites = 0;

	if (skin)
	{
		sprites = skin->getSpriteBank();
		CurrentIconColor = skin->getColor(isEnabled() ? EGDC_WINDOW_SYMBOL : EGDC_GRAY_WINDOW_SYMBOL);
	}

	if (Horizontal)
	{
		s32 h = RelativeRect.getHeight();
		if (!UpButton)
		{
			//UpButton = new CGUIButton(Environment, this, -1, core::rect<s32>(0,0, h, h), NoClip);
			UpButton = Environment->addButton(core::rect<s32>(0, 0, h, h), this, -1, L"", L"");
			UpButton->setSubElement(true);
			UpButton->setTabStop(false);
		}
		if (sprites)
		{
			UpButton->setSpriteBank(sprites);
			UpButton->setSprite(EGBS_BUTTON_UP, skin->getIcon(EGDI_CURSOR_LEFT), CurrentIconColor);
			UpButton->setSprite(EGBS_BUTTON_DOWN, skin->getIcon(EGDI_CURSOR_LEFT), CurrentIconColor);
		}
		UpButton->setRelativePosition(core::rect<s32>(0,0, h, h));
		UpButton->setAlignment(EGUIA_UPPERLEFT, EGUIA_UPPERLEFT, EGUIA_UPPERLEFT, EGUIA_LOWERRIGHT);
		if (!DownButton)
		{
			//DownButton = new CGUIButton(Environment, this, -1, core::rect<s32>(RelativeRect.getWidth()-h, 0, RelativeRect.getWidth(), h), NoClip);
			DownButton = Environment->addButton(core::rect<s32>(RelativeRect.getWidth() - h, 0, RelativeRect.getWidth(), h), this, -1, L"", L"");
			DownButton->setSubElement(true);
			DownButton->setTabStop(false);
		}
		if (sprites)
		{
			DownButton->setSpriteBank(sprites);
			DownButton->setSprite(EGBS_BUTTON_UP, skin->getIcon(EGDI_CURSOR_RIGHT), CurrentIconColor);
			DownButton->setSprite(EGBS_BUTTON_DOWN, skin->getIcon(EGDI_CURSOR_RIGHT), CurrentIconColor);
		}
		DownButton->setRelativePosition(core::rect<s32>(RelativeRect.getWidth()-h, 0, RelativeRect.getWidth(), h));
		DownButton->setAlignment(EGUIA_LOWERRIGHT, EGUIA_LOWERRIGHT, EGUIA_UPPERLEFT, EGUIA_LOWERRIGHT);
	}
	else
	{
		s32 w = RelativeRect.getWidth();
		if (!UpButton)
		{
			//UpButton = new CGUIButton(Environment, this, -1, core::rect<s32>(0,0, w, w), NoClip);
			UpButton = Environment->addButton(core::rect<s32>(0, 0, w, w), this, -1, L"", L"");
			UpButton->setSubElement(true);
			UpButton->setTabStop(false);
		}
		if (sprites)
		{
			UpButton->setSpriteBank(sprites);
			UpButton->setSprite(EGBS_BUTTON_UP, skin->getIcon(EGDI_CURSOR_UP), CurrentIconColor);
			UpButton->setSprite(EGBS_BUTTON_DOWN, skin->getIcon(EGDI_CURSOR_UP), CurrentIconColor);
		}
		UpButton->setRelativePosition(core::rect<s32>(0,0, w, w));
		UpButton->setAlignment(EGUIA_UPPERLEFT, EGUIA_LOWERRIGHT, EGUIA_UPPERLEFT, EGUIA_UPPERLEFT);
		if (!DownButton)
		{
			//DownButton = new CGUIButton(Environment, this, -1, core::rect<s32>(0,RelativeRect.getHeight()-w, w, RelativeRect.getHeight()), NoClip);
			DownButton = Environment->addButton(core::rect<s32>(0, RelativeRect.getHeight() - w, w, RelativeRect.getHeight()), this, -1, L"", L"");
			DownButton->setSubElement(true);
			DownButton->setTabStop(false);
		}
		if (sprites)
		{
			DownButton->setSpriteBank(sprites);
			DownButton->setSprite(EGBS_BUTTON_UP, skin->getIcon(EGDI_CURSOR_DOWN), CurrentIconColor);
			DownButton->setSprite(EGBS_BUTTON_DOWN, skin->getIcon(EGDI_CURSOR_DOWN), CurrentIconColor);
		}
		DownButton->setRelativePosition(core::rect<s32>(0,RelativeRect.getHeight()-w, w, RelativeRect.getHeight()));
		DownButton->setAlignment(EGUIA_UPPERLEFT, EGUIA_LOWERRIGHT, EGUIA_LOWERRIGHT, EGUIA_LOWERRIGHT);
	}
}
Please note that I have replaced the calls to new CGUIButton(...) with Environment->addButton(...), and therefore I have commented out the calls to drop() in the destructor.
Rusty Rooster
Posts: 11
Joined: Thu May 24, 2018 6:43 pm
Location: USA

Re: scrollbar with auto-sizing slider

Post by Rusty Rooster »

Here's how I use it:

( Note that if I resize the form (getFormsHeight() result is changed, for example we expanded some widgets and made the form longer), the scrollbar slider will be auto-resized and repositioned to keep us looking at the same position in the form. )

Widget_EditArea::show()

Code: Select all

f32 lr = (f32)getEditAreaHeight() / 	// = std::min(getFormsHeight(), getMaxFormsHeight());
		(f32)getFormsHeight();	// = the total length of the form
							// getMaxFormsHeight() = the max length allowed by our edit window, also = length of the scrollbar

    if (lr < 1.0)
    {
        my_scrollbar = new CGUIScrollBar2(false, Environment, edit_panel, scrollbar_ID, core::rect<s32>(0, 0, 16, getEditAreaHeight()));
        my_scrollbar->drop();

        my_scrollbar->setMax(getFormsHeight());
        my_scrollbar->setSmallStep(24);

        int old_offset = scroll_offset;
        
        my_scrollbar->setPos(scroll_offset);

        scroll_offset = 0;
        setEditAreaScrollPos(old_offset);
    }
    else
    {
        my_scrollbar = NULL;
    }
Widget_EditArea::OnEvent(const SEvent& event)

Code: Select all

case EGET_SCROLL_BAR_CHANGED:
            {
                if (id == scrollbar_ID)
                {
                    setEditAreaScrollPos(my_scrollbar->getPos());
                    return true;
                }
            } break;

Code: Select all

void Widget_EditArea::setEditAreaScrollPos(int pos)
{
    core::list<gui::IGUIElement*> child_list = edit_panel->getChildren();
    core::list<gui::IGUIElement*>::Iterator it = child_list.begin();

    int move_dist = scroll_offset - pos;
    scroll_offset -= move_dist;

    for (; it != child_list.end(); ++it)
    {
        if (*it != my_scrollbar)
        {
            (*it)->move(core::vector2di(0, (move_dist)));
        }
    }
}
Post Reply