lcd_display.cc 55 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447
  1. #include "lcd_display.h"
  2. #include <vector>
  3. #include <algorithm>
  4. #include <font_awesome_symbols.h>
  5. #include <esp_log.h>
  6. #include <esp_err.h>
  7. #include <esp_lvgl_port.h>
  8. #include <esp_heap_caps.h>
  9. #include "assets/lang_config.h"
  10. #include <cstring>
  11. #include "settings.h"
  12. #include "sun.h" // 包含头文件
  13. #include "numbers.h"
  14. #include "application.h"
  15. #include "board.h"
  16. #define TAG "LcdDisplay"
  17. // Color definitions for dark theme
  18. #define GRAY_EDGE_COLOR lv_color_hex(0x2E2E2E)
  19. #define DARK_BACKGROUND_COLOR lv_color_hex(0x070707) // Dark background
  20. #define DARK_TEXT_COLOR lv_color_white() // White text
  21. #define DARK_CHAT_BACKGROUND_COLOR lv_color_hex(0x1E1E1E) // Slightly lighter than background
  22. #define DARK_USER_BUBBLE_COLOR lv_color_hex(0x1A6C37) // Dark green
  23. #define DARK_ASSISTANT_BUBBLE_COLOR lv_color_hex(0x333333) // Dark gray
  24. #define DARK_SYSTEM_BUBBLE_COLOR lv_color_hex(0x2A2A2A) // Medium gray
  25. #define DARK_SYSTEM_TEXT_COLOR lv_color_hex(0xAAAAAA) // Light gray text
  26. #define DARK_BORDER_COLOR lv_color_hex(0x333333) // Dark gray border
  27. #define DARK_LOW_BATTERY_COLOR lv_color_hex(0xFF0000) // Red for dark mode
  28. // Color definitions for light theme
  29. #define LIGHT_BACKGROUND_COLOR lv_color_white() // White background
  30. #define LIGHT_TEXT_COLOR lv_color_black() // Black text
  31. #define LIGHT_CHAT_BACKGROUND_COLOR lv_color_hex(0xE0E0E0) // Light gray background
  32. #define LIGHT_USER_BUBBLE_COLOR lv_color_hex(0x95EC69) // WeChat green
  33. #define LIGHT_ASSISTANT_BUBBLE_COLOR lv_color_white() // White
  34. #define LIGHT_SYSTEM_BUBBLE_COLOR lv_color_hex(0xE0E0E0) // Light gray
  35. #define LIGHT_SYSTEM_TEXT_COLOR lv_color_hex(0x666666) // Dark gray text
  36. #define LIGHT_BORDER_COLOR lv_color_hex(0xE0E0E0) // Light gray border
  37. #define LIGHT_LOW_BATTERY_COLOR lv_color_black() // Black for light mode
  38. // Define dark theme colors
  39. const ThemeColors DARK_THEME = {
  40. .background = DARK_BACKGROUND_COLOR,
  41. .text = DARK_TEXT_COLOR,
  42. .chat_background = DARK_CHAT_BACKGROUND_COLOR,
  43. .user_bubble = DARK_USER_BUBBLE_COLOR,
  44. .assistant_bubble = DARK_ASSISTANT_BUBBLE_COLOR,
  45. .system_bubble = DARK_SYSTEM_BUBBLE_COLOR,
  46. .system_text = DARK_SYSTEM_TEXT_COLOR,
  47. .border = DARK_BORDER_COLOR,
  48. .low_battery = DARK_LOW_BATTERY_COLOR
  49. };
  50. // Define light theme colors
  51. const ThemeColors LIGHT_THEME = {
  52. .background = LIGHT_BACKGROUND_COLOR,
  53. .text = LIGHT_TEXT_COLOR,
  54. .chat_background = LIGHT_CHAT_BACKGROUND_COLOR,
  55. .user_bubble = LIGHT_USER_BUBBLE_COLOR,
  56. .assistant_bubble = LIGHT_ASSISTANT_BUBBLE_COLOR,
  57. .system_bubble = LIGHT_SYSTEM_BUBBLE_COLOR,
  58. .system_text = LIGHT_SYSTEM_TEXT_COLOR,
  59. .border = LIGHT_BORDER_COLOR,
  60. .low_battery = LIGHT_LOW_BATTERY_COLOR
  61. };
  62. LV_FONT_DECLARE(font_awesome_30_4);
  63. LcdDisplay::LcdDisplay(esp_lcd_panel_io_handle_t panel_io, esp_lcd_panel_handle_t panel, DisplayFonts fonts, int width, int height)
  64. : panel_io_(panel_io), panel_(panel), fonts_(fonts) {
  65. width_ = width;
  66. height_ = height;
  67. // Load theme from settings
  68. Settings settings("display", false);
  69. current_theme_name_ = settings.GetString("theme", "light");
  70. // Update the theme
  71. if (current_theme_name_ == "dark") {
  72. current_theme_ = DARK_THEME;
  73. } else if (current_theme_name_ == "light") {
  74. current_theme_ = LIGHT_THEME;
  75. }
  76. }
  77. SpiLcdDisplay::SpiLcdDisplay(esp_lcd_panel_io_handle_t panel_io, esp_lcd_panel_handle_t panel,
  78. int width, int height, int offset_x, int offset_y, bool mirror_x, bool mirror_y, bool swap_xy,
  79. DisplayFonts fonts)
  80. : LcdDisplay(panel_io, panel, fonts, width, height) {
  81. // draw white
  82. std::vector<uint16_t> buffer(width_, 0xFFFF);
  83. for (int y = 0; y < height_; y++) {
  84. esp_lcd_panel_draw_bitmap(panel_, 0, y, width_, y + 1, buffer.data());
  85. }
  86. // Set the display to on
  87. ESP_LOGI(TAG, "Turning display on");
  88. ESP_ERROR_CHECK(esp_lcd_panel_disp_on_off(panel_, true));
  89. ESP_LOGI(TAG, "Initialize LVGL library");
  90. lv_init();
  91. ESP_LOGI(TAG, "Initialize LVGL port");
  92. lvgl_port_cfg_t port_cfg = ESP_LVGL_PORT_INIT_CONFIG();
  93. port_cfg.task_priority = 1;
  94. port_cfg.timer_period_ms = 50;
  95. lvgl_port_init(&port_cfg);
  96. ESP_LOGI(TAG, "Adding LCD display");
  97. const lvgl_port_display_cfg_t display_cfg = {
  98. .io_handle = panel_io_,
  99. .panel_handle = panel_,
  100. .control_handle = nullptr,
  101. .buffer_size = static_cast<uint32_t>(width_ * 20),
  102. .double_buffer = false,
  103. .trans_size = 0,
  104. .hres = static_cast<uint32_t>(width_),
  105. .vres = static_cast<uint32_t>(height_),
  106. .monochrome = false,
  107. .rotation = {
  108. .swap_xy = swap_xy,
  109. .mirror_x = mirror_x,
  110. .mirror_y = mirror_y,
  111. },
  112. .color_format = LV_COLOR_FORMAT_RGB565,
  113. .flags = {
  114. .buff_dma = 1,
  115. .buff_spiram = 0,
  116. .sw_rotate = 0,
  117. .swap_bytes = 1,
  118. .full_refresh = 0,
  119. .direct_mode = 0,
  120. },
  121. };
  122. display_ = lvgl_port_add_disp(&display_cfg);
  123. if (display_ == nullptr) {
  124. ESP_LOGE(TAG, "Failed to add display");
  125. return;
  126. }
  127. if (offset_x != 0 || offset_y != 0) {
  128. lv_display_set_offset(display_, offset_x, offset_y);
  129. }
  130. SetupUI();
  131. }
  132. // RGB LCD实现
  133. RgbLcdDisplay::RgbLcdDisplay(esp_lcd_panel_io_handle_t panel_io, esp_lcd_panel_handle_t panel,
  134. int width, int height, int offset_x, int offset_y,
  135. bool mirror_x, bool mirror_y, bool swap_xy,
  136. DisplayFonts fonts)
  137. : LcdDisplay(panel_io, panel, fonts, width, height) {
  138. // draw white
  139. std::vector<uint16_t> buffer(width_, 0xFFFF);
  140. for (int y = 0; y < height_; y++) {
  141. esp_lcd_panel_draw_bitmap(panel_, 0, y, width_, y + 1, buffer.data());
  142. }
  143. ESP_LOGI(TAG, "Initialize LVGL library");
  144. lv_init();
  145. ESP_LOGI(TAG, "Initialize LVGL port");
  146. lvgl_port_cfg_t port_cfg = ESP_LVGL_PORT_INIT_CONFIG();
  147. port_cfg.task_priority = 1;
  148. port_cfg.timer_period_ms = 50;
  149. lvgl_port_init(&port_cfg);
  150. ESP_LOGI(TAG, "Adding LCD display");
  151. const lvgl_port_display_cfg_t display_cfg = {
  152. .io_handle = panel_io_,
  153. .panel_handle = panel_,
  154. .buffer_size = static_cast<uint32_t>(width_ * 20),
  155. .double_buffer = true,
  156. .hres = static_cast<uint32_t>(width_),
  157. .vres = static_cast<uint32_t>(height_),
  158. .rotation = {
  159. .swap_xy = swap_xy,
  160. .mirror_x = mirror_x,
  161. .mirror_y = mirror_y,
  162. },
  163. .flags = {
  164. .buff_dma = 1,
  165. .swap_bytes = 0,
  166. .full_refresh = 1,
  167. .direct_mode = 1,
  168. },
  169. };
  170. const lvgl_port_display_rgb_cfg_t rgb_cfg = {
  171. .flags = {
  172. .bb_mode = true,
  173. .avoid_tearing = true,
  174. }
  175. };
  176. display_ = lvgl_port_add_disp_rgb(&display_cfg, &rgb_cfg);
  177. if (display_ == nullptr) {
  178. ESP_LOGE(TAG, "Failed to add RGB display");
  179. return;
  180. }
  181. if (offset_x != 0 || offset_y != 0) {
  182. lv_display_set_offset(display_, offset_x, offset_y);
  183. }
  184. SetupUI();
  185. }
  186. MipiLcdDisplay::MipiLcdDisplay(esp_lcd_panel_io_handle_t panel_io, esp_lcd_panel_handle_t panel,
  187. int width, int height, int offset_x, int offset_y,
  188. bool mirror_x, bool mirror_y, bool swap_xy,
  189. DisplayFonts fonts)
  190. : LcdDisplay(panel_io, panel, fonts, width, height) {
  191. // Set the display to on
  192. ESP_LOGI(TAG, "Turning display on");
  193. ESP_ERROR_CHECK(esp_lcd_panel_disp_on_off(panel_, true));
  194. ESP_LOGI(TAG, "Initialize LVGL library");
  195. lv_init();
  196. ESP_LOGI(TAG, "Initialize LVGL port");
  197. lvgl_port_cfg_t port_cfg = ESP_LVGL_PORT_INIT_CONFIG();
  198. lvgl_port_init(&port_cfg);
  199. ESP_LOGI(TAG, "Adding LCD display");
  200. const lvgl_port_display_cfg_t disp_cfg = {
  201. .io_handle = panel_io,
  202. .panel_handle = panel,
  203. .control_handle = nullptr,
  204. .buffer_size = static_cast<uint32_t>(width_ * 50),
  205. .double_buffer = false,
  206. .hres = static_cast<uint32_t>(width_),
  207. .vres = static_cast<uint32_t>(height_),
  208. .monochrome = false,
  209. /* Rotation values must be same as used in esp_lcd for initial settings of the screen */
  210. .rotation = {
  211. .swap_xy = swap_xy,
  212. .mirror_x = mirror_x,
  213. .mirror_y = mirror_y,
  214. },
  215. .flags = {
  216. .buff_dma = true,
  217. .buff_spiram =false,
  218. .sw_rotate = false,
  219. },
  220. };
  221. const lvgl_port_display_dsi_cfg_t dpi_cfg = {
  222. .flags = {
  223. .avoid_tearing = false,
  224. }
  225. };
  226. display_ = lvgl_port_add_disp_dsi(&disp_cfg, &dpi_cfg);
  227. if (display_ == nullptr) {
  228. ESP_LOGE(TAG, "Failed to add display");
  229. return;
  230. }
  231. if (offset_x != 0 || offset_y != 0) {
  232. lv_display_set_offset(display_, offset_x, offset_y);
  233. }
  234. SetupUI();
  235. }
  236. LcdDisplay::~LcdDisplay() {
  237. // 然后再清理 LVGL 对象
  238. if (content_ != nullptr) {
  239. lv_obj_del(content_);
  240. }
  241. if (status_bar_ != nullptr) {
  242. lv_obj_del(status_bar_);
  243. }
  244. if (side_bar_ != nullptr) {
  245. lv_obj_del(side_bar_);
  246. }
  247. if (container_ != nullptr) {
  248. lv_obj_del(container_);
  249. }
  250. if (display_ != nullptr) {
  251. lv_display_delete(display_);
  252. }
  253. if (panel_ != nullptr) {
  254. esp_lcd_panel_del(panel_);
  255. }
  256. if (panel_io_ != nullptr) {
  257. esp_lcd_panel_io_del(panel_io_);
  258. }
  259. }
  260. bool LcdDisplay::Lock(int timeout_ms) {
  261. return lvgl_port_lock(timeout_ms);
  262. }
  263. void LcdDisplay::Unlock() {
  264. lvgl_port_unlock();
  265. }
  266. #if CONFIG_USE_WECHAT_MESSAGE_STYLE
  267. void LcdDisplay::SetupUI() {
  268. DisplayLockGuard lock(this);
  269. auto screen = lv_screen_active();
  270. lv_obj_set_style_text_font(screen, fonts_.text_font, 0);
  271. lv_obj_set_style_text_color(screen, current_theme_.text, 0);
  272. lv_obj_set_style_bg_color(screen, current_theme_.gray_edge, 0);
  273. /* Container */
  274. container_ = lv_obj_create(screen);
  275. lv_obj_set_size(container_, LV_HOR_RES, LV_VER_RES);
  276. lv_obj_set_flex_flow(container_, LV_FLEX_FLOW_COLUMN);
  277. lv_obj_set_style_pad_all(container_, 0, 0);
  278. lv_obj_set_style_border_width(container_, 0, 0);
  279. lv_obj_set_style_pad_row(container_, 0, 0);
  280. lv_obj_set_style_bg_color(container_, current_theme_.gray_edge, 0);
  281. lv_obj_set_style_border_color(container_, current_theme_.border, 0);
  282. /* Status bar */
  283. status_bar_ = lv_obj_create(container_);
  284. lv_obj_set_size(status_bar_, LV_HOR_RES, LV_SIZE_CONTENT);
  285. lv_obj_set_style_radius(status_bar_, 0, 0);
  286. lv_obj_set_style_bg_color(status_bar_, current_theme_.background, 0);
  287. lv_obj_set_style_text_color(status_bar_, current_theme_.text, 0);
  288. /* Content - Chat area */
  289. content_ = lv_obj_create(container_);
  290. lv_obj_set_style_radius(content_, 0, 0);
  291. lv_obj_set_width(content_, LV_HOR_RES);
  292. lv_obj_set_flex_grow(content_, 1);
  293. lv_obj_set_style_pad_all(content_, 10, 0);
  294. lv_obj_set_style_bg_color(content_, current_theme_.chat_background, 0); // Background for chat area
  295. lv_obj_set_style_border_color(content_, current_theme_.border, 0); // Border color for chat area
  296. // Enable scrolling for chat content
  297. lv_obj_set_scrollbar_mode(content_, LV_SCROLLBAR_MODE_OFF);
  298. lv_obj_set_scroll_dir(content_, LV_DIR_VER);
  299. // Create a flex container for chat messages
  300. lv_obj_set_flex_flow(content_, LV_FLEX_FLOW_COLUMN);
  301. lv_obj_set_flex_align(content_, LV_FLEX_ALIGN_START, LV_FLEX_ALIGN_START, LV_FLEX_ALIGN_START);
  302. lv_obj_set_style_pad_row(content_, 10, 0); // Space between messages
  303. // We'll create chat messages dynamically in SetChatMessage
  304. chat_message_label_ = nullptr;
  305. /* Status bar */
  306. lv_obj_set_flex_flow(status_bar_, LV_FLEX_FLOW_ROW);
  307. lv_obj_set_style_pad_all(status_bar_, 0, 0);
  308. lv_obj_set_style_border_width(status_bar_, 0, 0);
  309. lv_obj_set_style_pad_column(status_bar_, 0, 0);
  310. lv_obj_set_style_pad_left(status_bar_, 10, 0);
  311. lv_obj_set_style_pad_right(status_bar_, 10, 0);
  312. lv_obj_set_style_pad_top(status_bar_, 2, 0);
  313. lv_obj_set_style_pad_bottom(status_bar_, 2, 0);
  314. lv_obj_set_scrollbar_mode(status_bar_, LV_SCROLLBAR_MODE_OFF);
  315. // 设置状态栏的内容垂直居中
  316. lv_obj_set_flex_align(status_bar_, LV_FLEX_ALIGN_SPACE_BETWEEN, LV_FLEX_ALIGN_CENTER, LV_FLEX_ALIGN_CENTER);
  317. // 创建emotion_label_在状态栏最左侧
  318. emotion_label_ = lv_label_create(status_bar_);
  319. lv_obj_set_style_text_font(emotion_label_, &font_awesome_30_4, 0);
  320. lv_obj_set_style_text_color(emotion_label_, current_theme_.text, 0);
  321. lv_label_set_text(emotion_label_, FONT_AWESOME_AI_CHIP);
  322. lv_obj_set_style_margin_right(emotion_label_, 5, 0); // 添加右边距,与后面的元素分隔
  323. notification_label_ = lv_label_create(status_bar_);
  324. lv_obj_set_flex_grow(notification_label_, 1);
  325. lv_obj_set_style_text_align(notification_label_, LV_TEXT_ALIGN_CENTER, 0);
  326. lv_obj_set_style_text_color(notification_label_, current_theme_.text, 0);
  327. lv_label_set_text(notification_label_, "");
  328. lv_obj_add_flag(notification_label_, LV_OBJ_FLAG_HIDDEN);
  329. status_label_ = lv_label_create(status_bar_);
  330. lv_obj_set_flex_grow(status_label_, 1);
  331. lv_label_set_long_mode(status_label_, LV_LABEL_LONG_SCROLL_CIRCULAR);
  332. lv_obj_set_style_text_align(status_label_, LV_TEXT_ALIGN_CENTER, 0);
  333. lv_obj_set_style_text_color(status_label_, current_theme_.text, 0);
  334. lv_label_set_text(status_label_, Lang::Strings::INITIALIZING);
  335. mute_label_ = lv_label_create(status_bar_);
  336. lv_label_set_text(mute_label_, "");
  337. lv_obj_set_style_text_font(mute_label_, fonts_.icon_font, 0);
  338. lv_obj_set_style_text_color(mute_label_, current_theme_.text, 0);
  339. network_label_ = lv_label_create(status_bar_);
  340. lv_label_set_text(network_label_, "");
  341. lv_obj_set_style_text_font(network_label_, fonts_.icon_font, 0);
  342. lv_obj_set_style_text_color(network_label_, current_theme_.text, 0);
  343. lv_obj_set_style_margin_left(network_label_, 5, 0); // 添加左边距,与前面的元素分隔
  344. battery_label_ = lv_label_create(status_bar_);
  345. lv_label_set_text(battery_label_, "");
  346. lv_obj_set_style_text_font(battery_label_, fonts_.icon_font, 0);
  347. lv_obj_set_style_text_color(battery_label_, current_theme_.text, 0);
  348. lv_obj_set_style_margin_left(battery_label_, 5, 0); // 添加左边距,与前面的元素分隔
  349. low_battery_popup_ = lv_obj_create(screen);
  350. lv_obj_set_scrollbar_mode(low_battery_popup_, LV_SCROLLBAR_MODE_OFF);
  351. lv_obj_set_size(low_battery_popup_, LV_HOR_RES * 0.9, fonts_.text_font->line_height * 2);
  352. lv_obj_align(low_battery_popup_, LV_ALIGN_BOTTOM_MID, 0, 0);
  353. lv_obj_set_style_bg_color(low_battery_popup_, current_theme_.low_battery, 0);
  354. lv_obj_set_style_radius(low_battery_popup_, 10, 0);
  355. low_battery_label_ = lv_label_create(low_battery_popup_);
  356. lv_label_set_text(low_battery_label_, Lang::Strings::BATTERY_NEED_CHARGE);
  357. lv_obj_set_style_text_color(low_battery_label_, lv_color_white(), 0);
  358. lv_obj_center(low_battery_label_);
  359. lv_obj_add_flag(low_battery_popup_, LV_OBJ_FLAG_HIDDEN);
  360. }
  361. #if CONFIG_IDF_TARGET_ESP32P4
  362. #define MAX_MESSAGES 40
  363. #else
  364. #define MAX_MESSAGES 20
  365. #endif
  366. void LcdDisplay::SetChatMessage(const char* role, const char* content) {
  367. DisplayLockGuard lock(this);
  368. if (content_ == nullptr) {
  369. return;
  370. }
  371. //避免出现空的消息框
  372. if(strlen(content) == 0) return;
  373. // 检查消息数量是否超过限制
  374. uint32_t child_count = lv_obj_get_child_cnt(content_);
  375. if (child_count >= MAX_MESSAGES) {
  376. // 删除最早的消息(第一个子对象)
  377. lv_obj_t* first_child = lv_obj_get_child(content_, 0);
  378. lv_obj_t* last_child = lv_obj_get_child(content_, child_count - 1);
  379. if (first_child != nullptr) {
  380. lv_obj_del(first_child);
  381. }
  382. // Scroll to the last message immediately
  383. if (last_child != nullptr) {
  384. lv_obj_scroll_to_view_recursive(last_child, LV_ANIM_OFF);
  385. }
  386. }
  387. // 折叠系统消息(如果是系统消息,检查最后一个消息是否也是系统消息)
  388. if (strcmp(role, "system") == 0 && child_count > 0) {
  389. // 获取最后一个消息容器
  390. lv_obj_t* last_container = lv_obj_get_child(content_, child_count - 1);
  391. if (last_container != nullptr && lv_obj_get_child_cnt(last_container) > 0) {
  392. // 获取容器内的气泡
  393. lv_obj_t* last_bubble = lv_obj_get_child(last_container, 0);
  394. if (last_bubble != nullptr) {
  395. // 检查气泡类型是否为系统消息
  396. void* bubble_type_ptr = lv_obj_get_user_data(last_bubble);
  397. if (bubble_type_ptr != nullptr && strcmp((const char*)bubble_type_ptr, "system") == 0) {
  398. // 如果最后一个消息也是系统消息,则删除它
  399. lv_obj_del(last_container);
  400. }
  401. }
  402. }
  403. }
  404. // Create a message bubble
  405. lv_obj_t* msg_bubble = lv_obj_create(content_);
  406. lv_obj_set_style_radius(msg_bubble, 8, 0);
  407. lv_obj_set_scrollbar_mode(msg_bubble, LV_SCROLLBAR_MODE_OFF);
  408. lv_obj_set_style_border_width(msg_bubble, 1, 0);
  409. lv_obj_set_style_border_color(msg_bubble, current_theme_.border, 0);
  410. lv_obj_set_style_pad_all(msg_bubble, 8, 0);
  411. // Create the message text
  412. lv_obj_t* msg_text = lv_label_create(msg_bubble);
  413. lv_label_set_text(msg_text, content);
  414. // 计算文本实际宽度
  415. lv_coord_t text_width = lv_txt_get_width(content, strlen(content), fonts_.text_font, 0);
  416. // 计算气泡宽度
  417. lv_coord_t max_width = LV_HOR_RES * 85 / 100 - 16; // 屏幕宽度的85%
  418. lv_coord_t min_width = 20;
  419. lv_coord_t bubble_width;
  420. // 确保文本宽度不小于最小宽度
  421. if (text_width < min_width) {
  422. text_width = min_width;
  423. }
  424. // 如果文本宽度小于最大宽度,使用文本宽度
  425. if (text_width < max_width) {
  426. bubble_width = text_width;
  427. } else {
  428. bubble_width = max_width;
  429. }
  430. // 设置消息文本的宽度
  431. lv_obj_set_width(msg_text, bubble_width); // 减去padding
  432. lv_label_set_long_mode(msg_text, LV_LABEL_LONG_WRAP);
  433. lv_obj_set_style_text_font(msg_text, fonts_.text_font, 0);
  434. // 设置气泡宽度
  435. lv_obj_set_width(msg_bubble, bubble_width);
  436. lv_obj_set_height(msg_bubble, LV_SIZE_CONTENT);
  437. // Set alignment and style based on message role
  438. if (strcmp(role, "user") == 0) {
  439. // User messages are right-aligned with green background
  440. lv_obj_set_style_bg_color(msg_bubble, current_theme_.user_bubble, 0);
  441. // Set text color for contrast
  442. lv_obj_set_style_text_color(msg_text, current_theme_.text, 0);
  443. // 设置自定义属性标记气泡类型
  444. lv_obj_set_user_data(msg_bubble, (void*)"user");
  445. // Set appropriate width for content
  446. lv_obj_set_width(msg_bubble, LV_SIZE_CONTENT);
  447. lv_obj_set_height(msg_bubble, LV_SIZE_CONTENT);
  448. // Don't grow
  449. lv_obj_set_style_flex_grow(msg_bubble, 0, 0);
  450. } else if (strcmp(role, "assistant") == 0) {
  451. // Assistant messages are left-aligned with white background
  452. lv_obj_set_style_bg_color(msg_bubble, current_theme_.assistant_bubble, 0);
  453. // Set text color for contrast
  454. lv_obj_set_style_text_color(msg_text, current_theme_.text, 0);
  455. // 设置自定义属性标记气泡类型
  456. lv_obj_set_user_data(msg_bubble, (void*)"assistant");
  457. // Set appropriate width for content
  458. lv_obj_set_width(msg_bubble, LV_SIZE_CONTENT);
  459. lv_obj_set_height(msg_bubble, LV_SIZE_CONTENT);
  460. // Don't grow
  461. lv_obj_set_style_flex_grow(msg_bubble, 0, 0);
  462. } else if (strcmp(role, "system") == 0) {
  463. // System messages are center-aligned with light gray background
  464. lv_obj_set_style_bg_color(msg_bubble, current_theme_.system_bubble, 0);
  465. // Set text color for contrast
  466. lv_obj_set_style_text_color(msg_text, current_theme_.system_text, 0);
  467. // 设置自定义属性标记气泡类型
  468. lv_obj_set_user_data(msg_bubble, (void*)"system");
  469. // Set appropriate width for content
  470. lv_obj_set_width(msg_bubble, LV_SIZE_CONTENT);
  471. lv_obj_set_height(msg_bubble, LV_SIZE_CONTENT);
  472. // Don't grow
  473. lv_obj_set_style_flex_grow(msg_bubble, 0, 0);
  474. }
  475. // Create a full-width container for user messages to ensure right alignment
  476. if (strcmp(role, "user") == 0) {
  477. // Create a full-width container
  478. lv_obj_t* container = lv_obj_create(content_);
  479. lv_obj_set_width(container, LV_HOR_RES);
  480. lv_obj_set_height(container, LV_SIZE_CONTENT);
  481. // Make container transparent and borderless
  482. lv_obj_set_style_bg_opa(container, LV_OPA_TRANSP, 0);
  483. lv_obj_set_style_border_width(container, 0, 0);
  484. lv_obj_set_style_pad_all(container, 0, 0);
  485. // Move the message bubble into this container
  486. lv_obj_set_parent(msg_bubble, container);
  487. // Right align the bubble in the container
  488. lv_obj_align(msg_bubble, LV_ALIGN_RIGHT_MID, -25, 0);
  489. // Auto-scroll to this container
  490. lv_obj_scroll_to_view_recursive(container, LV_ANIM_ON);
  491. } else if (strcmp(role, "system") == 0) {
  492. // 为系统消息创建全宽容器以确保居中对齐
  493. lv_obj_t* container = lv_obj_create(content_);
  494. lv_obj_set_width(container, LV_HOR_RES);
  495. lv_obj_set_height(container, LV_SIZE_CONTENT);
  496. // 使容器透明且无边框
  497. lv_obj_set_style_bg_opa(container, LV_OPA_TRANSP, 0);
  498. lv_obj_set_style_border_width(container, 0, 0);
  499. lv_obj_set_style_pad_all(container, 0, 0);
  500. // 将消息气泡移入此容器
  501. lv_obj_set_parent(msg_bubble, container);
  502. // 将气泡居中对齐在容器中
  503. lv_obj_align(msg_bubble, LV_ALIGN_CENTER, 0, 0);
  504. // 自动滚动底部
  505. lv_obj_scroll_to_view_recursive(container, LV_ANIM_ON);
  506. } else {
  507. // For assistant messages
  508. // Left align assistant messages
  509. lv_obj_align(msg_bubble, LV_ALIGN_LEFT_MID, 0, 0);
  510. // Auto-scroll to the message bubble
  511. lv_obj_scroll_to_view_recursive(msg_bubble, LV_ANIM_ON);
  512. }
  513. // Store reference to the latest message label
  514. chat_message_label_ = msg_text;
  515. }
  516. void LcdDisplay::SetPreviewImage(const lv_img_dsc_t* img_dsc) {
  517. DisplayLockGuard lock(this);
  518. if (content_ == nullptr) {
  519. return;
  520. }
  521. if (img_dsc != nullptr) {
  522. // Create a message bubble for image preview
  523. lv_obj_t* img_bubble = lv_obj_create(content_);
  524. lv_obj_set_style_radius(img_bubble, 8, 0);
  525. lv_obj_set_scrollbar_mode(img_bubble, LV_SCROLLBAR_MODE_OFF);
  526. lv_obj_set_style_border_width(img_bubble, 1, 0);
  527. lv_obj_set_style_border_color(img_bubble, current_theme_.border, 0);
  528. lv_obj_set_style_pad_all(img_bubble, 8, 0);
  529. // Set image bubble background color (similar to system message)
  530. lv_obj_set_style_bg_color(img_bubble, current_theme_.assistant_bubble, 0);
  531. // 设置自定义属性标记气泡类型
  532. lv_obj_set_user_data(img_bubble, (void*)"image");
  533. // Create the image object inside the bubble
  534. lv_obj_t* preview_image = lv_image_create(img_bubble);
  535. // Copy the image descriptor and data to avoid source data changes
  536. lv_img_dsc_t* copied_img_dsc = (lv_img_dsc_t*)heap_caps_malloc(sizeof(lv_img_dsc_t), MALLOC_CAP_8BIT);
  537. if (copied_img_dsc == nullptr) {
  538. ESP_LOGE(TAG, "Failed to allocate memory for image descriptor");
  539. lv_obj_del(img_bubble);
  540. return;
  541. }
  542. // Copy the header
  543. copied_img_dsc->header = img_dsc->header;
  544. copied_img_dsc->data_size = img_dsc->data_size;
  545. // Copy the image data
  546. uint8_t* copied_data = (uint8_t*)heap_caps_malloc(img_dsc->data_size, MALLOC_CAP_SPIRAM | MALLOC_CAP_8BIT);
  547. if (copied_data == nullptr) {
  548. // Fallback to internal RAM if SPIRAM allocation fails
  549. copied_data = (uint8_t*)heap_caps_malloc(img_dsc->data_size, MALLOC_CAP_8BIT);
  550. }
  551. if (copied_data == nullptr) {
  552. ESP_LOGE(TAG, "Failed to allocate memory for image data (size: %lu bytes)", img_dsc->data_size);
  553. heap_caps_free(copied_img_dsc);
  554. lv_obj_del(img_bubble);
  555. return;
  556. }
  557. memcpy(copied_data, img_dsc->data, img_dsc->data_size);
  558. copied_img_dsc->data = copied_data;
  559. // Calculate appropriate size for the image
  560. lv_coord_t max_width = LV_HOR_RES * 70 / 100; // 70% of screen width
  561. lv_coord_t max_height = LV_VER_RES * 50 / 100; // 50% of screen height
  562. // Calculate zoom factor to fit within maximum dimensions
  563. lv_coord_t img_width = copied_img_dsc->header.w;
  564. lv_coord_t img_height = copied_img_dsc->header.h;
  565. lv_coord_t zoom_w = (max_width * 256) / img_width;
  566. lv_coord_t zoom_h = (max_height * 256) / img_height;
  567. lv_coord_t zoom = (zoom_w < zoom_h) ? zoom_w : zoom_h;
  568. // Ensure zoom doesn't exceed 256 (100%)
  569. if (zoom > 256) zoom = 256;
  570. // Set image properties
  571. lv_image_set_src(preview_image, copied_img_dsc);
  572. lv_image_set_scale(preview_image, zoom);
  573. // Add event handler to clean up copied data when image is deleted
  574. lv_obj_add_event_cb(preview_image, [](lv_event_t* e) {
  575. lv_img_dsc_t* copied_img_dsc = (lv_img_dsc_t*)lv_event_get_user_data(e);
  576. if (copied_img_dsc != nullptr) {
  577. heap_caps_free((void*)copied_img_dsc->data);
  578. heap_caps_free(copied_img_dsc);
  579. }
  580. }, LV_EVENT_DELETE, (void*)copied_img_dsc);
  581. // Calculate actual scaled image dimensions
  582. lv_coord_t scaled_width = (img_width * zoom) / 256;
  583. lv_coord_t scaled_height = (img_height * zoom) / 256;
  584. // Set bubble size to be 16 pixels larger than the image (8 pixels on each side)
  585. lv_obj_set_width(img_bubble, scaled_width + 16);
  586. lv_obj_set_height(img_bubble, scaled_height + 16);
  587. // Don't grow in flex layout
  588. lv_obj_set_style_flex_grow(img_bubble, 0, 0);
  589. // Center the image within the bubble
  590. lv_obj_center(preview_image);
  591. // Left align the image bubble like assistant messages
  592. lv_obj_align(img_bubble, LV_ALIGN_LEFT_MID, 0, 0);
  593. // Auto-scroll to the image bubble
  594. lv_obj_scroll_to_view_recursive(img_bubble, LV_ANIM_ON);
  595. }
  596. }
  597. #else
  598. void LcdDisplay::SetupUI() {
  599. DisplayLockGuard lock(this);
  600. auto screen = lv_screen_active();
  601. lv_obj_set_style_text_font(screen, fonts_.text_font, 0);
  602. lv_obj_set_style_text_color(screen, current_theme_.text, 0);
  603. lv_obj_set_style_bg_color(screen, current_theme_.background, 0);
  604. /* Container */
  605. container_ = lv_obj_create(screen);
  606. lv_obj_set_size(container_, LV_HOR_RES, LV_VER_RES);
  607. lv_obj_set_flex_flow(container_, LV_FLEX_FLOW_COLUMN);
  608. lv_obj_set_style_pad_all(container_, 0, 0);
  609. lv_obj_set_style_border_width(container_, 0, 0);
  610. lv_obj_set_style_pad_row(container_, 0, 0);
  611. lv_obj_set_style_bg_color(container_, current_theme_.background, 0);
  612. lv_obj_set_style_border_color(container_, current_theme_.border, 0);
  613. /* Status bar */
  614. status_bar_ = lv_obj_create(container_);
  615. lv_obj_set_size(status_bar_, LV_HOR_RES, fonts_.text_font->line_height);
  616. lv_obj_set_style_radius(status_bar_, 0, 0);
  617. lv_obj_set_style_bg_color(status_bar_, current_theme_.background, 0);
  618. lv_obj_set_style_text_color(status_bar_, current_theme_.text, 0);
  619. /* Content */
  620. content_ = lv_obj_create(container_);
  621. lv_obj_set_scrollbar_mode(content_, LV_SCROLLBAR_MODE_OFF);
  622. lv_obj_set_style_radius(content_, 0, 0);
  623. lv_obj_set_width(content_, LV_HOR_RES);
  624. lv_obj_set_flex_grow(content_, 1);
  625. lv_obj_set_style_pad_all(content_, 5, 0);
  626. lv_obj_set_style_bg_color(content_, current_theme_.chat_background, 0);
  627. lv_obj_set_style_border_color(content_, current_theme_.border, 0); // Border color for content
  628. lv_obj_set_flex_flow(content_, LV_FLEX_FLOW_COLUMN); // 垂直布局(从上到下)
  629. lv_obj_set_flex_align(content_, LV_FLEX_ALIGN_CENTER, LV_FLEX_ALIGN_CENTER, LV_FLEX_ALIGN_SPACE_EVENLY); // 子对象居中对齐,等距分布
  630. emotion_label_ = lv_label_create(content_);
  631. lv_obj_set_style_text_font(emotion_label_, &font_awesome_30_4, 0);
  632. lv_obj_set_style_text_color(emotion_label_, current_theme_.text, 0);
  633. lv_label_set_text(emotion_label_, FONT_AWESOME_AI_CHIP);
  634. preview_image_ = lv_image_create(content_);
  635. lv_obj_set_size(preview_image_, width_ * 0.5, height_ * 0.5);
  636. lv_obj_align(preview_image_, LV_ALIGN_CENTER, 0, 0);
  637. lv_obj_add_flag(preview_image_, LV_OBJ_FLAG_HIDDEN);
  638. chat_message_label_ = lv_label_create(content_);
  639. lv_label_set_text(chat_message_label_, "");
  640. lv_obj_set_width(chat_message_label_, LV_HOR_RES * 0.9); // 限制宽度为屏幕宽度的 90%
  641. lv_label_set_long_mode(chat_message_label_, LV_LABEL_LONG_WRAP); // 设置为自动换行模式
  642. lv_obj_set_style_text_align(chat_message_label_, LV_TEXT_ALIGN_CENTER, 0); // 设置文本居中对齐
  643. lv_obj_set_style_text_color(chat_message_label_, current_theme_.text, 0);
  644. /* Status bar */
  645. lv_obj_set_flex_flow(status_bar_, LV_FLEX_FLOW_ROW);
  646. lv_obj_set_style_pad_all(status_bar_, 0, 0);
  647. lv_obj_set_style_border_width(status_bar_, 0, 0);
  648. lv_obj_set_style_pad_column(status_bar_, 0, 0);
  649. lv_obj_set_style_pad_left(status_bar_, 2, 0);
  650. lv_obj_set_style_pad_right(status_bar_, 2, 0);
  651. network_label_ = lv_label_create(status_bar_);
  652. lv_label_set_text(network_label_, "");
  653. lv_obj_set_style_text_font(network_label_, fonts_.icon_font, 0);
  654. lv_obj_set_style_text_color(network_label_, current_theme_.text, 0);
  655. notification_label_ = lv_label_create(status_bar_);
  656. lv_obj_set_flex_grow(notification_label_, 1);
  657. lv_obj_set_style_text_align(notification_label_, LV_TEXT_ALIGN_CENTER, 0);
  658. lv_obj_set_style_text_color(notification_label_, current_theme_.text, 0);
  659. lv_label_set_text(notification_label_, "");
  660. lv_obj_add_flag(notification_label_, LV_OBJ_FLAG_HIDDEN);
  661. status_label_ = lv_label_create(status_bar_);
  662. lv_obj_set_flex_grow(status_label_, 1);
  663. lv_label_set_long_mode(status_label_, LV_LABEL_LONG_SCROLL_CIRCULAR);
  664. lv_obj_set_style_text_align(status_label_, LV_TEXT_ALIGN_CENTER, 0);
  665. lv_obj_set_style_text_color(status_label_, current_theme_.text, 0);
  666. lv_label_set_text(status_label_, Lang::Strings::INITIALIZING);
  667. mute_label_ = lv_label_create(status_bar_);
  668. lv_label_set_text(mute_label_, "");
  669. lv_obj_set_style_text_font(mute_label_, fonts_.icon_font, 0);
  670. lv_obj_set_style_text_color(mute_label_, current_theme_.text, 0);
  671. battery_label_ = lv_label_create(status_bar_);
  672. lv_label_set_text(battery_label_, "");
  673. lv_obj_set_style_text_font(battery_label_, fonts_.icon_font, 0);
  674. lv_obj_set_style_text_color(battery_label_, current_theme_.text, 0);
  675. low_battery_popup_ = lv_obj_create(screen);
  676. lv_obj_set_scrollbar_mode(low_battery_popup_, LV_SCROLLBAR_MODE_OFF);
  677. lv_obj_set_size(low_battery_popup_, LV_HOR_RES * 0.9, fonts_.text_font->line_height * 2);
  678. lv_obj_align(low_battery_popup_, LV_ALIGN_BOTTOM_MID, 0, 0);
  679. lv_obj_set_style_bg_color(low_battery_popup_, current_theme_.low_battery, 0);
  680. lv_obj_set_style_radius(low_battery_popup_, 10, 0);
  681. low_battery_label_ = lv_label_create(low_battery_popup_);
  682. lv_label_set_text(low_battery_label_, Lang::Strings::BATTERY_NEED_CHARGE);
  683. lv_obj_set_style_text_color(low_battery_label_, lv_color_white(), 0);
  684. lv_obj_center(low_battery_label_);
  685. lv_obj_add_flag(low_battery_popup_, LV_OBJ_FLAG_HIDDEN);
  686. }
  687. void LcdDisplay::SetPreviewImage(const lv_img_dsc_t* img_dsc) {
  688. DisplayLockGuard lock(this);
  689. if (preview_image_ == nullptr) {
  690. return;
  691. }
  692. if (img_dsc != nullptr) {
  693. // zoom factor 0.5
  694. lv_image_set_scale(preview_image_, 128 * width_ / img_dsc->header.w);
  695. // 设置图片源并显示预览图片
  696. lv_image_set_src(preview_image_, img_dsc);
  697. lv_obj_clear_flag(preview_image_, LV_OBJ_FLAG_HIDDEN);
  698. // 隐藏emotion_label_
  699. if (emotion_label_ != nullptr) {
  700. lv_obj_add_flag(emotion_label_, LV_OBJ_FLAG_HIDDEN);
  701. }
  702. } else {
  703. // 隐藏预览图片并显示emotion_label_
  704. lv_obj_add_flag(preview_image_, LV_OBJ_FLAG_HIDDEN);
  705. if (emotion_label_ != nullptr) {
  706. lv_obj_clear_flag(emotion_label_, LV_OBJ_FLAG_HIDDEN);
  707. }
  708. }
  709. }
  710. #endif
  711. void LcdDisplay::SetEmotion(const char* emotion) {
  712. struct Emotion {
  713. const char* icon;
  714. const char* text;
  715. };
  716. static const std::vector<Emotion> emotions = {
  717. {"😶", "neutral"},
  718. {"🙂", "happy"},
  719. {"😆", "laughing"},
  720. {"😂", "funny"},
  721. {"😔", "sad"},
  722. {"😠", "angry"},
  723. {"😭", "crying"},
  724. {"😍", "loving"},
  725. {"😳", "embarrassed"},
  726. {"😯", "surprised"},
  727. {"😱", "shocked"},
  728. {"🤔", "thinking"},
  729. {"😉", "winking"},
  730. {"😎", "cool"},
  731. {"😌", "relaxed"},
  732. {"🤤", "delicious"},
  733. {"😘", "kissy"},
  734. {"😏", "confident"},
  735. {"😴", "sleepy"},
  736. {"😜", "silly"},
  737. {"🙄", "confused"}
  738. };
  739. // 查找匹配的表情
  740. std::string_view emotion_view(emotion);
  741. auto it = std::find_if(emotions.begin(), emotions.end(),
  742. [&emotion_view](const Emotion& e) { return e.text == emotion_view; });
  743. DisplayLockGuard lock(this);
  744. if (emotion_label_ == nullptr) {
  745. return;
  746. }
  747. // 如果找到匹配的表情就显示对应图标,否则显示默认的neutral表情
  748. lv_obj_set_style_text_font(emotion_label_, fonts_.emoji_font, 0);
  749. if (it != emotions.end()) {
  750. lv_label_set_text(emotion_label_, it->icon);
  751. } else {
  752. lv_label_set_text(emotion_label_, "😶");
  753. }
  754. #if !CONFIG_USE_WECHAT_MESSAGE_STYLE
  755. // 显示emotion_label_,隐藏preview_image_
  756. lv_obj_clear_flag(emotion_label_, LV_OBJ_FLAG_HIDDEN);
  757. if (preview_image_ != nullptr) {
  758. lv_obj_add_flag(preview_image_, LV_OBJ_FLAG_HIDDEN);
  759. }
  760. #endif
  761. }
  762. void LcdDisplay::SetIcon(const char* icon) {
  763. DisplayLockGuard lock(this);
  764. if (emotion_label_ == nullptr) {
  765. return;
  766. }
  767. lv_obj_set_style_text_font(emotion_label_, &font_awesome_30_4, 0);
  768. lv_label_set_text(emotion_label_, icon);
  769. #if !CONFIG_USE_WECHAT_MESSAGE_STYLE
  770. // 显示emotion_label_,隐藏preview_image_
  771. lv_obj_clear_flag(emotion_label_, LV_OBJ_FLAG_HIDDEN);
  772. if (preview_image_ != nullptr) {
  773. lv_obj_add_flag(preview_image_, LV_OBJ_FLAG_HIDDEN);
  774. }
  775. #endif
  776. }
  777. void LcdDisplay::SetTheme(const std::string& theme_name) {
  778. DisplayLockGuard lock(this);
  779. if (theme_name == "dark" || theme_name == "DARK") {
  780. current_theme_ = DARK_THEME;
  781. } else if (theme_name == "light" || theme_name == "LIGHT") {
  782. current_theme_ = LIGHT_THEME;
  783. } else {
  784. // Invalid theme name, return false
  785. ESP_LOGE(TAG, "Invalid theme name: %s", theme_name.c_str());
  786. return;
  787. }
  788. // Get the active screen
  789. lv_obj_t* screen = lv_screen_active();
  790. // Update the screen colors
  791. lv_obj_set_style_bg_color(screen, current_theme_.background, 0);
  792. lv_obj_set_style_text_color(screen, current_theme_.text, 0);
  793. // Update container colors
  794. if (container_ != nullptr) {
  795. lv_obj_set_style_bg_color(container_, current_theme_.background, 0);
  796. lv_obj_set_style_border_color(container_, current_theme_.border, 0);
  797. }
  798. // Update status bar colors
  799. if (status_bar_ != nullptr) {
  800. lv_obj_set_style_bg_color(status_bar_, current_theme_.background, 0);
  801. lv_obj_set_style_text_color(status_bar_, current_theme_.text, 0);
  802. // Update status bar elements
  803. if (network_label_ != nullptr) {
  804. lv_obj_set_style_text_color(network_label_, current_theme_.text, 0);
  805. }
  806. if (status_label_ != nullptr) {
  807. lv_obj_set_style_text_color(status_label_, current_theme_.text, 0);
  808. }
  809. if (notification_label_ != nullptr) {
  810. lv_obj_set_style_text_color(notification_label_, current_theme_.text, 0);
  811. }
  812. if (mute_label_ != nullptr) {
  813. lv_obj_set_style_text_color(mute_label_, current_theme_.text, 0);
  814. }
  815. if (battery_label_ != nullptr) {
  816. lv_obj_set_style_text_color(battery_label_, current_theme_.text, 0);
  817. }
  818. if (emotion_label_ != nullptr) {
  819. lv_obj_set_style_text_color(emotion_label_, current_theme_.text, 0);
  820. }
  821. }
  822. // Update content area colors
  823. if (content_ != nullptr) {
  824. lv_obj_set_style_bg_color(content_, current_theme_.chat_background, 0);
  825. lv_obj_set_style_border_color(content_, current_theme_.border, 0);
  826. // If we have the chat message style, update all message bubbles
  827. #if CONFIG_USE_WECHAT_MESSAGE_STYLE
  828. // Iterate through all children of content (message containers or bubbles)
  829. uint32_t child_count = lv_obj_get_child_cnt(content_);
  830. for (uint32_t i = 0; i < child_count; i++) {
  831. lv_obj_t* obj = lv_obj_get_child(content_, i);
  832. if (obj == nullptr) continue;
  833. lv_obj_t* bubble = nullptr;
  834. // 检查这个对象是容器还是气泡
  835. // 如果是容器(用户或系统消息),则获取其子对象作为气泡
  836. // 如果是气泡(助手消息),则直接使用
  837. if (lv_obj_get_child_cnt(obj) > 0) {
  838. // 可能是容器,检查它是否为用户或系统消息容器
  839. // 用户和系统消息容器是透明的
  840. lv_opa_t bg_opa = lv_obj_get_style_bg_opa(obj, 0);
  841. if (bg_opa == LV_OPA_TRANSP) {
  842. // 这是用户或系统消息的容器
  843. bubble = lv_obj_get_child(obj, 0);
  844. } else {
  845. // 这可能是助手消息的气泡自身
  846. bubble = obj;
  847. }
  848. } else {
  849. // 没有子元素,可能是其他UI元素,跳过
  850. continue;
  851. }
  852. if (bubble == nullptr) continue;
  853. // 使用保存的用户数据来识别气泡类型
  854. void* bubble_type_ptr = lv_obj_get_user_data(bubble);
  855. if (bubble_type_ptr != nullptr) {
  856. const char* bubble_type = static_cast<const char*>(bubble_type_ptr);
  857. // 根据气泡类型应用正确的颜色
  858. if (strcmp(bubble_type, "user") == 0) {
  859. lv_obj_set_style_bg_color(bubble, current_theme_.user_bubble, 0);
  860. } else if (strcmp(bubble_type, "assistant") == 0) {
  861. lv_obj_set_style_bg_color(bubble, current_theme_.assistant_bubble, 0);
  862. } else if (strcmp(bubble_type, "system") == 0) {
  863. lv_obj_set_style_bg_color(bubble, current_theme_.system_bubble, 0);
  864. } else if (strcmp(bubble_type, "image") == 0) {
  865. lv_obj_set_style_bg_color(bubble, current_theme_.system_bubble, 0);
  866. }
  867. // Update border color
  868. lv_obj_set_style_border_color(bubble, current_theme_.border, 0);
  869. // Update text color for the message
  870. if (lv_obj_get_child_cnt(bubble) > 0) {
  871. lv_obj_t* text = lv_obj_get_child(bubble, 0);
  872. if (text != nullptr) {
  873. // 根据气泡类型设置文本颜色
  874. if (strcmp(bubble_type, "system") == 0) {
  875. lv_obj_set_style_text_color(text, current_theme_.system_text, 0);
  876. } else {
  877. lv_obj_set_style_text_color(text, current_theme_.text, 0);
  878. }
  879. }
  880. }
  881. } else {
  882. // 如果没有标记,回退到之前的逻辑(颜色比较)
  883. // ...保留原有的回退逻辑...
  884. lv_color_t bg_color = lv_obj_get_style_bg_color(bubble, 0);
  885. // 改进bubble类型检测逻辑,不仅使用颜色比较
  886. bool is_user_bubble = false;
  887. bool is_assistant_bubble = false;
  888. bool is_system_bubble = false;
  889. // 检查用户bubble
  890. if (lv_color_eq(bg_color, DARK_USER_BUBBLE_COLOR) ||
  891. lv_color_eq(bg_color, LIGHT_USER_BUBBLE_COLOR) ||
  892. lv_color_eq(bg_color, current_theme_.user_bubble)) {
  893. is_user_bubble = true;
  894. }
  895. // 检查系统bubble
  896. else if (lv_color_eq(bg_color, DARK_SYSTEM_BUBBLE_COLOR) ||
  897. lv_color_eq(bg_color, LIGHT_SYSTEM_BUBBLE_COLOR) ||
  898. lv_color_eq(bg_color, current_theme_.system_bubble)) {
  899. is_system_bubble = true;
  900. }
  901. // 剩余的都当作助手bubble处理
  902. else {
  903. is_assistant_bubble = true;
  904. }
  905. // 根据bubble类型应用正确的颜色
  906. if (is_user_bubble) {
  907. lv_obj_set_style_bg_color(bubble, current_theme_.user_bubble, 0);
  908. } else if (is_assistant_bubble) {
  909. lv_obj_set_style_bg_color(bubble, current_theme_.assistant_bubble, 0);
  910. } else if (is_system_bubble) {
  911. lv_obj_set_style_bg_color(bubble, current_theme_.system_bubble, 0);
  912. }
  913. // Update border color
  914. lv_obj_set_style_border_color(bubble, current_theme_.border, 0);
  915. // Update text color for the message
  916. if (lv_obj_get_child_cnt(bubble) > 0) {
  917. lv_obj_t* text = lv_obj_get_child(bubble, 0);
  918. if (text != nullptr) {
  919. // 回退到颜色检测逻辑
  920. if (lv_color_eq(bg_color, current_theme_.system_bubble) ||
  921. lv_color_eq(bg_color, DARK_SYSTEM_BUBBLE_COLOR) ||
  922. lv_color_eq(bg_color, LIGHT_SYSTEM_BUBBLE_COLOR)) {
  923. lv_obj_set_style_text_color(text, current_theme_.system_text, 0);
  924. } else {
  925. lv_obj_set_style_text_color(text, current_theme_.text, 0);
  926. }
  927. }
  928. }
  929. }
  930. }
  931. #else
  932. // Simple UI mode - just update the main chat message
  933. if (chat_message_label_ != nullptr) {
  934. lv_obj_set_style_text_color(chat_message_label_, current_theme_.text, 0);
  935. }
  936. if (emotion_label_ != nullptr) {
  937. lv_obj_set_style_text_color(emotion_label_, current_theme_.text, 0);
  938. }
  939. #endif
  940. }
  941. // Update low battery popup
  942. if (low_battery_popup_ != nullptr) {
  943. lv_obj_set_style_bg_color(low_battery_popup_, current_theme_.low_battery, 0);
  944. }
  945. // No errors occurred. Save theme to settings
  946. Display::SetTheme(theme_name);
  947. }
  948. void LcdDisplay::SetupHomeScreen() {
  949. DisplayLockGuard lock(this);
  950. auto screen = lv_screen_active();
  951. // 创建主界面容器
  952. home_container_ = lv_obj_create(screen);
  953. lv_obj_set_size(home_container_, LV_HOR_RES, LV_VER_RES);
  954. lv_obj_set_style_bg_color(home_container_, lv_color_black(), 0);
  955. lv_obj_set_style_border_width(home_container_, 0, 0);
  956. lv_obj_set_style_pad_all(home_container_, 0, 0);
  957. lv_obj_set_style_radius(home_container_, 0, 0);
  958. // 创建时间数字图片容器
  959. timer_container_ = lv_obj_create(home_container_);
  960. lv_obj_set_size(timer_container_, 190, 85); // 调整宽度为156pt
  961. lv_obj_set_pos(timer_container_, 32, 62); // 调整x位置为42pt保持居中
  962. // 调试:为容器设置白色边框,方便观察边界
  963. lv_obj_set_style_border_color(timer_container_, lv_color_white(), 0);
  964. lv_obj_set_style_border_width(timer_container_, 1, 0); // 1像素边框
  965. lv_obj_set_style_border_opa(timer_container_, LV_OPA_COVER, 0);
  966. // 保持背景透明以便观察
  967. lv_obj_set_style_bg_opa(timer_container_, LV_OPA_TRANSP, 0);
  968. // 初始化时间数字图片对象(5个字符:HH:MM)
  969. // 位置计算:每个数字间隔8pt
  970. int positions[5] = {0, 35, 70, 94, 129}; // 0, 27+8, 70, 70+16+8, 129
  971. for (int i = 0; i <5; i++) {
  972. time_digits_[i] = lv_image_create(timer_container_);
  973. if (i == 2) { // 冒号位置
  974. lv_obj_set_size(time_digits_[i], 16, 45); // 冒号尺寸16×45pt
  975. // 垂直居中调整:数字高度47pt,冒号高度45pt,需要向下偏移1pt
  976. lv_obj_set_pos(time_digits_[i], positions[i], 11);
  977. } else {
  978. lv_obj_set_size(time_digits_[i], 27, 47); // 数字尺寸27×47pt
  979. lv_obj_set_pos(time_digits_[i], positions[i], 10);
  980. }
  981. }
  982. //太阳图片 - 坐标(88pt, 170pt),尺寸64pt×68pt
  983. lv_obj_t* sun_image = lv_image_create(home_container_);
  984. lv_obj_set_size(sun_image, 64, 68);
  985. lv_obj_set_pos(sun_image, 88, 170);
  986. lv_image_set_src(sun_image, &sun);
  987. lv_obj_add_flag(home_container_, LV_OBJ_FLAG_HIDDEN);
  988. }
  989. void LcdDisplay::ShowHomeScreen() {
  990. DisplayLockGuard lock(this);
  991. // 清理倒计时界面
  992. CleanupCountdownScreen();
  993. if (home_container_ == nullptr) {
  994. SetupHomeScreen();
  995. }
  996. // 隐藏原有UI
  997. if (container_ != nullptr) {
  998. lv_obj_add_flag(container_, LV_OBJ_FLAG_HIDDEN);
  999. }
  1000. // 显示主界面
  1001. lv_obj_clear_flag(home_container_, LV_OBJ_FLAG_HIDDEN);
  1002. is_home_screen_active_ = true;
  1003. // 立即更新时间显示
  1004. UpdateHomeTime();
  1005. }
  1006. // 添加清理函数
  1007. void LcdDisplay::CleanupCountdownScreen() {
  1008. if (countdown_container_ != nullptr) {
  1009. // 安全地删除倒计时容器
  1010. if (lv_obj_is_valid(countdown_container_)) {
  1011. lv_obj_del(countdown_container_);
  1012. }
  1013. countdown_container_ = nullptr;
  1014. countdown_label_ = nullptr;
  1015. countdown_circle_ = nullptr;
  1016. }
  1017. }
  1018. void LcdDisplay::HideHomeScreen() {
  1019. DisplayLockGuard lock(this);
  1020. // 隐藏主界面
  1021. if (home_container_ != nullptr) {
  1022. lv_obj_add_flag(home_container_, LV_OBJ_FLAG_HIDDEN);
  1023. }
  1024. // 显示原有UI
  1025. if (container_ != nullptr) {
  1026. lv_obj_clear_flag(container_, LV_OBJ_FLAG_HIDDEN);
  1027. }
  1028. is_home_screen_active_ = false;
  1029. }
  1030. void LcdDisplay::UpdateHomeTime() {
  1031. if (!is_home_screen_active_ || timer_container_ == nullptr) {
  1032. return;
  1033. }
  1034. DisplayLockGuard lock(this);
  1035. // 获取当前时间
  1036. time_t now = time(nullptr);
  1037. struct tm* timeinfo = localtime(&now);
  1038. // 检查系统时间是否已设置
  1039. // if (timeinfo->tm_year < (2025 - 1900)) {
  1040. // for (int i = 0; i < 5; i++) {
  1041. // lv_image_set_src(time_digits_[i], &number0);
  1042. // }
  1043. // if (date_label_ != nullptr) {
  1044. // lv_label_set_text(date_label_, "时间未设置");
  1045. // }
  1046. // return;
  1047. // }
  1048. // 更新时间数字图片
  1049. char time_str[6];
  1050. strftime(time_str, sizeof(time_str), "%H:%M", timeinfo);
  1051. for (int i = 0; i < 5; i++) {
  1052. char c = time_str[i];
  1053. const lv_img_dsc_t* digit_img = nullptr;
  1054. switch (c) {
  1055. case '0': digit_img = &number0; break;
  1056. case '1': digit_img = &number1; break;
  1057. case '2': digit_img = &number2; break;
  1058. case '3': digit_img = &number3; break;
  1059. case '4': digit_img = &number4; break;
  1060. case '5': digit_img = &number5; break;
  1061. case '6': digit_img = &number6; break;
  1062. case '7': digit_img = &number7; break;
  1063. case '8': digit_img = &number8; break;
  1064. case '9': digit_img = &number9; break;
  1065. case ':': digit_img = &timer_inside; break;
  1066. default: digit_img = &number0; break;
  1067. }
  1068. if (digit_img != nullptr && time_digits_[i] != nullptr) {
  1069. lv_image_set_src(time_digits_[i], digit_img);
  1070. }
  1071. }
  1072. // 更新日期显示
  1073. // if (date_label_ != nullptr) {
  1074. // char date_str[50];
  1075. // strftime(date_str, sizeof(date_str), "%Y/%m/%d", timeinfo);
  1076. // const char* weekdays[] = {"星期日", "星期一", "星期二", "星期三", "星期四", "星期五", "星期六"};
  1077. // int weekday = timeinfo->tm_wday;
  1078. // if (weekday >= 0 && weekday < 7) {
  1079. // strcat(date_str, " ");
  1080. // strcat(date_str, weekdays[weekday]);
  1081. // }
  1082. // lv_label_set_text(date_label_, date_str);
  1083. // }
  1084. }
  1085. void LcdDisplay::ShowCountdownScreen(int seconds, int rotation_angle) {
  1086. DisplayLockGuard lock(this);
  1087. // 隐藏其他界面
  1088. if (home_container_ != nullptr) {
  1089. lv_obj_add_flag(home_container_, LV_OBJ_FLAG_HIDDEN);
  1090. }
  1091. if (container_ != nullptr) {
  1092. lv_obj_add_flag(container_, LV_OBJ_FLAG_HIDDEN);
  1093. }
  1094. // 如果已经存在倒计时界面,先清除
  1095. if (countdown_container_ != nullptr) {
  1096. lv_obj_del(countdown_container_);
  1097. countdown_container_ = nullptr;
  1098. }
  1099. // 创建倒计时容器
  1100. countdown_container_ = lv_obj_create(lv_screen_active());
  1101. // 注意:不要直接设置容器的旋转,而是设置整个屏幕的旋转
  1102. // 这样坐标系统会更一致
  1103. lv_obj_set_size(countdown_container_, LV_HOR_RES, LV_VER_RES);
  1104. lv_obj_set_style_bg_color(countdown_container_, lv_color_black(), 0);
  1105. lv_obj_set_style_border_width(countdown_container_, 0, 0);
  1106. lv_obj_set_style_pad_all(countdown_container_, 0, 0);
  1107. lv_obj_set_style_radius(countdown_container_, 0, 0);
  1108. // 设置整个屏幕的旋转(通过显示器的旋转)
  1109. // 在LVGL中,可以通过lv_display_set_rotation来设置整个显示的旋转
  1110. // 但需要注意的是,这会影响到所有后续创建的UI元素
  1111. // 更好的方法是:使用独立的容器并设置其旋转
  1112. // 计算圆弧大小
  1113. lv_coord_t screen_width = LV_HOR_RES;
  1114. lv_coord_t screen_height = LV_VER_RES;
  1115. lv_coord_t min_dimension = (screen_width < screen_height) ? screen_width : screen_height;
  1116. lv_coord_t arc_size = min_dimension * 7 / 10;
  1117. // 创建圆形进度条
  1118. countdown_circle_ = lv_arc_create(countdown_container_);
  1119. lv_obj_set_size(countdown_circle_, arc_size, arc_size);
  1120. // 根据旋转角度调整圆弧的位置
  1121. switch (rotation_angle) {
  1122. case 90: // 顺时针旋转90度
  1123. lv_obj_align(countdown_circle_, LV_ALIGN_CENTER, 0, 0);
  1124. break;
  1125. case 180: // 顺时针旋转180度
  1126. lv_obj_align(countdown_circle_, LV_ALIGN_CENTER, 0, 0);
  1127. break;
  1128. case 270: // 顺时针旋转270度
  1129. lv_obj_align(countdown_circle_, LV_ALIGN_CENTER, 0, 0);
  1130. break;
  1131. default: // 0度
  1132. lv_obj_align(countdown_circle_, LV_ALIGN_CENTER, 0, 0);
  1133. break;
  1134. }
  1135. // 设置圆弧属性
  1136. lv_arc_set_rotation(countdown_circle_, 270);
  1137. lv_arc_set_bg_angles(countdown_circle_, 0, 360);
  1138. lv_arc_set_value(countdown_circle_, 100);
  1139. lv_obj_set_style_arc_width(countdown_circle_, 12, 0);
  1140. lv_obj_set_style_arc_color(countdown_circle_, lv_color_hex(0x1A6C37), 0);
  1141. lv_obj_set_style_bg_color(countdown_circle_, lv_color_black(), 0);
  1142. lv_obj_set_style_border_width(countdown_circle_, 0, 0);
  1143. // 创建倒计时数字标签
  1144. countdown_label_ = lv_label_create(countdown_container_);
  1145. lv_obj_set_style_text_font(countdown_label_, fonts_.text_font, 0);
  1146. lv_obj_set_style_text_color(countdown_label_, lv_color_white(), 0);
  1147. lv_obj_align(countdown_label_, LV_ALIGN_CENTER, 0, 0);
  1148. // 设置整个容器的旋转
  1149. // 注意:这里设置旋转中心为容器中心
  1150. lv_obj_set_style_transform_pivot_x(countdown_container_, screen_width / 2, 0);
  1151. lv_obj_set_style_transform_pivot_y(countdown_container_, screen_height / 2, 0);
  1152. lv_obj_set_style_transform_rotation(countdown_container_, rotation_angle * 10, 0); // LVGL使用deci-degrees
  1153. // 初始更新显示
  1154. UpdateCountdown(seconds);
  1155. }
  1156. void LcdDisplay::UpdateCountdown(int seconds) {
  1157. if (countdown_label_ == nullptr || countdown_circle_ == nullptr) {
  1158. return;
  1159. }
  1160. DisplayLockGuard lock(this);
  1161. // 格式化时间显示:MM:SS
  1162. int minutes = seconds / 60;
  1163. int remaining_seconds = seconds % 60;
  1164. char time_str[16];
  1165. snprintf(time_str, sizeof(time_str), "%02d:%02d", minutes, remaining_seconds);
  1166. lv_label_set_text(countdown_label_, time_str);
  1167. // 计算进度百分比
  1168. int total_seconds = 0;
  1169. DeviceState current_state = Application::GetInstance().GetDeviceState();
  1170. // 根据当前状态确定总时间
  1171. switch (current_state) {
  1172. case kDeviceStateCountdown10Min:
  1173. total_seconds = 10 * 60; // 600秒
  1174. break;
  1175. case kDeviceStateCountdown15Min:
  1176. total_seconds = 15 * 60; // 900秒
  1177. break;
  1178. case kDeviceStateCountdown20Min:
  1179. total_seconds = 20 * 60; // 1200秒
  1180. break;
  1181. default:
  1182. // 如果不是倒计时状态,使用传入的seconds作为总时间
  1183. total_seconds = seconds;
  1184. break;
  1185. }
  1186. if (total_seconds > 0) {
  1187. // 计算进度(0-100)
  1188. int progress = (seconds * 100) / total_seconds;
  1189. lv_arc_set_value(countdown_circle_, progress);
  1190. // 根据剩余时间比例改变颜色
  1191. if (progress > 50) {
  1192. // 超过50%:绿色
  1193. lv_obj_set_style_arc_color(countdown_circle_, lv_color_hex(0x1A6C37), 0);
  1194. } else if (progress > 20) {
  1195. // 20%-50%:黄色
  1196. lv_obj_set_style_arc_color(countdown_circle_, lv_color_hex(0xFFA500), 0);
  1197. } else {
  1198. // 低于20%:红色
  1199. lv_obj_set_style_arc_color(countdown_circle_, lv_color_hex(0xFF0000), 0);
  1200. }
  1201. }
  1202. }
  1203. // 在LcdDisplay类中添加恢复屏幕方向的方法
  1204. void LcdDisplay::ResetScreenRotation() {
  1205. DisplayLockGuard lock(this);
  1206. if (countdown_container_ != nullptr) {
  1207. // 重置旋转
  1208. lv_obj_set_style_transform_rotation(countdown_container_, 0, 0);
  1209. lv_obj_set_size(countdown_container_, LV_HOR_RES, LV_VER_RES);
  1210. }
  1211. }
  1212. void LcdDisplay::ShowCountdownEndScreen() {
  1213. DisplayLockGuard lock(this);
  1214. // 清除倒计时界面
  1215. if (countdown_container_ != nullptr) {
  1216. lv_obj_del(countdown_label_);
  1217. lv_obj_del(countdown_circle_);
  1218. }
  1219. // 显示"计时完成"
  1220. lv_obj_t* complete_label = lv_label_create(countdown_container_);
  1221. lv_obj_set_style_text_font(complete_label, fonts_.text_font, 0);
  1222. lv_obj_set_style_text_color(complete_label, lv_color_hex(0x1A6C37), 0);
  1223. lv_obj_center(complete_label);
  1224. lv_label_set_text(complete_label, "计时完成!");
  1225. }