第一部分:
HGE helper类中的GUI:
引擎版本:1.60release。
文件:hgegui.h , hgegui.cpp, hgeguictrls.h , hgeguictrls.cpp
大致类图:
其中,hgeGUIObject是抽象基类,具体的控件类如按钮,文本标签,都是从它派生而来。HgeGUI属于整个GUI系统的manager,它会保存所有的控件。
关于hgeGUIObject的8个成员变量,文档里已经有所描述:
int id; //控件ID
bool bStatic; //是否可以接受键盘焦点
bool bVisible; //是否可见
bool bEnabled; //是否有效
hgeRect rect; //控件大小
hgeGUI *gui; //其属于的 manager ,相当于父对象
hgeGUIObject *next; //用于双向链表,把所有控件连接在一起
hgeGUIObject *prev;
static HGE *hge; //方便使用HGE接口
关于其部分接口的描述:
Render: 用于渲染控件到屏幕上
Update: 用于更新其动画
Enter: 控件刚显示时的动画
Leave: 控件要消失时的动画
IsDone: 控件刚显示和消失时的状态查询函数
该类也就是提供了一个抽象而已,利用C++语言的多态机制来方便hgeGUI管理所有的控件。其他具体的控件继承了hgeGUIObject后,必须实现构造函数和Render函数。
关于hgeGUI:
这个类应该属于manager。它负责管理所有的控件。
其数据成员:
hgeGUIObject *ctrls; //保存所有控件
hgeGUIObject *ctrlLock; //可能是用来保存当前被鼠标操作的控件
hgeGUIObject *ctrlFocus; //保存焦点控件
hgeGUIObject *ctrlOver; //用来保存鼠标指针所指的控件
int navmode;
int nEnterLeave;
hgeSprite *sprCursor; //渲染鼠标指针用的
float mx,my; //鼠标坐标
int nWheel; //滚轮偏移量
bool bLPressed, bLLastPressed;//本帧左键状态,上一帧的左键状态
bool bRPressed, bRLastPressed;
其Update方法会负责控件的进入和离开动画,还会负责整体的状态设置---哪些控件拥有焦点,哪些控件被鼠标正在操作,哪些控件正被鼠标指针指着,这些控件它都会保存起来。(也就是说,我们还是可以通过检查控件的状态来设置当鼠标指针在其上时的新动画。)
总体而言,该引擎的GUI还是很简单的。一个manager,负责管理所有的控件,然后一个抽象基类,用来协助manager管理---其他具体的控件都必须从那个抽象基类派生。
下面具体看一个Button控件:
Button类的定义如下:
class hgeGUIButton : public hgeGUIObject
{
public:
hgeGUIButton(int id, float x, float y, float w, float h, HTEXTURE tex, float tx, float ty);
virtual ~hgeGUIButton();
void SetMode(bool _bTrigger) { bTrigger=_bTrigger; }
void SetState(bool _bPressed) { bPressed=_bPressed; }
bool GetState() const { return bPressed; }
virtual void Render();
virtual bool MouseLButton(bool bDown);
private:
bool bTrigger;
bool bPressed;
bool bOldState;
hgeSprite *sprUp, *sprDown;
};
其中bTrigger表示该按钮的行为是否象一个RadioButton,bPressed表示当前按钮是否被按下,bOldState表示上一次按钮状态,特别用来实现bTrigger的,sprUp,sprDown分别用来绘制弹起和按下时的按钮外观。这两个精灵的创建都是从构造函数的tex上创建而来的,它要求两个状态必须保存在一幅纹理上,且顺序为从左至右。
按钮的实现代码也很简单:
void hgeGUIButton::Render()
{
if(bPressed) sprDown->Render(rect.x1, rect.y1);
else sprUp->Render(rect.x1, rect.y1);
}
bool hgeGUIButton::MouseLButton(bool bDown)
{
if(bDown)
{
bOldState=bPressed; bPressed=true;
return false;
}
else
{
if(bTrigger) bPressed=!bOldState;
else bPressed=false;
return true;
}
}
联系起来,当hgeGUI::Update里处理ProcessCtrl时,如果鼠标左键按下且其指针在按钮范围内,那么就调用hgeGUIButton::MouseLButton( true ),这个时候button的bPressed=true,那么在渲染的时候,自然就表现出被按下时的状态。
事实上对于这种类型的按钮---如同windows下的窗体按钮,我们一般不检查其是否被按下,而是检查其是否发生了clicked 这个事件,而这个事件是在先按下在弹起的情况下发生的。因此,判断该事件发生的条件就为:上一帧状态被按下,这一帧没被按下。
虽然HGE引擎的GUI很简单,但是其扩展性很好。因为hgeGUI::Update基本上派发了所有控件需要的消息---键盘操作,以及鼠标操作;而hgeGUIObject基类的很多成员函数都会处理这些消息,我们只需要派生hgeGUIObject,然后重载我们需要的消息处理即可。
第二部分:
HGE扩展GUI库,从HGE官方论坛下载(作者不明):
工程结构:
其中,guitest.cpp为测试文件。
类结构:
整个系统的工作原理:
用户继承抽象基类GUIApp,实现具体的OnEvent函数,然后该类会管理所有的GUIAppWindow对象,GUIAppWindow窗口对象会管理其上的所有子控件。
相应地,GUIApp派生类会直接得到鼠标和键盘消息,然后派发给所有窗口对象,然后窗口对象再把消息派发到具体的控件对象上。
这种Parent-Child关系大致为:
GUIAppWindow类保存有其所属的GUIApp对象指针,每个具体的控件又保存有其所属的GUIAppWindow 的对象指针。
当一个控件处理了某个事件后,例如按钮处理了鼠标单击事件,它就需要告诉外界用户单击了这个按钮。这里采用的方法是:在基类GUIAppObject里定义了一个虚函数OnEvent( int id)
,然后在其派生类GUIAppWindow里把这个函数重载为纯虚函数,函数有一个参数,那就是控件ID。当一个控件处理了某个事件后,就通过其内部保存的父窗口指针来调用OnEvent函数,然后GUIAppWindow的派生类---如果该类能产生对象,那么其必然实现了OnEvent的具体代码(这就是为什么在GUIAppWindow里要把OnEvent又重载为纯虚函数的原因),然后在此代码里,窗口根据传进来的控件ID来得知哪个控件发生了事件!
GUIAppWindow里有一个容器,它保存了所有该窗口上的控件。
所有控件再创建时,都是以其父窗口为参考坐标系的,也就是相对坐标,但是其实际保存的坐标却是绝对坐标—既相对于整个屏幕的坐标(如果是窗口程序,就相对于整个窗口)。大致过程为:在GUIAppWindow派生类中创建子控件时,给子控件指定的坐标为相对坐标,然后当 AddCtrl 时就会重新把子控件的坐标改变为绝对坐标。
GUIApp里直接有了BeginScene和EndScene的渲染代码。
要使用该扩展库,大致步骤为:
1. 继承GUIAppWindow类,在这个派生类里重载具体的处理OnEvent的函数,并创建所有该窗口上的子控件。
2. 继承GUIApp类,在这个派生类中创建窗口对象,并把窗口对象AddCtrl,在这里可以进行其他的初始化工作
3. 在FrameFunc里调用GUIApp::FrameFunc函数。
总体而言,这个扩展GUI主要是扩展了GUI Manager以及GUI Object,并且加入了Parent-Child机制。比较经典的部分在于提供了一个 OnEvent 函数,这样就可以让客户程序员能够得知窗体上的控件发生的事件。 ----其实这种方法的目的就跟Windows中的消息机制,Qt中的signal/slot机制一样。