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

Kindly hosted by


Support This Project

 

GUI Tutorial : Part #6



Table of Contents


Introduction

Welcome to the sixth tutorial in GUI series. I've updated them to a higher standard (hence, more words?) so that
you enjoy them more and understand a little deeper of what goes behind a custom GUI.
Please don't stop reading it just because it's an OpenGL GUI, because it only needs OpenGL to render widgets and
create textures/materials, but the rest of the code doesn't depend on OpenGL.
This time we will look into details of how RadioGroup control operates and what it looks like.

DropList Control Class

A DropList control is a pretty complicated widget (or I made it complicated?). Basically, it's a button
that have a number of children that become visible when DropList-button is being clicked. Of course, you can
have a droplist control like in Windows where you click on down arrow and it expands - to do this, I give
you a hint, you just get a first button and react to it's message (GUI_Message_ButtonPressed) instead of reacting
on the actual control. So, the main pain (cool!) in the ass for us becomes the adjusting/resizing function that you see below:

int CGUIDropList::Resize(tRect& NewRect)
{
	UINT MaxWidth = 0;

	// Find longest text entry size
	for (UINT i=0; i<GetChildCount(); i++)
	{
		CGUIElement *pElement = GetChild(i);

		if(pElement->GetType() == GUI_Button)
		{
			if(((CGUIButton *)pElement)->GetText() !="")
			{
				UINT TotalWidth = 0;
				for (UINT j=0; j<((CGUIButton *)pElement)->GetLineCount(); j++)
				{
					CBitmapFont *pBitmapFont = (CBitmapFont *)((CGUIButton *)
pElement)->GetFont(); if(pBitmapFont !=NULL) { UINT FontLetterWidth = pBitmapFont->GetQuadLength() +
pBitmapFont->GetSpacing(); TotalWidth = ((CGUIButton *)pElement)->GetLineLength(j) *
FontLetterWidth; if(MaxWidth <= TotalWidth) MaxWidth = TotalWidth; } } } } if(m_iListType !=1) { // Get absolute maximum width if(MaxWidth < GetWidth() || MaxWidth > GetWidth()) MaxWidth = GetWidth(); } // Figure out sub-parameters if(m_iListType == 0) // All entries are of the same width! { //LimitWidth(true); m_bIdentSize = true; } else if(m_iListType == 1) // A droplist width becomes the width of the 'lengthiest' button // and all entries are of the same width! { tRect CurrentRect = GetRect(); CurrentRect.right = CurrentRect.left + MaxWidth; SetRect(CurrentRect); m_bIdentSize = true; } else if(m_iListType == 2) // A droplist width becomes the width of the 'widthest' button // but all entries stay with their own width! { tRect CurrentRect = GetRect(); CurrentRect.right = CurrentRect.left + MaxWidth; SetRect(CurrentRect); m_bIdentSize = false; } } // OK, we got max length, assign each menu its rectangle tRect PreviousRect = GetRect(); for (UINT j=0; j<GetChildCount(); j++) { CGUIElement *pElement = GetChild(j); if(pElement->GetType() == GUI_Button) { // We store previous rect so that next button will know its rectangle coords UINT YCount = 0; UINT CurrentWidth = MaxWidth; if(!m_bIdentSize) { UINT Width = 0; for (UINT i=0; i<((CGUIButton *)pElement)->GetLineCount(); i++) { CBitmapFont *pBitmapFont = (CBitmapFont *)((CGUIButton *)
pElement)->GetFont(); if(pBitmapFont !=NULL) { UINT FontLetterWidth = pBitmapFont->GetQuadLength() +
pBitmapFont->GetSpacing(); Width = ((CGUIButton *)pElement)->
GetLineLength(i) * FontLetterWidth; if(CurrentWidth < Width) CurrentWidth = Width; } } CurrentWidth = Width; } CBitmapFont *pBitmapFont = (CBitmapFont *)((CGUIButton *)pElement)->GetFont(); tRect NewElementRect; NewElementRect.left = PreviousRect.left; NewElementRect.right = PreviousRect.left + CurrentWidth; NewElementRect.top = PreviousRect.bottom - m_iSpacing; if(pBitmapFont == NULL) NewElementRect.bottom = NewElementRect.top - 20 - 1; else NewElementRect.bottom = NewElementRect.top -
pBitmapFont->GetQuadHeight() - 1; pElement->SetRect(NewElementRect); PreviousRect = NewElementRect; YCount++; } } return CGUIElement::Resize(NewRect); }

As you see, firstly we check for the lengthiest entry in the droplist and we find it's length in pixels.
Then we adjust child buttons by using the list type variable. If that variable has a value of 0, then all the entries
become of the same width as the drop list. If that variable has a value of 1, then drop list width is set to the
lengthiest entry in it BUT all entries stay with their original size. And lastly, if the variable is of value of 2, then
all the entries and the drop list control adjust their width to the lengthiest entry. Pretty easy to say, but hard to 
implement. As you understand from the code, it goes through various acquisitions of data to adjust the control/children.

If it's hard for you to understand of what's happening inside the above-written function, go slowly over it again
and again and try to understand the steps I've taken in the function that are requred to achieve final result.

Anyway, moving on, we come to the ProcessMessage function, and assitant procedure that we use to
figure out if we need to expand or collapse our drop list. Let's have a look:

void CGUIDropList::ProcessMessage(tGUIMessage& Message)
{
	switch(Message.uiMessage)
	{
		// On Movement/resize we re-calculate our buttons rectangles!
	case GUI_Message_MoveX:
	case GUI_Message_MoveY:
	case GUI_Message_MoveXY:

		if(Message.pSender == this)
			Resize(GetRect());
		break;

	case GUI_Message_ButtonPressed:
		{
			switch(Message.pSender->GetType())
			{
			case GUI_DropList:
				{
					if(Message.pSender == this) // If the button was this droplist
					{
						if(m_bExpanded)	// Do its stuff
							Collapse();
						else
							Expand();
					}
					break;
				}

			case GUI_Button:
				{
					// If the button was the child of this droplist
					if(!IsChild(Message.pSender))
						break;

					UINT Count = 0;
					for (UINT i=0; i<GetChildCount(); i++)
					{
						CGUIElement *pElement = GetChild(i);

						if(pElement->GetType() == GUI_Button)	
// Find out which button has sent it (by count)
{ if(Message.pSender == pElement) { SetCurSel(Count);
// And set the selection count index
Collapse(); } Count++; } } break; } } } } }

As it's easy to see, we look for GUI_Message_ButtonPressed, then we determine if the sender was one of the
children of the drop list, and if it was, then we make it the current selection and collapse the list. If the sender WAS
the drop list, then we either collapse or expand the list (depending on if it's already has been expanded/collapsed).

Remember, there's no need to resize the drop list and it's children each time we draw it since it's a pretty costly
operation, instead do it where it's supposed to be done - in Resize function. You catch messages you need in
the ProcessMessage, then use them to do what you have to.

Well, that's it for the drop list. It's really easy to understand the logic for it (expand - collapse), but it's much harder
to write a decent, expandable and easy to understand drop list class that can be reused by derived classes.

Next tutorial we will be creating a listbox control, which is, I have to say, really easy to make..

EXTRA NOTES
Binary: To run these binaries, you require FreeImage library runtime files - DLL and LIB in the application directory!
Source: To debug the application using the source code, you need a DLL and the FreeImageD. lib files in the application directory!

Download FreeImage(RELEASE)

Download FreeImage(DEBUG)