Object oriented GUI concept

Started by
17 comments, last by kloffy 18 years ago
UGH!

I would NOT extend it that way. Having a list, calling every single callback in every signle object just to trigger the one that is supposed to be? Random ints as event types?

Not good ideas. And what's this fixation with listeners? Your UI is going to need to determine the topmost object(s) in order to direct input to the correct object. How's it going to do that with arbitrary listener lists? How are the Actions going to determine if they're actually the top if they just recieve an event?

No, this is the sort of thing that I've done to extend it.

First, you determine the 4 basic input types based on parameters. Keyboard [key, modifier], Character [char], Mouse [event, x, y, modifier], and Mousewheel [delta, x, y, modifier]. Key, Modifier, and MouseEvent are all enumerations [Modifier is a flag for shift/alt/ctrl].

Next, you create an interface to query input bindings, accept new bindings, and control some utility flags. The utility flags I use are:

PassUp - Should a mouse event on this UI object consider it invisible.
CharInput - Flag to consider keyboard input as text. [so you don't have to bind every single key to get some text, just the char functor]
kbOpaque - Flag to catch [and discard] all keyboard input if it has not been dealt with yet.

Now the UI has an interface to use to determine where to direct input.

Finally, you create some concrete versions of that interface. One that stores no functors, and responds to queries accordingly. A few that handle only certain input, and one that handles all. For the actual storage, it's pretty simple. std::map does simple mapping for modifier -> functor-mapping. The functor mapping is again just a std::map for key or mouseevent -> functor.

Every UIobject is given an interface for input queries [defaults empty]. The actual input handling code then can easily determine which object has the key bound that it's looking for and trigger the action accordingly.

Advertisement
Hi Kloffy!

I was just about to ask an almost identical question on this forum when I saw your post. Very interesting....

I'm not going to get involved with the great OOP best practise debate but I would suggest looking at boost signals for your callback method.
Boost.Signals

Good Luck.

Simon
Just when I thought I knew which way I'm gonna do it, there comes another bunch of suggestions... :)
I'll have another look at boost. Last time I used it for serialization, I wasn't too happy with it because it was rather slow. But that's probably just the serialization part of it.
Quote:Original post by Telastyn
... calling every single callback in every signle object just to trigger the one that is supposed to be?...


erm,sorry,but you got me wrong there (last line of my post)

the UI would only call the Listeners of the Button(or whatever) in focus
not every single object in the hierarchy


PS.:

about the "int" event:
I wouldn't use an int type either, but kloffy's question was about getting some ideas, not concrete implementations right? ;)

cheers

[Edited by - ze moo on April 7, 2006 2:17:35 AM]
I've got another question. I thinking about using Interfaces like IRenderable or IInteractive. For example CButton would be IRenderable and IInteractive, a textbox or an image would be IRenderable only, etc...

class IRenderable{   // render   virtual void render(){};}class IInteractive{   // bind functions to events   virtual void bind(unsigned int _eventtype, boost::function<void()>* _function){ functions[_eventtype] = _function; }   // handle events   virtual void handle_event(unsigned int _eventtype){ (*functions[_eventtype])(); }private:   std::map<unsigned int, boost::function<void()>* > functions;}class CGUIObject{   // GUI object base class}class CButton : public CGUIObject, public IRenderable, public IInteractive{public:   // everything a button needs}class CGame{public:   CGame()   {      CButton* pause = new CButton();      pause->bind(/*ON_CLICK_EVENT or something like that*/, new boost::function<void()>(boost::bind(&CGame::pause, this)));      objects["Pause"] = pause;   }   ~CGame()   {   }   bool execute()   {      // this is the tricky part   }   void pause()   {      /*...*/    }private:   std::map<string, CGUIObject*> objects;}

The problem is finding out whether an object is IRenderable or IInteractive later on.

Again, I've thought of two ways, but I'm not satisfied with either one of them. The first one would use a dynamic cast:

// same class setup as aboveclass CGame{public:   CGame()   {      CButton* pause = new CButton();      pause->bind(/*ON_CLICK_EVENT or something like that*/, new boost::function<void()>(boost::bind(&CGame::pause, this)));      objects["Pause"] = pause;   }   ~CGame()   {   }   bool execute()   {      // this is the tricky part      std::map<string, CGUIObject*>::iterator itGUIObject;        for(itGUIObject = objects.begin(); itGUIObject != objects.end(); itGUIObject++)      {         IInteractive* interact = dynamic_cast<IInteractive*>(itGUIObject->second);         if(interact)         {            interact->handle_event(eventtype);         }      }   }   void pause()   {      /*...*/    }private:   std::map<string, CGUIObject*> objects;}

Now, I know this is bad, because dynamic cast is slow. So I came up with another possibility:

class IRenderable{   // render   virtual void render(){}   // helper   virtual bool is_renderable(){ return true; }}class IInteractive{   // bind functions to events   virtual void bind(unsigned int _eventtype, boost::function<void()>* _function){ functions[_eventtype] = _function; }   // handle events   virtual void handle_event(unsigned int _eventtype){ (*functions[_eventtype])(); }   // helper   virtual bool is_interactive(){ return true; }private:   std::map<unsigned int, boost::function<void()>* > functions;}class CGUIObject{   // GUI object base class   virtual bool is_renderable(){ return false; }   virtual bool is_interactive(){ return false; }}class CButton : public CGUIObject, public IRenderable, public IInteractive{public:   // everything a button needs}class CGame{public:   CGame()   {      CButton* pause = new CButton();      pause->bind(/*ON_CLICK_EVENT or something like that*/, new boost::function<void()>(boost::bind(&CGame::pause, this)));      objects["Pause"] = pause;   }   ~CGame()   {   }   bool execute()   {      // this is the tricky part      std::map<string, CGUIObject*>::iterator itGUIObject;        for(itGUIObject = objects.begin(); itGUIObject != objects.end(); itGUIObject++)      {         if(itGUIObject->second->is_interactive())         {            ((IInteractive*)(itGUIObject->second))->handle_event(eventtype);         }      }   }   void pause()   {      /*...*/    }private:   std::map<string, CGUIObject*> objects;}

However, I don't think this is a very nice solution either. This is pretty basic OOP stuff and I feel kinda stupid, because I can't work it out on my own.
To my knowledge, that's pretty much it. Such type detection is the downside to that sort of composition of interfaces.
I see, no wonder that I couldn't come up with an elegant way to do it...

Btw.:
The second solution doesn't work the way I posted it. Since I store CGUIObject* in my map, a call to objects["Pause"]->is_renderable() always returns false, since that's the value of is_renderable() in CGUIObject.
(And ((CButton*)objects["Pause"])->is_renderable() fails, because it's ambiguous.)

However, it can be done using this setup:
             ____ CObject ____            /        |        \           ./         |         \.  IInteractive  CGUIObject   IRenderable   (virtual)    (virtual)     (virtual)           \         |         /            \_____CButton_____/


This is what it would look like in code:

#include <iostream>using namespace std;class CObject {  public:	  virtual bool is_renderable()	{ return false; }	  virtual bool is_serializable(){ return false; }	  virtual bool is_interactive()	{ return false; }  };class CRenderable: public virtual CObject {  public:	  virtual bool is_renderable()	{ return true; }  };class CSerializable: public virtual CObject {  public:	  virtual bool is_serializable(){ return true; }  };class CInteractive: public virtual CObject {  public:	  virtual bool is_interactive()	{ return true; }  };class CGUIObject: public virtual CObject {  public:  };class CButton: public CInteractive, public CRenderable, public CGUIObject {  public:  };class CText: public CRenderable, public CGUIObject {  public:  };int main () {  CGUIObject* obj1 = new CButton();  CGUIObject* obj2 = new CText();  CGUIObject* obj3 = new CGUIObject();  cout << "Button\n" << obj1->is_renderable() << '\n' << obj1->is_serializable() << '\n' << obj1->is_interactive() << '\n';   cout << "Text\n" << obj2->is_renderable() << '\n' << obj2->is_serializable() << '\n' << obj2->is_interactive() << '\n';   cout << "GUIObject\n" << obj3->is_renderable() << '\n' << obj3->is_serializable() << '\n' << obj3->is_interactive() << '\n';  system("PAUSE");  return 0;}

What do you think? Good, bad, horrible?
Thats an excellent idea btw. I was thinking about using multiple inheritance in my 3d editor project as well to seperate reduntant implementations that were involved with single inheritance designs.

In my case I want to seperate the data representation from the data manipulation in a good oop fashion.


http://www.8ung.at/basiror/theironcross.html
Quote:Original post by Basiror
Thats an excellent idea btw. I was thinking about using multiple inheritance in my 3d editor project as well to seperate reduntant implementations that were involved with single inheritance designs.

In my case I want to seperate the data representation from the data manipulation in a good oop fashion.

Thanks! I'm glad to hear you like my idea. The only unfortunate thing about multiple inheritance is that your not going to get around using dynamic_cast if you want to downcast. (Explanation)

Edit: Actually, nevermind. There might not be any need for casting after all.

#include <iostream>#include <typeinfo>using namespace std;#pragma warning(disable:4250)class CRenderable;class CSerializable;class CInteractive;class CObject {  public:	  virtual CRenderable*   get_renderable()   { return NULL; }	  virtual CSerializable* get_serializable() { return NULL; }	  virtual CInteractive*  get_interactive()  { return NULL; }  };class CRenderable: public virtual CObject {  public:	  virtual void render() {}	  virtual CRenderable*   get_renderable()   { return this; }  };class CSerializable: public virtual CObject {  public:	  virtual CSerializable* get_serializable() { return this; }  };class CInteractive: public virtual CObject {  public:	  virtual CInteractive*  get_interactive()  { return this; }  };class CGUIObject: public virtual CObject {  public:  };class CButton: public CInteractive, public CRenderable, public CGUIObject {  public:       virtual void render() { cout << "Look at me, I'm a Button!\n\n"; }  };class CText: public CRenderable, public CGUIObject {  public:  };void print_info(CGUIObject* obj){  cout << typeid(*obj).name() << "\n";  cout << "Renderable:   " << obj->get_renderable() << "\n";  cout << "Serializable: " << obj->get_serializable() << "\n";  cout << "Interactive:  " << obj->get_interactive() << "\n\n"; };int main () {  CGUIObject* obj1 = new CButton();  CGUIObject* obj2 = new CText();  CGUIObject* obj3 = new CGUIObject();  print_info(obj1);  print_info(obj2);  print_info(obj3);  if(obj1->get_renderable())  {	obj1->get_renderable()->render();  }  system("PAUSE");  return 0;};


[Edited by - kloffy on April 12, 2006 6:18:50 AM]

This topic is closed to new replies.

Advertisement