Hi,
I´m currently developing a little menu system for a console game. As a starting point I divided the Menu in three areas: the actual data structure, a navigation class which can navigate through the "tree" and holds the state (currently active SubMenu) and a class which draws the whole thing. In its simplest form I want it to behave/look like the following:
1) Every "Node" represents a MenuItem
1.1) Each MenuItem can either be a SubMenuItem or an ActionItem
1.2) Every SubMenu can contain 0 to N childs
2) If I navigate to a SubMenuItem and it has childs I want to display them. If I navigate to an ActionItem it should trigger a function.
As a class hirarchy to actually build the menu I thought something like this would work:
class MenuItem {
public:
virtual void action() = 0;
virtual string& getItemName() = 0;
}
class ActionItem : public MenuItem {
public:
void action() override {
//Trigger stuff
}
string& getItemName() {
return itemName;
}
}
class SubMenuItem : public MenuItem {
private:
vector<MenuItem*> childItems;
public:
void action() override {
//What to do?
}
string& getItemName() {
return itemName;
}
void add(MenuItem* m) {...}
void rm(MenuItem* m) {...}
MenuItem* find(const string& name) {...}
}
To create the menu I could then do this:
SubMenuItem root, options;
ActionItem exit, start, fullScreenMode, windowMode
options.add(&fullScreenMode);
options.add(&windowMode);
root.add(&start);
root.add(&options);
root.add(&exit);
// Should look like this
+--start
|
+--options--+--fullScreenMode
| +--windowMode
+--exit
But now I created myself a problem:
How should I actually get the type of my MenuItem? Lets say I want to find the options SubMenu and add some ActionItems at runtime. In order to do this I can call find on the root object but then I only have a MenuItem pointer which can call action() or getItemName(). To get around this problem I could add some methods to my MenuItem interface, but I think that would be the wrong way (as you can see with the action method -> SubMenuItem has no real use for it). Another solution could be the use of dynamic_cast but wouldn´t it be a sign of a bad design?
MenuItem* options = root.find("options");
options. ???
//Fix?
class MenuItem {
public:
virtual void action() = 0;
virtual string& getItemName() = 0;
virtual bool isSubmenu() = 0;
virtual vector<MenuItem*> getChild() = 0;
}
MenuItem* options = root.find("options");
if(options->isSubmenu())
options->getChild().push_back(new ActionItem());
//Or maybe this?
SubMenuItem* options = dynamic_cast<SubMenuItem*>(root.find("options"));
if(options)
options->add(new ActionItem())
Am I on the right track or is this complete bs?
Should I use templates (CRTP) for this kind of problem or is maybe the visitor pattern a good match for this problem?
I hope you understand my problem can can give me some hints!