HGE系列之七 管中窺豹(圖形界面)

    HGE系列之七管中窺豹(圖形界面)ide

 

  此次的HGE源碼之旅,讓咱們來看看HGE的圖形用戶界面(GUI)的實現,話說電腦技術發展至今,當年轟動一時的圖形用戶界面,而今早已司空見慣,想來不得不感嘆一下技術的突飛猛進啊……HGE做爲一款出色的2D遊戲引擎,GUI方面的支持天然不在話下,而且從實現方面來講仍是至關具備擴展性的 :)好了,簡略的介紹就到此爲止吧,就讓咱們立刻來看看源碼 :)函數

 

  類名hgeGUIObject動畫

  功能:界面對象基類,像文本、按鈕之類的GUI控件皆派生於她ui

  頭文件hge/hge181/include/hgegui.hthis

  實現文件:無spa

 

  如下是該類的頭文件聲明,注意一下其中的各個虛擬函數:.net

 

class hgeGUIObject指針

{ orm

public:對象

    // 默認的構造函數,用以建立hge對象以及設置顏色(color)

       hgeGUIObject()     { hge=hgeCreate(HGE_VERSION); color=0xFFFFFFFF; }

    // 虛擬析構函數,用以釋放以前建立的HGE

       virtual                   ~hgeGUIObject() { hge->Release(); }

    // 純虛函數Render,功能顧名思義了 )

       virtual void     Render() = 0;

    // 根據間隔時間(dt)來更新內部狀態

       virtual void     Update(float dt) {}

   

    // 「進入」此界面對象時執行的函數

       virtual void     Enter() {}

    // 「離開」此界面對象時執行的函數

       virtual void     Leave() {}

    // 重置界面對象狀態

       virtual void     Reset() {}

    // 是否Enter或者Leave的調用已經結束

       virtual bool     IsDone() { return true; }

    // 當控件獲取或失去焦點時調用

       virtual void     Focus(bool bFocused) {}

    // 當鼠標進入或者離開控件範圍時調用

       virtual void     MouseOver(bool bOver) {}

 

    // 響應鼠標移動

       virtual bool     MouseMove(float x, float y) { return false; }

       // 響應鼠標左鍵

    virtual bool     MouseLButton(bool bDown) { return false; }

       // 響應鼠標右鍵

    virtual bool     MouseRButton(bool bDown) { return false; }

       // 響應鼠標滑輪

    virtual bool     MouseWheel(int nNotches) { return false; }

       // 響應按鍵

    virtual bool     KeyClick(int key, int chr) { return false; }

    // 設置控件顏色

       virtual void     SetColor(DWORD _color) { color=_color; }

      

    // 控件id

       int                        id;

    // 是否靜態

       bool               bStatic;

    // 是否可見

       bool               bVisible;

    // 是否使能

       bool               bEnabled;

    // 區域範圍,使用了hgeRect :)

       hgeRect                 rect;

    // 控件顏色,簡單的使用了一個DWORD進行表示

       DWORD               color;

 

    // hgeGUI對象,用於管理hgeGUIObject,詳述見後

       hgeGUI                 *gui;

    // 控件鏈表後向指針

       hgeGUIObject *next;

    // 控件鏈表前向指針

       hgeGUIObject *prev;

 

protected:

    // 將複製構造函數及對應的賦值函數定義爲保護類型,

    // 禁止外部的調用(子類和自身成員函數除外)

       hgeGUIObject(const hgeGUIObject &go);

       hgeGUIObject&     operator= (const hgeGUIObject &go);

    // 靜態hge指針,用於獲取hge提供的核心(core)函數

       static HGE             *hge;

};

 

  一目瞭然,這個控件基類仍是相對簡單的,各類成員函數和變量的意義都十分清晰,藉助於上面的註釋和HGE文檔的幫助,相信你們都可以了其大意,惟一想再說一說的,只是這麼一段:

 

 

// 將複製構造函數及對應的賦值函數定義爲保護類型,

// 禁止外部的調用(子類和自身成員函數除外)

hgeGUIObject(const hgeGUIObject &go);

hgeGUIObject&     operator= (const hgeGUIObject &go);

 

  正如註釋所解,這段代碼的做用是屏蔽外界對於這兩個函數的調用,可是做者在這方面仍是作的不是特別完全,由於將其設置爲保護域(protected)並不能防止派生類對其的不當調用,雖然因爲這兩個函數源代碼中只是進行了聲明,並未進行實現,這意味着即便派生類中調用了這兩個函數,也總會遇到連接錯誤(Link),可是我想爲何不直接作的更完全一點呢?很簡單,將保護域(protected)聲明爲私有域(private)就OK了,固然最後的

 

static HGE             *hge;

 

前仍是要再加一個「protected:」,不然派生類就用不了hge了(至少不能直接使用) :)

 

類名hgeGUI

功能:界面控件管理類,用以控制各個界面

頭文件hge/hge181/include/hgegui.h

實現文件hge/hge181/src/helpers/hgegui.cpp

 

  一樣的步驟,讓咱們想看一看該類的聲明:

 

class hgeGUI

{

public:

       hgeGUI();

       ~hgeGUI();

 

    // 添加控件

       void               AddCtrl(hgeGUIObject *ctrl);

    // 移除控件

       void               DelCtrl(int id);

    // 經過控件id獲取相對應控件

       hgeGUIObject*      GetCtrl(int id) const;

   

    // 移動控件

       void               MoveCtrl(int id, float x, float y);

    // 設置控件的顯示或隱藏

       void               ShowCtrl(int id, bool bVisible);

    // 使能控件

       void               EnableCtrl(int id, bool bEnabled);

 

    // 設置控件的顯示模式

       void               SetNavMode(int mode);

    // 設置鼠標光標

       void               SetCursor(hgeSprite *spr);

    // 設置全部控件的顏色

       void               SetColor(DWORD color);

    // 設置控件的失焦穫焦狀態

       void               SetFocus(int id);

    // 獲取控件的失焦穫焦狀態

       int                        GetFocus() const;

      

    // 開啓GUI「進入」動畫

       void               Enter();

    // 開啓GUI「離開」動畫

       void               Leave();

    // 重置控件狀態

       void               Reset();

    // 移動全部控件

       void               Move(float dx, float dy);

 

    // 更新控件狀態

       int                        Update(float dt);

    // 繪製控件

       void               Render();

 

private:

    // 私有化複製構造函數和賦值函數,防止外界調用

       hgeGUI(const hgeGUI &);

       hgeGUI&                     operator= (const hgeGUI&);

    // 處理控件邏輯

       bool               ProcessCtrl(hgeGUIObject *ctrl);

 

       static HGE             *hge;

 

    // 控件鏈表(雙向)表頭

       hgeGUIObject *ctrls;

    // 指向被鎖定的控件

       hgeGUIObject *ctrlLock;

    // 指向獲的焦點的控件

       hgeGUIObject *ctrlFocus;

    // 指向鼠標移至其上的控件

       hgeGUIObject *ctrlOver;

 

    // 顯示模式

       int                        navmode;

    // Enter/Leave 標記

       int                        nEnterLeave;

       // 鼠標光標

    hgeSprite        *sprCursor;

   

    // 鼠標xy座標

       float               mx,my;

    // 鼠標滑輪移動值

       int                        nWheel;

    // 鼠標左鍵是否按下及釋放

       bool               bLPressed, bLReleased;

    // 鼠標右鍵是否按下及釋放

       bool               bRPressed, bRReleased;

};

 

  能夠看到。hgeGUI的代碼也依然十分清晰,在此也沒必要完整列出實現的全部源碼,挑選一些值得注意的成員函數實現我想便足矣足矣:

 

  首先是構造函數,很簡單,建立HGE並初始化成員變量,不過若是使用成員初始化列表的話效率會略高一些 :)

 

hgeGUI::hgeGUI()

{

       hge=hgeCreate(HGE_VERSION);

 

       ctrls=0;

       ctrlLock=0;

       ctrlFocus=0;

       ctrlOver=0;

       navmode=HGEGUI_NONAVKEYS;

       bLPressed=bLReleased=false;

       bRPressed=bRReleased=false;

       nWheel=0;

       mx=my=0.0f;

       nEnterLeave=0;

       sprCursor=0;

}

 

  析構函數也很日常,除了釋放資源以外再無其餘:

 

hgeGUI::~hgeGUI()

{

       hgeGUIObject *ctrl=ctrls, *nextctrl;

 

    // 依次釋放鏈表(雙向)元素

       while(ctrl)

       {

              nextctrl=ctrl->next;

              delete ctrl;

              ctrl=nextctrl;

       }

    // 釋放hge

       hge->Release();

}

 

  接着讓咱們看看AddCtrl  

 

void hgeGUI::AddCtrl(hgeGUIObject *ctrl)

{

       hgeGUIObject *last=ctrls;

   

    // 設置控件的hgeGUI指針

       ctrl->gui=this;

   

    // 若是指針鏈表爲空,則設置指針鏈表

       if(!ctrls)

       {

              ctrls=ctrl;

              ctrl->prev=0;

              ctrl->next=0;

       }

    // 不然,將控件添加至鏈表尾部

       else

       {

              while(last->next) last=last->next;

              last->next=ctrl;

              ctrl->prev=last;

              ctrl->next=0;

       }

}

 

  一樣道理,void hgeGUI::DelCtrl(int id) 也即是依次查找對應id的控件,並刪除之。

 

  hgeGUIObject* hgeGUI::GetCtrl(int id) const 這個成員變量則更加簡單,依次查找並返回控件指針,若是未有找到,則返回NULL

 

  移動控件的函數也並不困難: 

 

void hgeGUI::MoveCtrl(int id, float x, float y)

{

       // 首先獲取控件的指針

    hgeGUIObject *ctrl=GetCtrl(id);

    // 將控件的區域範圍設置爲 xy 座標爲起點

       ctrl->rect.x2=x + (ctrl->rect.x2 - ctrl->rect.x1);

       ctrl->rect.y2=y + (ctrl->rect.y2 - ctrl->rect.y1);

       ctrl->rect.x1=x;

       ctrl->rect.y1=y;

}

 

  以後的一些設置函數相對簡單,僅是賦值而已,在此再也不羅列。有興趣的朋友能夠看一看源文件 :)

 

  Reset函數將控件鏈表中的元素依次重置: 

 

void hgeGUI::Reset()

{

       hgeGUIObject *ctrl=ctrls;

 

       while(ctrl)

       {

              ctrl->Reset();

              ctrl=ctrl->next;

       }

 

       ctrlLock=0;

       ctrlOver=0;

       ctrlFocus=0;

}

 

Move函數則是將全部控件依次移動dxdy位移:

 

void hgeGUI::Move(float dx, float dy)

{

       hgeGUIObject *ctrl=ctrls;

 

       while(ctrl)

       {

              ctrl->rect.x1 += dx;

              ctrl->rect.y1 += dy;

              ctrl->rect.x2 += dx;

              ctrl->rect.y2 += dy;

 

              ctrl=ctrl->next;

       }

}

 

  讓咱們再來看看SetFoucus函數:

 

void hgeGUI::SetFocus(int id)

{

    // 獲取給定id的控件指針

       hgeGUIObject *ctrlNewFocus=GetCtrl(id);

   

    // 若是當前的控件已是焦點控件,則當即返回

       if(ctrlNewFocus==ctrlFocus) return;

    // 若是獲取的控件爲空

       if(!ctrlNewFocus)

       {

        // 則將原先的焦點控件失焦

              if(ctrlFocus) ctrlFocus->Focus(false);

        // 並將焦點控件置空

              ctrlFocus=0;

       }

    // 不然(找到了),若是空間不是靜態的,而且可見、使能

       else if(!ctrlNewFocus->bStatic && ctrlNewFocus->bVisible && ctrlNewFocus->bEnabled)

       {

        // 原先的焦點控件失焦

              if(ctrlFocus) ctrlFocus->Focus(false);

        // 當前的控件獲焦

              if(ctrlNewFocus) ctrlNewFocus->Focus(true);

        // 設置焦點控件指針

              ctrlFocus=ctrlNewFocus;

       }

}

 

  看來代碼依然十分清晰明瞭 :)

 

  接着即是EnterLeave函數: 

 

void hgeGUI::Enter()

{

       hgeGUIObject *ctrl=ctrls;

   

    // 依次調用控件鏈表中元素的Enter函數

       while(ctrl)

       {

              ctrl->Enter();

              ctrl=ctrl->next;

       }

    // Enter/Leave標記

       nEnterLeave=2;

}

 

void hgeGUI::Leave()

{

       hgeGUIObject *ctrl=ctrls;

    // 依次調用控件鏈表中的Leave函數

       while(ctrl)

       {

              ctrl->Leave();

              ctrl=ctrl->next;

       }

 

       ctrlFocus=0;

       ctrlOver=0;

       ctrlLock=0;

    // Enter/Leave標記

       nEnterLeave=1;

}

 

  Render函數與之相似,亦是遍歷並渲染的那一套,在此就不列出代碼了 :)

 

  用於處理控件的ProcessCtrl以下: 

 

bool hgeGUI::ProcessCtrl(hgeGUIObject *ctrl)

{

       bool bResult=false;

    // 處理鼠標左鍵按下

       if(bLPressed)  { ctrlLock=ctrl;SetFocus(ctrl->id);bResult=bResult || ctrl->MouseLButton(true); }

    // 處理鼠標右鍵按下

       if(bRPressed)  { ctrlLock=ctrl;SetFocus(ctrl->id);bResult=bResult || ctrl->MouseRButton(true); }

    // 處理鼠標左鍵釋放

       if(bLReleased)       { bResult=bResult || ctrl->MouseLButton(false); }

    // 處理鼠標右鍵釋放

       if(bRReleased)       { bResult=bResult || ctrl->MouseRButton(false); }

    // 處理鼠標滑輪

       if(nWheel)             { bResult=bResult || ctrl->MouseWheel(nWheel); }

    // 最後處理鼠標的移動

       bResult=bResult || ctrl->MouseMove(mx-ctrl->rect.x1,my-ctrl->rect.y1);

 

       // 只要前面有一個消息處理成功,則bResult爲真

    return bResult;

}

 

  註釋中基本道明瞭這個函數的處理流程和做用,最後,讓咱們來看一看hgeGUIUpdate函數,不像以前遇到的成員函數,這但是個你們夥,因此作好準備了

 

  首先,他獲取鼠標的各個按鍵狀況:

 

    // 更新鼠標變量

 

       hge->Input_GetMousePos(&mx, &my);

       bLPressed  = hge->Input_KeyDown(HGEK_LBUTTON);

       bLReleased = hge->Input_KeyUp(HGEK_LBUTTON);

       bRPressed  = hge->Input_KeyDown(HGEK_RBUTTON);

       bRReleased = hge->Input_KeyUp(HGEK_RBUTTON);

       nWheel=hge->Input_GetMouseWheel();

 

  接着,其遍歷控件鏈表並依次調用各個控件的Update函數,用以更新控件的內部狀態:

 

    // Update all controls

 

       ctrl=ctrls;

       while(ctrl)

       {

              ctrl->Update(dt);

              ctrl=ctrl->next;

       }

 

  而後,處理EnterLeave

 

    // Handle Enter/Leave

 

       if(nEnterLeave)

       {

              ctrl=ctrls; bDone=true;

        // 查看各個控件的EnterLeave是否結束

              while(ctrl)

              {

                     if(!ctrl->IsDone()) { bDone=false; break; }

                     ctrl=ctrl->next;

              }

        // 若是任意一個控件的Enter/Leave 沒有結束便返回0

              if(!bDone) return 0;

              else

              {

            // 若是nEnterLeave1(即調用Leave時),則返回-1

                     if(nEnterLeave==1) return -1;

            // 不然返回0

                     else nEnterLeave=0;

              }

       }

 

  再接着,即是處理鍵盤按鍵:

 

    // Handle keys

 

       key=hge->Input_GetKey();

    // 若是顯示模式爲左右模式而且Left鍵被按下

    // 或者顯示模式爲上下模式而且Up鍵被按下

       if(((navmode & HGEGUI_LEFTRIGHT) && key==HGEK_LEFT) ||

              ((navmode & HGEGUI_UPDOWN) && key==HGEK_UP))

       {

              // 獲取焦點控件

        ctrl=ctrlFocus;

        // 若是焦點控件爲空,則嘗試使用第一個鏈表控件元素

              if(!ctrl)

              {

                     ctrl=ctrls;

            // 若是控件仍是爲空(此時沒有控件),則直接返回0

                     if(!ctrl) return 0;

              }

        // 查找位於焦點控件以前的符合顯示條件的控件

              do {

                     ctrl=ctrl->prev;

                     if(!ctrl && ((navmode & HGEGUI_CYCLED) || !ctrlFocus))

                     {

                            ctrl=ctrls;

                            while(ctrl->next) ctrl=ctrl->next;

                     }

                     if(!ctrl || ctrl==ctrlFocus) break;

              } while(ctrl->bStatic==true || ctrl->bVisible==false || ctrl->bEnabled==false);

       

        // 從新設置焦點控件

              if(ctrl && ctrl!=ctrlFocus)

              {

                     if(ctrlFocus) ctrlFocus->Focus(false);

                     if(ctrl) ctrl->Focus(true);

                     ctrlFocus=ctrl;

              }

       }

    // 若是顯示模式爲左右模式而且Right鍵被按下

    // 或者顯示模式爲上下模式而且Down鍵被按下

       else if(((navmode & HGEGUI_LEFTRIGHT) && key==HGEK_RIGHT) ||

              ((navmode & HGEGUI_UPDOWN) && key==HGEK_DOWN))

       {

              ctrl=ctrlFocus;

              if(!ctrl)

              {

                     ctrl=ctrls;

                     if(!ctrl) return 0;

            // 獲取控件鏈表最後的元素

                     while(ctrl->next) ctrl=ctrl->next;

              }

        // 查找位於焦點控件以後的符合顯示條件的控件

              do {

                     ctrl=ctrl->next;

                     if(!ctrl && ((navmode & HGEGUI_CYCLED) || !ctrlFocus)) ctrl=ctrls;

                     if(!ctrl || ctrl==ctrlFocus) break;

              } while(ctrl->bStatic==true || ctrl->bVisible==false || ctrl->bEnabled==false);

        // 從新設置焦點控件

              if(ctrl && ctrl!=ctrlFocus)

              {

                     if(ctrlFocus) ctrlFocus->Focus(false);

                     if(ctrl) ctrl->Focus(true);

                     ctrlFocus=ctrl;

              }

       }

    // 處理其餘按鍵狀況

       else if(ctrlFocus && key && key!=HGEK_LBUTTON && key!=HGEK_RBUTTON)

       {

              if(ctrlFocus->KeyClick(key, hge->Input_GetChar())) return ctrlFocus->id;

       }

 

  最後則是對於鼠標的處理,使用了以前所見的ProcessCtrl函數,不過私認爲這個函數的名字不是太穩當,由於其內部只是處理裏鼠標的邏輯,因此稱其爲ProcessMouseCtrl之類的名字可能更適合,再者,既然單獨分離出了一個鼠標控制的函數,那麼其餘類型的控制爲什麼也不依勢分離一下呢?譬如來個ProcessKeyboardCtrl,固然,這僅是一家之言 )

 

    // Handle mouse

 

       bool bLDown = hge->Input_GetKeyState(HGEK_LBUTTON);

       bool bRDown = hge->Input_GetKeyState(HGEK_RBUTTON);

   

    // 若是鎖定控件不爲空

       if(ctrlLock)

       {

              ctrl=ctrlLock;

        // 若是鼠標左鍵和右鍵都不爲空,則置鎖定控件爲空

              if(!bLDown && !bRDown) ctrlLock=0;

        // 使用ProcessCtrl處理鼠標按鍵

              if(ProcessCtrl(ctrl)) return ctrl->id;

       }

       else

       {

              // Find last (topmost) control

 

              ctrl=ctrls;

              if(ctrl)

            // 獲取最後一個控件元素

                     while(ctrl->next) ctrl=ctrl->next;

 

              while(ctrl)

              {

            // 若是鼠標位於控件區域內而且控件使能

                     if(ctrl->rect.TestPoint(mx,my) && ctrl->bEnabled)

                     {

                // 從新設置ctrlOver控件

                            if(ctrlOver != ctrl)

                            {

                                   if(ctrlOver) ctrlOver->MouseOver(false);

                                   ctrl->MouseOver(true);

                                   ctrlOver=ctrl;

                            }

                // 調用ProcessCtrl處理鼠標按鍵

                            if(ProcessCtrl(ctrl)) return ctrl->id;

                            else return 0;

                     }

            // 處理前一個控件

                     ctrl=ctrl->prev;

              }

        // 處理完畢以後,將ctrlOver控件置爲空

              if(ctrlOver) {ctrlOver->MouseOver(false); ctrlOver=0;}

 

       }

 

  呼,終算是將hgeGUIUpdate函數講完了,不過咱們還不能休息,由於目前講述的兩個類還不能讓咱們在屏幕上組織圖形界面(不要忘了hgeGUIObject仍是個虛基類!),咱們必須手工編寫派生自hgeGUIObject的子類,並重載那些hgeGUIObject中留下的虛擬函數,看來工做量不小啊……幸而HGE爲咱們已經編寫好了四個派生自hgeGUIObject的子類,他們分別是:hgeGUIText(文本)、hgeGUIButton(按鈕)、hgeGUISlider(滾動條)以及hgeGUIListbox(列表框),限於篇幅在此不能所有講述,但因爲其間原理大同小異,因此讓咱們先細看看其中比較簡單的hgeGUIText,相信其餘三個的類亦能舉一反三 :)

 

  類名hgeGUIText

  功能:文本類

  頭文件hge/hge181/include/hgeguictrls.h

  實現文件hge/hge181/src/helpers/hgeguictrls.cpp

 

  首先天然是頭文件的聲明:

 

class hgeGUIText : public hgeGUIObject

{

public:

       hgeGUIText(int id, float x, float y, float w, float h, hgeFont *fnt);

    // 設置對其方式

       void               SetMode(int _align);

    // 設置文本內容

       void               SetText(const char *_text);

    // 不定參數形式的輸出函數

       void               printf(const char *format, ...);

    // 渲染函數

       virtual void     Render();

 

private:

    // 使用hgeFont進行渲染

       hgeFont*        font;

    // 文本顯示的起始座標

       float               tx, ty;

    // 對其方式

       int                        align;

    // 文本緩衝區

       char               text[256];

};

 

  「清澈見底」 :)實現也是分外清明:

 

// 簡單的構造函數,仍是使用成員初始化列表更好一些 :)

hgeGUIText::hgeGUIText(int _id, float x, float y, float w, float h, hgeFont *fnt)

{

       id=_id;

       bStatic=true;

       bVisible=true;

       bEnabled=true;

       rect.Set(x, y, x+w, y+h);

 

       font=fnt;

       tx=x;

    // 用於居中顯示

       ty=y+(h-fnt->GetHeight())/2.0f;

 

       text[0]=0;

}

 

// 設置對其模式,注意座標的設置

void hgeGUIText::SetMode(int _align)

{

       align=_align;

       if(align==HGETEXT_RIGHT) tx=rect.x2;

       else if(align==HGETEXT_CENTER) tx=(rect.x1+rect.x2)/2.0f;

       else tx=rect.x1;

}

 

// 僅是一個strcpy,應該加個inline :)

void hgeGUIText::SetText(const char *_text)

{

       strcpy(text, _text);

}

 

// 使用vsprintf完成任務,一樣應該加個inline :)

void hgeGUIText::printf(const char *format, ...)

{

       vsprintf(text, format, (char *)&format+sizeof(format));

}

 

// 依舊簡單,設置顏色,而後渲染

void hgeGUIText::Render()

{

       font->SetColor(color);

       font->Render(tx,ty,align,text);

}

 

  好了,至此hgeGUI部分也算是講解完畢了,一路上並不輕鬆,可是也未有遇到多大的羈絆,不過相信你們對於HGEGUI系統應該有了一些認識,另一提的即是,HGE自帶的教程(tutorials)中,tutorial6正好是關於GUI的一個示例,有興趣的朋友能夠看一看 :)

  OK,就此停筆了,我以爲我說的已經夠多,再說一下去本身都要受不了了,不過呢,最後我仍是得照例來上一句:下次再見嘍 :)

本文同步分享在 博客「tkokof1」(CSDN)。
若有侵權,請聯繫 support@oschina.cn 刪除。
本文參與「OSC源創計劃」,歡迎正在閱讀的你也加入,一塊兒分享。

相關文章
相關標籤/搜索