I thought this would be an opertune time to demonstrate how a menu system can get out of control. The concept I had going here was very solid, but through coding I ended up adding a lot of functionality "hacks" that made the system kinda combursom.
EVENTS: drive everything , including the UI so, heres the class header
Code:
////////////////////////////////////
// event.h
// game processes class file
// all entities that require some sort of
// processing or handling inherit the event object
#ifndef PR_EVENT
#define PR_EVENT
#include "global.h"
// Objects that need to be managed will
// inherit this as base class
class CEventNode {
friend class CEventList;
public:
CEventNode();
~CEventNode();
void setThread(CThread *thread);
virtual bool process(CEventList *owner) = 0; // if a process returns true it was handled and should be removed
// else it remains in teh que for the next game tick
protected:
__int64 spawn; // onlything important relavent to all processes is when it was spawned
// anything else is defined in subclasses
static long int instanceCount;
long int id; // event id - I have no idea of the potential ammount of events in a single game long should be fine
CEventNode * next; // points to next event to be processed
CEventNode * prev; // points to prev event (easier removal of nodes)
CThread *thread; // thread spawning
};
class ListCleanUp : public CEventNode { // a node that on process tells the managing list it's empty
public:
ListCleanUp(bool fullclean){ this->fullclean = fullclean; };
~ListCleanUp() {};
bool process(CEventList *owner);
private:
bool fullclean;
};
// Special CEventNode() that handles setting or removing a CThread
// depreciated ?
class CEventNodeThreader : public CEventNode {
public:
CEventNodeThreader();
~CEventNodeThreader();
bool process(CEventList *owner);
private:
CThread *thread;
};
// event/object managers will
// inherit this as base class
class CEventList {
public:
CEventList();
~CEventList();
public:
void add( CEventNode *e );
void empty(bool really);
void ignore();
void remove( long int id );
void setOwner(CThread *owner);
void spawnThread(CThread *thread);
int size();
bool handle();
private:
CThread *owner;
CEventNode *head;
CEventNode *tail;
bool ignoreEvents;
};
// not the traditional thread in multithreading
// but more along teh lines of a guide line strand.... right.
class CThread {
public:
CThread();
~CThread();
bool Run();
void setProcedure(CThread *thread); // sets a procedure
void removeProcedure();
protected:
CThread * pActiveProcedure;
CEventList list;
public:
char * name;
};
#else
#endif
UI: Header file
[code]
///////////////////////////
// UI.h
// user interface header file
#ifndef PR_UI
#define PR_UI
#include "global.h"
#include "font.h"
// The functionality of the User Interface is such that
// at runtime the main windows thread spawns our top level
// menu. This top level menu is a UIMain object, that spawns it's own thread
// at this point the main window loop is waiting for this thread to return(finish)
// The menu is a running thread with a list of events that need to be processed before
// it can return to the main window loop and thus exit the application.
// the list contains UIMenuItem objects that inherit EventNode, the list is cycled through
// and the menu item performs as defined, some items might actually call for a new UIMenu
// to spawn create a sub menu - at this point the winmain thread is waiting for the Main menu,
// witch is now waiting on the submenu, the submenu then has it's own event list that needs to
// be processed before it returns. Generally an Exit menu item will be the trigger to end the
// event list and collapse back into teh calling thread. A UIMenuItem will also be capable of
// spawning a GameThreadEvent witch up can return to the calling menu several methods - manual
// escape, winning or losing - etc.
class UIMenuItem;
class UIMain : public CThread {
public:
UIMain();
~UIMain();
void addItem(UIMenuItem *item);
// thread process info
private:
// uhhhhhhh stuff
};
// UIMenuItem is an abstract interface for menu items
// that range from strings, to media items
// menu items actually decide if they are being
// interacted with by the user rather then the menu
// manager class doing so.
class UIMenuItem : public CEventNode {
public:
UIMenuItem();
~UIMenuItem();
// rather then obtain this global information manualy in each function,
// a interface method is being implimented in case at some future point
// i want this info obtain through strict process by authorized class managers
// this saves time for redoing the process in teh long run.
sKey getKey(int key); // checks global key structure for key activity
sMouse getMouse(); // grab mouse info from global structure
void setColor(float r, float g, float b);
void setPos(int x, int y);
void setSpawnEvent(CEventNode *e);
bool hotSpot(int textx, int testy);
protected:
// uhhh other stuff
CEventNode * spawn; // an event that spawns as a result of this thing being handled
int x, y; // screen position;
int width, height;
sRGB color;
};
// UIItemStaticText
// simple text lables
class UIItemStaticText : public UIMenuItem {
public:
UIItemStaticText();
~UIItemStaticText();
bool process(CEventList *owner);
public:
void setFont(char *name, int size);
void setString(char *string);
protected:
char *string;
GLFONT *pFont;
};
// UIItemTextOption
// text item that interacts with user
class UIItemTextOption : public UIItemStaticText {
public:
UIItemTextOption();
~UIItemTextOption();
bool process(CEventList *owner);
void setInteractColors(sRGB base, sRGB hightlight, sRGB down);
private:
sRGB highlightColor;
sRGB downColor;
};
#else
#endif
[/quote]
UI: function definitions
Code:
#include "global.h"
UIMain::UIMain() {};
UIMain::~UIMain() {};
void UIMain::addItem(UIMenuItem *item) {
list.add(item);
};
// thread process info
UIMenuItem::UIMenuItem() {
this->spawn=0; // nothing by default
};
void UIMenuItem::setSpawnEvent(CEventNode *e) {
this->spawn = e; // set an event to be triggered.
};
UIMenuItem::~UIMenuItem() { };
sKey UIMenuItem::getKey(int key) {
// this needs protection data entry in place
// actually at this point its only read data
// should be protected when writing
return keys[key];
};
sMouse UIMenuItem::getMouse() {
// read data
return mouse; // this needs protection data entry in place
};
// set render position top left of an item
void UIMenuItem::setPos(int x, int y) {
this->x = x;
this->y = y;
};
// set the color option
void UIMenuItem::setColor(float r, float g, float b) {
this->color.r = r;
this->color.g = g;
this->color.b = b;
};
// test a pixel position against rectangle hotspot
bool UIMenuItem::hotSpot(int testx, int testy) {
if(testx > x && testx < x+width)
if(testy > y && testy < y+height)
return true;
return false;
};
// constructor for static text items
// defaults to arial, size 14, "Menu Item" color black
UIItemStaticText::UIItemStaticText() {
string = "Menu Item";
this->pFont = new GLFONT("Arial", 14);
this->height = 14;
this->x = 20;
this->y = 20;
sRGB c = { 0, 0, 0 };
this->color = c;
this->width = pFont->stringWidth(string); // calculate size of default
};
UIItemStaticText::~UIItemStaticText() {
};
// Static text is kind of a "permanent" event.
// such that it is never handled and removed from the list
// for this reason in order to exit from any thread that
// contains a permanent event, a special exit event must
// be spawned and processed in order to tell the thread to
// forcefully empty it's event list and return true;
bool UIItemStaticText::process(CEventList *owner) {
glColor3f(color.r, color.g, color.b);
pFont->display(x, y, string);
return false;
};
// set the font and size of static text item
void UIItemStaticText::setFont(char *name, int size) {
this->pFont = new GLFONT(name, size);
this->height = size;
this->width = pFont->stringWidth(string); // calculate size of new font
};
// set teh string, of text.
void UIItemStaticText::setString(char* string) {
this->string = string;
this->width = pFont->stringWidth(string); // calculate size of new string
};
// set default highlight and down colors
// initialize default string and font
UIItemTextOption::UIItemTextOption() {
sRGB h = { 1.0f, 0.0f, 0.0f }; // default highlight color is red
this->highlightColor = h;
sRGB d = { 1.0f, 1.0f, 0.0f };
this->downColor = d;
};
UIItemTextOption::~UIItemTextOption() {
};
// set colors for the text options, unfocused, focused and down
void UIItemTextOption::setInteractColors(sRGB base, sRGB highlight, sRGB down) {
this->highlightColor = highlight;
this->downColor = down;
this->color = base;
};
bool UIItemTextOption::process(CEventList *owner) {
sMouse mpos = getMouse();
font->display(this->x - 30, this->y, "width: %i", width);
font->display(this->x + width, this->y, "|<- end|");
font->display(10.0f, 30.0f, "MB: %i, Mx: %i, My: %i, RECT(%i, %i, %i, %i)", mpos.button, mpos.x, mpos.y, this->x, this->y, this->x+this->width, this->y+this->height);
if(this->hotSpot(mpos.x, mpos.y+this->height)) { // option have focus?
glColor3f(this->highlightColor.r, this->highlightColor.g, this->highlightColor.b);
if(!this->hotSpot(mpos.prevx, mpos.prevy)) // fist time having focus?
{
//ADDME: play mouse over sound
}
if(mpos.button == 1) {
glColor3f(this->downColor.r, this->downColor.g, this->downColor.b);
//ADDME: play mouse over sound
//ADDME: spawn some event
if(this->thread != 0) // if there is a thread to spawn
owner->spawnThread(this->thread); // tell the list to spawn this thread
if(this->spawn != 0) // if there is an event to spawn
owner->add(this->spawn); // tell the list to spawn this event
pFont->display(x, y, string);
return false;
}
pFont->display(x, y, string);
} else { // option does not have focus
glColor3f(color.r, color.g, color.b);
pFont->display(x, y, string);
}
return false;
};
And actually using the UI in the Initialize()
Code:
// menu / process testing
MainMenu = new UIMain();
MainMenu->name = "Main Menu";
// make a menu item
// set properties
UIItemStaticText *item = new UIItemStaticText();
item->setString("Hyperspace 2005");
item->setFont("Times New Roman", 20);
item->setColor(0.25f, 0.50f, 0.75f);
item->setPos(g_winWidth/2, g_winHeight/2);
// add it to the menu
MainMenu->addItem(item);
UIItemTextOption *item2 = new UIItemTextOption();
item2->setString("Start A New Game");
//item2->setInteractColors();
item2->setFont("Verdana", 20);
item2->setPos(g_winWidth/2, (g_winHeight/2)+22);
// add it to teh menu
MainMenu->addItem(item2);
item2 = new UIItemTextOption();
item2->setString("Another menu item.");
item2->setFont("Arial", 20);
item2->setPos(g_winWidth/2, (g_winHeight/2)+44);
MainMenu->addItem(item2);
UIItemTextOption *exit = new UIItemTextOption();
exit->setString("Exit");
exit->setFont("Arial", 18);
exit->setColor(0.01f, 0.01f, 0.8f);
exit->setPos(g_winWidth/2, (g_winHeight/2)+66);
exit->setSpawnEvent(new ListCleanUp(1));
MainMenu->addItem(exit);
// Sub menu test
UIMain *subMenu = new UIMain();
subMenu->name = "Sub Menu";
UIItemStaticText *subitem = new UIItemStaticText();
subitem->setString("This is a submenu!");
subitem->setFont("Times New Roman", 18);
subitem->setPos(g_winWidth/2, (g_winHeight/2));
subMenu->addItem(subitem);
UIItemTextOption *subitem2 = new UIItemTextOption();
subitem2->setString("Exit");
subitem2->setColor(0.3f, 0.9f, 0.4f);
subitem2->setPos(g_winWidth/2, (g_winHeight/2)+22);
subitem2->setFont("Sans Sarif", 20);
subitem2->setSpawnEvent(new ListCleanUp(0));
subMenu->addItem(subitem2);
// assign sub menu, to a button in main menu
item2->setThread(subMenu);
// officially associate the main menu as the primary game thread
GameThread = MainMenu; // set the game thread pointer to teh MAIN Menu