#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) // forward-declare local event callback static void on_home_button_clicked(lv_event_t* event); 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) { // Configure parent as flexbox column layout lv_obj_set_flex_flow(parent, LV_FLEX_FLOW_COLUMN); lv_obj_set_flex_align(parent, LV_FLEX_ALIGN_START, LV_FLEX_ALIGN_START, LV_FLEX_ALIGN_START); lv_obj_set_style_pad_all(parent, 0, 0); lv_obj_set_style_pad_gap(parent, 0, 0); // Create header (top, fixed height) _header = lv_obj_create(parent); lv_obj_set_width(_header, lv_pct(100)); lv_obj_set_height(_header, HEADER_HEIGHT); lv_obj_set_style_bg_color(_header, lv_color_hex(0xFFFFFF), 0); lv_obj_set_style_border_width(_header, 0, 0); lv_obj_set_style_border_color(_header, lv_color_hex(0x000000), 0); lv_obj_set_style_border_width(_header, 1, LV_BORDER_SIDE_BOTTOM); lv_obj_set_style_pad_all(_header, 0, 0); lv_obj_set_style_radius(_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_black(), 0); lv_obj_align(_header_label, LV_ALIGN_LEFT_MID, 10, 0); // Create app container (middle, flexible - grows to fill available space) _app_container = lv_obj_create(parent); lv_obj_set_width(_app_container, lv_pct(100)); lv_obj_set_flex_grow(_app_container, 1); 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); lv_obj_set_style_radius(_app_container, 0, 0); // Create navigation bar (bottom, fixed height) _nav_bar = lv_obj_create(parent); lv_obj_set_width(_nav_bar, lv_pct(100)); lv_obj_set_height(_nav_bar, NAV_BAR_HEIGHT); lv_obj_set_style_bg_color(_nav_bar, lv_color_hex(0xFFFFFF), 0); lv_obj_set_style_border_color(_nav_bar, lv_color_hex(0x000000), 0); lv_obj_set_style_border_width(_nav_bar, 1, LV_BORDER_SIDE_TOP); lv_obj_set_style_pad_all(_nav_bar, 5, 0); lv_obj_set_style_radius(_nav_bar, 0, 0); // Configure nav bar as flexbox row layout with space-between lv_obj_set_flex_flow(_nav_bar, LV_FLEX_FLOW_ROW); lv_obj_set_flex_align(_nav_bar, LV_FLEX_ALIGN_SPACE_BETWEEN, LV_FLEX_ALIGN_CENTER, LV_FLEX_ALIGN_CENTER); // Create back button (aligned to start by flex layout) _back_button = lv_btn_create(_nav_bar); lv_obj_set_size(_back_button, 60, NAV_BAR_HEIGHT - 10); lv_obj_set_style_bg_color(_back_button, lv_color_hex(0x555555), 0); lv_obj_add_event_cb(_back_button, on_back_button_clicked, LV_EVENT_CLICKED, _ui_handler); lv_obj_add_flag(_back_button, LV_OBJ_FLAG_HIDDEN); // 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_black(), 0); lv_obj_align(back_label, LV_ALIGN_CENTER, 0, 0); // Create home button (aligned to end by flex layout) lv_obj_t* home_button = lv_btn_create(_nav_bar); lv_obj_set_size(home_button, 60, NAV_BAR_HEIGHT - 10); lv_obj_set_style_bg_color(home_button, lv_color_hex(0x555555), 0); lv_obj_t* home_label = lv_label_create(home_button); lv_label_set_text(home_label, LV_SYMBOL_HOME); lv_obj_set_style_text_color(home_label, lv_color_white(), 0); lv_obj_align(home_label, LV_ALIGN_CENTER, 0, 0); lv_obj_add_event_cb(home_button, on_home_button_clicked, LV_EVENT_CLICKED, _ui_handler); ESP_LOGI(TAG, "Layout created with flexible design: Header=%d, NavBar=%d", HEADER_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 app container content (icons are rendered in the app area) if (!_app_container) { ESP_LOGE(TAG, "App container not initialized"); return ESP_FAIL; } lv_obj_clean(_app_container); // 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", (int)app_descriptors.size()); // Calculate icon spacing inside the app container int icon_count = app_descriptors.size(); int icon_width = 96; int icon_height = 96; int icon_spacing = DISPLAY_WIDTH / (icon_count + 1); int x_offset = icon_spacing; int y_offset = (APP_CONTAINER_HEIGHT - icon_height) / 2; // Render each app icon into the app container for (size_t i = 0; i < app_descriptors.size(); i++) { AppDescriptor* descriptor = app_descriptors[i]; lv_obj_t* icon_container = lv_obj_create(_app_container); lv_obj_set_size(icon_container, icon_width, icon_height); lv_obj_set_pos(icon_container, x_offset - icon_width / 2, y_offset); lv_obj_set_style_bg_opa(icon_container, LV_OPA_TRANSP, 0); lv_obj_set_style_pad_all(icon_container, 0, 0); // add a border for debugging lv_obj_set_style_border_color(icon_container, lv_color_hex(0x000000), 0); lv_obj_set_style_border_width(icon_container, 1, 0); lv_obj_set_user_data(icon_container, descriptor); descriptor->draw_icon(icon_container); 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; } 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) { // Use the current target (the object the callback was attached to) // instead of the event target, which may be a child (like a label). lv_obj_t* icon_container = static_cast(lv_event_get_current_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(); } } static void on_home_button_clicked(lv_event_t* event) { UIHandler* handler = static_cast(lv_event_get_user_data(event)); if (!handler) { ESP_LOGE(TAG, "Invalid handler in home button click"); return; } handler->return_to_main_screen(); }