Kindly hosted by
GUI Tutorial : Part #7
Table of Contents
Welcome to the seventh tutorial in GUI series. This time we are going to take a look at progress bar control. It's going to be a very
interesting tutorial, I promise...
So, let's start.
ProgressBar Control class
A simple progress bar is basically 2 elements:
one representing positive (progressed) part, and the other is the one that's left to go (negative).
We also have variables such as the type of progress bar (vertical or horizontal, can be also any angle),
Minimum/Maximum/Current values as well.
We also add the Minimum to Maximum variable - m_bMinToMax - to indicate where this progress bar is going to be updating
itself - from negative to positive,
or other way around. Take a look at the class:
class CGUIProgressBar : public CGUIStatic
{
public:
virtual int Parse(TiXmlNode *node, char *filename);
virtual void OnDraw();
void SetRange(int min, int max, bool mintomax = true);
tVERTEX2f GetRange();
int GetCurPos();
void SetCurPos(int pos);
virtual int OnSize(tRect newRect);
virtual void ProcessMessages();
CGUIProgressBar();
virtual ~CGUIProgressBar();
private:
void OnUpdate(float curpos);
CGUIElement m_Element[2]; // 2 elements that represent that's done and what's yet to do
int m_iProgressBarType;
bool m_bMinToMax; // Direction in which it progesses
//negative - from right to left (top to bottom)
// positive - left to right (bottom to top)
int m_iCurPos, m_iMinPos, m_iMaxPos;
double m_fCurPos, m_fPPV;
};
Pretty simple, yet elegant.
Interesting Functions
We are going to take a look at the following functions - the OnSize() and SetRange() ones. Here's the SetRange() function:
void CGUIProgressBar::SetRange(int min, int max, bool mintomax)
{
m_iMinPos = min;
m_iMaxPos = max;
if(GetType() == HorizontalProgressBar) // If this is a horizontal progress bar
m_fPPV = float(GetWidth() - 2*DEFAULT_PROGRESSBAR_SPACINGX) / (m_iMaxPos - m_iMinPos);
else if(GetType() == VerticalProgressBar) // If its a vertical one
m_fPPV = float(GetHeight() - 2*DEFAULT_PROGRESSBAR_SPACINGY) / (m_iMaxPos - m_iMinPos);
m_bMinToMax = mintomax;
}
In it, we are refreshing the m_fPPV value, which stands for Pixel Per Value - e.g how much pixels our elements are going to be resized
basing on the values passed (Max minus Min). But here's another trick - in next tutorial on ScrollBar Control we are going to have to calculate the
m_fPPV value and use it as well only in reverse, since we drag the thumb N pixels (e.g we have to calculate how much the value will change according
to displacement in pixels).
Well, now onto the OnSize() function:
int CGUIProgressBar::OnSize(tRect newRect)
{
int ret = CGUIElement::OnSize(newRect);
CGUIElement *pos = FindChild(Simple, PROGRESSBAR_ELMNT_POSITIVE);
CGUIElement *neg = FindChild(Simple, PROGRESSBAR_ELMNT_NEGATIVE);
if(GetWidth() >= GetHeight())
SetType(HorizontalProgressBar);
else
SetType(VerticalProgressBar);
int tmp_icurpos = m_iCurPos, tmp_fcurpos = m_fCurPos;
if(!m_bMinToMax) // Swap colors for pos/neg areas and substract value because it goes from other end
{
m_iCurPos = m_iMaxPos - m_iCurPos;
m_fCurPos = float(m_iMaxPos) - m_fCurPos;
}
tRect r[2];
if(GetType() == HorizontalProgressBar) // We have horizontal progress bar
{
// Change to m_fCurPos to have more precision
r[0].m_iLeft = GetRect().m_iLeft + DEFAULT_PROGRESSBAR_SPACINGX;
r[0].m_iRight = r[0].m_iLeft + m_fPPV*m_iCurPos;
r[0].m_iTop = GetRect().m_iTop - DEFAULT_PROGRESSBAR_SPACINGY;
r[0].m_iBottom = GetRect().m_iBottom + DEFAULT_PROGRESSBAR_SPACINGY;
r[1].m_iLeft = r[0].m_iRight;
r[1].m_iRight = GetRect().m_iRight - DEFAULT_PROGRESSBAR_SPACINGX;
r[1].m_iTop = GetRect().m_iTop - DEFAULT_PROGRESSBAR_SPACINGY;
r[1].m_iBottom = GetRect().m_iBottom + DEFAULT_PROGRESSBAR_SPACINGY;
}
else if(GetType() == VerticalProgressBar) // We have a vertical progress bar
{
// Change to m_fCurPos to have more precision
r[0].m_iLeft = GetRect().m_iLeft + DEFAULT_PROGRESSBAR_SPACINGX;
r[0].m_iRight = GetRect().m_iRight - DEFAULT_PROGRESSBAR_SPACINGX;
r[0].m_iTop = GetRect().m_iTop - DEFAULT_PROGRESSBAR_SPACINGY;
r[0].m_iBottom = r[0].m_iTop - m_fPPV*m_iCurPos;
r[1].m_iLeft = GetRect().m_iLeft + DEFAULT_PROGRESSBAR_SPACINGX;
r[1].m_iRight = GetRect().m_iRight - DEFAULT_PROGRESSBAR_SPACINGX;
r[1].m_iTop = r[0].m_iBottom;
r[1].m_iBottom = GetRect().m_iBottom + DEFAULT_PROGRESSBAR_SPACINGY;
}
pos->SetRect(r[0]);
neg->SetRect(r[1]);
if(!m_bMinToMax)
{
m_iCurPos = tmp_icurpos;
m_fCurPos = tmp_fcurpos;
}
SetRange(m_iMinPos, m_iMaxPos, m_bMinToMax);
return ret;
}
It is pretty complex, so let's see what steps are done in here:
- Aquire Positive/Negative elements.
- Set progress bar type based on width/height proportions.
- Check positive/negative order.
- Set rectangles of elements according to current progress bar value.
Pretty much it with slight modifications which are required to keep all in balance.
ProgressBar data description (XML)
Now let's take a look at the proper way to store information about progress bar.
I've chosen XML format (see my explanation why I did so):
<ProgressBar Rect="200,400,520,500" BorderFile="Defaults/Border/default_border.xml" Min="0" Max="100" Cur="50" Type="Horizontal">
<Element Name="Min" Color="1,0,0"/>
<Element Name="Max" Color="0,1,0"/>
</ProgressBar>
As you may see, it's pretty easy to understand. We store progress bar rectangle, Min/MAx/Cur values, Border filename and a type.
It's children are Positive and Negative elements (first one is Positive) with their respective color. Very easy.
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;
}
}
What's our progress?
Well, onto the drawing part. Basically, all rectangle recalculations are happening in the OnSize() function, so here we are simply processing
messages (by calling ProcessMessages()) and drawing the children. We also convert the value to text (or we may not, its entirely up to you) by
using last four lines:
void CGUIProgressBar::OnDraw()
{
ProcessMessages();
if(!Visible())
return;
// When we have horizontal progress bar, minimum is on m_iLeft, max - m_iRight
// When we have a vertical progress bar, minimum is m_iTop, max - m_iBottom
CGUIElement::OnDraw(); // First we draw underneath the progess bar
if(m_iCurPos !=m_fCurPos)
{
m_fCurPos = m_iCurPos;
OnSize(GetRect());
}
char val[10] = "";
itoa(m_iCurPos, val, 10);
SetText(val);
DrawText();
}
Here, we firstly draw the underneath (parent) element rectangle and it's children, and after all we draw the value! Simplicity is the best way!
Well, that's about it! Here's our work results!
Next tutorial we will go into ScrollBar control. Cyas there and stay tuned!
|