| 1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447 |
- #include "lcd_display.h"
- #include <vector>
- #include <algorithm>
- #include <font_awesome_symbols.h>
- #include <esp_log.h>
- #include <esp_err.h>
- #include <esp_lvgl_port.h>
- #include <esp_heap_caps.h>
- #include "assets/lang_config.h"
- #include <cstring>
- #include "settings.h"
- #include "sun.h" // 包含头文件
- #include "numbers.h"
- #include "application.h"
- #include "board.h"
- #define TAG "LcdDisplay"
- // Color definitions for dark theme
- #define GRAY_EDGE_COLOR lv_color_hex(0x2E2E2E)
- #define DARK_BACKGROUND_COLOR lv_color_hex(0x070707) // Dark background
- #define DARK_TEXT_COLOR lv_color_white() // White text
- #define DARK_CHAT_BACKGROUND_COLOR lv_color_hex(0x1E1E1E) // Slightly lighter than background
- #define DARK_USER_BUBBLE_COLOR lv_color_hex(0x1A6C37) // Dark green
- #define DARK_ASSISTANT_BUBBLE_COLOR lv_color_hex(0x333333) // Dark gray
- #define DARK_SYSTEM_BUBBLE_COLOR lv_color_hex(0x2A2A2A) // Medium gray
- #define DARK_SYSTEM_TEXT_COLOR lv_color_hex(0xAAAAAA) // Light gray text
- #define DARK_BORDER_COLOR lv_color_hex(0x333333) // Dark gray border
- #define DARK_LOW_BATTERY_COLOR lv_color_hex(0xFF0000) // Red for dark mode
- // Color definitions for light theme
- #define LIGHT_BACKGROUND_COLOR lv_color_white() // White background
- #define LIGHT_TEXT_COLOR lv_color_black() // Black text
- #define LIGHT_CHAT_BACKGROUND_COLOR lv_color_hex(0xE0E0E0) // Light gray background
- #define LIGHT_USER_BUBBLE_COLOR lv_color_hex(0x95EC69) // WeChat green
- #define LIGHT_ASSISTANT_BUBBLE_COLOR lv_color_white() // White
- #define LIGHT_SYSTEM_BUBBLE_COLOR lv_color_hex(0xE0E0E0) // Light gray
- #define LIGHT_SYSTEM_TEXT_COLOR lv_color_hex(0x666666) // Dark gray text
- #define LIGHT_BORDER_COLOR lv_color_hex(0xE0E0E0) // Light gray border
- #define LIGHT_LOW_BATTERY_COLOR lv_color_black() // Black for light mode
- // Define dark theme colors
- const ThemeColors DARK_THEME = {
- .background = DARK_BACKGROUND_COLOR,
- .text = DARK_TEXT_COLOR,
- .chat_background = DARK_CHAT_BACKGROUND_COLOR,
- .user_bubble = DARK_USER_BUBBLE_COLOR,
- .assistant_bubble = DARK_ASSISTANT_BUBBLE_COLOR,
- .system_bubble = DARK_SYSTEM_BUBBLE_COLOR,
- .system_text = DARK_SYSTEM_TEXT_COLOR,
- .border = DARK_BORDER_COLOR,
- .low_battery = DARK_LOW_BATTERY_COLOR
- };
- // Define light theme colors
- const ThemeColors LIGHT_THEME = {
- .background = LIGHT_BACKGROUND_COLOR,
- .text = LIGHT_TEXT_COLOR,
- .chat_background = LIGHT_CHAT_BACKGROUND_COLOR,
- .user_bubble = LIGHT_USER_BUBBLE_COLOR,
- .assistant_bubble = LIGHT_ASSISTANT_BUBBLE_COLOR,
- .system_bubble = LIGHT_SYSTEM_BUBBLE_COLOR,
- .system_text = LIGHT_SYSTEM_TEXT_COLOR,
- .border = LIGHT_BORDER_COLOR,
- .low_battery = LIGHT_LOW_BATTERY_COLOR
- };
- LV_FONT_DECLARE(font_awesome_30_4);
- LcdDisplay::LcdDisplay(esp_lcd_panel_io_handle_t panel_io, esp_lcd_panel_handle_t panel, DisplayFonts fonts, int width, int height)
- : panel_io_(panel_io), panel_(panel), fonts_(fonts) {
- width_ = width;
- height_ = height;
- // Load theme from settings
- Settings settings("display", false);
- current_theme_name_ = settings.GetString("theme", "light");
- // Update the theme
- if (current_theme_name_ == "dark") {
- current_theme_ = DARK_THEME;
- } else if (current_theme_name_ == "light") {
- current_theme_ = LIGHT_THEME;
- }
- }
- SpiLcdDisplay::SpiLcdDisplay(esp_lcd_panel_io_handle_t panel_io, esp_lcd_panel_handle_t panel,
- int width, int height, int offset_x, int offset_y, bool mirror_x, bool mirror_y, bool swap_xy,
- DisplayFonts fonts)
- : LcdDisplay(panel_io, panel, fonts, width, height) {
- // draw white
- std::vector<uint16_t> buffer(width_, 0xFFFF);
- for (int y = 0; y < height_; y++) {
- esp_lcd_panel_draw_bitmap(panel_, 0, y, width_, y + 1, buffer.data());
- }
- // Set the display to on
- ESP_LOGI(TAG, "Turning display on");
- ESP_ERROR_CHECK(esp_lcd_panel_disp_on_off(panel_, true));
- ESP_LOGI(TAG, "Initialize LVGL library");
- lv_init();
- ESP_LOGI(TAG, "Initialize LVGL port");
- lvgl_port_cfg_t port_cfg = ESP_LVGL_PORT_INIT_CONFIG();
- port_cfg.task_priority = 1;
- port_cfg.timer_period_ms = 50;
- lvgl_port_init(&port_cfg);
- ESP_LOGI(TAG, "Adding LCD display");
- const lvgl_port_display_cfg_t display_cfg = {
- .io_handle = panel_io_,
- .panel_handle = panel_,
- .control_handle = nullptr,
- .buffer_size = static_cast<uint32_t>(width_ * 20),
- .double_buffer = false,
- .trans_size = 0,
- .hres = static_cast<uint32_t>(width_),
- .vres = static_cast<uint32_t>(height_),
- .monochrome = false,
- .rotation = {
- .swap_xy = swap_xy,
- .mirror_x = mirror_x,
- .mirror_y = mirror_y,
- },
- .color_format = LV_COLOR_FORMAT_RGB565,
- .flags = {
- .buff_dma = 1,
- .buff_spiram = 0,
- .sw_rotate = 0,
- .swap_bytes = 1,
- .full_refresh = 0,
- .direct_mode = 0,
- },
- };
- display_ = lvgl_port_add_disp(&display_cfg);
- if (display_ == nullptr) {
- ESP_LOGE(TAG, "Failed to add display");
- return;
- }
- if (offset_x != 0 || offset_y != 0) {
- lv_display_set_offset(display_, offset_x, offset_y);
- }
- SetupUI();
- }
- // RGB LCD实现
- RgbLcdDisplay::RgbLcdDisplay(esp_lcd_panel_io_handle_t panel_io, esp_lcd_panel_handle_t panel,
- int width, int height, int offset_x, int offset_y,
- bool mirror_x, bool mirror_y, bool swap_xy,
- DisplayFonts fonts)
- : LcdDisplay(panel_io, panel, fonts, width, height) {
- // draw white
- std::vector<uint16_t> buffer(width_, 0xFFFF);
- for (int y = 0; y < height_; y++) {
- esp_lcd_panel_draw_bitmap(panel_, 0, y, width_, y + 1, buffer.data());
- }
- ESP_LOGI(TAG, "Initialize LVGL library");
- lv_init();
- ESP_LOGI(TAG, "Initialize LVGL port");
- lvgl_port_cfg_t port_cfg = ESP_LVGL_PORT_INIT_CONFIG();
- port_cfg.task_priority = 1;
- port_cfg.timer_period_ms = 50;
- lvgl_port_init(&port_cfg);
- ESP_LOGI(TAG, "Adding LCD display");
- const lvgl_port_display_cfg_t display_cfg = {
- .io_handle = panel_io_,
- .panel_handle = panel_,
- .buffer_size = static_cast<uint32_t>(width_ * 20),
- .double_buffer = true,
- .hres = static_cast<uint32_t>(width_),
- .vres = static_cast<uint32_t>(height_),
- .rotation = {
- .swap_xy = swap_xy,
- .mirror_x = mirror_x,
- .mirror_y = mirror_y,
- },
- .flags = {
- .buff_dma = 1,
- .swap_bytes = 0,
- .full_refresh = 1,
- .direct_mode = 1,
- },
- };
- const lvgl_port_display_rgb_cfg_t rgb_cfg = {
- .flags = {
- .bb_mode = true,
- .avoid_tearing = true,
- }
- };
-
- display_ = lvgl_port_add_disp_rgb(&display_cfg, &rgb_cfg);
- if (display_ == nullptr) {
- ESP_LOGE(TAG, "Failed to add RGB display");
- return;
- }
-
- if (offset_x != 0 || offset_y != 0) {
- lv_display_set_offset(display_, offset_x, offset_y);
- }
- SetupUI();
- }
- MipiLcdDisplay::MipiLcdDisplay(esp_lcd_panel_io_handle_t panel_io, esp_lcd_panel_handle_t panel,
- int width, int height, int offset_x, int offset_y,
- bool mirror_x, bool mirror_y, bool swap_xy,
- DisplayFonts fonts)
- : LcdDisplay(panel_io, panel, fonts, width, height) {
- // Set the display to on
- ESP_LOGI(TAG, "Turning display on");
- ESP_ERROR_CHECK(esp_lcd_panel_disp_on_off(panel_, true));
- ESP_LOGI(TAG, "Initialize LVGL library");
- lv_init();
- ESP_LOGI(TAG, "Initialize LVGL port");
- lvgl_port_cfg_t port_cfg = ESP_LVGL_PORT_INIT_CONFIG();
- lvgl_port_init(&port_cfg);
- ESP_LOGI(TAG, "Adding LCD display");
- const lvgl_port_display_cfg_t disp_cfg = {
- .io_handle = panel_io,
- .panel_handle = panel,
- .control_handle = nullptr,
- .buffer_size = static_cast<uint32_t>(width_ * 50),
- .double_buffer = false,
- .hres = static_cast<uint32_t>(width_),
- .vres = static_cast<uint32_t>(height_),
- .monochrome = false,
- /* Rotation values must be same as used in esp_lcd for initial settings of the screen */
- .rotation = {
- .swap_xy = swap_xy,
- .mirror_x = mirror_x,
- .mirror_y = mirror_y,
- },
- .flags = {
- .buff_dma = true,
- .buff_spiram =false,
- .sw_rotate = false,
- },
- };
- const lvgl_port_display_dsi_cfg_t dpi_cfg = {
- .flags = {
- .avoid_tearing = false,
- }
- };
- display_ = lvgl_port_add_disp_dsi(&disp_cfg, &dpi_cfg);
- if (display_ == nullptr) {
- ESP_LOGE(TAG, "Failed to add display");
- return;
- }
- if (offset_x != 0 || offset_y != 0) {
- lv_display_set_offset(display_, offset_x, offset_y);
- }
- SetupUI();
- }
- LcdDisplay::~LcdDisplay() {
- // 然后再清理 LVGL 对象
- if (content_ != nullptr) {
- lv_obj_del(content_);
- }
- if (status_bar_ != nullptr) {
- lv_obj_del(status_bar_);
- }
- if (side_bar_ != nullptr) {
- lv_obj_del(side_bar_);
- }
- if (container_ != nullptr) {
- lv_obj_del(container_);
- }
- if (display_ != nullptr) {
- lv_display_delete(display_);
- }
- if (panel_ != nullptr) {
- esp_lcd_panel_del(panel_);
- }
- if (panel_io_ != nullptr) {
- esp_lcd_panel_io_del(panel_io_);
- }
- }
- bool LcdDisplay::Lock(int timeout_ms) {
- return lvgl_port_lock(timeout_ms);
- }
- void LcdDisplay::Unlock() {
- lvgl_port_unlock();
- }
- #if CONFIG_USE_WECHAT_MESSAGE_STYLE
- void LcdDisplay::SetupUI() {
- DisplayLockGuard lock(this);
- auto screen = lv_screen_active();
- lv_obj_set_style_text_font(screen, fonts_.text_font, 0);
- lv_obj_set_style_text_color(screen, current_theme_.text, 0);
- lv_obj_set_style_bg_color(screen, current_theme_.gray_edge, 0);
- /* Container */
- container_ = lv_obj_create(screen);
- lv_obj_set_size(container_, LV_HOR_RES, LV_VER_RES);
- lv_obj_set_flex_flow(container_, LV_FLEX_FLOW_COLUMN);
- lv_obj_set_style_pad_all(container_, 0, 0);
- lv_obj_set_style_border_width(container_, 0, 0);
- lv_obj_set_style_pad_row(container_, 0, 0);
- lv_obj_set_style_bg_color(container_, current_theme_.gray_edge, 0);
- lv_obj_set_style_border_color(container_, current_theme_.border, 0);
- /* Status bar */
- status_bar_ = lv_obj_create(container_);
- lv_obj_set_size(status_bar_, LV_HOR_RES, LV_SIZE_CONTENT);
- lv_obj_set_style_radius(status_bar_, 0, 0);
- lv_obj_set_style_bg_color(status_bar_, current_theme_.background, 0);
- lv_obj_set_style_text_color(status_bar_, current_theme_.text, 0);
- /* Content - Chat area */
- content_ = lv_obj_create(container_);
- lv_obj_set_style_radius(content_, 0, 0);
- lv_obj_set_width(content_, LV_HOR_RES);
- lv_obj_set_flex_grow(content_, 1);
- lv_obj_set_style_pad_all(content_, 10, 0);
- lv_obj_set_style_bg_color(content_, current_theme_.chat_background, 0); // Background for chat area
- lv_obj_set_style_border_color(content_, current_theme_.border, 0); // Border color for chat area
- // Enable scrolling for chat content
- lv_obj_set_scrollbar_mode(content_, LV_SCROLLBAR_MODE_OFF);
- lv_obj_set_scroll_dir(content_, LV_DIR_VER);
-
- // Create a flex container for chat messages
- lv_obj_set_flex_flow(content_, LV_FLEX_FLOW_COLUMN);
- lv_obj_set_flex_align(content_, LV_FLEX_ALIGN_START, LV_FLEX_ALIGN_START, LV_FLEX_ALIGN_START);
- lv_obj_set_style_pad_row(content_, 10, 0); // Space between messages
- // We'll create chat messages dynamically in SetChatMessage
- chat_message_label_ = nullptr;
- /* Status bar */
- lv_obj_set_flex_flow(status_bar_, LV_FLEX_FLOW_ROW);
- lv_obj_set_style_pad_all(status_bar_, 0, 0);
- lv_obj_set_style_border_width(status_bar_, 0, 0);
- lv_obj_set_style_pad_column(status_bar_, 0, 0);
- lv_obj_set_style_pad_left(status_bar_, 10, 0);
- lv_obj_set_style_pad_right(status_bar_, 10, 0);
- lv_obj_set_style_pad_top(status_bar_, 2, 0);
- lv_obj_set_style_pad_bottom(status_bar_, 2, 0);
- lv_obj_set_scrollbar_mode(status_bar_, LV_SCROLLBAR_MODE_OFF);
- // 设置状态栏的内容垂直居中
- lv_obj_set_flex_align(status_bar_, LV_FLEX_ALIGN_SPACE_BETWEEN, LV_FLEX_ALIGN_CENTER, LV_FLEX_ALIGN_CENTER);
- // 创建emotion_label_在状态栏最左侧
- emotion_label_ = lv_label_create(status_bar_);
- lv_obj_set_style_text_font(emotion_label_, &font_awesome_30_4, 0);
- lv_obj_set_style_text_color(emotion_label_, current_theme_.text, 0);
- lv_label_set_text(emotion_label_, FONT_AWESOME_AI_CHIP);
- lv_obj_set_style_margin_right(emotion_label_, 5, 0); // 添加右边距,与后面的元素分隔
- notification_label_ = lv_label_create(status_bar_);
- lv_obj_set_flex_grow(notification_label_, 1);
- lv_obj_set_style_text_align(notification_label_, LV_TEXT_ALIGN_CENTER, 0);
- lv_obj_set_style_text_color(notification_label_, current_theme_.text, 0);
- lv_label_set_text(notification_label_, "");
- lv_obj_add_flag(notification_label_, LV_OBJ_FLAG_HIDDEN);
- status_label_ = lv_label_create(status_bar_);
- lv_obj_set_flex_grow(status_label_, 1);
- lv_label_set_long_mode(status_label_, LV_LABEL_LONG_SCROLL_CIRCULAR);
- lv_obj_set_style_text_align(status_label_, LV_TEXT_ALIGN_CENTER, 0);
- lv_obj_set_style_text_color(status_label_, current_theme_.text, 0);
- lv_label_set_text(status_label_, Lang::Strings::INITIALIZING);
-
- mute_label_ = lv_label_create(status_bar_);
- lv_label_set_text(mute_label_, "");
- lv_obj_set_style_text_font(mute_label_, fonts_.icon_font, 0);
- lv_obj_set_style_text_color(mute_label_, current_theme_.text, 0);
- network_label_ = lv_label_create(status_bar_);
- lv_label_set_text(network_label_, "");
- lv_obj_set_style_text_font(network_label_, fonts_.icon_font, 0);
- lv_obj_set_style_text_color(network_label_, current_theme_.text, 0);
- lv_obj_set_style_margin_left(network_label_, 5, 0); // 添加左边距,与前面的元素分隔
- battery_label_ = lv_label_create(status_bar_);
- lv_label_set_text(battery_label_, "");
- lv_obj_set_style_text_font(battery_label_, fonts_.icon_font, 0);
- lv_obj_set_style_text_color(battery_label_, current_theme_.text, 0);
- lv_obj_set_style_margin_left(battery_label_, 5, 0); // 添加左边距,与前面的元素分隔
- low_battery_popup_ = lv_obj_create(screen);
- lv_obj_set_scrollbar_mode(low_battery_popup_, LV_SCROLLBAR_MODE_OFF);
- lv_obj_set_size(low_battery_popup_, LV_HOR_RES * 0.9, fonts_.text_font->line_height * 2);
- lv_obj_align(low_battery_popup_, LV_ALIGN_BOTTOM_MID, 0, 0);
- lv_obj_set_style_bg_color(low_battery_popup_, current_theme_.low_battery, 0);
- lv_obj_set_style_radius(low_battery_popup_, 10, 0);
- low_battery_label_ = lv_label_create(low_battery_popup_);
- lv_label_set_text(low_battery_label_, Lang::Strings::BATTERY_NEED_CHARGE);
- lv_obj_set_style_text_color(low_battery_label_, lv_color_white(), 0);
- lv_obj_center(low_battery_label_);
- lv_obj_add_flag(low_battery_popup_, LV_OBJ_FLAG_HIDDEN);
- }
- #if CONFIG_IDF_TARGET_ESP32P4
- #define MAX_MESSAGES 40
- #else
- #define MAX_MESSAGES 20
- #endif
- void LcdDisplay::SetChatMessage(const char* role, const char* content) {
- DisplayLockGuard lock(this);
- if (content_ == nullptr) {
- return;
- }
-
- //避免出现空的消息框
- if(strlen(content) == 0) return;
-
- // 检查消息数量是否超过限制
- uint32_t child_count = lv_obj_get_child_cnt(content_);
- if (child_count >= MAX_MESSAGES) {
- // 删除最早的消息(第一个子对象)
- lv_obj_t* first_child = lv_obj_get_child(content_, 0);
- lv_obj_t* last_child = lv_obj_get_child(content_, child_count - 1);
- if (first_child != nullptr) {
- lv_obj_del(first_child);
- }
- // Scroll to the last message immediately
- if (last_child != nullptr) {
- lv_obj_scroll_to_view_recursive(last_child, LV_ANIM_OFF);
- }
- }
-
- // 折叠系统消息(如果是系统消息,检查最后一个消息是否也是系统消息)
- if (strcmp(role, "system") == 0 && child_count > 0) {
- // 获取最后一个消息容器
- lv_obj_t* last_container = lv_obj_get_child(content_, child_count - 1);
- if (last_container != nullptr && lv_obj_get_child_cnt(last_container) > 0) {
- // 获取容器内的气泡
- lv_obj_t* last_bubble = lv_obj_get_child(last_container, 0);
- if (last_bubble != nullptr) {
- // 检查气泡类型是否为系统消息
- void* bubble_type_ptr = lv_obj_get_user_data(last_bubble);
- if (bubble_type_ptr != nullptr && strcmp((const char*)bubble_type_ptr, "system") == 0) {
- // 如果最后一个消息也是系统消息,则删除它
- lv_obj_del(last_container);
- }
- }
- }
- }
-
- // Create a message bubble
- lv_obj_t* msg_bubble = lv_obj_create(content_);
- lv_obj_set_style_radius(msg_bubble, 8, 0);
- lv_obj_set_scrollbar_mode(msg_bubble, LV_SCROLLBAR_MODE_OFF);
- lv_obj_set_style_border_width(msg_bubble, 1, 0);
- lv_obj_set_style_border_color(msg_bubble, current_theme_.border, 0);
- lv_obj_set_style_pad_all(msg_bubble, 8, 0);
- // Create the message text
- lv_obj_t* msg_text = lv_label_create(msg_bubble);
- lv_label_set_text(msg_text, content);
-
- // 计算文本实际宽度
- lv_coord_t text_width = lv_txt_get_width(content, strlen(content), fonts_.text_font, 0);
- // 计算气泡宽度
- lv_coord_t max_width = LV_HOR_RES * 85 / 100 - 16; // 屏幕宽度的85%
- lv_coord_t min_width = 20;
- lv_coord_t bubble_width;
-
- // 确保文本宽度不小于最小宽度
- if (text_width < min_width) {
- text_width = min_width;
- }
- // 如果文本宽度小于最大宽度,使用文本宽度
- if (text_width < max_width) {
- bubble_width = text_width;
- } else {
- bubble_width = max_width;
- }
-
- // 设置消息文本的宽度
- lv_obj_set_width(msg_text, bubble_width); // 减去padding
- lv_label_set_long_mode(msg_text, LV_LABEL_LONG_WRAP);
- lv_obj_set_style_text_font(msg_text, fonts_.text_font, 0);
- // 设置气泡宽度
- lv_obj_set_width(msg_bubble, bubble_width);
- lv_obj_set_height(msg_bubble, LV_SIZE_CONTENT);
- // Set alignment and style based on message role
- if (strcmp(role, "user") == 0) {
- // User messages are right-aligned with green background
- lv_obj_set_style_bg_color(msg_bubble, current_theme_.user_bubble, 0);
- // Set text color for contrast
- lv_obj_set_style_text_color(msg_text, current_theme_.text, 0);
-
- // 设置自定义属性标记气泡类型
- lv_obj_set_user_data(msg_bubble, (void*)"user");
-
- // Set appropriate width for content
- lv_obj_set_width(msg_bubble, LV_SIZE_CONTENT);
- lv_obj_set_height(msg_bubble, LV_SIZE_CONTENT);
-
- // Don't grow
- lv_obj_set_style_flex_grow(msg_bubble, 0, 0);
- } else if (strcmp(role, "assistant") == 0) {
- // Assistant messages are left-aligned with white background
- lv_obj_set_style_bg_color(msg_bubble, current_theme_.assistant_bubble, 0);
- // Set text color for contrast
- lv_obj_set_style_text_color(msg_text, current_theme_.text, 0);
-
- // 设置自定义属性标记气泡类型
- lv_obj_set_user_data(msg_bubble, (void*)"assistant");
-
- // Set appropriate width for content
- lv_obj_set_width(msg_bubble, LV_SIZE_CONTENT);
- lv_obj_set_height(msg_bubble, LV_SIZE_CONTENT);
-
- // Don't grow
- lv_obj_set_style_flex_grow(msg_bubble, 0, 0);
- } else if (strcmp(role, "system") == 0) {
- // System messages are center-aligned with light gray background
- lv_obj_set_style_bg_color(msg_bubble, current_theme_.system_bubble, 0);
- // Set text color for contrast
- lv_obj_set_style_text_color(msg_text, current_theme_.system_text, 0);
-
- // 设置自定义属性标记气泡类型
- lv_obj_set_user_data(msg_bubble, (void*)"system");
-
- // Set appropriate width for content
- lv_obj_set_width(msg_bubble, LV_SIZE_CONTENT);
- lv_obj_set_height(msg_bubble, LV_SIZE_CONTENT);
-
- // Don't grow
- lv_obj_set_style_flex_grow(msg_bubble, 0, 0);
- }
-
- // Create a full-width container for user messages to ensure right alignment
- if (strcmp(role, "user") == 0) {
- // Create a full-width container
- lv_obj_t* container = lv_obj_create(content_);
- lv_obj_set_width(container, LV_HOR_RES);
- lv_obj_set_height(container, LV_SIZE_CONTENT);
-
- // Make container transparent and borderless
- lv_obj_set_style_bg_opa(container, LV_OPA_TRANSP, 0);
- lv_obj_set_style_border_width(container, 0, 0);
- lv_obj_set_style_pad_all(container, 0, 0);
-
- // Move the message bubble into this container
- lv_obj_set_parent(msg_bubble, container);
-
- // Right align the bubble in the container
- lv_obj_align(msg_bubble, LV_ALIGN_RIGHT_MID, -25, 0);
-
- // Auto-scroll to this container
- lv_obj_scroll_to_view_recursive(container, LV_ANIM_ON);
- } else if (strcmp(role, "system") == 0) {
- // 为系统消息创建全宽容器以确保居中对齐
- lv_obj_t* container = lv_obj_create(content_);
- lv_obj_set_width(container, LV_HOR_RES);
- lv_obj_set_height(container, LV_SIZE_CONTENT);
-
- // 使容器透明且无边框
- lv_obj_set_style_bg_opa(container, LV_OPA_TRANSP, 0);
- lv_obj_set_style_border_width(container, 0, 0);
- lv_obj_set_style_pad_all(container, 0, 0);
-
- // 将消息气泡移入此容器
- lv_obj_set_parent(msg_bubble, container);
-
- // 将气泡居中对齐在容器中
- lv_obj_align(msg_bubble, LV_ALIGN_CENTER, 0, 0);
-
- // 自动滚动底部
- lv_obj_scroll_to_view_recursive(container, LV_ANIM_ON);
- } else {
- // For assistant messages
- // Left align assistant messages
- lv_obj_align(msg_bubble, LV_ALIGN_LEFT_MID, 0, 0);
- // Auto-scroll to the message bubble
- lv_obj_scroll_to_view_recursive(msg_bubble, LV_ANIM_ON);
- }
-
- // Store reference to the latest message label
- chat_message_label_ = msg_text;
- }
- void LcdDisplay::SetPreviewImage(const lv_img_dsc_t* img_dsc) {
- DisplayLockGuard lock(this);
- if (content_ == nullptr) {
- return;
- }
-
- if (img_dsc != nullptr) {
- // Create a message bubble for image preview
- lv_obj_t* img_bubble = lv_obj_create(content_);
- lv_obj_set_style_radius(img_bubble, 8, 0);
- lv_obj_set_scrollbar_mode(img_bubble, LV_SCROLLBAR_MODE_OFF);
- lv_obj_set_style_border_width(img_bubble, 1, 0);
- lv_obj_set_style_border_color(img_bubble, current_theme_.border, 0);
- lv_obj_set_style_pad_all(img_bubble, 8, 0);
-
- // Set image bubble background color (similar to system message)
- lv_obj_set_style_bg_color(img_bubble, current_theme_.assistant_bubble, 0);
-
- // 设置自定义属性标记气泡类型
- lv_obj_set_user_data(img_bubble, (void*)"image");
-
- // Create the image object inside the bubble
- lv_obj_t* preview_image = lv_image_create(img_bubble);
-
- // Copy the image descriptor and data to avoid source data changes
- lv_img_dsc_t* copied_img_dsc = (lv_img_dsc_t*)heap_caps_malloc(sizeof(lv_img_dsc_t), MALLOC_CAP_8BIT);
- if (copied_img_dsc == nullptr) {
- ESP_LOGE(TAG, "Failed to allocate memory for image descriptor");
- lv_obj_del(img_bubble);
- return;
- }
-
- // Copy the header
- copied_img_dsc->header = img_dsc->header;
- copied_img_dsc->data_size = img_dsc->data_size;
-
- // Copy the image data
- uint8_t* copied_data = (uint8_t*)heap_caps_malloc(img_dsc->data_size, MALLOC_CAP_SPIRAM | MALLOC_CAP_8BIT);
- if (copied_data == nullptr) {
- // Fallback to internal RAM if SPIRAM allocation fails
- copied_data = (uint8_t*)heap_caps_malloc(img_dsc->data_size, MALLOC_CAP_8BIT);
- }
- if (copied_data == nullptr) {
- ESP_LOGE(TAG, "Failed to allocate memory for image data (size: %lu bytes)", img_dsc->data_size);
- heap_caps_free(copied_img_dsc);
- lv_obj_del(img_bubble);
- return;
- }
-
- memcpy(copied_data, img_dsc->data, img_dsc->data_size);
- copied_img_dsc->data = copied_data;
-
- // Calculate appropriate size for the image
- lv_coord_t max_width = LV_HOR_RES * 70 / 100; // 70% of screen width
- lv_coord_t max_height = LV_VER_RES * 50 / 100; // 50% of screen height
-
- // Calculate zoom factor to fit within maximum dimensions
- lv_coord_t img_width = copied_img_dsc->header.w;
- lv_coord_t img_height = copied_img_dsc->header.h;
-
- lv_coord_t zoom_w = (max_width * 256) / img_width;
- lv_coord_t zoom_h = (max_height * 256) / img_height;
- lv_coord_t zoom = (zoom_w < zoom_h) ? zoom_w : zoom_h;
-
- // Ensure zoom doesn't exceed 256 (100%)
- if (zoom > 256) zoom = 256;
-
- // Set image properties
- lv_image_set_src(preview_image, copied_img_dsc);
- lv_image_set_scale(preview_image, zoom);
-
- // Add event handler to clean up copied data when image is deleted
- lv_obj_add_event_cb(preview_image, [](lv_event_t* e) {
- lv_img_dsc_t* copied_img_dsc = (lv_img_dsc_t*)lv_event_get_user_data(e);
- if (copied_img_dsc != nullptr) {
- heap_caps_free((void*)copied_img_dsc->data);
- heap_caps_free(copied_img_dsc);
- }
- }, LV_EVENT_DELETE, (void*)copied_img_dsc);
-
- // Calculate actual scaled image dimensions
- lv_coord_t scaled_width = (img_width * zoom) / 256;
- lv_coord_t scaled_height = (img_height * zoom) / 256;
-
- // Set bubble size to be 16 pixels larger than the image (8 pixels on each side)
- lv_obj_set_width(img_bubble, scaled_width + 16);
- lv_obj_set_height(img_bubble, scaled_height + 16);
-
- // Don't grow in flex layout
- lv_obj_set_style_flex_grow(img_bubble, 0, 0);
-
- // Center the image within the bubble
- lv_obj_center(preview_image);
-
- // Left align the image bubble like assistant messages
- lv_obj_align(img_bubble, LV_ALIGN_LEFT_MID, 0, 0);
- // Auto-scroll to the image bubble
- lv_obj_scroll_to_view_recursive(img_bubble, LV_ANIM_ON);
- }
- }
- #else
- void LcdDisplay::SetupUI() {
- DisplayLockGuard lock(this);
- auto screen = lv_screen_active();
- lv_obj_set_style_text_font(screen, fonts_.text_font, 0);
- lv_obj_set_style_text_color(screen, current_theme_.text, 0);
- lv_obj_set_style_bg_color(screen, current_theme_.background, 0);
- /* Container */
- container_ = lv_obj_create(screen);
- lv_obj_set_size(container_, LV_HOR_RES, LV_VER_RES);
- lv_obj_set_flex_flow(container_, LV_FLEX_FLOW_COLUMN);
- lv_obj_set_style_pad_all(container_, 0, 0);
- lv_obj_set_style_border_width(container_, 0, 0);
- lv_obj_set_style_pad_row(container_, 0, 0);
- lv_obj_set_style_bg_color(container_, current_theme_.background, 0);
- lv_obj_set_style_border_color(container_, current_theme_.border, 0);
- /* Status bar */
- status_bar_ = lv_obj_create(container_);
- lv_obj_set_size(status_bar_, LV_HOR_RES, fonts_.text_font->line_height);
- lv_obj_set_style_radius(status_bar_, 0, 0);
- lv_obj_set_style_bg_color(status_bar_, current_theme_.background, 0);
- lv_obj_set_style_text_color(status_bar_, current_theme_.text, 0);
-
- /* Content */
- content_ = lv_obj_create(container_);
- lv_obj_set_scrollbar_mode(content_, LV_SCROLLBAR_MODE_OFF);
- lv_obj_set_style_radius(content_, 0, 0);
- lv_obj_set_width(content_, LV_HOR_RES);
- lv_obj_set_flex_grow(content_, 1);
- lv_obj_set_style_pad_all(content_, 5, 0);
- lv_obj_set_style_bg_color(content_, current_theme_.chat_background, 0);
- lv_obj_set_style_border_color(content_, current_theme_.border, 0); // Border color for content
- lv_obj_set_flex_flow(content_, LV_FLEX_FLOW_COLUMN); // 垂直布局(从上到下)
- lv_obj_set_flex_align(content_, LV_FLEX_ALIGN_CENTER, LV_FLEX_ALIGN_CENTER, LV_FLEX_ALIGN_SPACE_EVENLY); // 子对象居中对齐,等距分布
- emotion_label_ = lv_label_create(content_);
- lv_obj_set_style_text_font(emotion_label_, &font_awesome_30_4, 0);
- lv_obj_set_style_text_color(emotion_label_, current_theme_.text, 0);
- lv_label_set_text(emotion_label_, FONT_AWESOME_AI_CHIP);
- preview_image_ = lv_image_create(content_);
- lv_obj_set_size(preview_image_, width_ * 0.5, height_ * 0.5);
- lv_obj_align(preview_image_, LV_ALIGN_CENTER, 0, 0);
- lv_obj_add_flag(preview_image_, LV_OBJ_FLAG_HIDDEN);
- chat_message_label_ = lv_label_create(content_);
- lv_label_set_text(chat_message_label_, "");
- lv_obj_set_width(chat_message_label_, LV_HOR_RES * 0.9); // 限制宽度为屏幕宽度的 90%
- lv_label_set_long_mode(chat_message_label_, LV_LABEL_LONG_WRAP); // 设置为自动换行模式
- lv_obj_set_style_text_align(chat_message_label_, LV_TEXT_ALIGN_CENTER, 0); // 设置文本居中对齐
- lv_obj_set_style_text_color(chat_message_label_, current_theme_.text, 0);
- /* Status bar */
- lv_obj_set_flex_flow(status_bar_, LV_FLEX_FLOW_ROW);
- lv_obj_set_style_pad_all(status_bar_, 0, 0);
- lv_obj_set_style_border_width(status_bar_, 0, 0);
- lv_obj_set_style_pad_column(status_bar_, 0, 0);
- lv_obj_set_style_pad_left(status_bar_, 2, 0);
- lv_obj_set_style_pad_right(status_bar_, 2, 0);
- network_label_ = lv_label_create(status_bar_);
- lv_label_set_text(network_label_, "");
- lv_obj_set_style_text_font(network_label_, fonts_.icon_font, 0);
- lv_obj_set_style_text_color(network_label_, current_theme_.text, 0);
- notification_label_ = lv_label_create(status_bar_);
- lv_obj_set_flex_grow(notification_label_, 1);
- lv_obj_set_style_text_align(notification_label_, LV_TEXT_ALIGN_CENTER, 0);
- lv_obj_set_style_text_color(notification_label_, current_theme_.text, 0);
- lv_label_set_text(notification_label_, "");
- lv_obj_add_flag(notification_label_, LV_OBJ_FLAG_HIDDEN);
- status_label_ = lv_label_create(status_bar_);
- lv_obj_set_flex_grow(status_label_, 1);
- lv_label_set_long_mode(status_label_, LV_LABEL_LONG_SCROLL_CIRCULAR);
- lv_obj_set_style_text_align(status_label_, LV_TEXT_ALIGN_CENTER, 0);
- lv_obj_set_style_text_color(status_label_, current_theme_.text, 0);
- lv_label_set_text(status_label_, Lang::Strings::INITIALIZING);
- mute_label_ = lv_label_create(status_bar_);
- lv_label_set_text(mute_label_, "");
- lv_obj_set_style_text_font(mute_label_, fonts_.icon_font, 0);
- lv_obj_set_style_text_color(mute_label_, current_theme_.text, 0);
- battery_label_ = lv_label_create(status_bar_);
- lv_label_set_text(battery_label_, "");
- lv_obj_set_style_text_font(battery_label_, fonts_.icon_font, 0);
- lv_obj_set_style_text_color(battery_label_, current_theme_.text, 0);
- low_battery_popup_ = lv_obj_create(screen);
- lv_obj_set_scrollbar_mode(low_battery_popup_, LV_SCROLLBAR_MODE_OFF);
- lv_obj_set_size(low_battery_popup_, LV_HOR_RES * 0.9, fonts_.text_font->line_height * 2);
- lv_obj_align(low_battery_popup_, LV_ALIGN_BOTTOM_MID, 0, 0);
- lv_obj_set_style_bg_color(low_battery_popup_, current_theme_.low_battery, 0);
- lv_obj_set_style_radius(low_battery_popup_, 10, 0);
- low_battery_label_ = lv_label_create(low_battery_popup_);
- lv_label_set_text(low_battery_label_, Lang::Strings::BATTERY_NEED_CHARGE);
- lv_obj_set_style_text_color(low_battery_label_, lv_color_white(), 0);
- lv_obj_center(low_battery_label_);
- lv_obj_add_flag(low_battery_popup_, LV_OBJ_FLAG_HIDDEN);
- }
- void LcdDisplay::SetPreviewImage(const lv_img_dsc_t* img_dsc) {
- DisplayLockGuard lock(this);
- if (preview_image_ == nullptr) {
- return;
- }
-
- if (img_dsc != nullptr) {
- // zoom factor 0.5
- lv_image_set_scale(preview_image_, 128 * width_ / img_dsc->header.w);
- // 设置图片源并显示预览图片
- lv_image_set_src(preview_image_, img_dsc);
- lv_obj_clear_flag(preview_image_, LV_OBJ_FLAG_HIDDEN);
- // 隐藏emotion_label_
- if (emotion_label_ != nullptr) {
- lv_obj_add_flag(emotion_label_, LV_OBJ_FLAG_HIDDEN);
- }
- } else {
- // 隐藏预览图片并显示emotion_label_
- lv_obj_add_flag(preview_image_, LV_OBJ_FLAG_HIDDEN);
- if (emotion_label_ != nullptr) {
- lv_obj_clear_flag(emotion_label_, LV_OBJ_FLAG_HIDDEN);
- }
- }
- }
- #endif
- void LcdDisplay::SetEmotion(const char* emotion) {
- struct Emotion {
- const char* icon;
- const char* text;
- };
- static const std::vector<Emotion> emotions = {
- {"😶", "neutral"},
- {"🙂", "happy"},
- {"😆", "laughing"},
- {"😂", "funny"},
- {"😔", "sad"},
- {"😠", "angry"},
- {"😭", "crying"},
- {"😍", "loving"},
- {"😳", "embarrassed"},
- {"😯", "surprised"},
- {"😱", "shocked"},
- {"🤔", "thinking"},
- {"😉", "winking"},
- {"😎", "cool"},
- {"😌", "relaxed"},
- {"🤤", "delicious"},
- {"😘", "kissy"},
- {"😏", "confident"},
- {"😴", "sleepy"},
- {"😜", "silly"},
- {"🙄", "confused"}
- };
-
- // 查找匹配的表情
- std::string_view emotion_view(emotion);
- auto it = std::find_if(emotions.begin(), emotions.end(),
- [&emotion_view](const Emotion& e) { return e.text == emotion_view; });
- DisplayLockGuard lock(this);
- if (emotion_label_ == nullptr) {
- return;
- }
- // 如果找到匹配的表情就显示对应图标,否则显示默认的neutral表情
- lv_obj_set_style_text_font(emotion_label_, fonts_.emoji_font, 0);
- if (it != emotions.end()) {
- lv_label_set_text(emotion_label_, it->icon);
- } else {
- lv_label_set_text(emotion_label_, "😶");
- }
- #if !CONFIG_USE_WECHAT_MESSAGE_STYLE
- // 显示emotion_label_,隐藏preview_image_
- lv_obj_clear_flag(emotion_label_, LV_OBJ_FLAG_HIDDEN);
- if (preview_image_ != nullptr) {
- lv_obj_add_flag(preview_image_, LV_OBJ_FLAG_HIDDEN);
- }
- #endif
- }
- void LcdDisplay::SetIcon(const char* icon) {
- DisplayLockGuard lock(this);
- if (emotion_label_ == nullptr) {
- return;
- }
- lv_obj_set_style_text_font(emotion_label_, &font_awesome_30_4, 0);
- lv_label_set_text(emotion_label_, icon);
- #if !CONFIG_USE_WECHAT_MESSAGE_STYLE
- // 显示emotion_label_,隐藏preview_image_
- lv_obj_clear_flag(emotion_label_, LV_OBJ_FLAG_HIDDEN);
- if (preview_image_ != nullptr) {
- lv_obj_add_flag(preview_image_, LV_OBJ_FLAG_HIDDEN);
- }
- #endif
- }
- void LcdDisplay::SetTheme(const std::string& theme_name) {
- DisplayLockGuard lock(this);
-
- if (theme_name == "dark" || theme_name == "DARK") {
- current_theme_ = DARK_THEME;
- } else if (theme_name == "light" || theme_name == "LIGHT") {
- current_theme_ = LIGHT_THEME;
- } else {
- // Invalid theme name, return false
- ESP_LOGE(TAG, "Invalid theme name: %s", theme_name.c_str());
- return;
- }
-
- // Get the active screen
- lv_obj_t* screen = lv_screen_active();
-
- // Update the screen colors
- lv_obj_set_style_bg_color(screen, current_theme_.background, 0);
- lv_obj_set_style_text_color(screen, current_theme_.text, 0);
-
- // Update container colors
- if (container_ != nullptr) {
- lv_obj_set_style_bg_color(container_, current_theme_.background, 0);
- lv_obj_set_style_border_color(container_, current_theme_.border, 0);
- }
-
- // Update status bar colors
- if (status_bar_ != nullptr) {
- lv_obj_set_style_bg_color(status_bar_, current_theme_.background, 0);
- lv_obj_set_style_text_color(status_bar_, current_theme_.text, 0);
-
- // Update status bar elements
- if (network_label_ != nullptr) {
- lv_obj_set_style_text_color(network_label_, current_theme_.text, 0);
- }
- if (status_label_ != nullptr) {
- lv_obj_set_style_text_color(status_label_, current_theme_.text, 0);
- }
- if (notification_label_ != nullptr) {
- lv_obj_set_style_text_color(notification_label_, current_theme_.text, 0);
- }
- if (mute_label_ != nullptr) {
- lv_obj_set_style_text_color(mute_label_, current_theme_.text, 0);
- }
- if (battery_label_ != nullptr) {
- lv_obj_set_style_text_color(battery_label_, current_theme_.text, 0);
- }
- if (emotion_label_ != nullptr) {
- lv_obj_set_style_text_color(emotion_label_, current_theme_.text, 0);
- }
- }
-
- // Update content area colors
- if (content_ != nullptr) {
- lv_obj_set_style_bg_color(content_, current_theme_.chat_background, 0);
- lv_obj_set_style_border_color(content_, current_theme_.border, 0);
-
- // If we have the chat message style, update all message bubbles
- #if CONFIG_USE_WECHAT_MESSAGE_STYLE
- // Iterate through all children of content (message containers or bubbles)
- uint32_t child_count = lv_obj_get_child_cnt(content_);
- for (uint32_t i = 0; i < child_count; i++) {
- lv_obj_t* obj = lv_obj_get_child(content_, i);
- if (obj == nullptr) continue;
-
- lv_obj_t* bubble = nullptr;
-
- // 检查这个对象是容器还是气泡
- // 如果是容器(用户或系统消息),则获取其子对象作为气泡
- // 如果是气泡(助手消息),则直接使用
- if (lv_obj_get_child_cnt(obj) > 0) {
- // 可能是容器,检查它是否为用户或系统消息容器
- // 用户和系统消息容器是透明的
- lv_opa_t bg_opa = lv_obj_get_style_bg_opa(obj, 0);
- if (bg_opa == LV_OPA_TRANSP) {
- // 这是用户或系统消息的容器
- bubble = lv_obj_get_child(obj, 0);
- } else {
- // 这可能是助手消息的气泡自身
- bubble = obj;
- }
- } else {
- // 没有子元素,可能是其他UI元素,跳过
- continue;
- }
-
- if (bubble == nullptr) continue;
-
- // 使用保存的用户数据来识别气泡类型
- void* bubble_type_ptr = lv_obj_get_user_data(bubble);
- if (bubble_type_ptr != nullptr) {
- const char* bubble_type = static_cast<const char*>(bubble_type_ptr);
-
- // 根据气泡类型应用正确的颜色
- if (strcmp(bubble_type, "user") == 0) {
- lv_obj_set_style_bg_color(bubble, current_theme_.user_bubble, 0);
- } else if (strcmp(bubble_type, "assistant") == 0) {
- lv_obj_set_style_bg_color(bubble, current_theme_.assistant_bubble, 0);
- } else if (strcmp(bubble_type, "system") == 0) {
- lv_obj_set_style_bg_color(bubble, current_theme_.system_bubble, 0);
- } else if (strcmp(bubble_type, "image") == 0) {
- lv_obj_set_style_bg_color(bubble, current_theme_.system_bubble, 0);
- }
-
- // Update border color
- lv_obj_set_style_border_color(bubble, current_theme_.border, 0);
-
- // Update text color for the message
- if (lv_obj_get_child_cnt(bubble) > 0) {
- lv_obj_t* text = lv_obj_get_child(bubble, 0);
- if (text != nullptr) {
- // 根据气泡类型设置文本颜色
- if (strcmp(bubble_type, "system") == 0) {
- lv_obj_set_style_text_color(text, current_theme_.system_text, 0);
- } else {
- lv_obj_set_style_text_color(text, current_theme_.text, 0);
- }
- }
- }
- } else {
- // 如果没有标记,回退到之前的逻辑(颜色比较)
- // ...保留原有的回退逻辑...
- lv_color_t bg_color = lv_obj_get_style_bg_color(bubble, 0);
-
- // 改进bubble类型检测逻辑,不仅使用颜色比较
- bool is_user_bubble = false;
- bool is_assistant_bubble = false;
- bool is_system_bubble = false;
-
- // 检查用户bubble
- if (lv_color_eq(bg_color, DARK_USER_BUBBLE_COLOR) ||
- lv_color_eq(bg_color, LIGHT_USER_BUBBLE_COLOR) ||
- lv_color_eq(bg_color, current_theme_.user_bubble)) {
- is_user_bubble = true;
- }
- // 检查系统bubble
- else if (lv_color_eq(bg_color, DARK_SYSTEM_BUBBLE_COLOR) ||
- lv_color_eq(bg_color, LIGHT_SYSTEM_BUBBLE_COLOR) ||
- lv_color_eq(bg_color, current_theme_.system_bubble)) {
- is_system_bubble = true;
- }
- // 剩余的都当作助手bubble处理
- else {
- is_assistant_bubble = true;
- }
-
- // 根据bubble类型应用正确的颜色
- if (is_user_bubble) {
- lv_obj_set_style_bg_color(bubble, current_theme_.user_bubble, 0);
- } else if (is_assistant_bubble) {
- lv_obj_set_style_bg_color(bubble, current_theme_.assistant_bubble, 0);
- } else if (is_system_bubble) {
- lv_obj_set_style_bg_color(bubble, current_theme_.system_bubble, 0);
- }
-
- // Update border color
- lv_obj_set_style_border_color(bubble, current_theme_.border, 0);
-
- // Update text color for the message
- if (lv_obj_get_child_cnt(bubble) > 0) {
- lv_obj_t* text = lv_obj_get_child(bubble, 0);
- if (text != nullptr) {
- // 回退到颜色检测逻辑
- if (lv_color_eq(bg_color, current_theme_.system_bubble) ||
- lv_color_eq(bg_color, DARK_SYSTEM_BUBBLE_COLOR) ||
- lv_color_eq(bg_color, LIGHT_SYSTEM_BUBBLE_COLOR)) {
- lv_obj_set_style_text_color(text, current_theme_.system_text, 0);
- } else {
- lv_obj_set_style_text_color(text, current_theme_.text, 0);
- }
- }
- }
- }
- }
- #else
- // Simple UI mode - just update the main chat message
- if (chat_message_label_ != nullptr) {
- lv_obj_set_style_text_color(chat_message_label_, current_theme_.text, 0);
- }
-
- if (emotion_label_ != nullptr) {
- lv_obj_set_style_text_color(emotion_label_, current_theme_.text, 0);
- }
- #endif
- }
-
- // Update low battery popup
- if (low_battery_popup_ != nullptr) {
- lv_obj_set_style_bg_color(low_battery_popup_, current_theme_.low_battery, 0);
- }
- // No errors occurred. Save theme to settings
- Display::SetTheme(theme_name);
- }
- void LcdDisplay::SetupHomeScreen() {
- DisplayLockGuard lock(this);
-
- auto screen = lv_screen_active();
-
- // 创建主界面容器
- home_container_ = lv_obj_create(screen);
- lv_obj_set_size(home_container_, LV_HOR_RES, LV_VER_RES);
- lv_obj_set_style_bg_color(home_container_, lv_color_black(), 0);
- lv_obj_set_style_border_width(home_container_, 0, 0);
- lv_obj_set_style_pad_all(home_container_, 0, 0);
- lv_obj_set_style_radius(home_container_, 0, 0);
-
-
- // 创建时间数字图片容器
- timer_container_ = lv_obj_create(home_container_);
- lv_obj_set_size(timer_container_, 190, 85); // 调整宽度为156pt
- lv_obj_set_pos(timer_container_, 32, 62); // 调整x位置为42pt保持居中
-
- // 调试:为容器设置白色边框,方便观察边界
- lv_obj_set_style_border_color(timer_container_, lv_color_white(), 0);
- lv_obj_set_style_border_width(timer_container_, 1, 0); // 1像素边框
- lv_obj_set_style_border_opa(timer_container_, LV_OPA_COVER, 0);
-
- // 保持背景透明以便观察
- lv_obj_set_style_bg_opa(timer_container_, LV_OPA_TRANSP, 0);
-
- // 初始化时间数字图片对象(5个字符:HH:MM)
- // 位置计算:每个数字间隔8pt
- int positions[5] = {0, 35, 70, 94, 129}; // 0, 27+8, 70, 70+16+8, 129
-
- for (int i = 0; i <5; i++) {
- time_digits_[i] = lv_image_create(timer_container_);
-
- if (i == 2) { // 冒号位置
- lv_obj_set_size(time_digits_[i], 16, 45); // 冒号尺寸16×45pt
- // 垂直居中调整:数字高度47pt,冒号高度45pt,需要向下偏移1pt
- lv_obj_set_pos(time_digits_[i], positions[i], 11);
- } else {
- lv_obj_set_size(time_digits_[i], 27, 47); // 数字尺寸27×47pt
- lv_obj_set_pos(time_digits_[i], positions[i], 10);
- }
- }
-
- //太阳图片 - 坐标(88pt, 170pt),尺寸64pt×68pt
- lv_obj_t* sun_image = lv_image_create(home_container_);
- lv_obj_set_size(sun_image, 64, 68);
- lv_obj_set_pos(sun_image, 88, 170);
- lv_image_set_src(sun_image, &sun);
- lv_obj_add_flag(home_container_, LV_OBJ_FLAG_HIDDEN);
- }
- void LcdDisplay::ShowHomeScreen() {
- DisplayLockGuard lock(this);
-
- // 清理倒计时界面
- CleanupCountdownScreen();
-
- if (home_container_ == nullptr) {
- SetupHomeScreen();
- }
-
- // 隐藏原有UI
- if (container_ != nullptr) {
- lv_obj_add_flag(container_, LV_OBJ_FLAG_HIDDEN);
- }
-
- // 显示主界面
- lv_obj_clear_flag(home_container_, LV_OBJ_FLAG_HIDDEN);
-
- is_home_screen_active_ = true;
-
- // 立即更新时间显示
- UpdateHomeTime();
- }
- // 添加清理函数
- void LcdDisplay::CleanupCountdownScreen() {
- if (countdown_container_ != nullptr) {
- // 安全地删除倒计时容器
- if (lv_obj_is_valid(countdown_container_)) {
- lv_obj_del(countdown_container_);
- }
- countdown_container_ = nullptr;
- countdown_label_ = nullptr;
- countdown_circle_ = nullptr;
- }
- }
- void LcdDisplay::HideHomeScreen() {
- DisplayLockGuard lock(this);
-
- // 隐藏主界面
- if (home_container_ != nullptr) {
- lv_obj_add_flag(home_container_, LV_OBJ_FLAG_HIDDEN);
- }
-
- // 显示原有UI
- if (container_ != nullptr) {
- lv_obj_clear_flag(container_, LV_OBJ_FLAG_HIDDEN);
- }
-
- is_home_screen_active_ = false;
- }
- void LcdDisplay::UpdateHomeTime() {
- if (!is_home_screen_active_ || timer_container_ == nullptr) {
- return;
- }
-
- DisplayLockGuard lock(this);
-
- // 获取当前时间
- time_t now = time(nullptr);
- struct tm* timeinfo = localtime(&now);
-
- // 检查系统时间是否已设置
- // if (timeinfo->tm_year < (2025 - 1900)) {
- // for (int i = 0; i < 5; i++) {
- // lv_image_set_src(time_digits_[i], &number0);
- // }
- // if (date_label_ != nullptr) {
- // lv_label_set_text(date_label_, "时间未设置");
- // }
- // return;
- // }
-
- // 更新时间数字图片
- char time_str[6];
- strftime(time_str, sizeof(time_str), "%H:%M", timeinfo);
-
- for (int i = 0; i < 5; i++) {
- char c = time_str[i];
- const lv_img_dsc_t* digit_img = nullptr;
-
- switch (c) {
- case '0': digit_img = &number0; break;
- case '1': digit_img = &number1; break;
- case '2': digit_img = &number2; break;
- case '3': digit_img = &number3; break;
- case '4': digit_img = &number4; break;
- case '5': digit_img = &number5; break;
- case '6': digit_img = &number6; break;
- case '7': digit_img = &number7; break;
- case '8': digit_img = &number8; break;
- case '9': digit_img = &number9; break;
- case ':': digit_img = &timer_inside; break;
- default: digit_img = &number0; break;
- }
-
- if (digit_img != nullptr && time_digits_[i] != nullptr) {
- lv_image_set_src(time_digits_[i], digit_img);
- }
- }
-
- // 更新日期显示
- // if (date_label_ != nullptr) {
- // char date_str[50];
- // strftime(date_str, sizeof(date_str), "%Y/%m/%d", timeinfo);
-
- // const char* weekdays[] = {"星期日", "星期一", "星期二", "星期三", "星期四", "星期五", "星期六"};
- // int weekday = timeinfo->tm_wday;
- // if (weekday >= 0 && weekday < 7) {
- // strcat(date_str, " ");
- // strcat(date_str, weekdays[weekday]);
- // }
-
- // lv_label_set_text(date_label_, date_str);
- // }
- }
- void LcdDisplay::ShowCountdownScreen(int seconds, int rotation_angle) {
- DisplayLockGuard lock(this);
-
- // 隐藏其他界面
- if (home_container_ != nullptr) {
- lv_obj_add_flag(home_container_, LV_OBJ_FLAG_HIDDEN);
- }
- if (container_ != nullptr) {
- lv_obj_add_flag(container_, LV_OBJ_FLAG_HIDDEN);
- }
-
- // 如果已经存在倒计时界面,先清除
- if (countdown_container_ != nullptr) {
- lv_obj_del(countdown_container_);
- countdown_container_ = nullptr;
- }
-
- // 创建倒计时容器
- countdown_container_ = lv_obj_create(lv_screen_active());
-
- // 注意:不要直接设置容器的旋转,而是设置整个屏幕的旋转
- // 这样坐标系统会更一致
- lv_obj_set_size(countdown_container_, LV_HOR_RES, LV_VER_RES);
- lv_obj_set_style_bg_color(countdown_container_, lv_color_black(), 0);
- lv_obj_set_style_border_width(countdown_container_, 0, 0);
- lv_obj_set_style_pad_all(countdown_container_, 0, 0);
- lv_obj_set_style_radius(countdown_container_, 0, 0);
-
- // 设置整个屏幕的旋转(通过显示器的旋转)
- // 在LVGL中,可以通过lv_display_set_rotation来设置整个显示的旋转
- // 但需要注意的是,这会影响到所有后续创建的UI元素
- // 更好的方法是:使用独立的容器并设置其旋转
-
- // 计算圆弧大小
- lv_coord_t screen_width = LV_HOR_RES;
- lv_coord_t screen_height = LV_VER_RES;
- lv_coord_t min_dimension = (screen_width < screen_height) ? screen_width : screen_height;
- lv_coord_t arc_size = min_dimension * 7 / 10;
-
- // 创建圆形进度条
- countdown_circle_ = lv_arc_create(countdown_container_);
- lv_obj_set_size(countdown_circle_, arc_size, arc_size);
-
- // 根据旋转角度调整圆弧的位置
- switch (rotation_angle) {
- case 90: // 顺时针旋转90度
- lv_obj_align(countdown_circle_, LV_ALIGN_CENTER, 0, 0);
- break;
- case 180: // 顺时针旋转180度
- lv_obj_align(countdown_circle_, LV_ALIGN_CENTER, 0, 0);
- break;
- case 270: // 顺时针旋转270度
- lv_obj_align(countdown_circle_, LV_ALIGN_CENTER, 0, 0);
- break;
- default: // 0度
- lv_obj_align(countdown_circle_, LV_ALIGN_CENTER, 0, 0);
- break;
- }
-
- // 设置圆弧属性
- lv_arc_set_rotation(countdown_circle_, 270);
- lv_arc_set_bg_angles(countdown_circle_, 0, 360);
- lv_arc_set_value(countdown_circle_, 100);
- lv_obj_set_style_arc_width(countdown_circle_, 12, 0);
- lv_obj_set_style_arc_color(countdown_circle_, lv_color_hex(0x1A6C37), 0);
- lv_obj_set_style_bg_color(countdown_circle_, lv_color_black(), 0);
- lv_obj_set_style_border_width(countdown_circle_, 0, 0);
-
- // 创建倒计时数字标签
- countdown_label_ = lv_label_create(countdown_container_);
- lv_obj_set_style_text_font(countdown_label_, fonts_.text_font, 0);
- lv_obj_set_style_text_color(countdown_label_, lv_color_white(), 0);
- lv_obj_align(countdown_label_, LV_ALIGN_CENTER, 0, 0);
-
- // 设置整个容器的旋转
- // 注意:这里设置旋转中心为容器中心
- lv_obj_set_style_transform_pivot_x(countdown_container_, screen_width / 2, 0);
- lv_obj_set_style_transform_pivot_y(countdown_container_, screen_height / 2, 0);
- lv_obj_set_style_transform_rotation(countdown_container_, rotation_angle * 10, 0); // LVGL使用deci-degrees
-
- // 初始更新显示
- UpdateCountdown(seconds);
- }
- void LcdDisplay::UpdateCountdown(int seconds) {
- if (countdown_label_ == nullptr || countdown_circle_ == nullptr) {
- return;
- }
-
- DisplayLockGuard lock(this);
-
- // 格式化时间显示:MM:SS
- int minutes = seconds / 60;
- int remaining_seconds = seconds % 60;
-
- char time_str[16];
- snprintf(time_str, sizeof(time_str), "%02d:%02d", minutes, remaining_seconds);
- lv_label_set_text(countdown_label_, time_str);
-
- // 计算进度百分比
- int total_seconds = 0;
- DeviceState current_state = Application::GetInstance().GetDeviceState();
-
- // 根据当前状态确定总时间
- switch (current_state) {
- case kDeviceStateCountdown10Min:
- total_seconds = 10 * 60; // 600秒
- break;
- case kDeviceStateCountdown15Min:
- total_seconds = 15 * 60; // 900秒
- break;
- case kDeviceStateCountdown20Min:
- total_seconds = 20 * 60; // 1200秒
- break;
- default:
- // 如果不是倒计时状态,使用传入的seconds作为总时间
- total_seconds = seconds;
- break;
- }
-
- if (total_seconds > 0) {
- // 计算进度(0-100)
- int progress = (seconds * 100) / total_seconds;
- lv_arc_set_value(countdown_circle_, progress);
-
- // 根据剩余时间比例改变颜色
- if (progress > 50) {
- // 超过50%:绿色
- lv_obj_set_style_arc_color(countdown_circle_, lv_color_hex(0x1A6C37), 0);
- } else if (progress > 20) {
- // 20%-50%:黄色
- lv_obj_set_style_arc_color(countdown_circle_, lv_color_hex(0xFFA500), 0);
- } else {
- // 低于20%:红色
- lv_obj_set_style_arc_color(countdown_circle_, lv_color_hex(0xFF0000), 0);
- }
- }
- }
- // 在LcdDisplay类中添加恢复屏幕方向的方法
- void LcdDisplay::ResetScreenRotation() {
- DisplayLockGuard lock(this);
-
- if (countdown_container_ != nullptr) {
- // 重置旋转
- lv_obj_set_style_transform_rotation(countdown_container_, 0, 0);
- lv_obj_set_size(countdown_container_, LV_HOR_RES, LV_VER_RES);
- }
- }
- void LcdDisplay::ShowCountdownEndScreen() {
- DisplayLockGuard lock(this);
-
- // 清除倒计时界面
- if (countdown_container_ != nullptr) {
- lv_obj_del(countdown_label_);
- lv_obj_del(countdown_circle_);
- }
-
- // 显示"计时完成"
- lv_obj_t* complete_label = lv_label_create(countdown_container_);
- lv_obj_set_style_text_font(complete_label, fonts_.text_font, 0);
- lv_obj_set_style_text_color(complete_label, lv_color_hex(0x1A6C37), 0);
- lv_obj_center(complete_label);
- lv_label_set_text(complete_label, "计时完成!");
-
- }
|