Class Architecture: Menus and Menu Items

Started by
9 comments, last by szecs 14 years, 1 month ago
I'm trying to figure out a nice way to represent menus (just a collection of options for the user to select.) and menu items in my game. Right now I have a screen manager that has a stack of objects that implement the interface IGameScreen. IGameScreen just requires the basics like an Update, Render, Initialize and Cleanup methods. I was thinking of creating a class that implements this interface but has a map of strings to function pointers to link the string representation of a menu item to what happens when you select it. However, if I were to do this, I don't know where the actual functions should live. What if I want a function to be able to send a message up the object hierarchy to tell the Screen Manager this screen needs to be removed. (I could possibly have a flag on each screen object that the Screen Manager checks, actually.) Another solution I was pondering is to create a class MenuScreen that implements IGameScreen and has a vector of objects of type MenuItem. MenuItem would have methods such as OnSelect and OnHighlight that define what happens when a user selects vs simply rolls over an item. The MenuItems would also handle their own rendering. This seems like it could be an over-compartmentalization. What seems better and why? MenuScreen class with a map from strings to function pointers, or a MenuScreen class with a vector of MenuItem objects that have their own methods for interaction.
Advertisement
Whatever... Both will likely work. It just depends on how much flexibility you need, and what the rest of your UI code uses (consistency is good).
Personally I would go for the second approach. I prefer inheritence and virtual functions over callback functions.
________________________________Blog...
Principally is separation of representation and logic a Good Thing, IMHO. That means to not use inheritance to augment MenuItem with functionality besides those necessary for the representation. Possible solutions include the aforementioned callbacks, messages, but also listeners/observers, prototypes of commands, or mediators.
Wow! Everybody is making GUIs!

Question (because I'm making one too): What does "listener" mean?
I have a system that loops through the items when the mouse is moved, to decide, which one the mouse is over. In case of click events (or mouse roll, whatever), the corresponding function of the mouse-on item executes (after some other stuff of course, windowing/whatever). What does this system called? (I don't follow tutorials or articles, so I don't know what I'm doing). Maybe immediate mode GUI?
Quote:Original post by szecs
Wow! Everybody is making GUIs!

Question (because I'm making one too): What does "listener" mean?
I have a system that loops through the items when the mouse is moved, to decide, which one the mouse is over. In case of click events (or mouse roll, whatever), the corresponding function of the mouse-on item executes (after some other stuff of course, windowing/whatever). What does this system called? (I don't follow tutorials or articles, so I don't know what I'm doing). Maybe immediate mode GUI?


seriously, im making one too.

I am representing EVERYTHING as square buttons, and a menu is just a list of buttons with text. when a specific button gets clicked, i fire an event with it's ID, then my scripting language calls the buttons Handler function using the ID and the function does whatever it is supposed to do.

A good example implementation of this (visually anyways) is Supreme Commander 2's interface. you'll notice everything fits inside a square bounds, even the arrows and such.

Its' a first iteration but it is pretty simple to implement.

------------------------------

redwoodpixel.com

I was thinking about having a function pointer (just like for the other functions) for the selection too, so you can have rectangle in most cases, but you can have any other method too. For example circle, or 2 rectangles etc.
Quote:Original post by szecs
Question (because I'm making one too): What does "listener" mean?

The Listerner concept means that an interface class exists that declares a handler for each possible event. One way is to declare such an interface for the various kinds of GUI element actions. E.g. a PushButton has a one-shot trigger action. A suitable Listerner interface may be (using C++ for demonstration here)
class TriggerListener   : public Listener {public:   virtual void onTrigger() =0;};

A CycleButton has N>1 stable states (the ToggleButton has N=2, typical for e.g. check-boxes). A suitable Listener interface may look like
class StateListener   : public Listener {public:   virtual void onStateChange(uint oldState, uint newState) =0;};


A Menu may provide a PushMenuItem, a ToggleMenuItem, and perhaps other types, but from the sight of Listener there is no difference to a PushButton or ToggleButton, resp., so the same interfaces can be used.

A client who is interested in being notified on events then implements the appropriate interface and registers an instance of that class with the event source (which is typically the GUI element itself). When a GUI element then determines that its action is actuated, it notifies all registered listeners about this by iterating the Listener instances and invoking the appropriate routine.

The advantage of such a concept (as is also of the several other concepts I've named) is that the GUI element is responsible for its representation, but the actual effect triggered when the element is actuated is externalized. IMHO an important feature of a well done GUI system!

Quote:Original post by szecs
I have a system that loops through the items when the mouse is moved, to decide, which one the mouse is over. In case of click events (or mouse roll, whatever), the corresponding function of the mouse-on item executes (after some other stuff of course, windowing/whatever). What does this system called? (I don't follow tutorials or articles, so I don't know what I'm doing). Maybe immediate mode GUI?
An immediate mode GUI doesn't have items one can iterate. Instead, there is just code. I personally think that immediate mode GUI doesn't work when the GUI should be somewhat flexible. However, there are other threads that discuss this issue in depth.

Please notice that the system you've described above does not explicitely say that the "corresponding function of the mouse-on item" actually means to run the high level functionality. I could say that it just notifies the registered Listeners, and we are on the same line. As said, IMHO the representation stuff should be done by the GUI, and all you've explicitely described above fits in that thinking.
By externalized you mean that for example the toggle doesn't toggle the assigned game variable, just itself. Then on an other function, these values are read, or discarded (ok/cancel/apply buttons). Am I right?
So that means I'm on the right track with the design.
All I have to make better is the communication between the stuff, now I have global variables (uuugh), but I guess function pointers with a pointer to an item (mostly itself, I use C, so the concept of "this" is a bit hard for me yet) as argument would suffice.
I started a thread on my current GUI BTW.
Quote:Original post by szecs
By externalized you mean that for example the toggle doesn't toggle the assigned game variable, just itself. Then on an other function, these values are read, or discarded (ok/cancel/apply buttons). Am I right?
Probably yes, but I'm not sure if I understand. So I give an example.

Let's use a ToggleButton for the example. You've instanciated a ToggleButton
ToggleButton * button = new ToggleButton();

and parametrized it with an image showing an empty rectangle for state 0 and an image showing a check-mark for state 1, so it looks like a typical check-box. You implement the StateListener interface with the purpose that the rendering of a model should be supressed in case that the check-box is not checked. Basically so:
class MyStateListener : public StateListener {public:   MyStateListener( Model * forModel ) : model( forModel) {}   void onStateChange(uint oldState, uint newState) {      model->shouldBeRendered( newState==1 );   }   Model * model;};

Then you register those listener with the event source
button->registerListener( new MyStateListener( myModel ) );

and you're done.

Now if the use mouse-clicks over the ToggleButton, the input sub-system determines the button as click target and invokes it onClick routine. The routine changes the state of the button from 0 to 1 (or 1 to 0, dependend on the current state, of course) and causes a redraw, because its visual representation has changed with its state. It then notifies the registered listeners w/o ever knowing what the state toggle causes outside.

You may lament that this would create a couple of additional classes, and you'd be right. In languages allowing anonymous classes the situation is a bit better. However, the shown concpet is just the basic concept. You can extend the interface with the event sender as parameter, allowing the same Listener instance to be registered several times (as long as the action is matched) because the mediator pattern can be applied internally. E.g.
class StateListener {public:   virtual void onStateChange(Widget * sender, uint oldState, uint newState) =0;};class MyStateListener : public StateListener {public:   ...   void onStateChange(Widget * sender, uint oldState, uint newState) {      if( sender==_myWidget1 ) {         ...      }      else if( sender==_myWidget2 ) {         ...      }   }   ...};

It is also possible to derive a handler that implements several Listener interfaces at once, of course.

It is further possible to allow listeners to deny toggle. Assume an interface like this:
class StateListener {public:   virtual bool onPreStateChange(Widget * sender, uint oldState, uint newState) { return true; }   virtual bool onPostStateChange(Widget * sender, uint oldState, uint newState) =0;};
When ToggleButton::onClick is invoked, all registered Listeners are notified by using onPreStateChange. If at least 1 Listener returns false then the toggle is denied, and nothing happens. Otherwise the state is updated, the redraw is caused, and the registered Listeners are notified by using onPostStateChange.


As ever, all this stuff should show only possibilities.

This topic is closed to new replies.

Advertisement