Kindly hosted by
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; 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)
{ if(MaxWidth < GetWidth() || MaxWidth > GetWidth())
MaxWidth = GetWidth();
} if(m_iListType == 0) { m_bIdentSize = true;
}
else if(m_iListType == 1) {
tRect CurrentRect = GetRect();
CurrentRect.right = CurrentRect.left + MaxWidth;
SetRect(CurrentRect);
m_bIdentSize = true;
}
else if(m_iListType == 2) {
tRect CurrentRect = GetRect();
CurrentRect.right = CurrentRect.left + MaxWidth;
SetRect(CurrentRect);
m_bIdentSize = false;
}
} tRect PreviousRect = GetRect();
for (UINT j=0; j<GetChildCount(); j++)
{
CGUIElement *pElement = GetChild(j);
if(pElement->GetType() == GUI_Button)
{ 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)
{ 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(m_bExpanded) Collapse();
else
Expand();
}
break;
}
case GUI_Button:
{ if(!IsChild(Message.pSender))
break;
UINT Count = 0;
for (UINT i=0; i<GetChildCount(); i++)
{
CGUIElement *pElement = GetChild(i);
if(pElement->GetType() == GUI_Button) {
if(Message.pSender == pElement)
{
SetCurSel(Count); 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!
|