#include "ui/root_layout.h" #include "ui/ui_handler.h" #include "ui/app_registry.h" #include "esp_log.h" #define TAG "RootLayout" // Display dimensions #define DISPLAY_WIDTH 800 #define DISPLAY_HEIGHT 480 // Layout dimensions #define HEADER_HEIGHT 40 #define NAV_BAR_HEIGHT 50 #define APP_CONTAINER_HEIGHT (DISPLAY_HEIGHT - HEADER_HEIGHT - NAV_BAR_HEIGHT) RootLayout::RootLayout(UIHandler* ui_handler) : _ui_handler(ui_handler) { } esp_err_t RootLayout::init(lv_obj_t* parent) { if (!parent) { ESP_LOGE(TAG, "Parent object is null"); return ESP_ERR_INVALID_ARG; } ESP_LOGI(TAG, "Initializing RootLayout"); if (create_layout(parent) != ESP_OK) { ESP_LOGE(TAG, "Failed to create layout"); return ESP_FAIL; } ESP_LOGI(TAG, "RootLayout initialized successfully"); return ESP_OK; } esp_err_t RootLayout::deinit(void) { ESP_LOGI(TAG, "Deinitializing RootLayout"); // LVGL will handle cleanup when parent is destroyed _header = nullptr; _header_label = nullptr; _app_container = nullptr; _nav_bar = nullptr; _back_button = nullptr; return ESP_OK; } esp_err_t RootLayout::create_layout(lv_obj_t* parent) { // Create header (top) _header = lv_obj_create(parent); lv_obj_set_size(_header, DISPLAY_WIDTH, HEADER_HEIGHT); lv_obj_set_pos(_header, 0, 0); lv_obj_set_style_bg_color(_header, lv_color_hex(0x333333), 0); lv_obj_set_style_border_width(_header, 0, 0); _header_label = lv_label_create(_header); lv_label_set_text(_header_label, "App"); lv_obj_set_style_text_color(_header_label, lv_color_white(), 0); lv_obj_align(_header_label, LV_ALIGN_LEFT_MID, 10, 0); // Create app container (middle) _app_container = lv_obj_create(parent); lv_obj_set_size(_app_container, DISPLAY_WIDTH, APP_CONTAINER_HEIGHT); lv_obj_set_pos(_app_container, 0, HEADER_HEIGHT); lv_obj_set_style_bg_color(_app_container, lv_color_white(), 0); lv_obj_set_style_border_width(_app_container, 0, 0); lv_obj_set_style_pad_all(_app_container, 0, 0); // Create navigation bar (bottom) _nav_bar = lv_obj_create(parent); lv_obj_set_size(_nav_bar, DISPLAY_WIDTH, NAV_BAR_HEIGHT); lv_obj_set_pos(_nav_bar, 0, HEADER_HEIGHT + APP_CONTAINER_HEIGHT); lv_obj_set_style_bg_color(_nav_bar, lv_color_hex(0x333333), 0); lv_obj_set_style_border_width(_nav_bar, 0, 0); ESP_LOGI(TAG, "Layout created: Header=%d, AppContainer=%d, NavBar=%d", HEADER_HEIGHT, APP_CONTAINER_HEIGHT, NAV_BAR_HEIGHT); return ESP_OK; } void RootLayout::update_header(std::string app_name) { if (!_header_label) { return; } if (app_name.empty() == false) { lv_label_set_text(_header_label, app_name.c_str()); } else { lv_label_set_text(_header_label, "App"); } } esp_err_t RootLayout::render_app_icons(void) { if (!_nav_bar) { ESP_LOGE(TAG, "Navigation bar not initialized"); return ESP_FAIL; } // Clear existing nav bar content lv_obj_clean(_nav_bar); // Get all registered apps from registry const auto& app_descriptors = AppRegistry::instance().get_app_descriptors(); if (app_descriptors.empty()) { ESP_LOGW(TAG, "No apps registered in AppRegistry"); lv_obj_t* nav_label = lv_label_create(_nav_bar); lv_label_set_text(nav_label, "No apps available"); lv_obj_set_style_text_color(nav_label, lv_color_white(), 0); lv_obj_align(nav_label, LV_ALIGN_CENTER, 0, 0); return ESP_OK; } ESP_LOGI(TAG, "Rendering %d app icons", app_descriptors.size()); // Calculate icon spacing int icon_count = app_descriptors.size(); int icon_spacing = DISPLAY_WIDTH / (icon_count + 1); int x_offset = icon_spacing; // Render each app icon for (size_t i = 0; i < app_descriptors.size(); i++) { AppDescriptor* descriptor = app_descriptors[i]; // Create a container for this app icon lv_obj_t* icon_container = lv_obj_create(_nav_bar); lv_obj_set_size(icon_container, icon_spacing - 10, NAV_BAR_HEIGHT - 10); lv_obj_set_pos(icon_container, x_offset - (icon_spacing - 10) / 2, 5); lv_obj_set_style_bg_opa(icon_container, LV_OPA_TRANSP, 0); lv_obj_set_style_border_width(icon_container, 0, 0); lv_obj_set_style_pad_all(icon_container, 0, 0); // Store both the descriptor and ui_handler as user data lv_obj_set_user_data(icon_container, descriptor); // Let the descriptor draw its icon descriptor->draw_icon(icon_container); // Add click event handler lv_obj_add_flag(icon_container, LV_OBJ_FLAG_CLICKABLE); lv_obj_add_event_cb(icon_container, on_app_icon_clicked, LV_EVENT_CLICKED, _ui_handler); x_offset += icon_spacing; } // Create back button on the left side of the nav bar _back_button = lv_btn_create(_nav_bar); lv_obj_set_size(_back_button, 60, NAV_BAR_HEIGHT - 10); lv_obj_set_pos(_back_button, 5, 5); lv_obj_set_style_bg_color(_back_button, lv_color_hex(0x555555), 0); // Add back arrow label lv_obj_t* back_label = lv_label_create(_back_button); lv_label_set_text(back_label, LV_SYMBOL_LEFT); lv_obj_set_style_text_color(back_label, lv_color_white(), 0); lv_obj_align(back_label, LV_ALIGN_CENTER, 0, 0); // Add click event handler lv_obj_add_event_cb(_back_button, on_back_button_clicked, LV_EVENT_CLICKED, _ui_handler); // Initially hide back button (shown when app is active) lv_obj_add_flag(_back_button, LV_OBJ_FLAG_HIDDEN); return ESP_OK; } void RootLayout::show_back_button(void) { if (_back_button) { lv_obj_clear_flag(_back_button, LV_OBJ_FLAG_HIDDEN); } } void RootLayout::hide_back_button(void) { if (_back_button) { lv_obj_add_flag(_back_button, LV_OBJ_FLAG_HIDDEN); } } void RootLayout::on_app_icon_clicked(lv_event_t* event) { lv_obj_t* icon_container = static_cast(lv_event_get_target(event)); UIHandler* handler = static_cast(lv_event_get_user_data(event)); AppDescriptor* descriptor = static_cast(lv_obj_get_user_data(icon_container)); if (!handler || !descriptor) { ESP_LOGE(TAG, "Invalid event data in app icon click"); return; } ESP_LOGI(TAG, "App icon clicked: %s", descriptor->get_name().c_str()); handler->switch_app(descriptor); } void RootLayout::on_back_button_clicked(lv_event_t* event) { UIHandler* handler = static_cast(lv_event_get_user_data(event)); if (!handler) { ESP_LOGE(TAG, "Invalid handler in back button click"); return; } // Get the active app UIApp* active_app = handler->get_active_app(); if (!active_app) { ESP_LOGW(TAG, "Back button pressed but no active app"); return; } // Let the app handle the back button press bool handled = active_app->on_back_button_pressed(); if (handled) { ESP_LOGI(TAG, "Back button handled by app: %s", active_app->get_name()); } else { ESP_LOGI(TAG, "Back button not handled by app, returning to main screen"); handler->return_to_main_screen(); } }