From dd1702e3e90db0291d5459661e471c3c06085731 Mon Sep 17 00:00:00 2001 From: GW_MC <72297530+GWMCwing@users.noreply.github.com> Date: Sat, 24 Jan 2026 13:13:28 +0800 Subject: [PATCH] feat: implement PageStack class for multi-page navigation in LVGL apps --- main/ui/page_stack.cpp | 115 +++++++++++++++++++++++++++++++++++++++++ main/ui/page_stack.h | 86 ++++++++++++++++++++++++++++++ 2 files changed, 201 insertions(+) create mode 100644 main/ui/page_stack.cpp create mode 100644 main/ui/page_stack.h diff --git a/main/ui/page_stack.cpp b/main/ui/page_stack.cpp new file mode 100644 index 0000000..3b65173 --- /dev/null +++ b/main/ui/page_stack.cpp @@ -0,0 +1,115 @@ +#include "page_stack.h" +#include "esp_log.h" + +static const char* TAG = "PageStack"; + +PageStack::PageStack(lv_obj_t* parent_container) + : parent_container_(parent_container) { + if (!parent_container_) { + ESP_LOGE(TAG, "Parent container is null"); + } +} + +PageStack::~PageStack() { + clear(); +} + +lv_obj_t* PageStack::create_page_container() { + lv_obj_t* page = lv_obj_create(parent_container_); + + // Fill parent container + lv_obj_set_size(page, LV_PCT(100), LV_PCT(100)); + lv_obj_set_pos(page, 0, 0); + + // Remove padding and scrollbars + lv_obj_set_style_pad_all(page, 0, 0); + lv_obj_set_scrollbar_mode(page, LV_SCROLLBAR_MODE_OFF); + + // White background + lv_obj_set_style_bg_color(page, lv_color_white(), 0); + lv_obj_set_style_bg_opa(page, LV_OPA_COVER, 0); + + // Remove border + lv_obj_set_style_border_width(page, 0, 0); + + return page; +} + +lv_obj_t* PageStack::push(PageBuilder builder, PageCleanup cleanup) { + if (!parent_container_) { + ESP_LOGE(TAG, "Cannot push page: parent container is null"); + return nullptr; + } + + if (!builder) { + ESP_LOGE(TAG, "Cannot push page: builder is null"); + return nullptr; + } + + // Hide current page if any + if (!pages_.empty()) { + lv_obj_add_flag(pages_.back().container, LV_OBJ_FLAG_HIDDEN); + } + + // Create new page container + lv_obj_t* page = create_page_container(); + + // Build page content + builder(page); + + // Add to stack + pages_.push_back({page, cleanup}); + + ESP_LOGD(TAG, "Pushed page (depth: %d)", pages_.size()); + return page; +} + +bool PageStack::pop() { + if (pages_.empty()) { + ESP_LOGW(TAG, "Cannot pop: stack is empty"); + return false; + } + + // Get and remove current page + Page current = pages_.back(); + pages_.pop_back(); + + // Call cleanup callback if provided + if (current.cleanup) { + current.cleanup(current.container); + } + + // Delete page container + lv_obj_del(current.container); + + // Show previous page if any + if (!pages_.empty()) { + lv_obj_clear_flag(pages_.back().container, LV_OBJ_FLAG_HIDDEN); + } + + ESP_LOGD(TAG, "Popped page (depth: %d)", pages_.size()); + return true; +} + +void PageStack::clear() { + ESP_LOGD(TAG, "Clearing all pages (depth: %d)", pages_.size()); + + // Pop all pages (calls cleanup callbacks) + while (!pages_.empty()) { + Page current = pages_.back(); + pages_.pop_back(); + + if (current.cleanup) { + current.cleanup(current.container); + } + + lv_obj_del(current.container); + } +} + +lv_obj_t* PageStack::current_page() const { + if (pages_.empty()) { + return nullptr; + } + return pages_.back().container; +} diff --git a/main/ui/page_stack.h b/main/ui/page_stack.h new file mode 100644 index 0000000..0a3633a --- /dev/null +++ b/main/ui/page_stack.h @@ -0,0 +1,86 @@ +#pragma once + +#include "lvgl.h" +#include +#include + +/** + * @brief Reusable page stack for multi-page navigation within LVGL apps + * + * Manages a stack of LVGL containers, allowing apps to push/pop pages + * and implement hierarchical navigation. Thread-safe for use with LVGL. + */ +class PageStack { +public: + /** + * @brief Page builder callback + * @param page_container The LVGL container to build the page in + */ + using PageBuilder = std::function; + + /** + * @brief Page cleanup callback + * @param page_container The LVGL container being destroyed + */ + using PageCleanup = std::function; + + /** + * @brief Construct page stack with parent container + * @param parent_container Parent LVGL container for pages + */ + explicit PageStack(lv_obj_t* parent_container); + + /** + * @brief Destructor - clears all pages + */ + ~PageStack(); + + /** + * @brief Push a new page onto the stack + * @param builder Function to build page content + * @param cleanup Optional cleanup function called when page is popped + * @return The created page container + */ + lv_obj_t* push(PageBuilder builder, PageCleanup cleanup = nullptr); + + /** + * @brief Pop the current page and return to previous + * @return true if page was popped, false if stack is empty + */ + bool pop(); + + /** + * @brief Clear all pages from the stack + */ + void clear(); + + /** + * @brief Get the current (top) page container + * @return Current page or nullptr if stack is empty + */ + lv_obj_t* current_page() const; + + /** + * @brief Get the number of pages in the stack + */ + size_t depth() const { return pages_.size(); } + + /** + * @brief Check if stack is empty + */ + bool empty() const { return pages_.empty(); } + +private: + struct Page { + lv_obj_t* container; + PageCleanup cleanup; + }; + + lv_obj_t* parent_container_; + std::vector pages_; + + /** + * @brief Create a page container + */ + lv_obj_t* create_page_container(); +};