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

Kindly hosted by


Support This Project


GUI Tutorial : Part #5

Table of Contents

Welcome to the fifth tutorial in GUI series. After first four tutorials, this is going to be... well, intermediate one.
So, let's start.

Button Class

A button is simply a static or a simple element (whatever you decide to derive it from) that has texture/materials animations assigned to
each mouse state (mouseover, mousedown, none, disabled, etc). This is how it looks like:

/*********** Button Animation structure ************/
struct tButtonAnimation
{
	CAnimatedTexture m_Animation;	// Reference to animation data
	int m_iEventType;				// Triggers this animation on certain event
};

/*********** Button State structure ************/
struct tButtonState
{
	CDoubleList <tButtonAnimation *> m_lstEvents;	// List of events for this state

	tButtonState() {};

	virtual ~tButtonState()
	{	m_lstEvents.erase(); };
};

class CGUIButton : public CGUIStatic
{
public:
	virtual int Parse(TiXmlNode *node, char *filename);
	
	virtual int OnLMouseUp(int x, int y);
	
	virtual void OnDraw();
	virtual void OnDestroy();

	tButtonAnimation *GetEvent(int event);  // Get current mouse event

	int GetButtonState(); // Get button state (how many times it was pressed)
	void SetButtonState(bool loopanim, int state);

        // CTor & DTor
	CGUIButton();
	virtual ~CGUIButton();

private:
	CDoubleList <tButtonState *> m_lstButtonStates; // List of button states
	bool m_bAnimationOn;
	int m_iButtonState;	// Simply used to get m_pState[m_iButtonState]
	bool m_bLoopState;	// E.g if a user pressed once and max states is 1, does it return to original
state or stay on the last one
CAnimatedTexture *GetCurrentAnimation(); bool ParseBtnStates(TiXmlNode *btn_xml_node); bool ParseStateEvents(tButtonState *state, TiXmlNode *state_xml_node); };

Simple enough, but here we are going into high hierarchies of:
Each Button state Has N events (4 events for now), while each event has a simple animation. Yet, each animation
has a number of texture/materials indices that refer to database. Pretty complicated, but let's take a look at it in detail.

Button XML Data

Well, such a complicated control wouldn't have a simple XML data, so let's take a look at what it is. Just for simplicity, I divided
the data into sub-sections (files), so it would be easy for you to understand the whole picture. Here's the Button XML description:

<Button Filename="Defaults/Button/default_button.xml" BorderFile="Defaults/Border/default_border.xml"/>

Very simple, yet descriptive. Here, we tell parser what file our button XML is stored in, as well as pointing to border file.

Button Intrinsics (Animations, States and Events) (XML)

Let's take a look at the default button structure (XML):

<Button Rect="34,193,400,300" Font="bitmap_font.xml" Text="">
<State Id="0">
<Event Type="Disabled" AnimationFile="Defaults/Button/disabled_event.txt"></Event>
<Event Type="Normal" AnimationFile="Defaults/Button/normal_event.txt"></Event>
<Event Type="MouseOver" AnimationFile="Defaults/Button/mouseover_event.txt"></Event>
<Event Type="MouseDown" AnimationFile="Defaults/Button/mousedown_event.txt"></Event>
</State>
</Button>

Here we read all the basics about button first (rectangle, font filename if we derive button from static, and text as well),
and only after we are reading different button states (which is in our case is only one). StateId is pretty straightforward,
but after that we go a little harder:
Each state has a number of events - the animations that are played when specific conditions are achieved. In our case,
we react on mouse messages (when it's over, LMB down over the button, etc) and for each event we specify the animation sequence file.
Here how it looks like:

<Animation Materials="1" Frames="1">
<Frame Id="0" MatByRefID="11" Rect="0,128,0,128"/>
</Animation>

A basic animation file consists of:
Header specifying that this file IS the animation file
Number of materials and frames in animation. Just a note - even inside' Animation' node you can add your specific materials/textures
that may not exist in "mat_tex.xml" file. Then goes the actual frames.
Each frame is referring to material ID in database and a sub-rectangle in the material's texture (if any). Pretty simple, as you may see.
So, let's now take a look at how to load all that data properly.

Button Intrinsics (Animations, States and Events) (C++)

Let's separate each step from each other, so it would be easier for us to see what we are doing right or wrong:

int CGUIButton::Parse(TiXmlNode *this_node, char *filename)
{
	if(!CGUIStatic::Parse(this_node, filename))
		return -1;
	
	TiXmlDocument doc(filename);
	if(filename !=NULL)
	{
		bool loadOkay = doc.LoadFile();

		if ( !loadOkay )
			return false;

		m_pXMLNode = doc.FirstChild();
	}
	else
		m_pXMLNode = this_node;
	
	return ParseBtnStates(m_pXMLNode);
}

The ParseBnStates function parses button states (and events with animations):

bool CGUIButton::ParseBtnStates(TiXmlNode *btn_xml_node)
{
	if(btn_xml_node == NULL)
		return false;

	TiXmlNode *state_node = NULL;
	TiXmlElement *state_element = NULL;

	state_node = btn_xml_node->FirstChild();

	const char *value = NULL;
	while(state_node !=NULL)
	{
		state_element = state_node->ToElement();
		if(stricmp(state_element->Value(), "state") == 0)
		{
			tButtonState *newState = new tButtonState;
			if(!ParseStateEvents(newState, state_node))
			{
				delete newState;
				newState = NULL;
			}
			else				
				m_lstButtonStates.push_back(newState);
		}

		state_node = btn_xml_node->IterateChildren(state_node);
	}
			
	return true;
}

From here on we are starting to parse events for each state:

bool CGUIButton::ParseStateEvents(tButtonState *btn_state, TiXmlNode *state_xml_node)
{
	const char *value = NULL;

	TiXmlNode *event_node = NULL;
	TiXmlElement *event_element = NULL;

	event_node = state_xml_node->FirstChild();

	while(event_node !=NULL)
	{
		event_element = event_node->ToElement();
		
		if(stricmp(event_element->Value(), "event") == 0)
		{
			tButtonAnimation *newEvent = new tButtonAnimation;

			value = event_element->Attribute("Type");
			if(value == NULL)
				return false;
			else
			{
				if(stricmp(value, "disabled") == 0)
					newEvent->m_iEventType = -1; // Disabled
				else if((stricmp(value, "normal") == 0) || (stricmp(value, "std") == 0) || (stricmp(value, "none") == 0))
					newEvent->m_iEventType = 0;
				else if((stricmp(value, "mouseover") == 0) || (stricmp(value, "mouse over") == 0))
					newEvent->m_iEventType = 1;
				else if((stricmp(value, "mousedown") == 0) || (stricmp(value, "mouse down") == 0))
					newEvent->m_iEventType = 2;

				value = NULL;
			}

			value = event_element->Attribute("AnimationFile");
			if(value !=NULL)
			{
				if(!newEvent->m_Animation.Parse((char *)value))
					return false;

				value = NULL;
			}
			else
			{
				if(!newEvent->m_Animation.Parse(NULL, event_node))
					return false;

				value = NULL;
			}

			btn_state->m_lstEvents.push_back(newEvent);
		}
		
		event_node = state_xml_node->IterateChildren(event_node);
	}
	
	return true;
}

Can't be eany easier, although just a bit too long. Here we are parsing events for each state and also load its animation. Each event is
specified by name ("mousedown", "mouseover", etc). It's easy to add new events, just make sure that you are adding proper event IDs to them.

Well, that's basically it. Now let's take a look at our drawing function:

void CGUIButton::OnDraw()
{
	CAnimatedTexture *cur = GetCurrentAnimation();

	if(cur !=NULL)
	{
		if(m_bAnimationOn)
			cur->Animate(100);

		tMaterial *mat = CMaterialManager::GetSingleton().GetMaterial(-1, (unsigned int)cur->m_piTexIndex[cur->m_iCurrentFrame]);
		if(mat !=NULL)
		{
			mat->m_Ambient = tRGBA(1,1,1,0);
			SetActiveMaterial(mat);

			if(mat->m_pTexPtr !=NULL)
			{
				if(cur->m_pFrameOffset !=NULL)
				{
					m_fTexCoord[0].x = (float)cur->m_pFrameOffset[cur->m_iCurrentFrame].m_iLeft / (float)mat->m_pTexPtr->m_iWidth;
					m_fTexCoord[0].y = (float)cur->m_pFrameOffset[cur->m_iCurrentFrame].m_iTop / (float)mat->m_pTexPtr->m_iHeight;

					m_fTexCoord[1].x = (float)cur->m_pFrameOffset[cur->m_iCurrentFrame].m_iRight / (float)mat->m_pTexPtr->m_iWidth;
					m_fTexCoord[1].y = (float)cur->m_pFrameOffset[cur->m_iCurrentFrame].m_iTop / (float)mat->m_pTexPtr->m_iHeight;

					m_fTexCoord[2].x = cur->m_pFrameOffset[cur->m_iCurrentFrame].m_iRight / (float)mat->m_pTexPtr->m_iWidth;
					m_fTexCoord[2].y = cur->m_pFrameOffset[cur->m_iCurrentFrame].m_iBottom / (float)mat->m_pTexPtr->m_iHeight;

					m_fTexCoord[3].x = (float)cur->m_pFrameOffset[cur->m_iCurrentFrame].m_iLeft / (float)mat->m_pTexPtr->m_iWidth;
					m_fTexCoord[3].y = (float)cur->m_pFrameOffset[cur->m_iCurrentFrame].m_iBottom / (float)mat->m_pTexPtr->m_iHeight;
				}
			}

		}
	}

	CGUIStatic::OnDraw();
}

In this function we firstly aquire the texture corresponding to the animation sequence, and then we set up texture coordinates (if there is any).
Then we call the derived class OnDraw() function. Pretty simple. The GetCurrentAnimation() function is returning the animation pointer based
on events that has taken place (if any). And if there's no events, it returns the default(0) animation.

Also we need to add the ability to click on the button. To do that we are going to create our own OnLMouseUp function for the button:

int CGUIButton::OnLMouseUp(int x, int y)
{
	int ret = CGUIElement::OnLMouseUp(x, y);
	if(ret)
	{
		if(m_iButtonState == (m_lstButtonStates.size() - 1))
		{
			if(m_bLoopState)
				m_iButtonState = 0;
		}
		else
			m_iButtonState++;
					
		return 1;
	}

	return ret;
}

What we are doing here is firstly checking if the mouse was released over the button (you can change that) and if it was, we check if there
is any more states are available to us. If there are no more states, we simply skip this procedure, otherwise we increment the state
counter - it plays important role in GetEvent() function that returns the event based on current state.

Well, that's about what we need. Let's now take a look at our final result and be proud of ourselves!

Next tutorial we will go into progressbars. Cyas there!