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

Kindly hosted by


Support This Project


GUI Tutorial : Part #9

Table of Contents

Welcome to the ninth tutorial in GUI series. This time we are going to take a look at DropList control.
So, let's start.

DropList Control class

A droplist is simply a button (the control that can accept clicks and broadcast them) that have children buttons that can be selected
after they're visible. So, when you see just a droplist control (no children), those children are hidden (e.g not drawn). But when we press
on the control, it calculates it's children rectangles and shows them all for the user to select! Simple as everything best.
So, here how it looks like:

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

class CGUIDropList : public CGUIButton
{
public:
    	// Must call OnCreate before using loading procedure
	virtual int Parse(TiXmlNode *this_node, char *filename = NULL);

	virtual void OnDraw();
	virtual void Expand();
	virtual void Collapse();

	virtual void OnPrepare();

	virtual void ProcessMessages();
	void SetCurSel(int sel);

	__inline int GetCurSel()
	{ return m_iCurSel; };

	CGUIDropList();
	virtual ~CGUIDropList();

private:
	int m_iSpacing, m_iListType;
	bool m_bExpanded, m_bIdentSize;
	int m_iCurSel;
	
};

A simple class as well, since we don't need anything fancy, just the simplest working thing other people can dance off from.
What it has? Well, it has the OnPrepare() function we haven't seen anywhere before (can be OnUpdate() as well),
the droplist type (whether all buttons must be of same width as their parent or not - depends on text length) and some
additional variables for it.

DropList data description (XML)

Well, not leaving too much for you to guess, here's my XML data description of the DropList format:

<DropList EqualSized="1" Text="Please Select..." CurSel="-1" Rect="100,400,400,380" 
BorderFile="Defaults/Border/default_border.xml"  Font="bitmap_font.xml">
<Button Filename="Defaults/DropList/default_droplist_button.xml" Text="Droplist Line #1" 
RestrictMoveX="1" RestrictMoveY="1"/>
<Button Filename="Defaults/DropList/default_droplist_button.xml" Text="Droplist Line #2" 
RestrictMoveX="1" RestrictMoveY="1"/>
<Button Filename="Defaults/DropList/default_droplist_button.xml" Text="Droplist Line #3" 
RestrictMoveX="1" RestrictMoveY="1"/>
<Button Filename="Defaults/DropList/default_droplist_button.xml" Text="Droplist Line #4" 
RestrictMoveX="1" RestrictMoveY="1"/>
<Button Filename="Defaults/DropList/default_droplist_button.xml" Text="Droplist Line #5" 
RestrictMoveX="1" RestrictMoveY="1"/>
</DropList>

As you may see, it uses custom button, but if the need arises, it can be any button you want. Each
button is assigned the restriction flags, so the user won't be able to reposition them when they are
visible. Why? Simply because he/she is not supposed to do that (yet?). What's the point of moving them
of after next expansion/collapse they will be on the same old positions as before?

Interesting Functions

The most interesting functions in this class are the OnPrepare() (As you correctly guessed, because it's new) and the
ProcessMessages(). So here it is:

void CGUIDropList::OnPrepare()
{
	int max_width = 0;
	
	// Find longest text entry size
	for (int i=0; i<GetChildCount(); i++)
	{
		CGUIElement *tmp = GetChild(i);
		
		if(tmp->GetType() == Button)
		{
			if(((CGUIButton *)tmp)->GetText() !=NULL && strlen(((CGUIButton *)tmp)->GetText()) !=0)
			{
				int ttl_width = 0;
				for (int i=0; i<strlen(((CGUIButton *)tmp)->GetText()) + 1; i++)
					ttl_width = ttl_width + ((CGUIButton *)tmp)-
>
GetFont()->m_iQuadLengthX + ((CGUIButton *)tmp)->GetFont()->m_iSpaceChar; if(max_width <= ttl_width) max_width = ttl_width; } } } if(max_width < GetWidth() || max_width > GetWidth()) max_width = GetWidth(); if(m_iListType == 0) { //LimitWidth(true); m_bIdentSize = true; } else if(m_iListType == 1) { tRect r = GetRect(); r.m_iRight = r.m_iLeft + max_width; SetRect(r); m_bIdentSize = true; } else if(m_iListType == 2) { tRect r = GetRect(); r.m_iRight = r.m_iLeft + max_width; SetRect(r); m_bIdentSize = false; } // OK, we got max length, assign each menu its rectangle for (int j=0; j<GetChildCount(); j++) { CGUIElement *tmp = GetChild(j); if(tmp->GetType() == Button) { int ycount = 0; tRect prev_rect; if(j == 0) prev_rect = GetRect(); else prev_rect = GetChild(j-1)->GetRect(); int cur_width = max_width; if(!m_bIdentSize) { int width = 0; for (int i=0; i<strlen(((CGUIButton *)tmp)->GetText()); i++) width = width + ((CGUIButton *)tmp)->GetFont()->m_iQuadLengthX +
((
CGUIButton *)tmp)->GetFont()->m_iSpaceChar; cur_width = width; } tRect r; r.m_iLeft = prev_rect.m_iLeft; r.m_iRight = prev_rect.m_iLeft + cur_width; r.m_iTop = prev_rect.m_iBottom -m_iSpacing; r.m_iBottom = r.m_iTop - ((CGUIButton *)tmp)->GetFont()->m_iQuadLengthY; tmp->SetRect(r); prev_rect = r; ycount++; } } }

What's happening here, you ask? Well, let's take a look at this function step by step. Firstly, we calculate
the maximum width of the text (in pixels, or OpenGL units) and after that we adjust our main rectangle to it.
After that comes the part where we set up the rectangles for our child buttons. If for example, the list type would be
0, all buttons width would've been of maximum text width and the parent' rectangle would've been of size of its text.
If it would've been 1, then the parent' rectangle would've been maximum width, while all children will be of the width
of their text. And at last if it would've been 2, then all rectangles will be of the same maximum text length.

Now, let's take a look at our ProcessMessages() function:

void CGUIDropList::ProcessMessages()
{
	tMessage *tmp = GetEventHandler()->GetNextMsg(NULL);
	
	while(tmp !=NULL)
	{
		switch(tmp->m_eMsg)
		{
			
			case MoveX:
			case MoveY:
			case MoveXY:
				OnPrepare();
			break;

			case ButtonPressed:
			{
				switch(tmp->m_pSender->GetType())
				{
					case DropList:
					{
						if(tmp->m_pSender == this)
						{
							if(m_bExpanded)
							{
								Collapse();
								GetEventHandler()->RemoveMessage(tmp);
							}
							else
							{
								OnPrepare();
								Expand();
								GetEventHandler()->RemoveMessage(tmp);
							}
						}
						break;
					}

					case Button:
					{
						if(!FindChild(tmp->m_pSender))
							break;

						int count = 0;
						for (int i=0; i<GetChildCount(); i++)
						{
							CGUIElement *btn = GetChild(i);
							
							if(btn->GetType() == Button)
							{
								if(tmp->m_pSender == btn)
								{
									SetCurSel(count);
									Collapse();
								}
							
								count++;
							}
						}
						break;
					}
				}
			}

		}

		tmp = GetEventHandler()->GetNextMsg(tmp);
	}
}

Here we make sure that if droplist is expanded, we re-calculate it's children rectangles each time it moves, hence
OnPrepare() with Move X/Y/XY messages. Also here we process clicks on our droplist and expand/collapse it
depending on it's current status. And at last, we set the text for droplist if one of it's children button has been clicked
to the text of that button. Very simple.

Well, that's about it. Good work everybody!

Next tutorial we will go into ListBox control that is very similar to our DropList control. Cyas there and be ready for it!