Home
Code & Snippets
Tutorials
GUI Docs
Projects
Drawings & Sketches
Links
Bio & Resume

Kindly hosted by


Support This Project


GUI Tutorial : Part #8

Table of Contents

Welcome to the eighth (that way?)tutorial in GUI series. This time we are going to take a look at scroll bar control.
As you might understand, the scroll bar is very similar in general concept to the progress bar, so you will find many
similarities in this and previous tutorial.
So, let's start.

ScrollBar Control class

A simple scroll bar consists of one to three sub-elements. They are drag button (the one user drags to change value) and two buttons on both ends,
which user clicks on to decrement/increment scroll bar value. The last two are optional because the user can control the value with just
a drag button. So, here how our usual scroll bar looks like:

So, let's see how the class looks like:

class CGUIScrollBar : public CGUIStatic
{
public:
		
	virtual int Parse(TiXmlNode *this_node, char *filename = NULL);

	virtual int OnSize(tRect newRect);
		
	virtual void OnDraw();
	virtual void ProcessMessages();

	virtual int OnUpdate(float curpos);

	int GetCurPos();
	void SetCurPos(int x);

	void SetRange(int min, int max);

	CGUIScrollBar();
	virtual ~CGUIScrollBar();

private:

	int m_iCurPos, m_iPrevPos;	// Current position value of a scrollbar
	float m_fCurPos;

	float m_fPPV;
	int	m_iMinPos,		// Minimum position counted on scrollbar (e.g top)
		m_iMaxPos,		// Maximum position counted on scrollbar (e.g bottom)

	// THIS SHOULD BE SPECIFIED FOR EACH BUTTON!
	m_iBtnInc; // Increments that much when clicked on button (either top - bottom, or left - right)
};

A very simple class. Parsing, Drawing and Updating the current element sizes/retangles depending on value of the scrollbar - that's all we need.

Interesting Functions

The most interesting functions in this class are the OnSize(), ProcessMessages() and OnUpdate() ones, although the last one is very similar to the
ScrollBar one.

int CGUIScrollBar::OnSize(tRect newRect)
{
	int ret = CGUIStatic::OnSize(newRect);

	CGUIElement *btn_drag = FindChild(Button, SCROLLBAR_BTN_DRAG);
	if(GetWidth() > GetHeight())
	{
		SetType(HorizontalScrollBar);
		btn_drag->m_RestrictionFlags.m_iFlag2 = Default;
		btn_drag->m_RestrictionFlags.m_iFlag3 = RestrictMoveY;
	}
	else 
	{
		SetType(VerticalScrollBar);
		btn_drag->m_RestrictionFlags.m_iFlag2 = RestrictMoveX;
		btn_drag->m_RestrictionFlags.m_iFlag3 = Default;
	}

	CGUIElement *btn_inc = FindChild(Button, SCROLLBAR_BTN_INC);
	CGUIElement *btn_dec = FindChild(Button, SCROLLBAR_BTN_DEC);
	
	int inc_size = 0, dec_size = 0;

	// Set increment/decrement buttons rectangles (if any)
	if(GetType() == HorizontalScrollBar)
	{
		tRect dec_rect, inc_rect;

		if(btn_dec !=NULL)
		{
			dec_rect.m_iLeft = GetRect().m_iLeft;
			dec_rect.m_iRight = GetRect().m_iLeft + GetHeight();
			dec_rect.m_iTop = GetRect().m_iTop;
			dec_rect.m_iBottom = GetRect().m_iBottom;

			btn_dec->SetRect(dec_rect);
		}

		if(btn_inc !=NULL)
		{
			inc_rect.m_iRight = GetRect().m_iRight;
			inc_rect.m_iLeft = GetRect().m_iRight - GetHeight();
			inc_rect.m_iTop = GetRect().m_iTop;
			inc_rect.m_iBottom = GetRect().m_iBottom;
			
			btn_inc->SetRect(inc_rect);
		}
	}
	else if(GetType() == VerticalScrollBar)
	{
		tRect dec_rect, inc_rect;

		if(btn_dec !=NULL)
		{
			dec_rect.m_iLeft = GetRect().m_iLeft;
			dec_rect.m_iRight = GetRect().m_iRight;
			dec_rect.m_iTop = GetRect().m_iTop;
			dec_rect.m_iBottom = GetRect().m_iTop - GetWidth();

			btn_dec->SetRect(dec_rect);
		}

		if(btn_inc !=NULL)
		{
			inc_rect.m_iLeft = GetRect().m_iLeft;
			inc_rect.m_iRight = GetRect().m_iRight;
			inc_rect.m_iBottom = GetRect().m_iBottom;
			inc_rect.m_iTop = inc_rect.m_iBottom + GetWidth();

			btn_inc->SetRect(inc_rect);
		}
	}

        // Compute drag button rectangle
	OnUpdate();

	return ret;
}

So, what are we doing here? Firstly, we check if there are such buttons have been loaded (increment/decrement ones), and they were, we set their
rectangles accordingly (in our case decrement is on left and increment is on right in horizontal). After that we call OnUpdate() function so that it
can assign rectangle for the drag button, based on current value, spacing, and scroll bar attributes (width & height).
Moving to the ProcessMessages(), we have the following code:
Well, now onto the OnSize() function:

void CGUIScrollBar::ProcessMessages()
{
	tMessage *tmp = GetEventHandler()->GetNextMsg(NULL);

	while(tmp !=NULL)
	{
		switch(tmp->m_eMsg)
		{
		
			case MoveX:
			case MoveY:
			case MoveXY:
			{
				if(tmp->m_pSender->GetID() == SCROLLBAR_BTN_DRAG)
				{
					float value_per_pixel = 1.0f / m_fPPV;
					int displacement = 0;
					if(GetType() == HorizontalScrollBar)
						displacement = CGUIUtility::GetSingleton().GetMousePos().x -
CGUIUtility::GetSingleton().GetPrevMousePos().x; else if(GetType() == VerticalScrollBar) displacement = -CGUIUtility::GetSingleton().GetMousePos().y -
CGUIUtility::GetSingleton().GetPrevMousePos().y; float val_change = displacement * value_per_pixel; if(m_fCurPos + val_change <= m_iMinPos) m_fCurPos = m_iMinPos; else if(m_fCurPos + val_change >= m_iMaxPos) m_fCurPos = m_iMaxPos; else m_fCurPos+=val_change; OnUpdate(m_fCurPos); GetEventHandler()->RemoveMessage(tmp); } else if(tmp->m_pSender == this) OnSize(GetRect()); break; } case ButtonPressed: { if(tmp->m_pSender->GetID() == SCROLLBAR_BTN_INC) { OnUpdate(m_fCurPos + m_iBtnInc); GetEventHandler()->RemoveMessage(tmp); } else if(tmp->m_pSender->GetID() == SCROLLBAR_BTN_DEC) { OnUpdate(m_fCurPos - m_iBtnInc); GetEventHandler()->RemoveMessage(tmp); } } } tmp = GetEventHandler()->GetNextMsg(tmp); } }

It's not so hard to understand what we are going to do here: we are going to catch messages from child buttons
and process them in the way that we need. So, if a decrement button has sent a ButtonPressed message, we are
going to decrement the value and call OnUpdate() function. On the other hand, if increment button was pressed, we
are going to increment value and again call OnUpdate(). But if a user dragged (messages Move(X/Y/XY)) the drag button,
we are going to calculate the amount she/he dragged it, calculate Pixel Per Value amount, and again call OnUpdate(). Pretty
straightforward.
And the last function we are going to look up is the OnUpdate() one. Here it is:

int CGUIScrollBar::OnUpdate(float curpos)
{
	CGUIElement *btn_inc = FindChild(Button, SCROLLBAR_BTN_INC);
	CGUIElement *btn_dec = FindChild(Button, SCROLLBAR_BTN_DEC);
	CGUIElement *btn_drag = FindChild(Button, SCROLLBAR_BTN_DRAG);

	if(curpos > m_iMaxPos)
	{
		m_fCurPos = m_iMaxPos;
		m_iCurPos = (int)m_iMaxPos;
	}
	else if(curpos < m_iMinPos)
	{
		m_fCurPos = m_iMinPos;
		m_iCurPos = (int)m_iMinPos;
	}
	if(curpos > m_iMinPos && curpos < m_iMaxPos)
	{
		m_fCurPos = curpos;
		m_iCurPos = (int)curpos;
	}

	tRect r;
		
	// Set drag button rectangle
	if(GetType() == HorizontalScrollBar)
	{
		int add_width = 0;

		if(btn_dec !=NULL)
			add_width+=GetHeight();

		if(btn_inc !=NULL)
			add_width+=GetHeight();

		int ttl_width = GetWidth() - add_width - GetHeight();
		m_fPPV = (float)ttl_width / ((float)m_iMaxPos - (float)m_iMinPos);

		int scrl_width = GetHeight();

		tRect r;
		
		int left_extend = GetRect().m_iLeft;
		if(btn_dec !=NULL)
			left_extend = btn_dec->GetRect().m_iRight;

		r.m_iLeft = left_extend + m_fCurPos * m_fPPV;
		r.m_iRight = left_extend + scrl_width + m_fCurPos * m_fPPV;
		r.m_iTop = GetRect().m_iTop;
		r.m_iBottom = GetRect().m_iBottom;

		btn_drag->SetRect(r);

	}
	if(GetType() == VerticalScrollBar)
	{
		int add_height = 0;

		if(btn_dec !=NULL)
			add_height+=GetWidth();

		if(btn_inc !=NULL)
			add_height+=GetWidth();

		int ttl_height = GetHeight() - add_height - GetWidth();
		m_fPPV = (float)ttl_height / ((float)m_iMaxPos - (float)m_iMinPos);

		int scrl_height = GetWidth();
	
		tRect r;
		
		int top_extend = GetRect().m_iTop;
		if(btn_dec !=NULL)
			top_extend = btn_dec->GetRect().m_iBottom;

		r.m_iTop = top_extend - m_fCurPos * m_fPPV;
		r.m_iBottom = top_extend - scrl_height - m_fCurPos * m_fPPV;
		r.m_iLeft = GetRect().m_iLeft;
		r.m_iRight = GetRect().m_iRight;
		
		btn_drag->SetRect(r);
	}

	BroadcastMessage(this, Scrolled, m_fCurPos, m_iPrevPos, 0, GetParent());
	return 1;
}

What do we do here? Very simple. Firstly we make sure that our value complies with our boundaries (min/max),
and after that we calculate Pixel Per Value value and set our drag button rectangle.

ScrollBar data description (XML)

Now let's take a look at the proper way to store information about scroll bar.
I've chosen XML format (see my explanation why I did so):

<ScrollBar Type="Horizontal" Rect="200,400,420,400" Min="0" Max="100" Cur="20" 
BorderFile
="Defaults/Border/default_border.xml" Color="0.5,0.5,0.5" Font="bitmap_font.xml" HAlign="Center"> <Button Filename="Defaults/Button/other_default_button.xml" Name="DragBtn"/>
<
Button Filename="Defaults/Button/other_default_button.xml" Name="DecBtn"/> <Button Filename="Defaults/Button/other_default_button.xml" Name="IncBtn"/> </ScrollBar>

As you may see, it's pretty easy to understand. We store scroll bar rectangle, Min/MAx/Cur values as with progress bar, Border filename and a type.
The children are the drag (must be first as it's not optional!), decrement/increment(optional) buttons.

Adding messages to CGUIElement

Well, now we can display our progress. Now it's time to handle some advanced things. Here is the example - if I would like to move my progress bar,
which way should I go? Of course, I can disable movement of child elements (Positive and Negative) and move only the parent. But that would require change of OnLMouseDown() and some other functions. But I'd rather leave a small space between positive/negative element and parent, so it can be visible (thus the user can move it by clicking on that visible part). So, let us add messaging notifications to OnMove() function, OK?:

int CGUIElement::OnMove(int x, int y)
{
	if(GetType() == GUI)
		return 0;
	
	int mx = Default, my = Default;
	if(m_bVisible)
	{	
		// Resize this element according to its restriction flags
		if(m_RestrictionFlags.m_iFlag2 !=RestrictMoveX && x !=0)
		{
			m_Rect.m_iLeft+=x;
			m_Rect.m_iRight+=x;
			mx = MoveX;
		}

		if(m_RestrictionFlags.m_iFlag3 !=RestrictMoveY && y !=0)
		{
			m_Rect.m_iTop+=y;
			m_Rect.m_iBottom+=y;
			my = MoveY;
		}
	}

	CGUIElement *border = FindChild(Border);
	if(border !=NULL)
		border->OnSize(m_Rect);

	if(mx != Default && my != Default)
	{
		BroadcastMessage(this, MoveXY, x, y, 0, NULL);	// Send message to children
			return MoveXY;
	}
	else if(mx == MoveX && my == Default)
	{
		BroadcastMessage(this, MoveX, x, y, 0, NULL);	// Send message to children
		return MoveX;
	}
	else if(my == MoveY && mx == Default)
	{
		BroadcastMessage(this, MoveY, x, y, 0, NULL);	// Send message to children
		return MoveY;
	}

	return 0;
}
}

That's it! We've done it! Now we can have a decent scroll bar for very little cost (work?).


Next tutorial we will go into DropList control. Cyas there and be ready for it!