feat: Add gif support (#1183)

* feat: Add gif support

* fix: compiling errors

* fix remove bg image
This commit is contained in:
Xiaoxia
2025-09-11 03:53:12 +08:00
committed by GitHub
parent 4048647ef8
commit 57c2c64047
34 changed files with 1690 additions and 327 deletions

View File

@@ -1,4 +1,5 @@
#include "lcd_display.h"
#include "gif/lvgl_gif.h"
#include "settings.h"
#include "lvgl_theme.h"
#include "assets/lang_config.h"
@@ -282,12 +283,33 @@ MipiLcdDisplay::MipiLcdDisplay(esp_lcd_panel_io_handle_t panel_io, esp_lcd_panel
LcdDisplay::~LcdDisplay() {
SetPreviewImage(nullptr);
// Clean up GIF controller
if (gif_controller_) {
gif_controller_->Stop();
gif_controller_.reset();
}
if (preview_timer_ != nullptr) {
esp_timer_stop(preview_timer_);
esp_timer_delete(preview_timer_);
}
// 然后再清理 LVGL 对象
if (preview_image_ != nullptr) {
lv_obj_del(preview_image_);
}
if (chat_message_label_ != nullptr) {
lv_obj_del(chat_message_label_);
}
if (emoji_label_ != nullptr) {
lv_obj_del(emoji_label_);
}
if (emoji_image_ != nullptr) {
lv_obj_del(emoji_image_);
}
if (emoji_box_ != nullptr) {
lv_obj_del(emoji_box_);
}
if (content_ != nullptr) {
lv_obj_del(content_);
}
@@ -337,6 +359,7 @@ void LcdDisplay::SetupUI() {
/* Container */
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);
@@ -431,11 +454,11 @@ void LcdDisplay::SetupUI() {
lv_obj_align(emoji_image_, LV_ALIGN_TOP_MID, 0, text_font->line_height + lvgl_theme->spacing(2));
// Display AI logo while booting
emotion_label_ = lv_label_create(screen);
lv_obj_center(emotion_label_);
lv_obj_set_style_text_font(emotion_label_, large_icon_font, 0);
lv_obj_set_style_text_color(emotion_label_, lvgl_theme->text_color(), 0);
lv_label_set_text(emotion_label_, FONT_AWESOME_MICROCHIP_AI);
emoji_label_ = lv_label_create(screen);
lv_obj_center(emoji_label_);
lv_obj_set_style_text_font(emoji_label_, large_icon_font, 0);
lv_obj_set_style_text_color(emoji_label_, lvgl_theme->text_color(), 0);
lv_label_set_text(emoji_label_, FONT_AWESOME_MICROCHIP_AI);
}
#if CONFIG_IDF_TARGET_ESP32P4
#define MAX_MESSAGES 40
@@ -483,7 +506,7 @@ void LcdDisplay::SetChatMessage(const char* role, const char* content) {
}
} else {
// 隐藏居中显示的 AI logo
lv_obj_add_flag(emotion_label_, LV_OBJ_FLAG_HIDDEN);
lv_obj_add_flag(emoji_label_, LV_OBJ_FLAG_HIDDEN);
}
//避免出现空的消息框
@@ -729,6 +752,7 @@ void LcdDisplay::SetupUI() {
/* Container */
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);
@@ -769,13 +793,14 @@ void LcdDisplay::SetupUI() {
lv_obj_set_style_pad_all(emoji_box_, 0, 0);
lv_obj_set_style_border_width(emoji_box_, 0, 0);
emotion_label_ = lv_label_create(emoji_box_);
lv_obj_set_style_text_font(emotion_label_, large_icon_font, 0);
lv_obj_set_style_text_color(emotion_label_, lvgl_theme->text_color(), 0);
lv_label_set_text(emotion_label_, FONT_AWESOME_MICROCHIP_AI);
emoji_label_ = lv_label_create(emoji_box_);
lv_obj_set_style_text_font(emoji_label_, large_icon_font, 0);
lv_obj_set_style_text_color(emoji_label_, lvgl_theme->text_color(), 0);
lv_label_set_text(emoji_label_, FONT_AWESOME_MICROCHIP_AI);
emoji_image_ = lv_img_create(emoji_box_);
lv_obj_center(emoji_image_);
lv_obj_add_flag(emoji_image_, LV_OBJ_FLAG_HIDDEN);
preview_image_ = lv_image_create(content_);
lv_obj_set_size(preview_image_, width_ / 2, height_ / 2);
@@ -865,39 +890,81 @@ void LcdDisplay::SetPreviewImage(const lv_img_dsc_t* img_dsc) {
lv_obj_add_flag(preview_image_, LV_OBJ_FLAG_HIDDEN);
}
}
void LcdDisplay::SetChatMessage(const char* role, const char* content) {
DisplayLockGuard lock(this);
if (chat_message_label_ == nullptr) {
return;
}
lv_label_set_text(chat_message_label_, content);
}
#endif
void LcdDisplay::SetEmotion(const char* emotion) {
// Stop any running GIF animation
if (gif_controller_) {
DisplayLockGuard lock(this);
gif_controller_->Stop();
gif_controller_.reset();
}
if (emoji_image_ == nullptr) {
return;
}
auto emoji_collection = static_cast<LvglTheme*>(current_theme_)->emoji_collection();
auto img_dsc = emoji_collection != nullptr ? emoji_collection->GetEmojiImage(emotion) : nullptr;
if (img_dsc == nullptr) {
auto image = emoji_collection != nullptr ? emoji_collection->GetEmojiImage(emotion) : nullptr;
if (image == nullptr) {
const char* utf8 = font_awesome_get_utf8(emotion);
if (utf8 != nullptr && emotion_label_ != nullptr) {
if (utf8 != nullptr && emoji_label_ != nullptr) {
DisplayLockGuard lock(this);
lv_label_set_text(emotion_label_, utf8);
lv_obj_remove_flag(emotion_label_, LV_OBJ_FLAG_HIDDEN);
lv_label_set_text(emoji_label_, utf8);
lv_obj_add_flag(emoji_image_, LV_OBJ_FLAG_HIDDEN);
lv_obj_remove_flag(emoji_label_, LV_OBJ_FLAG_HIDDEN);
}
return;
}
DisplayLockGuard lock(this);
lv_image_set_src(emoji_image_, img_dsc);
#if CONFIG_USE_WECHAT_MESSAGE_STYLE
// Wechat message style中如果emotion是neutral则隐藏emoji_image_
if (strcmp(emotion, "neutral") == 0) {
lv_obj_add_flag(emoji_image_, LV_OBJ_FLAG_HIDDEN);
if (image->IsGif()) {
// Create new GIF controller
gif_controller_ = std::make_unique<LvglGif>(image->image_dsc());
if (gif_controller_->IsLoaded()) {
// Set up frame update callback
gif_controller_->SetFrameCallback([this]() {
lv_image_set_src(emoji_image_, gif_controller_->image_dsc());
});
// Set initial frame and start animation
lv_image_set_src(emoji_image_, gif_controller_->image_dsc());
gif_controller_->Start();
// Show GIF, hide others
lv_obj_add_flag(emoji_label_, LV_OBJ_FLAG_HIDDEN);
lv_obj_remove_flag(emoji_image_, LV_OBJ_FLAG_HIDDEN);
} else {
ESP_LOGE(TAG, "Failed to load GIF for emotion: %s", emotion);
gif_controller_.reset();
}
} else {
lv_image_set_src(emoji_image_, image->image_dsc());
lv_obj_add_flag(emoji_label_, LV_OBJ_FLAG_HIDDEN);
lv_obj_remove_flag(emoji_image_, LV_OBJ_FLAG_HIDDEN);
}
#else
// 显示emoji_image_隐藏emotion_label_, preview_image_
lv_obj_remove_flag(emoji_image_, LV_OBJ_FLAG_HIDDEN);
lv_obj_add_flag(emotion_label_, LV_OBJ_FLAG_HIDDEN);
#if CONFIG_USE_WECHAT_MESSAGE_STYLE
// Wechat message style中如果emotion是neutral则不显示
if (strcmp(emotion, "neutral") == 0) {
// Stop GIF animation if running
if (gif_controller_) {
gif_controller_->Stop();
gif_controller_.reset();
}
lv_obj_add_flag(emoji_image_, LV_OBJ_FLAG_HIDDEN);
lv_obj_add_flag(emoji_label_, LV_OBJ_FLAG_HIDDEN);
}
#endif
}
@@ -913,9 +980,8 @@ void LcdDisplay::SetTheme(Theme* theme) {
auto text_font = lvgl_theme->text_font()->font();
auto icon_font = lvgl_theme->icon_font()->font();
auto large_icon_font = lvgl_theme->large_icon_font()->font();
lv_obj_set_style_text_font(screen, text_font, 0);
if (text_font->line_height >= 30) {
if (text_font->line_height >= 40) {
lv_obj_set_style_text_font(mute_label_, large_icon_font, 0);
lv_obj_set_style_text_font(battery_label_, large_icon_font, 0);
lv_obj_set_style_text_font(network_label_, large_icon_font, 0);
@@ -925,129 +991,112 @@ void LcdDisplay::SetTheme(Theme* theme) {
lv_obj_set_style_text_font(network_label_, icon_font, 0);
}
// Update the screen colors
lv_obj_set_style_bg_color(screen, lvgl_theme->background_color(), 0);
// Set parent text color
lv_obj_set_style_text_font(screen, text_font, 0);
lv_obj_set_style_text_color(screen, lvgl_theme->text_color(), 0);
// Update container colors
if (container_ != nullptr) {
// Set background image
if (lvgl_theme->background_image() != nullptr) {
lv_obj_set_style_bg_image_src(container_, lvgl_theme->background_image()->image_dsc(), 0);
} else {
lv_obj_set_style_bg_image_src(container_, nullptr, 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);
}
// Update status bar colors
if (status_bar_ != nullptr) {
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);
// Update status bar elements
if (network_label_ != nullptr) {
lv_obj_set_style_text_color(network_label_, lvgl_theme->text_color(), 0);
}
if (status_label_ != nullptr) {
lv_obj_set_style_text_color(status_label_, lvgl_theme->text_color(), 0);
}
if (notification_label_ != nullptr) {
lv_obj_set_style_text_color(notification_label_, lvgl_theme->text_color(), 0);
}
if (mute_label_ != nullptr) {
lv_obj_set_style_text_color(mute_label_, lvgl_theme->text_color(), 0);
}
if (battery_label_ != nullptr) {
lv_obj_set_style_text_color(battery_label_, lvgl_theme->text_color(), 0);
}
if (emotion_label_ != nullptr) {
lv_obj_set_style_text_color(emotion_label_, lvgl_theme->text_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 content area colors
if (content_ != nullptr) {
lv_obj_set_style_bg_color(content_, lvgl_theme->chat_background_color(), 0);
lv_obj_set_style_border_color(content_, lvgl_theme->border_color(), 0);
// If we have the chat message style, update all message bubbles
// Update status bar elements
lv_obj_set_style_text_color(network_label_, lvgl_theme->text_color(), 0);
lv_obj_set_style_text_color(status_label_, lvgl_theme->text_color(), 0);
lv_obj_set_style_text_color(notification_label_, lvgl_theme->text_color(), 0);
lv_obj_set_style_text_color(mute_label_, lvgl_theme->text_color(), 0);
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);
// 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++) {
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;
}
// 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 {
// 没有子元素可能是其他UI元素跳过
continue;
// 这可能是助手消息的气泡自身
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, lvgl_theme->user_bubble_color(), 0);
} else if (strcmp(bubble_type, "assistant") == 0) {
lv_obj_set_style_bg_color(bubble, lvgl_theme->assistant_bubble_color(), 0);
} else if (strcmp(bubble_type, "system") == 0) {
lv_obj_set_style_bg_color(bubble, lvgl_theme->system_bubble_color(), 0);
} else if (strcmp(bubble_type, "image") == 0) {
lv_obj_set_style_bg_color(bubble, lvgl_theme->system_bubble_color(), 0);
}
if (bubble == nullptr) continue;
// Update border color
lv_obj_set_style_border_color(bubble, lvgl_theme->border_color(), 0);
// 使用保存的用户数据来识别气泡类型
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, lvgl_theme->user_bubble_color(), 0);
} else if (strcmp(bubble_type, "assistant") == 0) {
lv_obj_set_style_bg_color(bubble, lvgl_theme->assistant_bubble_color(), 0);
} else if (strcmp(bubble_type, "system") == 0) {
lv_obj_set_style_bg_color(bubble, lvgl_theme->system_bubble_color(), 0);
} else if (strcmp(bubble_type, "image") == 0) {
lv_obj_set_style_bg_color(bubble, lvgl_theme->system_bubble_color(), 0);
}
// Update border color
lv_obj_set_style_border_color(bubble, lvgl_theme->border_color(), 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, lvgl_theme->system_text_color(), 0);
} else {
lv_obj_set_style_text_color(text, lvgl_theme->text_color(), 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, lvgl_theme->system_text_color(), 0);
} else {
lv_obj_set_style_text_color(text, lvgl_theme->text_color(), 0);
}
}
} else {
ESP_LOGW(TAG, "child[%lu] Bubble type is not found", i);
}
} else {
ESP_LOGW(TAG, "child[%lu] Bubble type is not found", i);
}
}
#else
// Simple UI mode - just update the main chat message
if (chat_message_label_ != nullptr) {
lv_obj_set_style_text_color(chat_message_label_, lvgl_theme->text_color(), 0);
}
if (emotion_label_ != nullptr) {
lv_obj_set_style_text_color(emotion_label_, lvgl_theme->text_color(), 0);
}
#endif
// Simple UI mode - just update the main chat message
if (chat_message_label_ != nullptr) {
lv_obj_set_style_text_color(chat_message_label_, lvgl_theme->text_color(), 0);
}
// Update low battery popup
if (low_battery_popup_ != nullptr) {
lv_obj_set_style_bg_color(low_battery_popup_, lvgl_theme->low_battery_color(), 0);
if (emoji_label_ != nullptr) {
lv_obj_set_style_text_color(emoji_label_, lvgl_theme->text_color(), 0);
}
#endif
// Update low battery popup
lv_obj_set_style_bg_color(low_battery_popup_, lvgl_theme->low_battery_color(), 0);
// No errors occurred. Save theme to settings
Display::SetTheme(lvgl_theme);