Refactor RootLayout and UIHandler for improved structure and functionality

- Updated RootLayout to manage layout initialization and deinitialization more effectively.
- Removed unnecessary dependencies and streamlined event handling for keyboard events.
- Enhanced UIHandler to utilize shared pointers for app descriptors, improving memory management.
- Added methods for showing and hiding navigation elements in RootLayout.
- Introduced textarea widget with instant response by disabling animations.
- Improved error handling and logging throughout the UI components.
This commit is contained in:
GW_MC
2026-02-01 13:03:56 +08:00
parent 237a3a96c5
commit 06e81301b2
22 changed files with 880 additions and 2198 deletions

View File

@@ -1,208 +1,288 @@
#include "ui/ui_handler.h"
#include "ui/root_layout.h"
#include "ui/app_registry.h"
#include "esp_log.h"
#include "lvgl.h"
#define TAG "UIHandler"
// Display dimensions from constants.h
#define DISPLAY_WIDTH 800
#define DISPLAY_HEIGHT 480
// Layout dimensions
#define HEADER_HEIGHT 40
#define NAV_BAR_HEIGHT 50
#define _APP_CONTAINERHEIGHT (DISPLAY_HEIGHT - HEADER_HEIGHT - NAV_BAR_HEIGHT)
UIHandler::~UIHandler() {
deinit();
}
esp_err_t UIHandler::init(void) {
ESP_LOGI(TAG, "Initializing UIHandler");
lv_obj_t* screen = lv_scr_act();
esp_err_t ret = ESP_OK;
// Create main screen
_main_screen = lv_obj_create(NULL);
if (!_main_screen) {
ESP_LOGE(TAG, "Failed to create main screen");
return ESP_FAIL;
}
lv_obj_set_style_bg_color(_main_screen, lv_color_black(), 0);
lv_obj_set_size(_main_screen, DISPLAY_WIDTH, DISPLAY_HEIGHT);
// Create root layout
_root_layout = new RootLayout(this);
if (!_root_layout) {
ESP_LOGE(TAG, "Failed to allocate RootLayout");
return ESP_FAIL;
// Create main screen layout
ret = create_main_screen_(screen);
if (ret != ESP_OK) {
ESP_LOGE(TAG, "Failed to create main screen layout");
return ret;
}
if (_root_layout->init(_main_screen) != ESP_OK) {
ESP_LOGE(TAG, "Failed to initialize root layout");
delete _root_layout;
_root_layout = nullptr;
return ESP_FAIL;
ret = interaction_handler_.init(root_layout_.get_app_container());
if (ret != ESP_OK) {
ESP_LOGE(TAG, "Failed to initialize InteractionHandler");
return ret;
}
// Render app icons from registry
if (_root_layout->render_app_icons() != ESP_OK) {
ESP_LOGW(TAG, "Failed to render app icons");
}
// Show the main screen
lv_scr_load(screen);
// Defer screen loading to prevent blocking during initialization
// Use LVGL timer to load screen after allowing watchdog reset
lv_timer_create([](lv_timer_t* timer) {
lv_obj_t* screen = static_cast<lv_obj_t*>(lv_timer_get_user_data(timer));
ESP_LOGI("UIHandler", "Loading main screen via timer");
lv_screen_load(screen);
lv_timer_del(timer);
}, 100, _main_screen); // 100ms delay to allow watchdog reset
ESP_LOGI(TAG, "UIHandler initialized successfully");
return ESP_OK;
return ret;
}
esp_err_t UIHandler::deinit(void) {
ESP_LOGI(TAG, "Deinitializing UIHandler");
// Deinit current app
if (_active_app) {
if (_active_app->deinit() != ESP_OK) {
ESP_LOGW(TAG, "Error deinitializing active app: %s", _active_app->get_name());
// Deinitialize current app if any
if (active_descriptor_) {
UIApp* app = active_descriptor_->get_app_instance();
if (app) {
esp_err_t ret = app->deinit();
if (ret != ESP_OK) {
ESP_LOGE("UIHandler", "Failed to deinitialize current app");
return ret;
}
}
_active_app = nullptr;
active_descriptor_ = nullptr;
}
// Delete shutdown app if cached
if (_shutdown_app) {
delete _shutdown_app;
_shutdown_app = nullptr;
// Destroy main screen layout
esp_err_t ret = destroy_main_screen_();
if (ret != ESP_OK) {
ESP_LOGE("UIHandler", "Failed to destroy main screen layout");
return ret;
}
// Clean up root layout
if (_root_layout) {
_root_layout->deinit();
delete _root_layout;
_root_layout = nullptr;
}
// Main screen will be cleaned up by LVGL
_main_screen = nullptr;
return ESP_OK;
}
esp_err_t UIHandler::switch_app(UIApp* app) {
if (!app) {
ESP_LOGE(TAG, "Cannot switch to null app");
return ESP_ERR_INVALID_ARG;
}
lv_obj_t* app_container = get_app_container();
if (!app_container) {
ESP_LOGE(TAG, "App container not initialized");
return ESP_FAIL;
}
ESP_LOGI(TAG, "Switching to app: %s", app->get_name());
// Deinit current app
if (_active_app) {
if (_active_app->deinit() != ESP_OK) {
ESP_LOGW(TAG, "Error deinitializing app: %s", _active_app->get_name());
}
}
// Clear the app container
lv_obj_clean(app_container);
// Initialize new app
if (app->init(app_container) != ESP_OK) {
ESP_LOGE(TAG, "Failed to initialize app: %s", app->get_name());
_active_app = nullptr;
return ESP_FAIL;
}
_active_app = app;
// Update header through RootLayout
if (_root_layout) {
_root_layout->update_header(_active_app->get_name());
_root_layout->show_back_button();
// Deinitialize interaction handler
ret = interaction_handler_.deinit();
if (ret != ESP_OK) {
ESP_LOGE("UIHandler", "Failed to deinitialize InteractionHandler");
return ret;
}
return ESP_OK;
}
esp_err_t UIHandler::switch_app(AppDescriptor* app_descriptor) {
esp_err_t UIHandler::switch_app(std::shared_ptr<AppDescriptor> app_descriptor) {
if (!app_descriptor) {
ESP_LOGE(TAG, "Cannot switch to null app descriptor");
ESP_LOGE(TAG, "Invalid app descriptor");
return ESP_ERR_INVALID_ARG;
}
UIApp* app = app_descriptor->get_app_instance();
if (!app) {
ESP_LOGE(TAG, "App descriptor has null app instance");
return ESP_ERR_INVALID_ARG;
esp_err_t ret = ESP_OK;
// Deinitialize current app if any
if (active_descriptor_) {
UIApp* current_app = active_descriptor_->get_app_instance();
if (current_app) {
ret = current_app->deinit();
if (ret != ESP_OK) {
ESP_LOGE(TAG, "Failed to deinitialize current app");
}
}
}
return switch_app(app);
}
void UIHandler::route_event(uint32_t event_type, void* event_data) {
if (_active_app) {
_active_app->handle_event(event_type, event_data);
}
}
esp_err_t UIHandler::show_shutdown_screen(std::string message) {
ESP_LOGI(TAG, "Showing shutdown screen");
lv_obj_t* app_container = get_app_container();
if (!app_container) {
ESP_LOGE(TAG, "App container not initialized");
return ESP_FAIL;
}
// Clear current app reference
_active_app = nullptr;
// Clear the app container
lv_obj_clean(app_container);
// Create shutdown message
lv_obj_t* shutdown_label = lv_label_create(app_container);
lv_label_set_text(shutdown_label, message.empty() ? "Shutting down..." : message.c_str());
lv_obj_set_style_text_color(shutdown_label, lv_color_white(), 0);
lv_obj_align(shutdown_label, LV_ALIGN_CENTER, 0, 0);
// Update header through RootLayout
if (_root_layout) {
_root_layout->update_header("System Shutdown");
lv_obj_t* app_container = root_layout_.get_app_container();
if (app_container) {
lv_obj_clean(app_container);
} else {
ESP_LOGE(TAG, "App container not available");
return ESP_ERR_INVALID_STATE;
}
// Set the new app as active
active_descriptor_ = app_descriptor;
// Initialize the new app
UIApp* new_app = active_descriptor_->get_app_instance();
if (!new_app) {
ESP_LOGE(TAG, "App instance not available");
active_descriptor_ = nullptr;
return ESP_ERR_INVALID_STATE;
}
ret = new_app->init(app_container);
if (ret != ESP_OK) {
ESP_LOGE(TAG, "Failed to initialize app: %s", new_app->get_name().c_str());
active_descriptor_ = nullptr;
return ret;
}
// Update header with app name
ret = update_header_title(new_app->get_name());
if (ret != ESP_OK) {
ESP_LOGW(TAG, "Failed to update header title");
}
// Show back button when in an app
root_layout_.show_back_button();
ESP_LOGI(TAG, "Switched to app: %s", new_app->get_name().c_str());
return ESP_OK;
}
esp_err_t UIHandler::show_shutdown_screen(const std::string& message) {
// Deinitialize current app if any
if (active_descriptor_) {
UIApp* app = active_descriptor_->get_app_instance();
if (app) {
app->deinit();
}
active_descriptor_ = nullptr;
}
// Clear the app container
lv_obj_t* app_container = root_layout_.get_app_container();
if (app_container) {
lv_obj_clean(app_container);
// Create a simple shutdown message screen
lv_obj_t* label = lv_label_create(app_container);
if (message.empty()) {
lv_label_set_text(label, "Shutting down...");
} else {
lv_label_set_text(label, message.c_str());
}
lv_obj_set_style_text_font(label, &lv_font_montserrat_14, 0);
lv_obj_center(label);
}
// Update header
update_header_title("System");
// Hide navigation buttons
root_layout_.hide_back_button();
root_layout_.hide_home_button();
ESP_LOGI(TAG, "Showing shutdown screen: %s", message.c_str());
return ESP_OK;
}
esp_err_t UIHandler::return_to_main_screen(void) {
ESP_LOGI(TAG, "Returning to main screen");
esp_err_t ret = ESP_OK;
// Deinit current app
if (_active_app) {
if (_active_app->deinit() != ESP_OK) {
ESP_LOGW(TAG, "Error deinitializing app: %s", _active_app->get_name());
// Deinitialize current app if any
if (active_descriptor_) {
UIApp* app = active_descriptor_->get_app_instance();
if (app) {
ret = app->deinit();
if (ret != ESP_OK) {
ESP_LOGE(TAG, "Failed to deinitialize app");
return ret;
}
}
_active_app = nullptr;
active_descriptor_ = nullptr;
}
// Clear the app container
lv_obj_t* app_container = get_app_container();
lv_obj_t* app_container = root_layout_.get_app_container();
if (app_container) {
lv_obj_clean(app_container);
// TODO: Display app launcher/home screen with app icons
// For now, just show a placeholder message
lv_obj_t* label = lv_label_create(app_container);
lv_label_set_text(label, "Home Screen\n\nApp icons will go here");
lv_obj_set_style_text_align(label, LV_TEXT_ALIGN_CENTER, 0);
lv_obj_center(label);
} else {
ESP_LOGE(TAG, "App container not available");
return ESP_ERR_INVALID_STATE;
}
// Update header and hide back button through RootLayout
if (_root_layout) {
_root_layout->update_header("");
_root_layout->hide_back_button();
// Update header
ret = update_header_title("Home");
if (ret != ESP_OK) {
ESP_LOGW(TAG, "Failed to update header title");
}
// Hide back button on home screen
root_layout_.hide_back_button();
ESP_LOGI(TAG, "Returned to main screen");
return ESP_OK;
}
esp_err_t UIHandler::update_header_title(const std::string& title) {
return root_layout_.update_header(title);
}
//
// Private methods
//
void UIHandler::on_back_button_pressed_(void) {
if (active_descriptor_) {
UIApp* app = active_descriptor_->get_app_instance();
if (app) {
bool handled = app->on_back_button_pressed();
if (!handled) {
// App didn't handle it, return to main screen
return_to_main_screen();
}
}
} else {
ESP_LOGW(TAG, "Back button pressed but no active app");
}
}
esp_err_t UIHandler::create_main_screen_(lv_obj_t* parent) {
esp_err_t ret = ESP_OK;
// Initialize root layout
ret = root_layout_.init(parent, this);
if (ret != ESP_OK) {
ESP_LOGE(TAG, "Failed to initialize RootLayout");
return ret;
}
// Register back button callback
lv_event_dsc_t* back_event_dsc = nullptr;
ret = root_layout_.register_back_button_callback(
[](lv_event_t* e) {
UIHandler* ui_handler = static_cast<UIHandler*>(lv_event_get_user_data(e));
ui_handler->on_back_button_pressed_();
},
this,
&back_event_dsc
);
if (ret != ESP_OK) {
ESP_LOGE(TAG, "Failed to register back button callback");
return ret;
}
// Register home button callback
lv_event_dsc_t* home_event_dsc = nullptr;
ret = root_layout_.register_home_button_callback(
[](lv_event_t* e) {
UIHandler* ui_handler = static_cast<UIHandler*>(lv_event_get_user_data(e));
ui_handler->return_to_main_screen();
},
this,
&home_event_dsc
);
if (ret != ESP_OK) {
ESP_LOGE(TAG, "Failed to register home button callback");
return ret;
}
ESP_LOGI(TAG, "Main screen layout created successfully");
return ESP_OK;
}
esp_err_t UIHandler::destroy_main_screen_(void) {
esp_err_t ret = root_layout_.deinit();
if (ret != ESP_OK) {
ESP_LOGE(TAG, "Failed to deinitialize RootLayout");
return ret;
}
ESP_LOGI(TAG, "Main screen layout destroyed successfully");
return ESP_OK;
}