Files
ink-board/main/ui/ui_handler.cpp

346 lines
9.0 KiB
C++

#include "ui/ui_handler.h"
#include "ui/apps/registry.h"
#include "esp_log.h"
#include <algorithm>
#define TAG "UIHandler"
struct AppClickUserData {
UIHandler* ui_handler;
std::string app_name;
};
UIHandler::~UIHandler() {
deinit();
// Clean up all allocated AppClickUserData
for (void* data : app_click_user_data_) {
delete static_cast<AppClickUserData*>(data);
}
app_click_user_data_.clear();
}
esp_err_t UIHandler::init(void) {
lv_obj_t* screen = lv_scr_act();
esp_err_t ret = ESP_OK;
// Create main screen layout
ret = create_main_screen_(screen);
if (ret != ESP_OK) {
ESP_LOGE(TAG, "Failed to create main screen layout");
return ret;
}
// Initialize InteractionHandler with screen as parent (not app_container)
// so keyboard survives app switches
ret = interaction_handler_.init(screen);
if (ret != ESP_OK) {
ESP_LOGE(TAG, "Failed to initialize InteractionHandler");
return ret;
}
// Show the main screen
lv_scr_load(screen);
return ret;
}
esp_err_t UIHandler::deinit(void) {
// 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_descriptor_ = 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;
}
// 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) {
if (!app_descriptor) {
ESP_LOGE(TAG, "Invalid app descriptor");
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");
}
}
}
// Clear the app container
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, &interaction_handler_);
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_err_t ret = ESP_OK;
// 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_descriptor_ = nullptr;
}
// Clear the 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
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;
}
// render all apps
for (const auto& [name, descriptor] : AppRegistry::instance()) {
lv_obj_t* app_icon_container = lv_obj_create(root_layout_.get_app_container());
lv_obj_set_size(app_icon_container, 100, 100);
lv_obj_set_style_pad_all(app_icon_container, 10, 0);
lv_obj_set_flex_flow(app_icon_container, LV_FLEX_FLOW_COLUMN);
lv_obj_set_flex_align(app_icon_container, LV_FLEX_ALIGN_CENTER, LV_FLEX_ALIGN_CENTER, LV_FLEX_ALIGN_CENTER);
// Draw the app icon
descriptor->draw_icon(app_icon_container);
// App name label
lv_obj_t* label = lv_label_create(app_icon_container);
lv_label_set_text(label, name.c_str());
lv_obj_set_style_text_align(label, LV_TEXT_ALIGN_CENTER, 0);
// Center the icon container
lv_obj_center(app_icon_container);
// Create and track user data for the callback
auto* click_data = new AppClickUserData { this, name };
app_click_user_data_.push_back(click_data);
// Register click event to switch to the app
lv_obj_add_event_cb(app_icon_container,
[](lv_event_t* e) {
AppClickUserData* user_data = static_cast<AppClickUserData*>(lv_event_get_user_data(e));
UIHandler* ui_handler = user_data->ui_handler;
std::string app_name = user_data->app_name;
AppDescriptor* descriptor = AppRegistry::instance()[app_name];
if (descriptor) {
ui_handler->switch_app(descriptor);
} else {
ESP_LOGE(TAG, "App descriptor not found for app: %s", app_name.c_str());
}
},
LV_EVENT_CLICKED,
click_data
);
}
// 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;
}