Update project version to 2.2.2, Noto fonts and emoji support. (#1720)
This commit is contained in:
@ -9,5 +9,5 @@ include($ENV{IDF_PATH}/tools/cmake/project.cmake)
|
|||||||
# "Trim" the build. Include the minimal set of components, main, and anything it depends on.
|
# "Trim" the build. Include the minimal set of components, main, and anything it depends on.
|
||||||
idf_build_set_property(MINIMAL_BUILD ON)
|
idf_build_set_property(MINIMAL_BUILD ON)
|
||||||
|
|
||||||
set(PROJECT_VER "2.2.1")
|
set(PROJECT_VER "2.2.2")
|
||||||
project(xiaozhi)
|
project(xiaozhi)
|
||||||
|
|||||||
@ -104,28 +104,28 @@ elseif(CONFIG_BOARD_TYPE_BREAD_COMPACT_ESP32_LCD)
|
|||||||
set(BUILTIN_ICON_FONT font_awesome_14_1)
|
set(BUILTIN_ICON_FONT font_awesome_14_1)
|
||||||
elseif(CONFIG_BOARD_TYPE_DF_K10)
|
elseif(CONFIG_BOARD_TYPE_DF_K10)
|
||||||
set(BOARD_TYPE "df-k10")
|
set(BOARD_TYPE "df-k10")
|
||||||
set(BUILTIN_TEXT_FONT font_puhui_basic_20_4)
|
set(BUILTIN_TEXT_FONT font_noto_basic_20_4)
|
||||||
set(BUILTIN_ICON_FONT font_awesome_20_4)
|
set(BUILTIN_ICON_FONT font_awesome_20_4)
|
||||||
set(DEFAULT_EMOJI_COLLECTION twemoji_64)
|
set(DEFAULT_EMOJI_COLLECTION noto-emoji_128)
|
||||||
elseif(CONFIG_BOARD_TYPE_DF_S3_AI_CAM)
|
elseif(CONFIG_BOARD_TYPE_DF_S3_AI_CAM)
|
||||||
set(BOARD_TYPE "df-s3-ai-cam")
|
set(BOARD_TYPE "df-s3-ai-cam")
|
||||||
elseif(CONFIG_BOARD_TYPE_ESP_BOX_3)
|
elseif(CONFIG_BOARD_TYPE_ESP_BOX_3)
|
||||||
set(BOARD_TYPE "esp-box-3")
|
set(BOARD_TYPE "esp-box-3")
|
||||||
set(BUILTIN_TEXT_FONT font_puhui_basic_20_4)
|
set(BUILTIN_TEXT_FONT font_noto_basic_20_4)
|
||||||
set(BUILTIN_ICON_FONT font_awesome_20_4)
|
set(BUILTIN_ICON_FONT font_awesome_20_4)
|
||||||
set(DEFAULT_EMOJI_COLLECTION twemoji_64)
|
set(DEFAULT_EMOJI_COLLECTION noto-emoji_128)
|
||||||
set(EMOTE_RESOLUTION "320_240")
|
set(EMOTE_RESOLUTION "320_240")
|
||||||
elseif(CONFIG_BOARD_TYPE_ESP_BOX)
|
elseif(CONFIG_BOARD_TYPE_ESP_BOX)
|
||||||
set(BOARD_TYPE "esp-box")
|
set(BOARD_TYPE "esp-box")
|
||||||
set(BUILTIN_TEXT_FONT font_puhui_basic_20_4)
|
set(BUILTIN_TEXT_FONT font_noto_basic_20_4)
|
||||||
set(BUILTIN_ICON_FONT font_awesome_20_4)
|
set(BUILTIN_ICON_FONT font_awesome_20_4)
|
||||||
set(DEFAULT_EMOJI_COLLECTION twemoji_64)
|
set(DEFAULT_EMOJI_COLLECTION noto-emoji_128)
|
||||||
set(EMOTE_RESOLUTION "320_240")
|
set(EMOTE_RESOLUTION "320_240")
|
||||||
elseif(CONFIG_BOARD_TYPE_ESP_BOX_LITE)
|
elseif(CONFIG_BOARD_TYPE_ESP_BOX_LITE)
|
||||||
set(BOARD_TYPE "esp-box-lite")
|
set(BOARD_TYPE "esp-box-lite")
|
||||||
set(BUILTIN_TEXT_FONT font_puhui_basic_20_4)
|
set(BUILTIN_TEXT_FONT font_noto_basic_20_4)
|
||||||
set(BUILTIN_ICON_FONT font_awesome_20_4)
|
set(BUILTIN_ICON_FONT font_awesome_20_4)
|
||||||
set(DEFAULT_EMOJI_COLLECTION twemoji_64)
|
set(DEFAULT_EMOJI_COLLECTION noto-emoji_128)
|
||||||
elseif(CONFIG_BOARD_TYPE_KEVIN_BOX_2)
|
elseif(CONFIG_BOARD_TYPE_KEVIN_BOX_2)
|
||||||
set(BOARD_TYPE "kevin-box-2")
|
set(BOARD_TYPE "kevin-box-2")
|
||||||
set(BUILTIN_TEXT_FONT font_puhui_basic_14_1)
|
set(BUILTIN_TEXT_FONT font_puhui_basic_14_1)
|
||||||
@ -134,14 +134,14 @@ elseif(CONFIG_BOARD_TYPE_KEVIN_C3)
|
|||||||
set(BOARD_TYPE "kevin-c3")
|
set(BOARD_TYPE "kevin-c3")
|
||||||
elseif(CONFIG_BOARD_TYPE_KEVIN_SP_V3_DEV)
|
elseif(CONFIG_BOARD_TYPE_KEVIN_SP_V3_DEV)
|
||||||
set(BOARD_TYPE "kevin-sp-v3-dev")
|
set(BOARD_TYPE "kevin-sp-v3-dev")
|
||||||
set(BUILTIN_TEXT_FONT font_puhui_basic_20_4)
|
set(BUILTIN_TEXT_FONT font_noto_basic_20_4)
|
||||||
set(BUILTIN_ICON_FONT font_awesome_20_4)
|
set(BUILTIN_ICON_FONT font_awesome_20_4)
|
||||||
set(DEFAULT_EMOJI_COLLECTION twemoji_64)
|
set(DEFAULT_EMOJI_COLLECTION noto-emoji_128)
|
||||||
elseif(CONFIG_BOARD_TYPE_KEVIN_SP_V4_DEV)
|
elseif(CONFIG_BOARD_TYPE_KEVIN_SP_V4_DEV)
|
||||||
set(BOARD_TYPE "kevin-sp-v4-dev")
|
set(BOARD_TYPE "kevin-sp-v4-dev")
|
||||||
set(BUILTIN_TEXT_FONT font_puhui_basic_20_4)
|
set(BUILTIN_TEXT_FONT font_noto_basic_20_4)
|
||||||
set(BUILTIN_ICON_FONT font_awesome_20_4)
|
set(BUILTIN_ICON_FONT font_awesome_20_4)
|
||||||
set(DEFAULT_EMOJI_COLLECTION twemoji_64)
|
set(DEFAULT_EMOJI_COLLECTION noto-emoji_128)
|
||||||
elseif(CONFIG_BOARD_TYPE_KEVIN_YUYING_313LCD)
|
elseif(CONFIG_BOARD_TYPE_KEVIN_YUYING_313LCD)
|
||||||
set(BOARD_TYPE "kevin-yuying-313lcd")
|
set(BOARD_TYPE "kevin-yuying-313lcd")
|
||||||
set(BUILTIN_TEXT_FONT font_puhui_basic_30_4)
|
set(BUILTIN_TEXT_FONT font_puhui_basic_30_4)
|
||||||
@ -149,9 +149,9 @@ elseif(CONFIG_BOARD_TYPE_KEVIN_YUYING_313LCD)
|
|||||||
set(DEFAULT_EMOJI_COLLECTION twemoji_64)
|
set(DEFAULT_EMOJI_COLLECTION twemoji_64)
|
||||||
elseif(CONFIG_BOARD_TYPE_LICHUANG_DEV_S3)
|
elseif(CONFIG_BOARD_TYPE_LICHUANG_DEV_S3)
|
||||||
set(BOARD_TYPE "lichuang-dev")
|
set(BOARD_TYPE "lichuang-dev")
|
||||||
set(BUILTIN_TEXT_FONT font_puhui_basic_20_4)
|
set(BUILTIN_TEXT_FONT font_noto_basic_20_4)
|
||||||
set(BUILTIN_ICON_FONT font_awesome_20_4)
|
set(BUILTIN_ICON_FONT font_awesome_20_4)
|
||||||
set(DEFAULT_EMOJI_COLLECTION twemoji_64)
|
set(DEFAULT_EMOJI_COLLECTION noto-emoji_128)
|
||||||
elseif(CONFIG_BOARD_TYPE_LICHUANG_DEV_C3)
|
elseif(CONFIG_BOARD_TYPE_LICHUANG_DEV_C3)
|
||||||
set(BOARD_TYPE "lichuang-c3-dev")
|
set(BOARD_TYPE "lichuang-c3-dev")
|
||||||
set(BUILTIN_TEXT_FONT font_puhui_basic_20_4)
|
set(BUILTIN_TEXT_FONT font_puhui_basic_20_4)
|
||||||
@ -436,29 +436,29 @@ elseif(CONFIG_BOARD_TYPE_MOVECALL_CUICAN_ESP32S3)
|
|||||||
set(DEFAULT_EMOJI_COLLECTION twemoji_64)
|
set(DEFAULT_EMOJI_COLLECTION twemoji_64)
|
||||||
elseif(CONFIG_BOARD_TYPE_ATK_DNESP32S3)
|
elseif(CONFIG_BOARD_TYPE_ATK_DNESP32S3)
|
||||||
set(BOARD_TYPE "atk-dnesp32s3")
|
set(BOARD_TYPE "atk-dnesp32s3")
|
||||||
set(BUILTIN_TEXT_FONT font_puhui_basic_20_4)
|
set(BUILTIN_TEXT_FONT font_noto_basic_20_4)
|
||||||
set(BUILTIN_ICON_FONT font_awesome_20_4)
|
set(BUILTIN_ICON_FONT font_awesome_20_4)
|
||||||
set(DEFAULT_EMOJI_COLLECTION twemoji_64)
|
set(DEFAULT_EMOJI_COLLECTION noto-emoji_128)
|
||||||
elseif(CONFIG_BOARD_TYPE_ATK_DNESP32S3_BOX)
|
elseif(CONFIG_BOARD_TYPE_ATK_DNESP32S3_BOX)
|
||||||
set(BOARD_TYPE "atk-dnesp32s3-box")
|
set(BOARD_TYPE "atk-dnesp32s3-box")
|
||||||
set(BUILTIN_TEXT_FONT font_puhui_basic_20_4)
|
set(BUILTIN_TEXT_FONT font_noto_basic_20_4)
|
||||||
set(BUILTIN_ICON_FONT font_awesome_20_4)
|
set(BUILTIN_ICON_FONT font_awesome_20_4)
|
||||||
set(DEFAULT_EMOJI_COLLECTION twemoji_64)
|
set(DEFAULT_EMOJI_COLLECTION noto-emoji_128)
|
||||||
elseif(CONFIG_BOARD_TYPE_ATK_DNESP32S3_BOX0)
|
elseif(CONFIG_BOARD_TYPE_ATK_DNESP32S3_BOX0)
|
||||||
set(BOARD_TYPE "atk-dnesp32s3-box0")
|
set(BOARD_TYPE "atk-dnesp32s3-box0")
|
||||||
set(BUILTIN_TEXT_FONT font_puhui_basic_20_4)
|
set(BUILTIN_TEXT_FONT font_noto_basic_20_4)
|
||||||
set(BUILTIN_ICON_FONT font_awesome_20_4)
|
set(BUILTIN_ICON_FONT font_awesome_20_4)
|
||||||
set(DEFAULT_EMOJI_COLLECTION twemoji_64)
|
set(DEFAULT_EMOJI_COLLECTION noto-emoji_128)
|
||||||
elseif(CONFIG_BOARD_TYPE_ATK_DNESP32S3_BOX2_WIFI)
|
elseif(CONFIG_BOARD_TYPE_ATK_DNESP32S3_BOX2_WIFI)
|
||||||
set(BOARD_TYPE "atk-dnesp32s3-box2-wifi")
|
set(BOARD_TYPE "atk-dnesp32s3-box2-wifi")
|
||||||
set(BUILTIN_TEXT_FONT font_puhui_basic_20_4)
|
set(BUILTIN_TEXT_FONT font_noto_basic_20_4)
|
||||||
set(BUILTIN_ICON_FONT font_awesome_20_4)
|
set(BUILTIN_ICON_FONT font_awesome_20_4)
|
||||||
set(DEFAULT_EMOJI_COLLECTION twemoji_64)
|
set(DEFAULT_EMOJI_COLLECTION noto-emoji_128)
|
||||||
elseif(CONFIG_BOARD_TYPE_ATK_DNESP32S3_BOX2_4G)
|
elseif(CONFIG_BOARD_TYPE_ATK_DNESP32S3_BOX2_4G)
|
||||||
set(BOARD_TYPE "atk-dnesp32s3-box2-4g")
|
set(BOARD_TYPE "atk-dnesp32s3-box2-4g")
|
||||||
set(BUILTIN_TEXT_FONT font_puhui_basic_20_4)
|
set(BUILTIN_TEXT_FONT font_noto_basic_20_4)
|
||||||
set(BUILTIN_ICON_FONT font_awesome_20_4)
|
set(BUILTIN_ICON_FONT font_awesome_20_4)
|
||||||
set(DEFAULT_EMOJI_COLLECTION twemoji_64)
|
set(DEFAULT_EMOJI_COLLECTION noto-emoji_128)
|
||||||
elseif(CONFIG_BOARD_TYPE_ATK_DNESP32S3M_WIFI)
|
elseif(CONFIG_BOARD_TYPE_ATK_DNESP32S3M_WIFI)
|
||||||
set(BOARD_TYPE "atk-dnesp32s3m-wifi")
|
set(BOARD_TYPE "atk-dnesp32s3m-wifi")
|
||||||
set(BUILTIN_TEXT_FONT font_puhui_basic_16_4)
|
set(BUILTIN_TEXT_FONT font_puhui_basic_16_4)
|
||||||
@ -499,24 +499,24 @@ elseif(CONFIG_BOARD_TYPE_XINGZHI_CUBE_0_96OLED_ML307)
|
|||||||
set(BUILTIN_ICON_FONT font_awesome_14_1)
|
set(BUILTIN_ICON_FONT font_awesome_14_1)
|
||||||
elseif(CONFIG_BOARD_TYPE_XINGZHI_CUBE_1_54TFT_WIFI)
|
elseif(CONFIG_BOARD_TYPE_XINGZHI_CUBE_1_54TFT_WIFI)
|
||||||
set(BOARD_TYPE "xingzhi-cube-1.54tft-wifi")
|
set(BOARD_TYPE "xingzhi-cube-1.54tft-wifi")
|
||||||
set(BUILTIN_TEXT_FONT font_puhui_basic_20_4)
|
set(BUILTIN_TEXT_FONT font_noto_basic_20_4)
|
||||||
set(BUILTIN_ICON_FONT font_awesome_20_4)
|
set(BUILTIN_ICON_FONT font_awesome_20_4)
|
||||||
set(DEFAULT_EMOJI_COLLECTION twemoji_64)
|
set(DEFAULT_EMOJI_COLLECTION noto-emoji_128)
|
||||||
elseif(CONFIG_BOARD_TYPE_XINGZHI_CUBE_1_54TFT_ML307)
|
elseif(CONFIG_BOARD_TYPE_XINGZHI_CUBE_1_54TFT_ML307)
|
||||||
set(BOARD_TYPE "xingzhi-cube-1.54tft-ml307")
|
set(BOARD_TYPE "xingzhi-cube-1.54tft-ml307")
|
||||||
set(BUILTIN_TEXT_FONT font_puhui_basic_20_4)
|
set(BUILTIN_TEXT_FONT font_noto_basic_20_4)
|
||||||
set(BUILTIN_ICON_FONT font_awesome_20_4)
|
set(BUILTIN_ICON_FONT font_awesome_20_4)
|
||||||
set(DEFAULT_EMOJI_COLLECTION twemoji_64)
|
set(DEFAULT_EMOJI_COLLECTION noto-emoji_128)
|
||||||
elseif(CONFIG_BOARD_TYPE_XINGZHI_METAL_1_54_WIFI)
|
elseif(CONFIG_BOARD_TYPE_XINGZHI_METAL_1_54_WIFI)
|
||||||
set(BOARD_TYPE "xingzhi-metal-1.54-wifi")
|
set(BOARD_TYPE "xingzhi-metal-1.54-wifi")
|
||||||
set(BUILTIN_TEXT_FONT font_puhui_basic_20_4)
|
set(BUILTIN_TEXT_FONT font_noto_basic_20_4)
|
||||||
set(BUILTIN_ICON_FONT font_awesome_20_4)
|
set(BUILTIN_ICON_FONT font_awesome_20_4)
|
||||||
set(DEFAULT_EMOJI_COLLECTION twemoji_64)
|
set(DEFAULT_EMOJI_COLLECTION noto-emoji_128)
|
||||||
elseif(CONFIG_BOARD_TYPE_SEEED_STUDIO_SENSECAP_WATCHER)
|
elseif(CONFIG_BOARD_TYPE_SEEED_STUDIO_SENSECAP_WATCHER)
|
||||||
set(BOARD_TYPE "sensecap-watcher")
|
set(BOARD_TYPE "sensecap-watcher")
|
||||||
set(BUILTIN_TEXT_FONT font_puhui_basic_30_4)
|
set(BUILTIN_TEXT_FONT font_noto_basic_30_4)
|
||||||
set(BUILTIN_ICON_FONT font_awesome_20_4)
|
set(BUILTIN_ICON_FONT font_awesome_20_4)
|
||||||
set(DEFAULT_EMOJI_COLLECTION twemoji_64)
|
set(DEFAULT_EMOJI_COLLECTION noto-emoji_128)
|
||||||
elseif(CONFIG_BOARD_TYPE_DOIT_S3_AIBOX)
|
elseif(CONFIG_BOARD_TYPE_DOIT_S3_AIBOX)
|
||||||
set(BOARD_TYPE "doit-s3-aibox")
|
set(BOARD_TYPE "doit-s3-aibox")
|
||||||
elseif(CONFIG_BOARD_TYPE_MIXGO_NOVA)
|
elseif(CONFIG_BOARD_TYPE_MIXGO_NOVA)
|
||||||
|
|||||||
@ -808,7 +808,8 @@ void Application::HandleStateChangedEvent() {
|
|||||||
case kDeviceStateUnknown:
|
case kDeviceStateUnknown:
|
||||||
case kDeviceStateIdle:
|
case kDeviceStateIdle:
|
||||||
display->SetStatus(Lang::Strings::STANDBY);
|
display->SetStatus(Lang::Strings::STANDBY);
|
||||||
display->SetEmotion("neutral");
|
display->ClearChatMessages(); // Clear messages first
|
||||||
|
display->SetEmotion("neutral"); // Then set emotion (wechat mode checks child count)
|
||||||
audio_service_.EnableVoiceProcessing(false);
|
audio_service_.EnableVoiceProcessing(false);
|
||||||
audio_service_.EnableWakeWordDetection(true);
|
audio_service_.EnableWakeWordDetection(true);
|
||||||
break;
|
break;
|
||||||
|
|||||||
@ -45,6 +45,10 @@ void Display::SetChatMessage(const char* role, const char* content) {
|
|||||||
ESP_LOGW(TAG, " %s", content);
|
ESP_LOGW(TAG, " %s", content);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void Display::ClearChatMessages() {
|
||||||
|
// Default empty implementation, override in subclasses if needed
|
||||||
|
}
|
||||||
|
|
||||||
void Display::SetTheme(Theme* theme) {
|
void Display::SetTheme(Theme* theme) {
|
||||||
current_theme_ = theme;
|
current_theme_ = theme;
|
||||||
Settings settings("display", true);
|
Settings settings("display", true);
|
||||||
|
|||||||
@ -35,6 +35,7 @@ public:
|
|||||||
virtual void ShowNotification(const std::string ¬ification, int duration_ms = 3000);
|
virtual void ShowNotification(const std::string ¬ification, int duration_ms = 3000);
|
||||||
virtual void SetEmotion(const char* emotion);
|
virtual void SetEmotion(const char* emotion);
|
||||||
virtual void SetChatMessage(const char* role, const char* content);
|
virtual void SetChatMessage(const char* role, const char* content);
|
||||||
|
virtual void ClearChatMessages();
|
||||||
virtual void SetTheme(Theme* theme);
|
virtual void SetTheme(Theme* theme);
|
||||||
virtual Theme* GetTheme() { return current_theme_; }
|
virtual Theme* GetTheme() { return current_theme_; }
|
||||||
virtual void UpdateStatusBar(bool update_all = false);
|
virtual void UpdateStatusBar(bool update_all = false);
|
||||||
|
|||||||
@ -556,7 +556,6 @@ void LcdDisplay::SetChatMessage(const char* role, const char* content) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
auto lvgl_theme = static_cast<LvglTheme*>(current_theme_);
|
auto lvgl_theme = static_cast<LvglTheme*>(current_theme_);
|
||||||
auto text_font = lvgl_theme->text_font()->font();
|
|
||||||
|
|
||||||
// Create a message bubble
|
// Create a message bubble
|
||||||
lv_obj_t* msg_bubble = lv_obj_create(content_);
|
lv_obj_t* msg_bubble = lv_obj_create(content_);
|
||||||
@ -774,6 +773,26 @@ void LcdDisplay::SetPreviewImage(std::unique_ptr<LvglImage> image) {
|
|||||||
// Auto-scroll to the image bubble
|
// Auto-scroll to the image bubble
|
||||||
lv_obj_scroll_to_view_recursive(img_bubble, LV_ANIM_ON);
|
lv_obj_scroll_to_view_recursive(img_bubble, LV_ANIM_ON);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void LcdDisplay::ClearChatMessages() {
|
||||||
|
DisplayLockGuard lock(this);
|
||||||
|
if (content_ == nullptr) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Use lv_obj_clean to delete all children of content_ (chat message bubbles)
|
||||||
|
lv_obj_clean(content_);
|
||||||
|
|
||||||
|
// Reset chat_message_label_ as it has been deleted
|
||||||
|
chat_message_label_ = nullptr;
|
||||||
|
|
||||||
|
// Show the centered AI logo (emoji_label_) again
|
||||||
|
if (emoji_label_ != nullptr) {
|
||||||
|
lv_obj_remove_flag(emoji_label_, LV_OBJ_FLAG_HIDDEN);
|
||||||
|
}
|
||||||
|
|
||||||
|
ESP_LOGI(TAG, "Chat messages cleared");
|
||||||
|
}
|
||||||
#else
|
#else
|
||||||
void LcdDisplay::SetupUI() {
|
void LcdDisplay::SetupUI() {
|
||||||
DisplayLockGuard lock(this);
|
DisplayLockGuard lock(this);
|
||||||
@ -891,29 +910,35 @@ void LcdDisplay::SetupUI() {
|
|||||||
lv_label_set_text(status_label_, Lang::Strings::INITIALIZING);
|
lv_label_set_text(status_label_, Lang::Strings::INITIALIZING);
|
||||||
lv_obj_align(status_label_, LV_ALIGN_CENTER, 0, 0);
|
lv_obj_align(status_label_, LV_ALIGN_CENTER, 0, 0);
|
||||||
|
|
||||||
/* Top layer: Bottom bar - fixed at bottom, minimum height 48, height can be adaptive */
|
/* Top layer: Bottom bar - fixed height at bottom */
|
||||||
bottom_bar_ = lv_obj_create(screen);
|
bottom_bar_ = lv_obj_create(screen);
|
||||||
lv_obj_set_width(bottom_bar_, LV_HOR_RES);
|
lv_obj_set_size(bottom_bar_, LV_HOR_RES, text_font->line_height + lvgl_theme->spacing(12));
|
||||||
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_radius(bottom_bar_, 0, 0);
|
||||||
lv_obj_set_style_bg_color(bottom_bar_, lvgl_theme->background_color(), 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_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_all(bottom_bar_, 0, 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_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_pad_right(bottom_bar_, lvgl_theme->spacing(4), 0);
|
||||||
lv_obj_set_style_border_width(bottom_bar_, 0, 0);
|
lv_obj_set_style_border_width(bottom_bar_, 0, 0);
|
||||||
|
lv_obj_set_scrollbar_mode(bottom_bar_, LV_SCROLLBAR_MODE_OFF);
|
||||||
lv_obj_align(bottom_bar_, LV_ALIGN_BOTTOM_MID, 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_ placed in bottom_bar_, single-line horizontal scroll */
|
||||||
chat_message_label_ = lv_label_create(bottom_bar_);
|
chat_message_label_ = lv_label_create(bottom_bar_);
|
||||||
lv_label_set_text(chat_message_label_, "");
|
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_obj_set_width(chat_message_label_, LV_HOR_RES - lvgl_theme->spacing(8));
|
||||||
lv_label_set_long_mode(chat_message_label_, LV_LABEL_LONG_WRAP); // Auto wrap mode
|
lv_label_set_long_mode(chat_message_label_, LV_LABEL_LONG_SCROLL_CIRCULAR);
|
||||||
lv_obj_set_style_text_align(chat_message_label_, LV_TEXT_ALIGN_CENTER, 0); // Center text alignment
|
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);
|
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_
|
lv_obj_align(chat_message_label_, LV_ALIGN_CENTER, 0, 0);
|
||||||
|
|
||||||
|
// Start scrolling after a delay (short text won't scroll)
|
||||||
|
static lv_anim_t a;
|
||||||
|
lv_anim_init(&a);
|
||||||
|
lv_anim_set_delay(&a, 1000);
|
||||||
|
lv_anim_set_repeat_count(&a, LV_ANIM_REPEAT_INFINITE);
|
||||||
|
lv_obj_set_style_anim(chat_message_label_, &a, LV_PART_MAIN);
|
||||||
|
lv_obj_set_style_anim_duration(chat_message_label_, lv_anim_speed_clamped(60, 300, 60000), LV_PART_MAIN);
|
||||||
|
|
||||||
low_battery_popup_ = lv_obj_create(screen);
|
low_battery_popup_ = lv_obj_create(screen);
|
||||||
lv_obj_set_scrollbar_mode(low_battery_popup_, LV_SCROLLBAR_MODE_OFF);
|
lv_obj_set_scrollbar_mode(low_battery_popup_, LV_SCROLLBAR_MODE_OFF);
|
||||||
@ -972,6 +997,14 @@ void LcdDisplay::SetChatMessage(const char* role, const char* content) {
|
|||||||
}
|
}
|
||||||
lv_label_set_text(chat_message_label_, content);
|
lv_label_set_text(chat_message_label_, content);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void LcdDisplay::ClearChatMessages() {
|
||||||
|
DisplayLockGuard lock(this);
|
||||||
|
// In non-wechat mode, just clear the chat message label
|
||||||
|
if (chat_message_label_ != nullptr) {
|
||||||
|
lv_label_set_text(chat_message_label_, "");
|
||||||
|
}
|
||||||
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
void LcdDisplay::SetEmotion(const char* emotion) {
|
void LcdDisplay::SetEmotion(const char* emotion) {
|
||||||
@ -1005,6 +1038,8 @@ void LcdDisplay::SetEmotion(const char* emotion) {
|
|||||||
gif_controller_ = std::make_unique<LvglGif>(image->image_dsc());
|
gif_controller_ = std::make_unique<LvglGif>(image->image_dsc());
|
||||||
|
|
||||||
if (gif_controller_->IsLoaded()) {
|
if (gif_controller_->IsLoaded()) {
|
||||||
|
// Set loop delay to 1000ms
|
||||||
|
gif_controller_->SetLoopDelay(3000);
|
||||||
// Set up frame update callback
|
// Set up frame update callback
|
||||||
gif_controller_->SetFrameCallback([this]() {
|
gif_controller_->SetFrameCallback([this]() {
|
||||||
lv_image_set_src(emoji_image_, gif_controller_->image_dsc());
|
lv_image_set_src(emoji_image_, gif_controller_->image_dsc());
|
||||||
|
|||||||
@ -49,6 +49,7 @@ public:
|
|||||||
~LcdDisplay();
|
~LcdDisplay();
|
||||||
virtual void SetEmotion(const char* emotion) override;
|
virtual void SetEmotion(const char* emotion) override;
|
||||||
virtual void SetChatMessage(const char* role, const char* content) override;
|
virtual void SetChatMessage(const char* role, const char* content) override;
|
||||||
|
virtual void ClearChatMessages() override;
|
||||||
virtual void SetPreviewImage(std::unique_ptr<LvglImage> image) override;
|
virtual void SetPreviewImage(std::unique_ptr<LvglImage> image) override;
|
||||||
|
|
||||||
// Add theme switching function
|
// Add theme switching function
|
||||||
|
|||||||
@ -5,7 +5,8 @@
|
|||||||
#define TAG "LvglGif"
|
#define TAG "LvglGif"
|
||||||
|
|
||||||
LvglGif::LvglGif(const lv_img_dsc_t* img_dsc)
|
LvglGif::LvglGif(const lv_img_dsc_t* img_dsc)
|
||||||
: gif_(nullptr), timer_(nullptr), last_call_(0), playing_(false), loaded_(false) {
|
: gif_(nullptr), timer_(nullptr), last_call_(0), playing_(false), loaded_(false),
|
||||||
|
loop_delay_ms_(0), loop_waiting_(false), loop_wait_start_(0) {
|
||||||
if (!img_dsc || !img_dsc->data) {
|
if (!img_dsc || !img_dsc->data) {
|
||||||
ESP_LOGE(TAG, "Invalid image descriptor");
|
ESP_LOGE(TAG, "Invalid image descriptor");
|
||||||
return;
|
return;
|
||||||
@ -66,6 +67,7 @@ void LvglGif::Start() {
|
|||||||
|
|
||||||
if (timer_) {
|
if (timer_) {
|
||||||
playing_ = true;
|
playing_ = true;
|
||||||
|
loop_waiting_ = false; // Reset loop waiting state
|
||||||
last_call_ = lv_tick_get();
|
last_call_ = lv_tick_get();
|
||||||
lv_timer_resume(timer_);
|
lv_timer_resume(timer_);
|
||||||
lv_timer_reset(timer_);
|
lv_timer_reset(timer_);
|
||||||
@ -104,9 +106,15 @@ void LvglGif::Stop() {
|
|||||||
lv_timer_pause(timer_);
|
lv_timer_pause(timer_);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Reset loop waiting state
|
||||||
|
loop_waiting_ = false;
|
||||||
|
|
||||||
if (gif_) {
|
if (gif_) {
|
||||||
gd_rewind(gif_);
|
gd_rewind(gif_);
|
||||||
NextFrame();
|
// Render first frame without advancing
|
||||||
|
if (gif_->canvas) {
|
||||||
|
gd_render_frame(gif_, gif_->canvas);
|
||||||
|
}
|
||||||
ESP_LOGD(TAG, "GIF animation stopped and rewound");
|
ESP_LOGD(TAG, "GIF animation stopped and rewound");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -134,6 +142,15 @@ void LvglGif::SetLoopCount(int32_t count) {
|
|||||||
gif_->loop_count = count;
|
gif_->loop_count = count;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
uint32_t LvglGif::GetLoopDelay() const {
|
||||||
|
return loop_delay_ms_;
|
||||||
|
}
|
||||||
|
|
||||||
|
void LvglGif::SetLoopDelay(uint32_t delay_ms) {
|
||||||
|
loop_delay_ms_ = delay_ms;
|
||||||
|
ESP_LOGD(TAG, "Loop delay set to %lu ms", delay_ms);
|
||||||
|
}
|
||||||
|
|
||||||
uint16_t LvglGif::width() const {
|
uint16_t LvglGif::width() const {
|
||||||
if (!loaded_ || !gif_) {
|
if (!loaded_ || !gif_) {
|
||||||
return 0;
|
return 0;
|
||||||
@ -157,6 +174,18 @@ void LvglGif::NextFrame() {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Check if we're in loop wait state (only for infinite loop GIFs with delay)
|
||||||
|
if (loop_waiting_) {
|
||||||
|
uint32_t wait_elapsed = lv_tick_elaps(loop_wait_start_);
|
||||||
|
if (wait_elapsed < loop_delay_ms_) {
|
||||||
|
// Still waiting for loop delay
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// Loop delay completed, continue playing
|
||||||
|
loop_waiting_ = false;
|
||||||
|
ESP_LOGD(TAG, "Loop delay completed, continuing GIF");
|
||||||
|
}
|
||||||
|
|
||||||
// Check if enough time has passed for the next frame
|
// Check if enough time has passed for the next frame
|
||||||
uint32_t elapsed = lv_tick_elaps(last_call_);
|
uint32_t elapsed = lv_tick_elaps(last_call_);
|
||||||
if (elapsed < gif_->gce.delay * 10) {
|
if (elapsed < gif_->gce.delay * 10) {
|
||||||
@ -165,15 +194,30 @@ void LvglGif::NextFrame() {
|
|||||||
|
|
||||||
last_call_ = lv_tick_get();
|
last_call_ = lv_tick_get();
|
||||||
|
|
||||||
|
// Save file position before getting next frame to detect loop
|
||||||
|
uint32_t pos_before = gif_->f_rw_p;
|
||||||
|
|
||||||
// Get next frame
|
// Get next frame
|
||||||
int has_next = gd_get_frame(gif_);
|
int has_next = gd_get_frame(gif_);
|
||||||
if (has_next == 0) {
|
if (has_next == 0) {
|
||||||
// Animation finished, pause timer
|
// Animation truly finished (non-infinite loop)
|
||||||
playing_ = false;
|
playing_ = false;
|
||||||
if (timer_) {
|
if (timer_) {
|
||||||
lv_timer_pause(timer_);
|
lv_timer_pause(timer_);
|
||||||
}
|
}
|
||||||
ESP_LOGD(TAG, "GIF animation completed");
|
ESP_LOGD(TAG, "GIF animation completed");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Detect loop by checking if file position jumped back (rewound to start)
|
||||||
|
// This works for looping GIFs regardless of when loop_count is set
|
||||||
|
if (loop_delay_ms_ > 0 && gif_->f_rw_p < pos_before) {
|
||||||
|
// File position decreased, meaning GIF looped back to beginning
|
||||||
|
// Start waiting before rendering this frame
|
||||||
|
loop_waiting_ = true;
|
||||||
|
loop_wait_start_ = lv_tick_get();
|
||||||
|
ESP_LOGD(TAG, "GIF completed one cycle, waiting %lu ms before next loop", loop_delay_ms_);
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Render current frame
|
// Render current frame
|
||||||
|
|||||||
@ -58,6 +58,17 @@ public:
|
|||||||
*/
|
*/
|
||||||
void SetLoopCount(int32_t count);
|
void SetLoopCount(int32_t count);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get loop delay in milliseconds (delay between loops)
|
||||||
|
*/
|
||||||
|
uint32_t GetLoopDelay() const;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set loop delay in milliseconds (delay between loops)
|
||||||
|
* @param delay_ms Delay in milliseconds before starting next loop. 0 means no delay.
|
||||||
|
*/
|
||||||
|
void SetLoopDelay(uint32_t delay_ms);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get GIF dimensions
|
* Get GIF dimensions
|
||||||
*/
|
*/
|
||||||
@ -86,6 +97,11 @@ private:
|
|||||||
bool playing_;
|
bool playing_;
|
||||||
bool loaded_;
|
bool loaded_;
|
||||||
|
|
||||||
|
// Loop delay configuration
|
||||||
|
uint32_t loop_delay_ms_; // Delay between loops in milliseconds
|
||||||
|
bool loop_waiting_; // Whether we're waiting for the next loop
|
||||||
|
uint32_t loop_wait_start_; // Timestamp when loop wait started
|
||||||
|
|
||||||
// Frame update callback
|
// Frame update callback
|
||||||
std::function<void()> frame_callback_;
|
std::function<void()> frame_callback_;
|
||||||
|
|
||||||
|
|||||||
@ -27,7 +27,7 @@ dependencies:
|
|||||||
version: ~0.2.1
|
version: ~0.2.1
|
||||||
rules:
|
rules:
|
||||||
- if: target not in [esp32]
|
- if: target not in [esp32]
|
||||||
78/xiaozhi-fonts: ~1.5.5
|
78/xiaozhi-fonts: ~1.6.0
|
||||||
espressif/led_strip: ~3.0.2
|
espressif/led_strip: ~3.0.2
|
||||||
espressif/esp_codec_dev: ~1.5.4
|
espressif/esp_codec_dev: ~1.5.4
|
||||||
espressif/esp-sr: ~2.3.0
|
espressif/esp-sr: ~2.3.0
|
||||||
|
|||||||
@ -693,7 +693,10 @@ def get_text_font_path(builtin_text_font, xiaozhi_fonts_path):
|
|||||||
|
|
||||||
# Convert from basic to common font name
|
# Convert from basic to common font name
|
||||||
# e.g., font_puhui_basic_16_4 -> font_puhui_common_16_4.bin
|
# e.g., font_puhui_basic_16_4 -> font_puhui_common_16_4.bin
|
||||||
font_name = builtin_text_font.replace('basic', 'common') + '.bin'
|
if builtin_text_font.startswith('font_noto_'):
|
||||||
|
font_name = builtin_text_font.replace('basic', 'qwen') + '.bin'
|
||||||
|
else:
|
||||||
|
font_name = builtin_text_font.replace('basic', 'common') + '.bin'
|
||||||
font_path = os.path.join(xiaozhi_fonts_path, 'cbin', font_name)
|
font_path = os.path.join(xiaozhi_fonts_path, 'cbin', font_name)
|
||||||
|
|
||||||
if os.path.exists(font_path):
|
if os.path.exists(font_path):
|
||||||
@ -709,7 +712,8 @@ def get_emoji_collection_path(default_emoji_collection, xiaozhi_fonts_path, proj
|
|||||||
Returns the emoji directory path or None if no emoji collection is needed
|
Returns the emoji directory path or None if no emoji collection is needed
|
||||||
|
|
||||||
Supports:
|
Supports:
|
||||||
- PNG emoji collections from xiaozhi-fonts (e.g., emojis_32)
|
- PNG emoji collections from xiaozhi-fonts (e.g., emojis_32, twemoji_64)
|
||||||
|
- GIF emoji collections from xiaozhi-fonts (e.g., noto-emoji_128, noto-emoji_64)
|
||||||
- Otto GIF emoji collection (otto-gif)
|
- Otto GIF emoji collection (otto-gif)
|
||||||
"""
|
"""
|
||||||
if not default_emoji_collection:
|
if not default_emoji_collection:
|
||||||
@ -729,13 +733,18 @@ def get_emoji_collection_path(default_emoji_collection, xiaozhi_fonts_path, proj
|
|||||||
print("Warning: project_root not provided, cannot locate otto-gif collection")
|
print("Warning: project_root not provided, cannot locate otto-gif collection")
|
||||||
return None
|
return None
|
||||||
|
|
||||||
# Default behavior for PNG emoji collections
|
# Try PNG emoji collections first (e.g., emojis_32, twemoji_64)
|
||||||
emoji_path = os.path.join(xiaozhi_fonts_path, 'png', default_emoji_collection)
|
emoji_path = os.path.join(xiaozhi_fonts_path, 'png', default_emoji_collection)
|
||||||
if os.path.exists(emoji_path):
|
if os.path.exists(emoji_path):
|
||||||
return emoji_path
|
return emoji_path
|
||||||
else:
|
|
||||||
print(f"Warning: Emoji collection directory not found: {emoji_path}")
|
# Try GIF emoji collections (e.g., noto-emoji_128, noto-emoji_64, noto-emoji_32)
|
||||||
return None
|
emoji_path = os.path.join(xiaozhi_fonts_path, 'gif', default_emoji_collection)
|
||||||
|
if os.path.exists(emoji_path):
|
||||||
|
return emoji_path
|
||||||
|
|
||||||
|
print(f"Warning: Emoji collection directory not found in png/ or gif/: {default_emoji_collection}")
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
def build_assets_integrated(wakenet_model_paths, multinet_model_paths, text_font_path, emoji_collection_path, extra_files_path, output_path, multinet_model_info=None):
|
def build_assets_integrated(wakenet_model_paths, multinet_model_paths, text_font_path, emoji_collection_path, extra_files_path, output_path, multinet_model_info=None):
|
||||||
|
|||||||
Reference in New Issue
Block a user