From ea0208083fba7765b7761a41a5a142b8963e0568 Mon Sep 17 00:00:00 2001 From: Peter Siegmund Date: Sun, 15 Jun 2025 09:19:14 +0200 Subject: [PATCH] more interaction Signed-off-by: Peter Siegmund --- components/insa/include/common/Menu.h | 175 ++++++++---------- components/insa/include/data/MenuItem.h | 33 +--- .../insa/include/ui/LightSettingsMenu.h | 21 ++- components/insa/src/common/Menu.cpp | 45 ++++- components/insa/src/data/MenuItem.cpp | 24 ++- components/insa/src/ui/LightMenu.cpp | 55 ++++-- components/insa/src/ui/LightSettingsMenu.cpp | 48 ++--- 7 files changed, 222 insertions(+), 179 deletions(-) diff --git a/components/insa/include/common/Menu.h b/components/insa/include/common/Menu.h index ae8c24c..f73e8e1 100644 --- a/components/insa/include/common/Menu.h +++ b/components/insa/include/common/Menu.h @@ -24,30 +24,30 @@ * with support for various types of interactive menu items. It handles user input * through directional navigation and action buttons, provides visual feedback * through selection highlighting, and supports scrolling for long menu lists. - * + * * The menu system supports four types of menu items: * - Text buttons: Simple selectable text items * - Selection items: Dropdown/list selection with multiple options * - Number inputs: Numeric value adjustment controls * - Toggle items: Boolean on/off switches - * + * * @note Menu items are identified by unique IDs and can be dynamically added * after menu creation. - * + * * @see Widget * @see MenuItem * @see menu_options_t */ class Menu : public Widget { -public: + public: /** * @brief Constructs a new Menu instance with the specified configuration * @param options Pointer to menu configuration options structure - * + * * @pre options must not be nullptr and must remain valid for the menu's lifetime * @post Menu is initialized with the provided configuration and ready for item addition - * + * * @note The menu does not take ownership of the options structure and assumes * it remains valid throughout the menu's lifetime. */ @@ -64,10 +64,10 @@ public: * @brief Adds a text-based menu item (button) to the menu * @param id Unique identifier for this menu item (must be unique within the menu) * @param text Display text shown on the menu item - * + * * @pre id must be unique within this menu instance * @post A new text menu item is added to the menu's item collection - * + * * @note Text items act as buttons and generate selection events when activated */ void addText(uint8_t id, const std::string &text); @@ -83,7 +83,7 @@ public: * @pre values vector must not be empty * @pre value must be one of the strings in the values vector * @post A new selection menu item is added with the specified options - * + * * @note The value parameter is modified directly when the user changes the selection */ void addSelection(uint8_t id, const std::string &text, const std::vector &values, int index); @@ -93,24 +93,24 @@ public: * @param id Unique identifier for this menu item (must be unique within the menu) * @param text Display text/label for the toggle item * @param selected Current state of the toggle (true = on/enabled, false = off/disabled) - * + * * @pre id must be unique within this menu instance * @post A new toggle menu item is added with the specified initial state - * + * * @note Toggle state can be changed through user interaction with select button */ void addToggle(uint8_t id, const std::string &text, bool selected); -protected: + protected: /** * @brief Virtual callback method for handling button press events on specific menu items * @param item The menu item that received the button press * @param button The type of button that was pressed - * + * * @details This method can be overridden by derived classes to implement custom * button handling logic for specific menu items. The base implementation * is empty, allowing derived classes to selectively handle events. - * + * * @note Override this method in derived classes to implement custom menu item * interaction behavior beyond the standard navigation and value modification. */ @@ -119,114 +119,103 @@ protected: // Base implementation intentionally empty - override in derived classes as needed } + /** + * @brief Retrieves a menu item by its index position + * @param index Zero-based index of the menu item to retrieve + * @return MenuItem object at the specified index + * + * @pre index must be within valid range [0, getItemCount()-1] + * @post Returns a copy of the menu item at the specified position + * + * @throws std::out_of_range if index is invalid + * + * @note This method returns a copy of the menu item, not a reference + */ MenuItem getItem(int index); + /** + * @brief Gets the total number of menu items in the menu + * @return Size of the menu items collection + * + * @post Returns current count of menu items (>= 0) + * + * @note This count includes all types of menu items (text, selection, toggle) + */ [[nodiscard]] size_t getItemCount() const; + /** + * @brief Dynamically adjusts the number of menu items to the specified size + * @param size Target number of menu items the menu should contain + * + * @details If the target size is larger than current item count, new selection + * items are added using the first item's values as template. If the + * target size is smaller, excess items are removed from the end. + * + * @pre size must be > 0 and at least one menu item must exist as template + * @post Menu contains exactly 'size' number of items + * + * @note New items are created as selection items with auto-generated names + * in the format "Section X" where X is the item number + */ void setItemSize(size_t size); + /** + * @brief Toggles the boolean state of a toggle menu item + * @param menuItem The toggle menu item whose state should be flipped + * + * @pre menuItem must be of type TOGGLE + * @post The menu item's value is switched between "true" and "false" + * + * @details Changes "true" to "false" and "false" to "true" for toggle items. + * The modified item replaces the original in the menu's item collection. + * + * @note This method directly modifies the menu's internal state + */ + void toggle(const MenuItem &menuItem); + + /** + * @brief Changes the selected value of a selection menu item based on button input + * @param menuItem The selection menu item to modify + * @param button The directional button pressed (LEFT or RIGHT) + * + * @pre menuItem must be of type SELECTION with valid values array + * @post The menu item's selected index is adjusted based on button direction + * + * @details LEFT button moves to previous option (wraps to end if at beginning), + * RIGHT button moves to next option (wraps to beginning if at end). + * Other button types are ignored. + * + * @note The modified item replaces the original in the menu's item collection + */ + void switchValue(const MenuItem &menuItem, ButtonType button); + + private: void replaceItem(int index, const MenuItem &item); -private: - /** - * @brief Renders the entire menu on screen - * @details Override from Widget base class. Handles the complete rendering process - * including menu items, selection highlighting, and scroll indicators. - * - * @note This method is called during each frame's render cycle - */ void render() override; - /** - * @brief Handles button press events from the input system - * @param button The button that was pressed - * @details Override from Widget base class. Processes user input and delegates - * to appropriate handler methods based on button type. - * - * @see ButtonType for available button types - */ void onButtonClicked(ButtonType button) override; - // Navigation event handlers - /** - * @brief Handles down arrow/stick input - moves selection down in the menu - * @details Moves the current selection to the next menu item, wrapping to the - * beginning if at the end of the list. - */ void onPressedDown(); - /** - * @brief Handles up arrow/stick input - moves selection up in the menu - * @details Moves the current selection to the previous menu item, wrapping to the - * end if at the beginning of the list. - */ void onPressedUp(); - /** - * @brief Handles left arrow/stick input - decreases value for current item - * @details For selection items: moves to previous option in the list - * For number items: decreases the numeric value - * For other items: no action - */ void onPressedLeft() const; - /** - * @brief Handles right arrow/stick input - increases value for current item - * @details For selection items: moves to next option in the list - * For number items: increases the numeric value - * For other items: no action - */ void onPressedRight() const; - /** - * @brief Handles select/confirm button press - * @details Activates the currently selected menu item: - * - Text items: triggers selection event - * - Toggle items: toggles the boolean state - * - Other items: context-dependent behavior - */ void onPressedSelect() const; - /** - * @brief Handles back/cancel button press - * @details Typically used to exit the menu or return to a previous screen. - * The specific behavior depends on the menu configuration. - */ void onPressedBack() const; - // Rendering helper methods - /** - * @brief Draws the scroll bar indicating position in long menus - * @details Renders a visual scroll indicator when the menu contains more items - * than can be displayed on screen simultaneously. - */ void drawScrollBar() const; - /** - * @brief Draws the selection highlight box around current menu item - * @details Renders visual feedback showing which menu item is currently selected - * and will respond to user input. - */ void drawSelectionBox() const; - /** - * @brief Renders an individual menu item widget - * @param item Pointer to the menu item to render (must not be nullptr) - * @param font Font to use for rendering text - * @param x X coordinate for rendering position - * @param y Y coordinate for rendering position - * - * @pre item must not be nullptr - * @pre font must be a valid u8g2 font - * @pre x and y must be valid screen coordinates - * - * @details Handles the rendering of a single menu item based on its type, - * including text, current values, and any type-specific visual elements. - */ void renderWidget(const MenuItem *item, const uint8_t *font, int x, int y) const; // Member variables - size_t m_selected_item = 0; ///< Index of currently selected menu item (0-based) - std::vector m_items; ///< Collection of all menu items in display order - menu_options_t *m_options; ///< Pointer to menu configuration options (not owned) + size_t m_selected_item = 0; + std::vector m_items; + menu_options_t *m_options; }; \ No newline at end of file diff --git a/components/insa/include/data/MenuItem.h b/components/insa/include/data/MenuItem.h index 8ebeaa9..6f6537c 100644 --- a/components/insa/include/data/MenuItem.h +++ b/components/insa/include/data/MenuItem.h @@ -142,35 +142,6 @@ public: MenuItem(uint8_t id, uint8_t type, std::string text, std::vector values, int index, ButtonCallback callback); - /** - * @brief Constructs a boolean/toggle menu item with on/off state - * @param id Unique identifier for this menu item within its parent menu - * @param type Type identifier defining the item's behavior and visual appearance - * @param text Display text shown to the user for this menu item - * @param selected Whether this item is currently selected/enabled/checked - * @param callback Function to call when the item is activated - * - * @pre id must be unique within the parent menu context - * @pre text should not be empty for proper user interface display - * @pre callback should be a valid callable object - * @post MenuItem is initialized as a boolean toggle item - * - * @details Creates a menu item that represents a boolean state (on/off, enabled/disabled, - * checked/unchecked). This type is ideal for settings that have binary states - * and need to show their current status visually. - * - * Typical use cases include: - * - Feature toggles (e.g., "Auto-save: ON") - * - Enable/disable settings (e.g., "Sound: ENABLED") - * - Checkbox-style options (e.g., "Show notifications: ✓") - * - Boolean configurations (e.g., "Dark mode: OFF") - * - * @note The selected state is converted to a string value internally for - * consistent value handling across all menu item types. - * @note The callback typically implements toggle logic to switch between states. - */ - MenuItem(uint8_t id, uint8_t type, std::string text, bool selected, ButtonCallback callback); - /** * @brief Gets the unique identifier of this menu item * @return The menu item's unique ID as assigned during construction @@ -282,10 +253,12 @@ public: [[nodiscard]] int getIndex() const; - std::vector getValues() const; + [[nodiscard]] std::vector getValues() const; [[nodiscard]] size_t getItemCount() const; + [[nodiscard]] MenuItem copyWith(const std::string &value) const; + [[nodiscard]] MenuItem copyWith(size_t index) const; private: diff --git a/components/insa/include/ui/LightSettingsMenu.h b/components/insa/include/ui/LightSettingsMenu.h index f6c0d3a..6ce5da9 100644 --- a/components/insa/include/ui/LightSettingsMenu.h +++ b/components/insa/include/ui/LightSettingsMenu.h @@ -2,13 +2,32 @@ #include "common/Menu.h" +/** + * @class LightSettingsMenu + * @brief Menu for configuring light system settings including sections and LED parameters + * @details This menu extends the base Menu class to provide specialized functionality + * for managing light system configurations. It handles dynamic section management + * and provides persistence for user settings. + */ class LightSettingsMenu final : public Menu { public: + /** + * @brief Constructs a LightSettingsMenu with the specified options + * @param options Pointer to menu configuration options structure + * @details Initializes the menu with section counter and default section settings + */ explicit LightSettingsMenu(menu_options_t *options); private: + /** + * @brief Handles button press events for light settings menu items + * @param menuItem The menu item that received the button press + * @param button The type of button that was pressed + * @details Manages value switching, dynamic section list updates, and + * persistence of section values when settings are modified + */ void onButtonPressed(const MenuItem& menuItem, ButtonType button) override; - menu_options_t *m_options; + menu_options_t *m_options; ///< Pointer to menu configuration options }; \ No newline at end of file diff --git a/components/insa/src/common/Menu.cpp b/components/insa/src/common/Menu.cpp index f6bcf6b..3c077fd 100644 --- a/components/insa/src/common/Menu.cpp +++ b/components/insa/src/common/Menu.cpp @@ -67,6 +67,47 @@ void Menu::setItemSize(const size_t size) m_items.erase(m_items.begin() + static_cast(size + 1), m_items.end()); } } +void Menu::toggle(const MenuItem &menuItem) +{ + const auto item = + menuItem.copyWith(menuItem.getValue() == std::to_string(true) ? std::to_string(false) : std::to_string(true)); + replaceItem(menuItem.getId(), item); +} + +void Menu::switchValue(const MenuItem& menuItem, ButtonType button) +{ + switch (button) + { + case ButtonType::LEFT: + if (menuItem.getIndex() > 0) + { + const auto item = menuItem.copyWith(menuItem.getIndex() - 1); + replaceItem(menuItem.getId(), item); + } + else + { + const auto item = menuItem.copyWith(menuItem.getItemCount() - 1); + replaceItem(menuItem.getId(), item); + } + break; + + case ButtonType::RIGHT: + if (menuItem.getIndex() < menuItem.getItemCount() - 1) + { + const auto item = menuItem.copyWith(menuItem.getIndex() + 1); + replaceItem(menuItem.getId(), item); + } + else + { + const auto item = menuItem.copyWith(0); + replaceItem(menuItem.getId(), item); + } + break; + + default: + break; + } +} void Menu::replaceItem(const int index, const MenuItem &item) { @@ -145,7 +186,7 @@ void Menu::renderWidget(const MenuItem *item, const uint8_t *font, const int x, u8g2_DrawFrame(u8g2, frameX, frameY, UIConstants::FRAME_BOX_SIZE, UIConstants::FRAME_BOX_SIZE); // Draw checkmark (X) if toggle is true - if (item->getValue() == "true") + if (item->getValue() == std::to_string(true)) { const int checkX1 = frameX + 2; const int checkY1 = frameY + 2; @@ -276,7 +317,7 @@ void Menu::addToggle(uint8_t id, const std::string &text, bool selected) auto callback = [this](const MenuItem &menuItem, const ButtonType button) -> void { onButtonPressed(menuItem, button); }; - m_items.emplace_back(id, MenuItemTypes::TOGGLE, text, selected, callback); + m_items.emplace_back(id, MenuItemTypes::TOGGLE, text, std::to_string(selected), callback); } void Menu::drawScrollBar() const diff --git a/components/insa/src/data/MenuItem.cpp b/components/insa/src/data/MenuItem.cpp index 92ad5f1..0e1e411 100644 --- a/components/insa/src/data/MenuItem.cpp +++ b/components/insa/src/data/MenuItem.cpp @@ -1,16 +1,19 @@ #include "data/MenuItem.h" +// Constructor for basic menu items (text buttons) MenuItem::MenuItem(const uint8_t id, const uint8_t type, std::string text, ButtonCallback callback) : m_id(id), m_type(type), m_text(std::move(text)), m_callback(std::move(callback)) { } +// Constructor for menu items with a single value (toggles) MenuItem::MenuItem(const uint8_t id, const uint8_t type, std::string text, std::string value, ButtonCallback callback) : m_id(id), m_type(type), m_text(std::move(text)), m_value(std::move(value)), m_callback(std::move(callback)) { } +// Constructor for menu items with multiple values (selections) MenuItem::MenuItem(const uint8_t id, const uint8_t type, std::string text, std::vector values, int index, ButtonCallback callback) : m_id(id), m_type(type), m_text(std::move(text)), m_values(std::move(values)), m_index(index), @@ -18,13 +21,6 @@ MenuItem::MenuItem(const uint8_t id, const uint8_t type, std::string text, std:: { } -MenuItem::MenuItem(const uint8_t id, const uint8_t type, std::string text, const bool selected, - ButtonCallback callback) - : m_id(id), m_type(type), m_text(std::move(text)), m_value(selected ? "true" : "false"), - m_callback(std::move(callback)) -{ -} - uint8_t MenuItem::getId() const { return m_id; @@ -42,10 +38,12 @@ const std::string &MenuItem::getText() const const std::string &MenuItem::getValue() const { + // Return the selected value from values array if available and index is valid if (!m_values.empty() && m_index >= 0 && m_index < m_values.size()) { return m_values.at(m_index); } + // Otherwise return the direct value return m_value; } @@ -56,6 +54,7 @@ void MenuItem::setValue(const std::string &value) void MenuItem::onButtonPressed(const ButtonType button) const { + // Execute the callback function if one is registered if (m_callback) { m_callback(*this, button); @@ -82,9 +81,20 @@ size_t MenuItem::getItemCount() const return m_values.size(); } +MenuItem MenuItem::copyWith(const std::string &value) const +{ + // Create a copy of this menu item with a new value + MenuItem copy = *this; + copy.m_value = value; + return copy; +} + MenuItem MenuItem::copyWith(const size_t index) const { + // Create a copy of this menu item with a new selected index MenuItem copy = *this; + + // Check for potential overflow when converting size_t to int if (index > std::numeric_limits::max()) { throw std::overflow_error("index is too large"); diff --git a/components/insa/src/ui/LightMenu.cpp b/components/insa/src/ui/LightMenu.cpp index 6d2b289..ffc0aa8 100644 --- a/components/insa/src/ui/LightMenu.cpp +++ b/components/insa/src/ui/LightMenu.cpp @@ -2,36 +2,69 @@ #include "ui/LightSettingsMenu.h" +/** + * @namespace LightMenuItem + * @brief Constants for light menu item identifiers + */ namespace LightMenuItem { -constexpr uint8_t MODE = 1; -constexpr uint8_t TOGGLE = 2; -constexpr uint8_t LED_SETTINGS = 3; -} +constexpr uint8_t ACTIVATE = 0; ///< ID for the light activation toggle +constexpr uint8_t MODE = 1; ///< ID for the light mode selection +constexpr uint8_t LED_SETTINGS = 2; ///< ID for the LED settings menu item +} // namespace LightMenuItem LightMenu::LightMenu(menu_options_t *options) : Menu(options), m_options(options) { + // Add toggle for enabling/disabling the light system + addToggle(LightMenuItem::ACTIVATE, "Einschalten", true); + + // Create mode selection options (Day/Night modes) std::vector values; - values.emplace_back("Tag"); - values.emplace_back("Nacht"); + values.emplace_back("Tag"); // Day mode + values.emplace_back("Nacht"); // Night mode addSelection(LightMenuItem::MODE, "Modus", values, 0); + // Add menu item for accessing LED settings submenu addText(LightMenuItem::LED_SETTINGS, "LED Einstellungen"); } void LightMenu::onButtonPressed(const MenuItem &menuItem, const ButtonType button) { std::shared_ptr widget; + + // Handle different menu items based on their ID switch (menuItem.getId()) { - case LightMenuItem::LED_SETTINGS: - widget = std::make_shared(m_options); - break; - - default: + case LightMenuItem::ACTIVATE: { + // Toggle the light activation state when SELECT is pressed + if (button == ButtonType::SELECT) + { + toggle(menuItem); + } break; } + case LightMenuItem::MODE: { + // Switch between day/night modes using left/right buttons + switchValue(menuItem, button); + break; + } + + case LightMenuItem::LED_SETTINGS: { + // Open the LED settings submenu when SELECT is pressed + if (button == ButtonType::SELECT) + { + widget = std::make_shared(m_options); + } + break; + } + + default: + // Handle unknown menu items (no action required) + break; + } + + // Push the new widget to the screen stack if one was created if (m_options && m_options->pushScreen) { m_options->pushScreen(widget); diff --git a/components/insa/src/ui/LightSettingsMenu.cpp b/components/insa/src/ui/LightSettingsMenu.cpp index a88e79f..0de7cd3 100644 --- a/components/insa/src/ui/LightSettingsMenu.cpp +++ b/components/insa/src/ui/LightSettingsMenu.cpp @@ -1,61 +1,39 @@ #include "ui/LightSettingsMenu.h" +/** + * @namespace LightSettingsMenuItem + * @brief Constants for light settings menu item identifiers + */ namespace LightSettingsMenuItem { -constexpr uint8_t SECTION_COUNTER = 0; +constexpr uint8_t SECTION_COUNTER = 0; ///< ID for the section counter menu item } LightSettingsMenu::LightSettingsMenu(menu_options_t *options) : Menu(options), m_options(options) { + // Create values vector for section counts (1-99) std::vector values; for (size_t i = 1; i <= 99; i++) { values.emplace_back(std::to_string(i)); } + + // Add section counter selection (allows choosing number of sections) addSelection(LightSettingsMenuItem::SECTION_COUNTER, "Sektionen", values, 0); + // Add first section configuration item addSelection(1, "Sektion 1", values, 0); } void LightSettingsMenu::onButtonPressed(const MenuItem &menuItem, const ButtonType button) { - /// change visible counter - switch (button) - { - case ButtonType::LEFT: - if (menuItem.getIndex() > 0) - { - const auto item = menuItem.copyWith(menuItem.getIndex() - 1); - replaceItem(menuItem.getId(), item); - } - else - { - const auto item = menuItem.copyWith(menuItem.getItemCount() - 1); - replaceItem(menuItem.getId(), item); - } - break; + // Handle value switching for the current menu item + switchValue(menuItem, button); - case ButtonType::RIGHT: - if (menuItem.getIndex() < menuItem.getItemCount() - 1) - { - const auto item = menuItem.copyWith(menuItem.getIndex() + 1); - replaceItem(menuItem.getId(), item); - } - else - { - const auto item = menuItem.copyWith(0); - replaceItem(menuItem.getId(), item); - } - break; - - default: - break; - } - - /// change section list + // Update the section list size based on the section counter value setItemSize(std::stoull(getItem(0).getValue())); - /// persist section values + // Persist the changed section values if persistence is available if (m_options && m_options->persistence && m_options->persistence->save) { const auto key = "section_" + std::to_string(menuItem.getId());