#include "ui.h" #include "display.h" #include "esp_lvgl_port.h" #include "lvgl.h" #include static lv_obj_t *scr_status, *lbl_title, *lbl_msg; static lv_obj_t *scr_cam, *canvas, *overlay, *lbl_place, *lbl_meta, *badge, *marker, *bar, *toast; static lv_obj_t *scr_menu, *menu_title, *menu_row[UI_MENU_ROWS], *menu_label[UI_MENU_ROWS], *menu_value[UI_MENU_ROWS]; static lv_obj_t *prev_screen; static lv_timer_t *toast_timer; static bool overlay_on = true; static lv_obj_t *label(lv_obj_t *parent, const lv_font_t *font, uint32_t rgb) { lv_obj_t *l = lv_label_create(parent); lv_obj_set_style_text_font(l, font, 0); lv_obj_set_style_text_color(l, lv_color_hex(rgb), 0); return l; } static lv_obj_t *plain_screen(void) { lv_obj_t *s = lv_obj_create(NULL); lv_obj_remove_style_all(s); lv_obj_set_size(s, LCD_H_RES, LCD_V_RES); lv_obj_set_style_bg_color(s, lv_color_hex(0x000000), 0); lv_obj_set_style_bg_opa(s, LV_OPA_COVER, 0); return s; } static void toast_hide_cb(lv_timer_t *t) { lv_obj_set_hidden(toast, true); toast_timer = NULL; } void ui_init(void) { lvgl_port_lock(0); // --- status page --- scr_status = plain_screen(); lbl_title = label(scr_status, &lv_font_montserrat_24, 0xffffff); lv_obj_set_width(lbl_title, LCD_H_RES - 24); lv_obj_set_style_text_align(lbl_title, LV_TEXT_ALIGN_CENTER, 0); lv_obj_align(lbl_title, LV_ALIGN_TOP_MID, 0, 34); lbl_msg = label(scr_status, &lv_font_montserrat_14, 0xb8c0cc); lv_obj_set_width(lbl_msg, LCD_H_RES - 24); lv_obj_set_style_text_align(lbl_msg, LV_TEXT_ALIGN_CENTER, 0); lv_label_set_long_mode(lbl_msg, LV_LABEL_LONG_WRAP); lv_obj_align(lbl_msg, LV_ALIGN_TOP_MID, 0, 80); // --- camera page --- scr_cam = plain_screen(); canvas = lv_canvas_create(scr_cam); lv_obj_set_pos(canvas, 0, 0); lv_obj_set_size(canvas, LCD_H_RES, LCD_V_RES); overlay = lv_obj_create(scr_cam); lv_obj_remove_style_all(overlay); lv_obj_set_size(overlay, LCD_H_RES, 44); lv_obj_align(overlay, LV_ALIGN_BOTTOM_MID, 0, 0); lv_obj_set_style_bg_color(overlay, lv_color_hex(0x000000), 0); lv_obj_set_style_bg_opa(overlay, LV_OPA_60, 0); lbl_place = label(overlay, &lv_font_montserrat_18, 0xffffff); lv_obj_set_width(lbl_place, LCD_H_RES - 16); lv_label_set_long_mode(lbl_place, LV_LABEL_LONG_DOT); lv_obj_set_pos(lbl_place, 8, 3); lbl_meta = label(overlay, &lv_font_montserrat_12, 0xc4ccd8); lv_obj_set_width(lbl_meta, LCD_H_RES - 16); lv_label_set_long_mode(lbl_meta, LV_LABEL_LONG_DOT); lv_obj_set_pos(lbl_meta, 8, 26); bar = lv_bar_create(scr_cam); lv_obj_remove_style_all(bar); lv_obj_set_size(bar, LCD_H_RES, 3); lv_obj_align(bar, LV_ALIGN_BOTTOM_MID, 0, 0); lv_bar_set_range(bar, 0, 1000); lv_obj_set_style_bg_color(bar, lv_color_hex(0xffffff), LV_PART_INDICATOR); lv_obj_set_style_bg_opa(bar, LV_OPA_80, LV_PART_INDICATOR); badge = label(scr_cam, &lv_font_montserrat_12, 0xffffff); lv_obj_set_style_bg_opa(badge, LV_OPA_COVER, 0); lv_obj_set_style_radius(badge, 3, 0); lv_obj_set_style_pad_hor(badge, 5, 0); lv_obj_set_style_pad_ver(badge, 2, 0); lv_obj_align(badge, LV_ALIGN_TOP_RIGHT, -6, 6); lv_obj_set_hidden(badge, true); marker = label(scr_cam, &lv_font_montserrat_12, 0x101010); lv_obj_set_style_bg_color(marker, lv_color_hex(0xffd040), 0); lv_obj_set_style_bg_opa(marker, LV_OPA_COVER, 0); lv_obj_set_style_radius(marker, 3, 0); lv_obj_set_style_pad_hor(marker, 5, 0); lv_obj_set_style_pad_ver(marker, 2, 0); lv_obj_align(marker, LV_ALIGN_TOP_LEFT, 6, 6); lv_obj_set_hidden(marker, true); // toast lives on the top layer so it shows over every page toast = label(lv_layer_top(), &lv_font_montserrat_14, 0xffffff); lv_obj_set_style_bg_color(toast, lv_color_hex(0x202830), 0); lv_obj_set_style_bg_opa(toast, LV_OPA_90, 0); lv_obj_set_style_radius(toast, 6, 0); lv_obj_set_style_pad_all(toast, 8, 0); lv_obj_align(toast, LV_ALIGN_CENTER, 0, -10); lv_obj_set_hidden(toast, true); // --- menu page --- scr_menu = plain_screen(); lv_obj_t *titlebar = lv_obj_create(scr_menu); lv_obj_remove_style_all(titlebar); lv_obj_set_size(titlebar, LCD_H_RES, 22); lv_obj_set_style_bg_color(titlebar, lv_color_hex(0x1c2430), 0); lv_obj_set_style_bg_opa(titlebar, LV_OPA_COVER, 0); menu_title = label(titlebar, &lv_font_montserrat_14, 0x9fb3c8); lv_obj_align(menu_title, LV_ALIGN_LEFT_MID, 8, 0); for (int i = 0; i < UI_MENU_ROWS; i++) { menu_row[i] = lv_obj_create(scr_menu); lv_obj_remove_style_all(menu_row[i]); lv_obj_set_size(menu_row[i], LCD_H_RES, 24); lv_obj_set_pos(menu_row[i], 0, 24 + i * 24); lv_obj_set_style_bg_opa(menu_row[i], LV_OPA_COVER, 0); menu_label[i] = label(menu_row[i], &lv_font_montserrat_14, 0xffffff); lv_obj_set_width(menu_label[i], 190); lv_label_set_long_mode(menu_label[i], LV_LABEL_LONG_DOT); lv_obj_align(menu_label[i], LV_ALIGN_LEFT_MID, 10, 0); menu_value[i] = label(menu_row[i], &lv_font_montserrat_14, 0xc4ccd8); lv_obj_align(menu_value[i], LV_ALIGN_RIGHT_MID, -10, 0); } lv_screen_load(scr_status); lvgl_port_unlock(); } void ui_status(const char *title, const char *msg) { lvgl_port_lock(0); lv_label_set_text(lbl_title, title ? title : ""); lv_label_set_text(lbl_msg, msg ? msg : ""); if (lv_screen_active() != scr_status) lv_screen_load(scr_status); lvgl_port_unlock(); } void ui_show_camera_page(void) { lvgl_port_lock(0); if (lv_screen_active() != scr_cam) lv_screen_load(scr_cam); lvgl_port_unlock(); } void ui_present_frame(uint16_t *rgb565) { lvgl_port_lock(0); lv_canvas_set_buffer(canvas, rgb565, LCD_H_RES, LCD_V_RES, LV_COLOR_FORMAT_RGB565); lv_obj_invalidate(canvas); lvgl_port_unlock(); } void ui_set_caption(const char *place, const char *meta) { lvgl_port_lock(0); lv_label_set_text(lbl_place, place ? place : ""); lv_label_set_text(lbl_meta, meta ? meta : ""); lvgl_port_unlock(); } void ui_set_badge(const char *text, uint32_t rgb) { lvgl_port_lock(0); if (!text || !overlay_on) { lv_obj_set_hidden(badge, true); } else { lv_label_set_text(badge, text); lv_obj_set_style_bg_color(badge, lv_color_hex(rgb), 0); lv_obj_set_hidden(badge, false); } lvgl_port_unlock(); } void ui_set_progress(int permille) { lvgl_port_lock(0); if (permille < 0 || !overlay_on) { lv_obj_set_hidden(bar, true); } else { lv_bar_set_value(bar, permille > 1000 ? 1000 : permille, LV_ANIM_OFF); lv_obj_set_hidden(bar, false); } lvgl_port_unlock(); } void ui_toast(const char *msg, uint32_t ms) { lvgl_port_lock(0); lv_label_set_text(toast, msg); lv_obj_set_hidden(toast, false); if (toast_timer) lv_timer_delete(toast_timer); toast_timer = lv_timer_create(toast_hide_cb, ms, NULL); lv_timer_set_repeat_count(toast_timer, 1); lvgl_port_unlock(); } void ui_set_overlay_visible(bool visible) { lvgl_port_lock(0); overlay_on = visible; if (visible) { lv_obj_set_hidden(overlay, false); } else { lv_obj_set_hidden(overlay, true); lv_obj_set_hidden(badge, true); lv_obj_set_hidden(marker, true); lv_obj_set_hidden(bar, true); } lvgl_port_unlock(); } bool ui_overlay_visible(void) { return overlay_on; } void ui_set_marker(const char *text) { lvgl_port_lock(0); if (!text || !overlay_on) { lv_obj_set_hidden(marker, true); } else { lv_label_set_text(marker, text); lv_obj_set_hidden(marker, false); } lvgl_port_unlock(); } void ui_menu_show(const char *title, const ui_menu_row_t *rows, int count, int top, int selected, bool editing) { lvgl_port_lock(0); lv_label_set_text(menu_title, title); for (int i = 0; i < UI_MENU_ROWS; i++) { int idx = top + i; bool present = idx < count; lv_obj_set_hidden(menu_row[i], !present); if (!present) continue; bool sel = idx == selected; lv_obj_set_style_bg_color(menu_row[i], lv_color_hex(sel ? 0x2f81f7 : 0x000000), 0); lv_label_set_text(menu_label[i], rows[idx].label); lv_obj_set_style_text_color(menu_label[i], lv_color_hex(rows[idx].dim ? 0x6c7684 : 0xffffff), 0); lv_label_set_text(menu_value[i], rows[idx].value ? rows[idx].value : ""); lv_obj_set_style_text_color(menu_value[i], lv_color_hex(sel && editing ? 0xffd040 : sel ? 0xffffff : 0xc4ccd8), 0); } if (lv_screen_active() != scr_menu) { prev_screen = lv_screen_active(); lv_screen_load(scr_menu); } lvgl_port_unlock(); } void ui_menu_hide(void) { lvgl_port_lock(0); if (lv_screen_active() == scr_menu) lv_screen_load(prev_screen ? prev_screen : scr_cam); lvgl_port_unlock(); }