more interaction

Signed-off-by: Peter Siegmund <developer@mars3142.org>
This commit is contained in:
2025-06-15 09:19:14 +02:00
parent 5464bacc52
commit ea0208083f
7 changed files with 222 additions and 179 deletions

View File

@@ -40,7 +40,7 @@
*/ */
class Menu : public Widget class Menu : public Widget
{ {
public: public:
/** /**
* @brief Constructs a new Menu instance with the specified configuration * @brief Constructs a new Menu instance with the specified configuration
* @param options Pointer to menu configuration options structure * @param options Pointer to menu configuration options structure
@@ -101,7 +101,7 @@ public:
*/ */
void addToggle(uint8_t id, const std::string &text, bool selected); 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 * @brief Virtual callback method for handling button press events on specific menu items
* @param item The menu item that received the button press * @param item The menu item that received the button press
@@ -119,114 +119,103 @@ protected:
// Base implementation intentionally empty - override in derived classes as needed // 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); 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; [[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); 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); 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; 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; 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(); 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(); 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; 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; 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; 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; 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; 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; 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; void renderWidget(const MenuItem *item, const uint8_t *font, int x, int y) const;
// Member variables // Member variables
size_t m_selected_item = 0; ///< Index of currently selected menu item (0-based) size_t m_selected_item = 0;
std::vector<MenuItem> m_items; ///< Collection of all menu items in display order std::vector<MenuItem> m_items;
menu_options_t *m_options; ///< Pointer to menu configuration options (not owned) menu_options_t *m_options;
}; };

View File

@@ -142,35 +142,6 @@ public:
MenuItem(uint8_t id, uint8_t type, std::string text, std::vector<std::string> values, int index, MenuItem(uint8_t id, uint8_t type, std::string text, std::vector<std::string> values, int index,
ButtonCallback callback); 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 * @brief Gets the unique identifier of this menu item
* @return The menu item's unique ID as assigned during construction * @return The menu item's unique ID as assigned during construction
@@ -282,10 +253,12 @@ public:
[[nodiscard]] int getIndex() const; [[nodiscard]] int getIndex() const;
std::vector<std::string> getValues() const; [[nodiscard]] std::vector<std::string> getValues() const;
[[nodiscard]] size_t getItemCount() const; [[nodiscard]] size_t getItemCount() const;
[[nodiscard]] MenuItem copyWith(const std::string &value) const;
[[nodiscard]] MenuItem copyWith(size_t index) const; [[nodiscard]] MenuItem copyWith(size_t index) const;
private: private:

View File

@@ -2,13 +2,32 @@
#include "common/Menu.h" #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 class LightSettingsMenu final : public Menu
{ {
public: 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); explicit LightSettingsMenu(menu_options_t *options);
private: 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; void onButtonPressed(const MenuItem& menuItem, ButtonType button) override;
menu_options_t *m_options; menu_options_t *m_options; ///< Pointer to menu configuration options
}; };

View File

@@ -67,6 +67,47 @@ void Menu::setItemSize(const size_t size)
m_items.erase(m_items.begin() + static_cast<int>(size + 1), m_items.end()); m_items.erase(m_items.begin() + static_cast<int>(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) 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); u8g2_DrawFrame(u8g2, frameX, frameY, UIConstants::FRAME_BOX_SIZE, UIConstants::FRAME_BOX_SIZE);
// Draw checkmark (X) if toggle is true // 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 checkX1 = frameX + 2;
const int checkY1 = frameY + 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 { auto callback = [this](const MenuItem &menuItem, const ButtonType button) -> void {
onButtonPressed(menuItem, button); 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 void Menu::drawScrollBar() const

View File

@@ -1,16 +1,19 @@
#include "data/MenuItem.h" #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) 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)) : 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, MenuItem::MenuItem(const uint8_t id, const uint8_t type, std::string text, std::string value,
ButtonCallback callback) ButtonCallback callback)
: m_id(id), m_type(type), m_text(std::move(text)), m_value(std::move(value)), m_callback(std::move(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<std::string> values, int index, MenuItem::MenuItem(const uint8_t id, const uint8_t type, std::string text, std::vector<std::string> values, int index,
ButtonCallback callback) ButtonCallback callback)
: m_id(id), m_type(type), m_text(std::move(text)), m_values(std::move(values)), m_index(index), : 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 uint8_t MenuItem::getId() const
{ {
return m_id; return m_id;
@@ -42,10 +38,12 @@ const std::string &MenuItem::getText() const
const std::string &MenuItem::getValue() 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()) if (!m_values.empty() && m_index >= 0 && m_index < m_values.size())
{ {
return m_values.at(m_index); return m_values.at(m_index);
} }
// Otherwise return the direct value
return m_value; return m_value;
} }
@@ -56,6 +54,7 @@ void MenuItem::setValue(const std::string &value)
void MenuItem::onButtonPressed(const ButtonType button) const void MenuItem::onButtonPressed(const ButtonType button) const
{ {
// Execute the callback function if one is registered
if (m_callback) if (m_callback)
{ {
m_callback(*this, button); m_callback(*this, button);
@@ -82,9 +81,20 @@ size_t MenuItem::getItemCount() const
return m_values.size(); 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 MenuItem MenuItem::copyWith(const size_t index) const
{ {
// Create a copy of this menu item with a new selected index
MenuItem copy = *this; MenuItem copy = *this;
// Check for potential overflow when converting size_t to int
if (index > std::numeric_limits<int>::max()) if (index > std::numeric_limits<int>::max())
{ {
throw std::overflow_error("index is too large"); throw std::overflow_error("index is too large");

View File

@@ -2,36 +2,69 @@
#include "ui/LightSettingsMenu.h" #include "ui/LightSettingsMenu.h"
/**
* @namespace LightMenuItem
* @brief Constants for light menu item identifiers
*/
namespace LightMenuItem namespace LightMenuItem
{ {
constexpr uint8_t MODE = 1; constexpr uint8_t ACTIVATE = 0; ///< ID for the light activation toggle
constexpr uint8_t TOGGLE = 2; constexpr uint8_t MODE = 1; ///< ID for the light mode selection
constexpr uint8_t LED_SETTINGS = 3; 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) 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<std::string> values; std::vector<std::string> values;
values.emplace_back("Tag"); values.emplace_back("Tag"); // Day mode
values.emplace_back("Nacht"); values.emplace_back("Nacht"); // Night mode
addSelection(LightMenuItem::MODE, "Modus", values, 0); addSelection(LightMenuItem::MODE, "Modus", values, 0);
// Add menu item for accessing LED settings submenu
addText(LightMenuItem::LED_SETTINGS, "LED Einstellungen"); addText(LightMenuItem::LED_SETTINGS, "LED Einstellungen");
} }
void LightMenu::onButtonPressed(const MenuItem &menuItem, const ButtonType button) void LightMenu::onButtonPressed(const MenuItem &menuItem, const ButtonType button)
{ {
std::shared_ptr<Widget> widget; std::shared_ptr<Widget> widget;
// Handle different menu items based on their ID
switch (menuItem.getId()) switch (menuItem.getId())
{ {
case LightMenuItem::LED_SETTINGS: case LightMenuItem::ACTIVATE: {
widget = std::make_shared<LightSettingsMenu>(m_options); // Toggle the light activation state when SELECT is pressed
break; if (button == ButtonType::SELECT)
{
default: toggle(menuItem);
}
break; 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<LightSettingsMenu>(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) if (m_options && m_options->pushScreen)
{ {
m_options->pushScreen(widget); m_options->pushScreen(widget);

View File

@@ -1,61 +1,39 @@
#include "ui/LightSettingsMenu.h" #include "ui/LightSettingsMenu.h"
/**
* @namespace LightSettingsMenuItem
* @brief Constants for light settings menu item identifiers
*/
namespace LightSettingsMenuItem 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) LightSettingsMenu::LightSettingsMenu(menu_options_t *options) : Menu(options), m_options(options)
{ {
// Create values vector for section counts (1-99)
std::vector<std::string> values; std::vector<std::string> values;
for (size_t i = 1; i <= 99; i++) for (size_t i = 1; i <= 99; i++)
{ {
values.emplace_back(std::to_string(i)); values.emplace_back(std::to_string(i));
} }
// Add section counter selection (allows choosing number of sections)
addSelection(LightSettingsMenuItem::SECTION_COUNTER, "Sektionen", values, 0); addSelection(LightSettingsMenuItem::SECTION_COUNTER, "Sektionen", values, 0);
// Add first section configuration item
addSelection(1, "Sektion 1", values, 0); addSelection(1, "Sektion 1", values, 0);
} }
void LightSettingsMenu::onButtonPressed(const MenuItem &menuItem, const ButtonType button) void LightSettingsMenu::onButtonPressed(const MenuItem &menuItem, const ButtonType button)
{ {
/// change visible counter // Handle value switching for the current menu item
switch (button) switchValue(menuItem, 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: // Update the section list size based on the section counter value
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
setItemSize(std::stoull(getItem(0).getValue())); 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) if (m_options && m_options->persistence && m_options->persistence->save)
{ {
const auto key = "section_" + std::to_string(menuItem.getId()); const auto key = "section_" + std::to_string(menuItem.getId());