I'm designing a "view" handling system for a game. The goal is to be able to have different "views" that can be shown in sequence or stacked on-top of each other. For example, the initial splash screen is one View
and the main menu that follows the splash screen is also a View
which is rendered when the splash screen terminates. The camera view of the game world is also a View
and the HUD is a View
that is rendered on top of the game View
.
While defining the interface for View
I realize that the stacked nature of View
s corresponds to the input focus priority. I.e. the View
that's at the top of the stack should be offered to act on any user input before any other View
s because in essence it is in the "foreground" for focus.
So the design of the interface for View
looks like this at the moment:
/**
* Interface for a "view". A view is a renderable target that can accept user input.
*
* One can think of it as a "layer", many layers can be drawn over each other in a
* stack like fashion where the input focus travels from the top to the bottom of
* the stack.
*/
class IView{
public:
virtual ~IView() {}
IView(const IView&) = delete; // Not allowed
void operator = (const IView&) = delete; // Not allowed
/**
* Called to handle user keyboard input. May be called multiple times per update.
* @param aKeyEvent The type of input event.
* @param aKeyCode The key code of the event key.
* @param aKeyboardState The current state of they keyboard.
* @return True if the event was handled. False if the event should continue to lower views.
*/
virtual bool handleInput(KeyEvent aKeyEvent, KeyCode aKeyCode, const KeyboardState& aKeyboardState) = 0;
/**
* Called to handle user mouse clicks. May be called multiple times per update.
* @param aMouseButtonEvent The event to handle.
* @param aMouseButton Which button it was.
* @param aMouseState The current mouse state.
* @return True if the event was handled. False if the event should continue to lower views.
*/
virtual bool handleInput(MouseButtonEvent aMouseButtonEvent, MouseButton aMouseButton,
const MouseState& aMouseState) = 0;
/**
* Called to handle user mouse movement. Only called once per update.
* @param aMouseEvent The event to handle.
* @param aMouseState
* @return True if the event was handled. False if the event should continue to lower views.
*/
virtual bool handleInput(MouseMoveEvent aMouseMoveEvent, const MouseState& aMouseState) = 0;
/**
* Called when this view becomes the foreground view.
*/
virtual void foreground() = 0;
/**
* Called when this view loses its foreground status and
* has another view drawn over it.
*/
virtual void background() = 0;
/**
* Called once every frame to perform periodic processing and rendering.
*/
virtual void update() = 0;
};
This isn't the final interface, I'm just prototyping at the moment. But it gives you an idea.
Consider a game View
, with a HUD on top, and then the game is paused so a pause View
is rendered on top of the HUD. The pause View
is offered first chance to act on user input to continue the game and it swallows all other input. To me this makes perfect sense.
Now, not all View
s are necessarily interested in receiving input. Some might only be interested in mouse events and some might only be interested in keyboard events. My initial approach was to just let them have pass-through input handlers. But then I remembered the Interface Segregation Principle so it would make sense to define two more interfaces IKeyboardHandler
and IMouseHandler
and let the relevant View
classes implement those when needed. But then I need to use dynamic_cast
when passing input to the View
objects to see if a particular View
implements for example IKeyboardHandler
.
I'm not sure if this approach is any more elegant than having empty input handlers and I'm looking for input and ideas on the design.