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:
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user