Advertisement

Custom GUI source/design docs/articles/whatever.

Started by July 01, 2000 07:02 AM
16 comments, last by Staffan 24 years, 5 months ago
Houdini, how are you handling callbacks? If you have a window that has a ''close'' button, do you have to create a class for that button, or can you just use the generic ButtonWidget class? I''ve seen a few implementations where people have had to create a new class for each button. I''ve made it so that you only need a new WindowWidget (where all the bulk of the work goes, anyways) and the ButtonWidget pushes the whenclickedon()function call back to the parent. Staffan, I would make sure when you implement your GUI that you do something similar..
sphet:
the code i listed above was designed to be independent of windows (i wanted a gui that plugged into directX, game sprockets and GGI etc). So for that i translate the messages from the GDI window. I have, however, written a windows custom GUI before.

basically, i maintained a list of all HWND to object pointer mappings. I used a static callback function, which then looked up the applicable class.

the nice side effect was that i could use the list to destroy the windows on exit... so i could just whack new windows around the place and not worry about cleaning them up.

hmm... i can''t seem to find the source to that. how annoying. if i find it i''ll post it.
Advertisement
i found the source:

(surplus stuff cut out)
header file:
        class zSimpleWindow : public Layer1Component {	static LRESULT CALLBACK staticWndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam);	static WinComList*	rl;};[/source]in the .cpp file:[source]WinComList* zSimpleWindow::rl = 0;LRESULT CALLBACK zSimpleWindow::staticWndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam) {	if (rl) {		// search for the window in the window lists		zSimpleWindow* p = (zSimpleWindow*)rl->find(hWnd);		if (p) p->WndInterface(uMsg, wParam, lParam);	}	return DefWindowProc(hWnd, uMsg, wParam, lParam);}        



okay, this is quite old source, so ignore the variable names (4 years ago)

(edited to fix a typo)

Edited by - zenic on July 5, 2000 8:13:23 PM
Ok so you wan''t the idea''s behind it not the graphics part? Well what I did is have a procude you can call that will paint a button of any size built out of 16x16 button "blocks". Ok how about I just stick in by button code: (Sorry folks in assembler! But some C programmer have requested that I do post asm code anyhow and you guess can convert yourself)
        ;"Start new game"        mov TmpRect.top, 200        mov TmpRect.left, 240        m2m TmpRect.right, TmpRect.left        add TmpRect.right, 320        m2m TmpRect.bottom, TmpRect.top        add TmpRect.bottom, 42        invoke MouseInClipper, TmpRect        .if eax == FALSE                invoke PaintButton, 240, 200, 20, 3, 1                RGB24BIT 255,255,255                invoke DD_Draw_Text,lpddssysback,ADDR NGMenuItem1, 13, 300, 214, eax, 0        .else            mov al, mouse_state.rgbButtons[0]            and al, 80h            .if al == NULL                invoke PaintButton, 240, 200, 20, 3, 1                RGB24BIT 0,0,255                invoke DD_Draw_Text,lpddssysback,ADDR NGMenuItem1, 13, 300, 214, eax, 0            .else                invoke PaintButton, 240, 200, 20, 3, 0                RGB24BIT 0,0,255                invoke DD_Draw_Text,lpddssysback,ADDR NGMenuItem1, 13, 302, 216, eax, 0                mov MenuChoice, MENUBUTTON1            .endif        .endif

Oh yah mouse_state is my DIMOUSESTATE structure returned from reading the mouse, AbsoluteMouseX and AbsoluteMouseY are my absolute MousePos'' and MenuItem1 is a string (the caption of the button). And PaintButton is a procedure to paint the buttons, it uses the x pos to paint buttin, y pos to paint button, width in 16pixel units, hieght in 16 pixel units, state (Up=1 or Down=0) And the button is always painted on my backbuffer.


So basically I set up a rect for the size of the button. Check to see if the mouse pos is in that rect. If it isn''t then paint the button up, with white text. If the mouse is in that rect paint then I check if the mouse button is down or not. If the mouse button is not clicked but the cursor is in the rect paint the button with red text up(Note that RGB macro is fliped, it''s really BGR) However if the mouse is in the rect with the mouse down, I draw it down with red text.. The next loop iteration when I check that MenuChoice Variable I''ll do a screentransition and change game states...

The reason I have this is I can add more or less button just by copying this code more or less time and adjusting a few variables in there, I know it''s not scriptable but it''d simple. Rember menu''s don''t have to be SUPER duper incredibly efficient cause there menu''s, It''s only a few blits per frame, I mean I''ve got one full screen blit for the background and a few lines of code per button (above) with some painting code for the buttons. I always get 60fps in menu''s.

I guess whatever works for you is good, so long as you can add new/more menu''s easily and can maintain a good frame rate (simplicity) your set!
See ya,
Ben
__________________________Mencken's Law:"For every human problem, there is a neat, simple solution; and it's always wrong."
"Computers in the future may weigh no more than 1.5 tons."- Popular Mechanics, forecasting the relentless march of science in 1949
Looks like you''re all doing it pretty much as I am. For the sake of it I will too paste my source code. I didn''t implement input or more complex output yet, however.

    class CWindow;class CControl;class IWindow{	friend class CWindow;	friend class CControl;public:	IWindow();	~IWindow();	virtual void Init() { }	static IWindow* CreateWindow(IWindow* parent);		// ...bool b_sysmodal	static IWindow* CreateControl(IWindow* parent, const type_info& ti) { return CreateControl(parent, ti.raw_name()); }	static IWindow* CreateControl(IWindow* parent, const char* raw_name); // I need the RTTI stuff for loading it from disk later - it''s just experimental for now	static IWindow* LoadWindow(const char* szfilename, ccabinet* cab, IWindow* parent);	bool SaveWindow(const char* szfilename);	static bool DestroyWindow(IWindow* w) { return w->p_parent->DeleteChildWindow(w); }	static bool DestroyChildWindow(IWindow* w);	bool DestroyAllChildren();	void ClientToScreen(RECT* lprect);	void ScreenToClient(RECT* lprect);	void ScreenToWindow(RECT* lprect);	void WindowToScreen(RECT* lprect);	void ClientToScreen(POINT* pt);	void ScreenToClient(POINT* pt);	void ScreenToWindow(POINT* pt);	void WindowToScreen(POINT* pt);	void GetWindowRect(RECT* lprect);	virtual void GetClientRect(RECT* lprect)  { GetWindowRect(lprect); }	IWindow* GetParent() { return p_parent; }	static IWindow* GetRoot() { return &root }	virtual bool IsActive() { return (p_active == this) ? true:false; }	bool HasInputFocus() { return (p_input == this) ? true:false; }	void SetInputFocus() { SetInputFocus(this); }	void SetInputFocus(IWindow* w) { p_input = w; }	virtual bool SetFocus();	void SetActive() { SetActive(this); }	virtual void SetActive(IWindow* w) { p_active = w; }	IWindow* GetActiveWindow() { return p_active; }	bool SetZ(long z);	long GetZ() { return z_order; }	void SetCoords(const RECT* lprect) { if(this == &root) return; memcpy(&coords, lprect, sizeof(RECT)); }	void SetCoords(long x, long y, long w, long h) { coords.left = x; coords.top = y; coords.right = coords.left + w; coords.bottom = coords.top + h; }	void GetCoords(RECT* lprect);	void ShowWindow(bool s) { b_visible = true; }	static IWindow* PointToWindow(POINT* pt) { return root._PointToWindow(pt); }	long GetChildCount() { return l_nodes; }	IWindow* GetChild(long z) { if(z >= l_nodes) return 0; return nodes[z]; }	void SetSize(SIZE* size) { coords.right = coords.left + size->cx; coords.bottom = coords.top + size->cy; }	void GetSize(SIZE* size) { size->cx = coords.right - coords.left; size->cy = coords.bottom - coords.top; }	// todo: SetPosition();	// todo: GetPosition();	static void Process();		// *	static bool Render() { return root._Render(); }protected:	bool AddChild(IWindow* w);	bool RemoveChild(IWindow* w);	void Sort();	virtual bool RenderWindow() { return false; }	void ClipToParent(RECT* lprect);	IWindow* _PointToWindow(POINT* pt);	bool _Render();	//virtual void _Process() {}	bool SaveWindow(cofstream* out);	bool DeleteChildWindow(IWindow* w);	virtual bool SavePropertiesToDisk(cofstream* f) { return false; }	virtual bool LoadPropertiesFromDisk(cifstream* f) { return false; }	long z_order;						// our current z-order. higher number means higher position	long l_nodes;						// number of nodes/children	vector<IWindow*> nodes;		IWindow* p_parent;	IWindow* p_active;	IWindow* p_input;	bool b_visible;	bool b_ctrl;	RECT coords;	static IWindow* LoadWindowFromDisk(cifstream* f, IWindow* parent);	static IWindow root;};class CWindow : public IWindow{public:	virtual void GetClientRect(RECT* lprect);protected:	virtual bool RenderWindow();	virtual bool SavePropertiesToDisk(cofstream* f);	virtual bool LoadPropertiesFromDisk(cifstream* f);};class CControl : public IWindow{public:	virtual bool SetFocus();	virtual bool IsActive() { return HasInputFocus(); }	virtual void SetActive(IWindow* w) { SetInputFocus(); }protected:	virtual bool RenderWindow();	virtual bool SavePropertiesToDisk(cofstream* f);	virtual bool LoadPropertiesFromDisk(cifstream* f);};    


"Paranoia is the belief in a hidden order behind the visible." - Anonymous
Sphet, it seems like we are doing pretty much the same thing. All buttons, edit boxes, and combo boxes (when the combo boxes get implemented anyways ) need an ID number (it will default to ID_DEFAULT if none was supplied) when creating them. Then when a button is pushed, it sends a MSG_BUTTwhenclickedonED message with the button ID its parent. The parent then would process the OnButtwhenclickedoned function and include a switch statement for the IDs of all buttons the window wishes to process.
- Houdini
Advertisement
Houdini:

I''m not using an ID. It works kind of like this:

In WindowWidget.Create():

pTempButton = new CGUIButton;
pTempButton->Create(this); // child of this window
pTempButton->SetRect(0,0,10,10); // top left corner of window
pTempButton->SetCallback(BUTTON_CLICK, OnCloseButton);


In ButtonWidget.OnLeftButtonUp()

if (( ... click conditions ) && pParent != NULL)
PerformCallback(BUTTON_CLICK, pParent); // call the callback in the parent''s context

Since the button is a child of the window (by Create()) I don''t need to keep a member variable pointing to it.

And since I set the BUTTON_CLICK callback to OnCloseButton, I don''t even have to care about how the ButtonWidget does its work as long as when its clicked it calls my OnCloseButton() function ..

Seems to work pretty well and doesn''t need any IDs or anything -- all pointer magic.

Figuring out the class-function-pointers and such is a bit of a pain..
Sphet, I like the way you are doing it. Shoulda thought of that myself =).

I think I''ll implement that into my GUI as well, thanks.
- Houdini

This topic is closed to new replies.

Advertisement