Menu¶
engine/menu.hpp
Menu is a ready-made scrolling list with a title bar, for title screens, pause menus and options. You give it an array of items, call its update() and render(), and override a few functions to react to the player.
Class¶
class Menu {
public:
static const uint16_t Separator = 65535;
struct Item {
uint16_t id;
const char *label;
};
Menu(std::string_view title, const Item *items = nullptr, int num_items = 0,
const Font &font = minimal_font);
virtual void update(uint32_t time);
void render();
void set_items(const Item *items, int num_items);
void set_display_rect(Rect rect);
std::string_view title;
protected:
virtual void item_activated(const Item &item); // A was pressed on an item
virtual void update_item(const Item &item); // every update, for the selected item
virtual void update_menu(uint32_t time); // every update
virtual void render_menu();
virtual void render_item(const Item &item, int y, int index) const;
virtual void render_header(int x, int y, int w);
virtual void render_footer(int x, int y, int w);
const Item *items;
int num_items;
int current_item = 0;
...
};
Using it¶
Derive a class and override item_activated():
enum MenuId { PLAY, OPTIONS, QUIT };
const Menu::Item main_items[] = {
{PLAY, "Play"},
{OPTIONS, "Options"},
{Menu::Separator, nullptr},
{QUIT, "Quit"},
};
class MainMenu : public Menu {
public:
MainMenu() : Menu("Chili Catch", main_items, 4) {}
protected:
void item_activated(const Item &item) override {
switch(item.id) {
case PLAY: start_game(); break;
case OPTIONS: show_options(); break;
case QUIT: save_and_quit(); break;
}
}
};
MainMenu main_menu;
void update(uint32_t time) {
if(state == TITLE)
main_menu.update(time);
}
void render(uint32_t time) {
if(state == TITLE)
main_menu.render();
}
Behaviour¶
update(time)- Moves the selection with UP and DOWN (repeating every 200 ms while held, and wrapping around), calls
item_activated()when A is released on an item, and scrolls smoothly to keep the selection on screen. Then it callsupdate_menu()andupdate_item()for the selected item. render()- Fills the menu's rectangle with the background colour and draws the header, items and footer. Call it in
render(), after drawing whatever is behind the menu: the default background is translucent. set_display_rect(rect)- Where the menu is drawn. By default it covers the whole screen, from the first
update(). set_items(items, num_items)- Replaces the items, for example to switch to a submenu, and selects the first one. The menu keeps a pointer to the array, so it must stay alive (a
constglobal is best).
Items¶
idis yours: use it to tell items apart initem_activated().- An item with a
nullptrlabel isn't drawn and can't be selected. With the idMenu::Separatorit leaves a gap; with any other id it takes no space, which lets you hide an item without rebuilding the array. - The first item should be selectable, because it's selected to begin with. A menu needs at least one selectable item: with none, pressing UP or DOWN loops forever looking for one.
Changing labels¶
Labels are plain const char *. For a setting that shows its value, such as "Sound: on", keep the text in a buffer you update, or override render_item() and draw the value yourself:
void render_item(const Item &item, int y, int index) const override {
Menu::render_item(item, y, index);
if(item.id == SOUND) {
screen.pen = foreground_colour;
screen.text(sound_on ? "on" : "off", font,
Point(display_rect.x + display_rect.w - item_padding_x, y + item_adjust_y),
true, TextAlign::top_right);
}
}
update_item() is a good place to change a setting with LEFT and RIGHT while its item is selected.
Appearance¶
The look is set by protected members you can change in your class's constructor:
| Member | Default | What |
|---|---|---|
background_colour |
Pen(30, 30, 50, 200) |
behind the items |
foreground_colour |
white | item text |
selected_item_background |
Pen(50, 50, 70) |
the selected item's bar |
header_background, header_foreground |
near-white, near-black | title and footer bars |
header_h, footer_h |
16, 16 |
bar heights; 0 hides the footer |
item_h, item_spacing |
9, 1 |
item height and gap |
item_padding_x |
5 |
text indent |
margin_y |
5 |
space between the header and the first item |
display_rect |
the screen | where it's drawn |
MainMenu() : Menu("Chili Catch", main_items, 4, outline_font) {
item_h = 12;
footer_h = 0;
selected_item_background = Pen(200, 40, 40);
set_display_rect(Rect(16, 24, 96, 80));
}
Override render_header() or render_footer() to draw your own bars, for example button hints in the footer. Call the base version first: it fills the bar's background.