原作者 : Kevin Lynx
BLOG:
http://blog.csdn.net/kevinlynx第一部分:
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機制一樣。