#ifndef _VERTICAL_MENU_H #define _VERTICAL_MENU_H #include "typeDefs.h" #include "libstd_replacements.h" #include "text_data_table.h" enum class MenuInputHandleState { /** * @brief This value means no input was handled */ NOT_HANDLED, /** * @brief This value means that the input was handled. */ HANDLED, /** * @brief Same as HANDLED, but also indicates that the viewport should be updated. * degrades to HANDLED after passing through vertical_menu::handle_input() */ HANDLED_UPDATE_VIEWPORT, /** * @brief This value means that a choice was made. * This means that if the vertical_menu is running with ::run(), * it should exit and return that choice. */ CHOICE_MADE, /** * @brief The menu was cancelled. */ CANCELLED }; /** * @brief This interface represents an item widget in the vertical menu. * It's responsible for rendering itself and handling its input if * the widget is focused. */ class i_item_widget { public: virtual ~i_item_widget(); virtual void render_item(text_data_table &text_table, unsigned x, unsigned y, bool is_focused) = 0; virtual MenuInputHandleState handle_input() = 0; protected: private: }; /** * @brief This interface allows you to register a handler * to deal with selection changes. * This is useful for managing external components, such as a cursor. */ class i_vertical_menu_state_changed_handler { public: virtual ~i_vertical_menu_state_changed_handler(); virtual void on_show() = 0; virtual void on_hide() = 0; virtual void on_selection_changed(unsigned new_index, unsigned x, unsigned y) = 0; protected: private: }; /** * @brief If you are using the vertical_menu::run() function, * you may want to manage external components on very run cycle. * This interface allows you to do that by implementing the i_run_cycle_handler */ class i_run_cycle_handler { public: virtual ~i_run_cycle_handler(); virtual void on_run_cycle() = 0; protected: private: }; typedef struct vertical_menu_settings { // bounds properties unsigned x; unsigned y; unsigned width; unsigned height; /** * @brief The top margin (in pixels) after which we start rendering the first menu item. */ unsigned margin_top; /** * @brief The bottom margin (in pixels) after which we stop rendering the last menu item. */ unsigned margin_bottom; /** * @brief The index of the first item to be focused when the menu is shown. */ unsigned initial_focus_index; /** * @brief The height of each menu item in pixels. Will be used to position the items. */ unsigned item_height; /** * @brief This index defines which text table to use for the menu entries. */ int text_table_index; /** * @brief This boolean indicates whether the menu * can be "cancelled with the B button." */ bool allow_cancel; /** * @brief This boolean indicates whether the menu should take * ownership of its item widgets. * That means that if this boolean is true, * the destructor of vertical_menu will delete/free all of its * item widgets. */ bool should_delete_item_widgets_on_destruct; /** * @brief This boolean indicates whether the state changed handler (cursor?) * should be hidden when the menu is not focused. */ bool should_hide_state_changed_handler_on_not_focused; } vertical_menu_settings; class vertical_menu { public: vertical_menu(const vertical_menu_settings &settings); ~vertical_menu(); void show(); void hide(); /** * @brief Get the current settings of this widget */ const vertical_menu_settings& get_settings() const; /** * @brief Replace the settings of this widget. */ void set_settings(const vertical_menu_settings &settings); /** * @brief This function is a way to bulk add item widgets to the menu. * The main difference between calling this function and add_item_widget() * multiple times is that this function will reserve() enough space in the * vector first, to avoid multiple resizes. */ void add_item_widgets(i_item_widget **item_widgets, unsigned num_widgets); /** * @brief This function adds an item widget to the menu. * The menu will take ownership of the pointer, if * settings_.should_delete_item_widgets_on_destruct is true. * Otherwise, the caller is responsible for managing the memory of the item widgets. */ void add_item_widget(i_item_widget *item_widget); /** * @brief This function clears all item widgets from the menu. * If settings_.should_delete_item_widgets_on_destruct is true, * it will also delete the item widgets. */ void clear_item_widgets(); /** * @brief This function gets the item widget * at the given index. */ i_item_widget* get_item_widget_at(unsigned index) const; /** * @brief Sets whether the menu is focused or not. * A focused menu will handle input. */ void set_focused(bool is_focused); /** * @brief This function handles button input for the menu. */ MenuInputHandleState handle_input(); /** * @brief This function updates the viewport by * re-rendering the visible items. */ void update_viewport(); void clear_viewport(); /** * @brief This function can be used to manage a blocking call * to vertical_menu, in which you want vertical_menu * to handle everything. * * This can be useful during one of those script_array commands. * Alternatively, you can just call handle_input() and manage the loop * manually. * * @return Will return the index of the chosen item if a choice was made, * or UINT32_MAX if no choice was made and the menu was exited in some other way * (ex: B button). */ unsigned run(); /** * @brief Sets the optional state changed handler. * This is useful for managing external components, such as a cursor. */ void set_state_changed_handler(i_vertical_menu_state_changed_handler *handler); /** * @brief This function sets a handler that will run on every * cycle in the run() function. * * It's only relevant if you use ::run() though. */ void set_run_cycle_handler(i_run_cycle_handler *handler); protected: private: void handle_selection_change(unsigned new_index, unsigned x, unsigned y); vertical_menu_settings settings_; i_vertical_menu_state_changed_handler *state_changed_handler_; i_run_cycle_handler *run_cycle_handler_; unsigned focused_index_; unsigned viewport_start_index_; ptgb::vector items_; bool is_focused_; }; /** * @brief Metadata for the simple_item_renderer below. */ typedef struct simple_item_widget_data { /** * @brief label text related properties. */ struct { /** * @brief The index of the text table (see vertical_menu_settings) entry to use as the label of this item. */ unsigned text_table_index; /** * @brief The left pixel offset after which we render the label text. */ unsigned margin_left; /** * @brief The top pixel offset after which we render the label text. */ unsigned margin_top; } text; /** * @brief The value this widget represents. This is useful for choice/input handling. * But it won't be used if an on_execute_callback is set. */ unsigned value; /** * @brief Optional execute callback. This will be executed when the user presses A while this item is focused. * If this is nullptr, then MenuInputHandleState::CHOICE_MADE will be returned when the user presses A, * and the caller can check the value field to know what choice was made. */ void (*on_execute_callback)(void *context); } simple_item_widget_data; /** * @brief This is the simplest possible item widget, which will just render a single line of text for a single item * and allow you to press A to execute a callback. It's useful for simple menus that don't require toggles or multiple options. */ class simple_item_renderer : public i_item_widget { public: simple_item_renderer(const simple_item_widget_data &data); virtual ~simple_item_renderer(); const simple_item_widget_data& get_data() const; void render_item(text_data_table &text_table, unsigned x, unsigned y, bool is_focused) override; MenuInputHandleState handle_input() override; protected: private: simple_item_widget_data data_; }; #endif