I am writing a UI framework for a microcontroller, in which I want to do a hierarchical menu system.
For that in particular I have the following classes:
class MenuNode {
public:
MenuNode(const std::string t, const UI::Image* img): title{t}, icon{img} {}
virtual ~MenuNode() = default;
const std::string title;
const UI::Image * icon;
virtual void execute(MenuNavigator * host) const { ESP_LOGE("MenuNode", "Did you forget to override execute()?"); }
virtual bool is_submenu() const { return false; }
};
class ListMenuNode: public MenuPresentable, public MenuNode, public UI::ListView {
public:
ListMenuNode(const std::string title, const std::vector<std::shared_ptr<MenuNode>>& items, const UI::Image* icon = nullptr):
subnodes(items),
UI::ListView(EGRectZero, {}), MenuNode(title, icon) {
}
void execute(MenuNavigator * host) const override {
host->push(std::make_shared<ListMenuNode>(*this));
}
void on_presented() override {
// update frames of things after push has set our frame correctly
...
}
void on_key_pressed(VirtualKey k, MenuNavigator* host) override {
if(k == RVK_CURS_DOWN) down();
else if(k == RVK_CURS_UP) up();
else if(k == RVK_CURS_ENTER) {
subnodes[selection]->execute(host);
}
else if(k == RVK_CURS_LEFT) {
host->pop();
}
}
protected:
const std::vector<std::shared_ptr<MenuNode>> subnodes;
};
In here, I have to make subnodes
a vector of shared_ptr<MenuNode>
instead of directly MenuNode
— otherwise upon selection of an item, only MenuNode::execute()
ever gets called instead of the descendant class override of this method.
I am fine with that for the most part, however, this means a declaration of the menu looks like the following
ListMenuNode(
"Settings",
{
std::make_shared<MenuNode>("Item 1", &icn_wifi),
std::make_shared<ListMenuNode>(ListMenuNode("Drill Down", {
std::make_shared<MenuNode>("Drill Item 1", &icn_cd),
std::make_shared<MenuNode>("Drill Item 2", &icn_bt),
std::make_shared<MenuNode>("Drill Item 3", &icn_radio),
}, &icn_about))
}
)
Which looks very unclean due to repetitive calls to make_shared
.
Is there some way to make the pointer creation implicit, so that I can just write something like the following instead?
ListMenuNode(
"Settings",
{
MenuNode("Item 1", &icn_wifi),
ListMenuNode("Drill Down", {
MenuNode("Drill Item 1", &icn_cd),
MenuNode("Drill Item 2", &icn_bt),
MenuNode("Drill Item 3", &icn_radio),
}, &icn_about)
}
)
One way I thought of was using #define
's for each class name, but that sounds not very clean to me.
Not sure it is a good idea, but, if your types are movable, you might use variadic template constructor.
using std::tuple
instead of std::vector
:
template <typename... Ts>
ListMenuNode(const std::string title,
std::tuple<Ts...>&& items,
const UI::Image* icon = nullptr) :
subnodes(std::apply([](auto&&... args) -> std::vector<std::shared_ptr<MenuNode>>
{
return { std::make_shared<Ts>(std::move(args))... };
}, std::move(items)),
UI::ListView(EGRectZero, {}),
MenuNode(title, icon)
{
}
With call similar to
ListMenuNode(
"Settings",
std::tuple{
MenuNode("Item 1", &icn_wifi),
ListMenuNode("Drill Down", std::tuple{
MenuNode("Drill Item 1", &icn_cd),
MenuNode("Drill Item 2", &icn_bt),
MenuNode("Drill Item 3", &icn_radio),
}, &icn_about)
}
)