@ -28,30 +28,30 @@ void LcdDisplay::InitializeLcdThemes() {
// light theme
auto light_theme = new LvglTheme ( " light " ) ;
light_theme - > set_background_color ( lv_color_hex ( 0xFFFFFF ) ) ; //rgb(255, 255, 255)
light_theme - > set_text_color ( lv_color_hex ( 0x000000 ) ) ; //rgb(0, 0, 0)
light_theme - > set_chat_background_color ( lv_color_hex ( 0xE0E0E0 ) ) ; //rgb(224, 224, 224)
light_theme - > set_user_bubble_color ( lv_color_hex ( 0x00FF00 ) ) ; //rgb(0, 128, 0)
light_theme - > set_assistant_bubble_color ( lv_color_hex ( 0xDDDDDD ) ) ; //rgb(221, 221, 221)
light_theme - > set_system_bubble_color ( lv_color_hex ( 0xFFFFFF ) ) ; //rgb(255, 255, 255)
light_theme - > set_system_text_color ( lv_color_hex ( 0x000000 ) ) ; //rgb(0, 0, 0)
light_theme - > set_border_color ( lv_color_hex ( 0x000000 ) ) ; //rgb(0, 0, 0)
light_theme - > set_low_battery_color ( lv_color_hex ( 0x000000 ) ) ; //rgb(0, 0, 0)
light_theme - > set_background_color ( lv_color_hex ( 0xFFFFFF ) ) ;
light_theme - > set_text_color ( lv_color_hex ( 0x000000 ) ) ;
light_theme - > set_chat_background_color ( lv_color_hex ( 0xE0E0E0 ) ) ;
light_theme - > set_user_bubble_color ( lv_color_hex ( 0x00FF00 ) ) ;
light_theme - > set_assistant_bubble_color ( lv_color_hex ( 0xDDDDDD ) ) ;
light_theme - > set_system_bubble_color ( lv_color_hex ( 0xFFFFFF ) ) ;
light_theme - > set_system_text_color ( lv_color_hex ( 0x000000 ) ) ;
light_theme - > set_border_color ( lv_color_hex ( 0x000000 ) ) ;
light_theme - > set_low_battery_color ( lv_color_hex ( 0x000000 ) ) ;
light_theme - > set_text_font ( text_font ) ;
light_theme - > set_icon_font ( icon_font ) ;
light_theme - > set_large_icon_font ( large_icon_font ) ;
// dark theme
auto dark_theme = new LvglTheme ( " dark " ) ;
dark_theme - > set_background_color ( lv_color_hex ( 0x000000 ) ) ; //rgb(0, 0, 0)
dark_theme - > set_text_color ( lv_color_hex ( 0xFFFFFF ) ) ; //rgb(255, 255, 255)
dark_theme - > set_chat_background_color ( lv_color_hex ( 0x1F1F1F ) ) ; //rgb(31, 31, 31)
dark_theme - > set_user_bubble_color ( lv_color_hex ( 0x00FF00 ) ) ; //rgb(0, 128, 0)
dark_theme - > set_assistant_bubble_color ( lv_color_hex ( 0x222222 ) ) ; //rgb(34, 34, 34)
dark_theme - > set_system_bubble_color ( lv_color_hex ( 0x000000 ) ) ; //rgb(0, 0, 0)
dark_theme - > set_system_text_color ( lv_color_hex ( 0xFFFFFF ) ) ; //rgb(255, 255, 255)
dark_theme - > set_border_color ( lv_color_hex ( 0xFFFFFF ) ) ; //rgb(255, 255, 255)
dark_theme - > set_low_battery_color ( lv_color_hex ( 0xFF0000 ) ) ; //rgb(255, 0, 0)
dark_theme - > set_background_color ( lv_color_hex ( 0x000000 ) ) ;
dark_theme - > set_text_color ( lv_color_hex ( 0xFFFFFF ) ) ;
dark_theme - > set_chat_background_color ( lv_color_hex ( 0x1F1F1F ) ) ;
dark_theme - > set_user_bubble_color ( lv_color_hex ( 0x00FF00 ) ) ;
dark_theme - > set_assistant_bubble_color ( lv_color_hex ( 0x222222 ) ) ;
dark_theme - > set_system_bubble_color ( lv_color_hex ( 0x000000 ) ) ;
dark_theme - > set_system_text_color ( lv_color_hex ( 0xFFFFFF ) ) ;
dark_theme - > set_border_color ( lv_color_hex ( 0xFFFFFF ) ) ;
dark_theme - > set_low_battery_color ( lv_color_hex ( 0xFF0000 ) ) ;
dark_theme - > set_text_font ( text_font ) ;
dark_theme - > set_icon_font ( icon_font ) ;
dark_theme - > set_large_icon_font ( large_icon_font ) ;
@ -173,7 +173,7 @@ SpiLcdDisplay::SpiLcdDisplay(esp_lcd_panel_io_handle_t panel_io, esp_lcd_panel_h
}
// RGB LCD实现
// RGB LCD implementation
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 )
@ -320,9 +320,15 @@ LcdDisplay::~LcdDisplay() {
if ( content_ ! = nullptr ) {
lv_obj_del ( content_ ) ;
}
if ( bottom_bar_ ! = nullptr ) {
lv_obj_del ( bottom_bar_ ) ;
}
if ( status_bar_ ! = nullptr ) {
lv_obj_del ( status_bar_ ) ;
}
if ( top_bar_ ! = nullptr ) {
lv_obj_del ( top_bar_ ) ;
}
if ( side_bar_ ! = nullptr ) {
lv_obj_del ( side_bar_ ) ;
}
@ -374,12 +380,76 @@ void LcdDisplay::SetupUI() {
lv_obj_set_style_bg_color ( container_ , lvgl_theme - > background_color ( ) , 0 ) ;
lv_obj_set_style_border_color ( container_ , lvgl_theme - > border_color ( ) , 0 ) ;
/* S tatus bar */
status _bar_ = lv_obj_create ( container_ ) ;
/* Layer 1: Top bar - for s tatus icons */
top _bar_ = lv_obj_create ( container_ ) ;
lv_obj_set_size ( top_bar_ , LV_HOR_RES , LV_SIZE_CONTENT ) ;
lv_obj_set_style_radius ( top_bar_ , 0 , 0 ) ;
lv_obj_set_style_bg_opa ( top_bar_ , LV_OPA_50 , 0 ) ; // 50% opacity background
lv_obj_set_style_bg_color ( top_bar_ , lvgl_theme - > background_color ( ) , 0 ) ;
lv_obj_set_style_border_width ( top_bar_ , 0 , 0 ) ;
lv_obj_set_style_pad_all ( top_bar_ , 0 , 0 ) ;
lv_obj_set_style_pad_top ( top_bar_ , lvgl_theme - > spacing ( 2 ) , 0 ) ;
lv_obj_set_style_pad_bottom ( top_bar_ , lvgl_theme - > spacing ( 2 ) , 0 ) ;
lv_obj_set_style_pad_left ( top_bar_ , lvgl_theme - > spacing ( 4 ) , 0 ) ;
lv_obj_set_style_pad_right ( top_bar_ , lvgl_theme - > spacing ( 4 ) , 0 ) ;
lv_obj_set_flex_flow ( top_bar_ , LV_FLEX_FLOW_ROW ) ;
lv_obj_set_flex_align ( top_bar_ , LV_FLEX_ALIGN_SPACE_BETWEEN , LV_FLEX_ALIGN_CENTER , LV_FLEX_ALIGN_CENTER ) ;
lv_obj_set_scrollbar_mode ( top_bar_ , LV_SCROLLBAR_MODE_OFF ) ;
// Left icon
network_label_ = lv_label_create ( top_bar_ ) ;
lv_label_set_text ( network_label_ , " " ) ;
lv_obj_set_style_text_font ( network_label_ , icon_font , 0 ) ;
lv_obj_set_style_text_color ( network_label_ , lvgl_theme - > text_color ( ) , 0 ) ;
// Right icons container
lv_obj_t * right_icons = lv_obj_create ( top_bar_ ) ;
lv_obj_set_size ( right_icons , LV_SIZE_CONTENT , LV_SIZE_CONTENT ) ;
lv_obj_set_style_bg_opa ( right_icons , LV_OPA_TRANSP , 0 ) ;
lv_obj_set_style_border_width ( right_icons , 0 , 0 ) ;
lv_obj_set_style_pad_all ( right_icons , 0 , 0 ) ;
lv_obj_set_flex_flow ( right_icons , LV_FLEX_FLOW_ROW ) ;
lv_obj_set_flex_align ( right_icons , LV_FLEX_ALIGN_END , LV_FLEX_ALIGN_CENTER , LV_FLEX_ALIGN_CENTER ) ;
mute_label_ = lv_label_create ( right_icons ) ;
lv_label_set_text ( mute_label_ , " " ) ;
lv_obj_set_style_text_font ( mute_label_ , icon_font , 0 ) ;
lv_obj_set_style_text_color ( mute_label_ , lvgl_theme - > text_color ( ) , 0 ) ;
battery_label_ = lv_label_create ( right_icons ) ;
lv_label_set_text ( battery_label_ , " " ) ;
lv_obj_set_style_text_font ( battery_label_ , icon_font , 0 ) ;
lv_obj_set_style_text_color ( battery_label_ , lvgl_theme - > text_color ( ) , 0 ) ;
lv_obj_set_style_margin_left ( battery_label_ , lvgl_theme - > spacing ( 2 ) , 0 ) ;
/* Layer 2: Status bar - for center text labels */
status_bar_ = lv_obj_create ( screen ) ;
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_ , lvgl_theme - > background_color ( ) , 0 ) ;
lv_obj_set_style_text_color ( status_bar_ , lvgl_theme - > text_color ( ) , 0 ) ;
lv_obj_set_style_bg_opa ( status_bar_ , LV_OPA_TRANSP , 0 ) ; // Transparent background
lv_obj_set_style_border_width ( status_bar_ , 0 , 0 ) ;
lv_obj_set_style_pad_all ( status_bar_ , 0 , 0 ) ;
lv_obj_set_style_pad_top ( status_bar_ , lvgl_theme - > spacing ( 2 ) , 0 ) ;
lv_obj_set_style_pad_bottom ( status_bar_ , lvgl_theme - > spacing ( 2 ) , 0 ) ;
lv_obj_set_scrollbar_mode ( status_bar_ , LV_SCROLLBAR_MODE_OFF ) ;
lv_obj_set_style_layout ( status_bar_ , LV_LAYOUT_NONE , 0 ) ; // Use absolute positioning
lv_obj_align ( status_bar_ , LV_ALIGN_TOP_MID , 0 , 0 ) ; // Overlap with top_bar_
notification_label_ = lv_label_create ( status_bar_ ) ;
lv_obj_set_width ( notification_label_ , LV_HOR_RES * 0.8 ) ;
lv_obj_set_style_text_align ( notification_label_ , LV_TEXT_ALIGN_CENTER , 0 ) ;
lv_obj_set_style_text_color ( notification_label_ , lvgl_theme - > text_color ( ) , 0 ) ;
lv_label_set_text ( notification_label_ , " " ) ;
lv_obj_align ( notification_label_ , LV_ALIGN_CENTER , 0 , 0 ) ;
lv_obj_add_flag ( notification_label_ , LV_OBJ_FLAG_HIDDEN ) ;
status_label_ = lv_label_create ( status_bar_ ) ;
lv_obj_set_width ( status_label_ , LV_HOR_RES * 0.8 ) ;
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_ , lvgl_theme - > text_color ( ) , 0 ) ;
lv_label_set_text ( status_label_ , Lang : : Strings : : INITIALIZING ) ;
lv_obj_align ( status_label_ , LV_ALIGN_CENTER , 0 , 0 ) ;
/* Content - Chat area */
content_ = lv_obj_create ( container_ ) ;
@ -402,49 +472,6 @@ void LcdDisplay::SetupUI() {
// 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_top ( status_bar_ , lvgl_theme - > spacing ( 2 ) , 0 ) ;
lv_obj_set_style_pad_bottom ( status_bar_ , lvgl_theme - > spacing ( 2 ) , 0 ) ;
lv_obj_set_style_pad_left ( status_bar_ , lvgl_theme - > spacing ( 4 ) , 0 ) ;
lv_obj_set_style_pad_right ( status_bar_ , lvgl_theme - > spacing ( 4 ) , 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 ) ;
network_label_ = lv_label_create ( status_bar_ ) ;
lv_label_set_text ( network_label_ , " " ) ;
lv_obj_set_style_text_font ( network_label_ , icon_font , 0 ) ;
lv_obj_set_style_text_color ( network_label_ , lvgl_theme - > text_color ( ) , 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_ , lvgl_theme - > text_color ( ) , 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_ , lvgl_theme - > text_color ( ) , 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_ , icon_font , 0 ) ;
lv_obj_set_style_text_color ( mute_label_ , lvgl_theme - > text_color ( ) , 0 ) ;
battery_label_ = lv_label_create ( status_bar_ ) ;
lv_label_set_text ( battery_label_ , " " ) ;
lv_obj_set_style_text_font ( battery_label_ , icon_font , 0 ) ;
lv_obj_set_style_text_color ( battery_label_ , lvgl_theme - > text_color ( ) , 0 ) ;
lv_obj_set_style_margin_left ( battery_label_ , lvgl_theme - > spacing ( 2 ) , 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 , text_font - > line_height * 2 ) ;
@ -478,10 +505,10 @@ void LcdDisplay::SetChatMessage(const char* role, const char* content) {
return ;
}
// 检查消息数量是否超过限制
// Check if message count exceeds limit
uint32_t child_count = lv_obj_get_child_cnt ( content_ ) ;
if ( child_count > = MAX_MESSAGES ) {
// 删除最早的消息(第一个子对象)
// Delete the oldest message (first child object)
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 ) {
@ -493,30 +520,30 @@ void LcdDisplay::SetChatMessage(const char* role, const char* content) {
}
}
// 折叠系统消息(如果是系统消息,检查最后一个消息是否也是系统消息)
// Collapse system messages (if it's a system message, check if the last message is also a system message)
if ( strcmp ( role , " system " ) = = 0 ) {
if ( child_count > 0 ) {
// 获取最后一个消息容器
// Get the last message container
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 ) {
// 获取容器内的气泡
// Get the bubble inside the container
lv_obj_t * last_bubble = lv_obj_get_child ( last_container , 0 ) ;
if ( last_bubble ! = nullptr ) {
// 检查气泡类型是否为系统消息
// Check if bubble type is system message
void * bubble_type_ptr = lv_obj_get_user_data ( last_bubble ) ;
if ( bubble_type_ptr ! = nullptr & & strcmp ( ( const char * ) bubble_type_ptr , " system " ) = = 0 ) {
// 如果最后一个消息也是系统消息,则删除它
// If the last message is also a system message, delete it
lv_obj_del ( last_container ) ;
}
}
}
}
} else {
// 隐藏居中显示的 AI logo
// Hide the centered AI logo
lv_obj_add_flag ( emoji_label_ , LV_OBJ_FLAG_HIDDEN ) ;
}
//避免出现空的消息框
// Avoid empty message boxes
if ( strlen ( content ) = = 0 ) {
return ;
}
@ -535,31 +562,31 @@ void LcdDisplay::SetChatMessage(const char* role, const char* content) {
lv_obj_t * msg_text = lv_label_create ( msg_bubble ) ;
lv_label_set_text ( msg_text , content ) ;
// 计算文本实际宽度
// Calculate actual text width
lv_coord_t text_width = lv_txt_get_width ( content , strlen ( content ) , text_font , 0 ) ;
// 计算气泡宽度
lv_coord_t max_width = LV_HOR_RES * 85 / 100 - 16 ; // 屏幕宽度的85%
// Calculate bubble width
lv_coord_t max_width = LV_HOR_RES * 85 / 100 - 16 ; // 85% of screen width
lv_coord_t min_width = 20 ;
lv_coord_t bubble_width ;
// 确保文本宽度不小于最小宽度
// Ensure text width is not less than minimum width
if ( text_width < min_width ) {
text_width = min_width ;
}
// 如果文本宽度小于最大宽度,使用文本宽度
// If text width is less than max width, use text width
if ( text_width < max_width ) {
bubble_width = text_width ;
} else {
bubble_width = max_width ;
}
// 设置消息文本的宽度
lv_obj_set_width ( msg_text , bubble_width ) ; // 减去 padding
// Set message text width
lv_obj_set_width ( msg_text , bubble_width ) ; // Subtract padding
lv_label_set_long_mode ( msg_text , LV_LABEL_LONG_WRAP ) ;
// 设置气泡宽度
// Set bubble width
lv_obj_set_width ( msg_bubble , bubble_width ) ;
lv_obj_set_height ( msg_bubble , LV_SIZE_CONTENT ) ;
@ -571,7 +598,7 @@ void LcdDisplay::SetChatMessage(const char* role, const char* content) {
// Set text color for contrast
lv_obj_set_style_text_color ( msg_text , lvgl_theme - > text_color ( ) , 0 ) ;
// 设置自定义属性标记气泡类型
// Set custom attribute to mark bubble type
lv_obj_set_user_data ( msg_bubble , ( void * ) " user " ) ;
// Set appropriate width for content
@ -587,7 +614,7 @@ void LcdDisplay::SetChatMessage(const char* role, const char* content) {
// Set text color for contrast
lv_obj_set_style_text_color ( msg_text , lvgl_theme - > text_color ( ) , 0 ) ;
// 设置自定义属性标记气泡类型
// Set custom attribute to mark bubble type
lv_obj_set_user_data ( msg_bubble , ( void * ) " assistant " ) ;
// Set appropriate width for content
@ -603,7 +630,7 @@ void LcdDisplay::SetChatMessage(const char* role, const char* content) {
// Set text color for contrast
lv_obj_set_style_text_color ( msg_text , lvgl_theme - > system_text_color ( ) , 0 ) ;
// 设置自定义属性标记气泡类型
// Set custom attribute to mark bubble type
lv_obj_set_user_data ( msg_bubble , ( void * ) " system " ) ;
// Set appropriate width for content
@ -635,23 +662,17 @@ void LcdDisplay::SetChatMessage(const char* role, const char* content) {
// Auto-scroll to this container
lv_obj_scroll_to_view_recursive ( container , LV_ANIM_ON ) ;
} else if ( strcmp ( role , " system " ) = = 0 ) {
// 为系统消息创建全宽容器以确保居中对齐
// Create full-width container for system messages to ensure center alignment
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
@ -688,7 +709,7 @@ void LcdDisplay::SetPreviewImage(std::unique_ptr<LvglImage> image) {
lv_obj_set_style_bg_color ( img_bubble , lvgl_theme - > assistant_bubble_color ( ) , 0 ) ;
lv_obj_set_style_bg_opa ( img_bubble , LV_OPA_70 , 0 ) ;
// 设置自定义属性标记气泡类型
// Set custom attribute to mark bubble type
lv_obj_set_user_data ( img_bubble , ( void * ) " image " ) ;
// Create the image object inside the bubble
@ -721,11 +742,11 @@ void LcdDisplay::SetPreviewImage(std::unique_ptr<LvglImage> image) {
// Add event handler to clean up LvglImage when image is deleted
// We need to transfer ownership of the unique_ptr to the event callback
LvglImage * raw_image = image . release ( ) ; // 释放智能指针的所有权
LvglImage * raw_image = image . release ( ) ; // Release ownership of smart pointer
lv_obj_add_event_cb ( preview_image , [ ] ( lv_event_t * e ) {
LvglImage * img = ( LvglImage * ) lv_event_get_user_data ( e ) ;
if ( img ! = nullptr ) {
delete img ; // 通过删除 LvglImage 对象来正确释放内存
delete img ; // Properly release memory by deleting LvglImage object
}
} , LV_EVENT_DELETE , ( void * ) raw_image ) ;
@ -762,49 +783,22 @@ void LcdDisplay::SetupUI() {
lv_obj_set_style_text_color ( screen , lvgl_theme - > text_color ( ) , 0 ) ;
lv_obj_set_style_bg_color ( screen , lvgl_theme - > background_color ( ) , 0 ) ;
/* Container */
/* Container - used as background */
container_ = lv_obj_create ( screen ) ;
lv_obj_set_size ( container_ , LV_HOR_RES , LV_VER_RES ) ;
lv_obj_set_style_radius ( container_ , 0 , 0 ) ;
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_ , lvgl_theme - > background_color ( ) , 0 ) ;
lv_obj_set_style_border_color ( container_ , lvgl_theme - > border_color ( ) , 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_ , lvgl_theme - > background_color ( ) , 0 ) ;
lv_obj_set_style_text_color ( status_bar_ , lvgl_theme - > text_color ( ) , 0 ) ;
lv_obj_set_flex_flow ( status_bar_ , LV_FLEX_FLOW_ROW ) ;
lv_obj_set_style_pad_top ( status_bar_ , lvgl_theme - > spacing ( 2 ) , 0 ) ;
lv_obj_set_style_pad_bottom ( status_bar_ , lvgl_theme - > spacing ( 2 ) , 0 ) ;
lv_obj_set_style_pad_left ( status_bar_ , lvgl_theme - > spacing ( 4 ) , 0 ) ;
lv_obj_set_style_pad_right ( status_bar_ , lvgl_theme - > spacing ( 4 ) , 0 ) ;
lv_obj_set_style_border_width ( status_bar_ , 0 , 0 ) ;
lv_obj_set_style_pad_column ( status_bar_ , 0 , 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_ , 0 , 0 ) ;
lv_obj_set_style_border_width ( content_ , 0 , 0 ) ;
lv_obj_set_style_bg_color ( content_ , lvgl_theme - > chat_background_color ( ) , 0 ) ;
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 ) ; // 子对象居中对齐,等距分布
emoji_box_ = lv_obj_create ( content_ ) ;
/* Bottom layer: emoji_box_ - centered display */
emoji_box _ = lv_obj_create ( screen ) ;
lv_obj_set_size ( emoji_box_ , LV_SIZE_CONTENT , LV_SIZE_CONTENT ) ;
lv_obj_set_style_bg_opa ( emoji_box_ , LV_OPA_TRANSP , 0 ) ;
lv_obj_set_style_pad_all ( emoji_box_ , 0 , 0 ) ;
lv_obj_set_style_border_width ( emoji_box_ , 0 , 0 ) ;
lv_obj_align ( emoji_box_ , LV_ALIGN_CENTER , 0 , 0 ) ;
emoji_label_ = lv_label_create ( emoji_box_ ) ;
lv_obj_set_style_text_font ( emoji_label_ , large_icon_font , 0 ) ;
@ -815,47 +809,107 @@ void LcdDisplay::SetupUI() {
lv_obj_center ( emoji_image_ ) ;
lv_obj_add_flag ( emoji_image_ , LV_OBJ_FLAG_HIDDEN ) ;
preview_image_ = lv_image_create ( content_ ) ;
/* Middle layer: preview_image_ - centered display */
preview_image_ = lv_image_create ( screen ) ;
lv_obj_set_size ( preview_image_ , width_ / 2 , height_ / 2 ) ;
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_ , width_ * 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 _, lvgl_theme - > text _color( ) , 0 ) ;
/* Layer 1: Top bar - for status icons */
top_bar_ = lv_obj_create ( screen ) ;
lv_obj_set_size ( top_bar_ , LV_HOR_RES , LV_SIZE_CONTENT ) ;
lv_obj_set_style_radius ( top_bar_ , 0 , 0 ) ;
lv_obj_set_style_bg_opa ( top_bar_ , LV_OPA_50 , 0 ) ; // 50% opacity background
lv_obj_set_style_bg _color ( top_bar _, lvgl_theme - > background _color( ) , 0 ) ;
lv_obj_set_style_border_width ( top_bar_ , 0 , 0 ) ;
lv_obj_set_style_pad_all ( top_bar_ , 0 , 0 ) ;
lv_obj_set_style_pad_top ( top_bar_ , lvgl_theme - > spacing ( 2 ) , 0 ) ;
lv_obj_set_style_pad_bottom ( top_bar_ , lvgl_theme - > spacing ( 2 ) , 0 ) ;
lv_obj_set_style_pad_left ( top_bar_ , lvgl_theme - > spacing ( 4 ) , 0 ) ;
lv_obj_set_style_pad_right ( top_bar_ , lvgl_theme - > spacing ( 4 ) , 0 ) ;
lv_obj_set_flex_flow ( top_bar_ , LV_FLEX_FLOW_ROW ) ;
lv_obj_set_flex_align ( top_bar_ , LV_FLEX_ALIGN_SPACE_BETWEEN , LV_FLEX_ALIGN_CENTER , LV_FLEX_ALIGN_CENTER ) ;
lv_obj_set_scrollbar_mode ( top_bar_ , LV_SCROLLBAR_MODE_OFF ) ;
lv_obj_align ( top_bar_ , LV_ALIGN_TOP_MID , 0 , 0 ) ;
/* Status bar */
network_label_ = lv_label_create ( status _bar_) ;
// Left icon
network_label_ = lv_label_create ( top _bar_) ;
lv_label_set_text ( network_label_ , " " ) ;
lv_obj_set_style_text_font ( network_label_ , icon_font , 0 ) ;
lv_obj_set_style_text_color ( network_label_ , lvgl_theme - > text_color ( ) , 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_C ENTER , 0 ) ;
lv_obj_set_style_text_color ( notification_label_ , lvgl_theme - > text_color ( ) , 0 ) ;
lv_label_set_text ( notification_label_ , " " ) ;
lv_obj_add_flag ( notification_label_ , LV_OBJ_FLAG_HIDDEN ) ;
// Right icons container
lv_obj_t * right_icons = lv_obj_create ( top_bar_ ) ;
lv_obj_set_size ( right_icons , LV_SIZE_CONT ENT , LV_SIZE_CONTENT ) ;
lv_obj_set_style_bg_opa ( right_icons , LV_OPA_TRANSP , 0 ) ;
lv_obj_set_style_border_width ( right_icons , 0 , 0 ) ;
lv_obj_set_style_pad_all ( right_icons , 0 , 0 ) ;
lv_obj_set_flex_flow ( right_icons , LV_FLEX_FLOW_ROW ) ;
lv_obj_set_flex_align ( right_icons , LV_FLEX_ALIGN_END , LV_FLEX_ALIGN_CENTER , LV_FLEX_ALIGN_CENTER ) ;
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_ , lvgl_theme - > text_color ( ) , 0 ) ;
lv_label_set_text ( status_label_ , Lang : : Strings : : INITIALIZING ) ;
mute_label_ = lv_label_create ( status_bar_ ) ;
mute _label_ = lv_label_create ( right_icons ) ;
lv_label_set_text ( mute_label_ , " " ) ;
lv_obj_set_style_text_font ( mute_label_ , icon_font , 0 ) ;
lv_obj_set_style_text_color ( mute_label_ , lvgl_theme - > text_color ( ) , 0 ) ;
battery_label_ = lv_label_create ( status_bar_ ) ;
battery_label_ = lv_label_create ( right_icons ) ;
lv_label_set_text ( battery_label_ , " " ) ;
lv_obj_set_style_text_font ( battery_label_ , icon_font , 0 ) ;
lv_obj_set_style_text_color ( battery_label_ , lvgl_theme - > text_color ( ) , 0 ) ;
lv_obj_set_style_margin_left ( battery_label_ , lvgl_theme - > spacing ( 2 ) , 0 ) ;
/* Layer 2: Status bar - for center text labels */
status_bar_ = lv_obj_create ( screen ) ;
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_opa ( status_bar_ , LV_OPA_TRANSP , 0 ) ; // Transparent background
lv_obj_set_style_border_width ( status_bar_ , 0 , 0 ) ;
lv_obj_set_style_pad_all ( status_bar_ , 0 , 0 ) ;
lv_obj_set_style_pad_top ( status_bar_ , lvgl_theme - > spacing ( 2 ) , 0 ) ;
lv_obj_set_style_pad_bottom ( status_bar_ , lvgl_theme - > spacing ( 2 ) , 0 ) ;
lv_obj_set_scrollbar_mode ( status_bar_ , LV_SCROLLBAR_MODE_OFF ) ;
lv_obj_set_style_layout ( status_bar_ , LV_LAYOUT_NONE , 0 ) ; // Use absolute positioning
lv_obj_align ( status_bar_ , LV_ALIGN_TOP_MID , 0 , 0 ) ; // Overlap with top_bar_
notification_label_ = lv_label_create ( status_bar_ ) ;
lv_obj_set_width ( notification_label_ , LV_HOR_RES * 0.75 ) ;
lv_obj_set_style_text_align ( notification_label_ , LV_TEXT_ALIGN_CENTER , 0 ) ;
lv_obj_set_style_text_color ( notification_label_ , lvgl_theme - > text_color ( ) , 0 ) ;
lv_label_set_text ( notification_label_ , " " ) ;
lv_obj_align ( notification_label_ , LV_ALIGN_CENTER , 0 , 0 ) ;
lv_obj_add_flag ( notification_label_ , LV_OBJ_FLAG_HIDDEN ) ;
status_label_ = lv_label_create ( status_bar_ ) ;
lv_obj_set_width ( status_label_ , LV_HOR_RES * 0.75 ) ;
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_ , lvgl_theme - > text_color ( ) , 0 ) ;
lv_label_set_text ( status_label_ , Lang : : Strings : : INITIALIZING ) ;
lv_obj_align ( status_label_ , LV_ALIGN_CENTER , 0 , 0 ) ;
/* Top layer: Bottom bar - fixed at bottom, minimum height 48, height can be adaptive */
bottom_bar_ = lv_obj_create ( screen ) ;
lv_obj_set_width ( bottom_bar_ , LV_HOR_RES ) ;
lv_obj_set_height ( bottom_bar_ , LV_SIZE_CONTENT ) ;
lv_obj_set_style_min_height ( bottom_bar_ , 48 , 0 ) ; // Set minimum height 48
lv_obj_set_style_radius ( bottom_bar_ , 0 , 0 ) ;
lv_obj_set_style_bg_color ( bottom_bar_ , lvgl_theme - > background_color ( ) , 0 ) ;
lv_obj_set_style_text_color ( bottom_bar_ , lvgl_theme - > text_color ( ) , 0 ) ;
lv_obj_set_style_pad_top ( bottom_bar_ , lvgl_theme - > spacing ( 2 ) , 0 ) ;
lv_obj_set_style_pad_bottom ( bottom_bar_ , lvgl_theme - > spacing ( 2 ) , 0 ) ;
lv_obj_set_style_pad_left ( bottom_bar_ , lvgl_theme - > spacing ( 4 ) , 0 ) ;
lv_obj_set_style_pad_right ( bottom_bar_ , lvgl_theme - > spacing ( 4 ) , 0 ) ;
lv_obj_set_style_border_width ( bottom_bar_ , 0 , 0 ) ;
lv_obj_align ( bottom_bar_ , LV_ALIGN_BOTTOM_MID , 0 , 0 ) ;
/* chat_message_label_ placed in bottom_bar_ and vertically centered */
chat_message_label_ = lv_label_create ( bottom_bar_ ) ;
lv_label_set_text ( chat_message_label_ , " " ) ;
lv_obj_set_width ( chat_message_label_ , LV_HOR_RES - lvgl_theme - > spacing ( 8 ) ) ; // Subtract left and right padding
lv_label_set_long_mode ( chat_message_label_ , LV_LABEL_LONG_WRAP ) ; // Auto wrap mode
lv_obj_set_style_text_align ( chat_message_label_ , LV_TEXT_ALIGN_CENTER , 0 ) ; // Center text alignment
lv_obj_set_style_text_color ( chat_message_label_ , lvgl_theme - > text_color ( ) , 0 ) ;
lv_obj_align ( chat_message_label_ , LV_ALIGN_CENTER , 0 , 0 ) ; // Vertically and horizontally centered in bottom_bar_
low_battery_popup_ = lv_obj_create ( screen ) ;
lv_obj_set_scrollbar_mode ( low_battery_popup_ , LV_SCROLLBAR_MODE_OFF ) ;
@ -891,7 +945,6 @@ void LcdDisplay::SetPreviewImage(std::unique_ptr<LvglImage> image) {
preview_image_cached_ = std : : move ( image ) ;
auto img_dsc = preview_image_cached_ - > image_dsc ( ) ;
// 设置图片源并显示预览图片
lv_image_set_src ( preview_image_ , img_dsc ) ;
if ( img_dsc - > header . w > 0 & & img_dsc - > header . h > 0 ) {
// zoom factor 0.5
@ -971,7 +1024,7 @@ void LcdDisplay::SetEmotion(const char* emotion) {
}
# if CONFIG_USE_WECHAT_MESSAGE_STYLE
// Wec hat message style中,如果 emotion是 neutral,则不显示
// In WeC hat message style, if emotion is neutral, don't display it
uint32_t child_count = lv_obj_get_child_cnt ( content_ ) ;
if ( strcmp ( emotion , " neutral " ) = = 0 & & child_count > 0 ) {
// Stop GIF animation if running
@ -1021,9 +1074,11 @@ void LcdDisplay::SetTheme(Theme* theme) {
lv_obj_set_style_bg_color ( container_ , lvgl_theme - > background_color ( ) , 0 ) ;
}
// Update status bar background color with 50% opacity
lv_obj_set_style_bg_opa ( status_bar_ , LV_OPA_50 , 0 ) ;
lv_obj_set_style_bg_color ( status_bar_ , lvgl_theme - > background_color ( ) , 0 ) ;
// Update top bar background color with 50% opacity
if ( top_bar_ ! = nullptr ) {
lv_obj_set_style_bg_opa ( top_bar_ , LV_OPA_50 , 0 ) ;
lv_obj_set_style_bg_color ( top_bar_ , lvgl_theme - > background_color ( ) , 0 ) ;
}
// Update status bar elements
lv_obj_set_style_text_color ( network_label_ , lvgl_theme - > text_color ( ) , 0 ) ;
@ -1033,11 +1088,11 @@ void LcdDisplay::SetTheme(Theme* theme) {
lv_obj_set_style_text_color ( battery_label_ , lvgl_theme - > text_color ( ) , 0 ) ;
lv_obj_set_style_text_color ( emoji_label_ , lvgl_theme - > text_color ( ) , 0 ) ;
// If we have the chat message style, update all message bubbles
# if CONFIG_USE_WECHAT_MESSAGE_STYLE
// Set content background opacity
lv_obj_set_style_bg_opa ( content_ , LV_OPA_TRANSP , 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 + + ) {
@ -1046,33 +1101,33 @@ void LcdDisplay::SetTheme(Theme* theme) {
lv_obj_t * bubble = nullptr ;
// 检查这个对象是容器还是气泡
// 如果是容器(用户或系统消息),则获取其子对象作为气泡
// 如果是气泡(助手消息),则直接使用
// Check if this object is a container or bubble
// If it's a container (user or system message), get its child as bubble
// If it's a bubble (assistant message), use it directly
if ( lv_obj_get_child_cnt ( obj ) > 0 ) {
// 可能是容器,检查它是否为用户或系统消息容器
// 用户和系统消息容器是透明的
// Might be a container, check if it's a user or system message container
// User and system message containers are transparent
lv_opa_t bg_opa = lv_obj_get_style_bg_opa ( obj , 0 ) ;
if ( bg_opa = = LV_OPA_TRANSP ) {
// 这是用户或系统消息的容器
// This is a user or system message container
bubble = lv_obj_get_child ( obj , 0 ) ;
} else {
// 这可能是助手消息的气泡自身
// This might be an assistant message bubble itself
bubble = obj ;
}
} else {
// 没有子元素, 可能是其他UI元素, 跳过
// No child elements, might be other UI elements, skip
continue ;
}
if ( bubble = = nullptr ) continue ;
// 使用保存的用户数据来识别气泡类型
// Use saved user data to identify bubble type
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 ) ;
// 根据气泡类型应用正确的颜色
// Apply correct color based on bubble type
if ( strcmp ( bubble_type , " user " ) = = 0 ) {
lv_obj_set_style_bg_color ( bubble , lvgl_theme - > user_bubble_color ( ) , 0 ) ;
} else if ( strcmp ( bubble_type , " assistant " ) = = 0 ) {
@ -1090,7 +1145,7 @@ void LcdDisplay::SetTheme(Theme* theme) {
if ( lv_obj_get_child_cnt ( bubble ) > 0 ) {
lv_obj_t * text = lv_obj_get_child ( bubble , 0 ) ;
if ( text ! = nullptr ) {
// 根据气泡类型设置文本颜色
// Set text color based on bubble type
if ( strcmp ( bubble_type , " system " ) = = 0 ) {
lv_obj_set_style_text_color ( text , lvgl_theme - > system_text_color ( ) , 0 ) ;
} else {
@ -1111,6 +1166,12 @@ void LcdDisplay::SetTheme(Theme* theme) {
if ( emoji_label_ ! = nullptr ) {
lv_obj_set_style_text_color ( emoji_label_ , lvgl_theme - > text_color ( ) , 0 ) ;
}
// Update bottom bar background color with 50% opacity
if ( bottom_bar_ ! = nullptr ) {
lv_obj_set_style_bg_opa ( bottom_bar_ , LV_OPA_50 , 0 ) ;
lv_obj_set_style_bg_color ( bottom_bar_ , lvgl_theme - > background_color ( ) , 0 ) ;
}
# endif
// Update low battery popup
@ -1119,3 +1180,17 @@ void LcdDisplay::SetTheme(Theme* theme) {
// No errors occurred. Save theme to settings
Display : : SetTheme ( lvgl_theme ) ;
}
void LcdDisplay : : SetHideSubtitle ( bool hide ) {
DisplayLockGuard lock ( this ) ;
hide_subtitle_ = hide ;
// Immediately update UI visibility based on the setting
if ( bottom_bar_ ! = nullptr ) {
if ( hide ) {
lv_obj_add_flag ( bottom_bar_ , LV_OBJ_FLAG_HIDDEN ) ;
} else {
lv_obj_remove_flag ( bottom_bar_ , LV_OBJ_FLAG_HIDDEN ) ;
}
}
}