mirror of
https://github.com/78/xiaozhi-esp32.git
synced 2026-02-11 06:33:48 +00:00
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.
|
||||
idf_build_set_property(MINIMAL_BUILD ON)
|
||||
|
||||
set(PROJECT_VER "2.2.1")
|
||||
set(PROJECT_VER "2.2.2")
|
||||
project(xiaozhi)
|
||||
|
||||
@@ -104,28 +104,28 @@ elseif(CONFIG_BOARD_TYPE_BREAD_COMPACT_ESP32_LCD)
|
||||
set(BUILTIN_ICON_FONT font_awesome_14_1)
|
||||
elseif(CONFIG_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(DEFAULT_EMOJI_COLLECTION twemoji_64)
|
||||
set(DEFAULT_EMOJI_COLLECTION noto-emoji_128)
|
||||
elseif(CONFIG_BOARD_TYPE_DF_S3_AI_CAM)
|
||||
set(BOARD_TYPE "df-s3-ai-cam")
|
||||
elseif(CONFIG_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(DEFAULT_EMOJI_COLLECTION twemoji_64)
|
||||
set(DEFAULT_EMOJI_COLLECTION noto-emoji_128)
|
||||
set(EMOTE_RESOLUTION "320_240")
|
||||
elseif(CONFIG_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(DEFAULT_EMOJI_COLLECTION twemoji_64)
|
||||
set(DEFAULT_EMOJI_COLLECTION noto-emoji_128)
|
||||
set(EMOTE_RESOLUTION "320_240")
|
||||
elseif(CONFIG_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(DEFAULT_EMOJI_COLLECTION twemoji_64)
|
||||
set(DEFAULT_EMOJI_COLLECTION noto-emoji_128)
|
||||
elseif(CONFIG_BOARD_TYPE_KEVIN_BOX_2)
|
||||
set(BOARD_TYPE "kevin-box-2")
|
||||
set(BUILTIN_TEXT_FONT font_puhui_basic_14_1)
|
||||
@@ -134,14 +134,14 @@ elseif(CONFIG_BOARD_TYPE_KEVIN_C3)
|
||||
set(BOARD_TYPE "kevin-c3")
|
||||
elseif(CONFIG_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(DEFAULT_EMOJI_COLLECTION twemoji_64)
|
||||
set(DEFAULT_EMOJI_COLLECTION noto-emoji_128)
|
||||
elseif(CONFIG_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(DEFAULT_EMOJI_COLLECTION twemoji_64)
|
||||
set(DEFAULT_EMOJI_COLLECTION noto-emoji_128)
|
||||
elseif(CONFIG_BOARD_TYPE_KEVIN_YUYING_313LCD)
|
||||
set(BOARD_TYPE "kevin-yuying-313lcd")
|
||||
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)
|
||||
elseif(CONFIG_BOARD_TYPE_LICHUANG_DEV_S3)
|
||||
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(DEFAULT_EMOJI_COLLECTION twemoji_64)
|
||||
set(DEFAULT_EMOJI_COLLECTION noto-emoji_128)
|
||||
elseif(CONFIG_BOARD_TYPE_LICHUANG_DEV_C3)
|
||||
set(BOARD_TYPE "lichuang-c3-dev")
|
||||
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)
|
||||
elseif(CONFIG_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(DEFAULT_EMOJI_COLLECTION twemoji_64)
|
||||
set(DEFAULT_EMOJI_COLLECTION noto-emoji_128)
|
||||
elseif(CONFIG_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(DEFAULT_EMOJI_COLLECTION twemoji_64)
|
||||
set(DEFAULT_EMOJI_COLLECTION noto-emoji_128)
|
||||
elseif(CONFIG_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(DEFAULT_EMOJI_COLLECTION twemoji_64)
|
||||
set(DEFAULT_EMOJI_COLLECTION noto-emoji_128)
|
||||
elseif(CONFIG_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(DEFAULT_EMOJI_COLLECTION twemoji_64)
|
||||
set(DEFAULT_EMOJI_COLLECTION noto-emoji_128)
|
||||
elseif(CONFIG_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(DEFAULT_EMOJI_COLLECTION twemoji_64)
|
||||
set(DEFAULT_EMOJI_COLLECTION noto-emoji_128)
|
||||
elseif(CONFIG_BOARD_TYPE_ATK_DNESP32S3M_WIFI)
|
||||
set(BOARD_TYPE "atk-dnesp32s3m-wifi")
|
||||
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)
|
||||
elseif(CONFIG_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(DEFAULT_EMOJI_COLLECTION twemoji_64)
|
||||
set(DEFAULT_EMOJI_COLLECTION noto-emoji_128)
|
||||
elseif(CONFIG_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(DEFAULT_EMOJI_COLLECTION twemoji_64)
|
||||
set(DEFAULT_EMOJI_COLLECTION noto-emoji_128)
|
||||
elseif(CONFIG_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(DEFAULT_EMOJI_COLLECTION twemoji_64)
|
||||
set(DEFAULT_EMOJI_COLLECTION noto-emoji_128)
|
||||
elseif(CONFIG_BOARD_TYPE_SEEED_STUDIO_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(DEFAULT_EMOJI_COLLECTION twemoji_64)
|
||||
set(DEFAULT_EMOJI_COLLECTION noto-emoji_128)
|
||||
elseif(CONFIG_BOARD_TYPE_DOIT_S3_AIBOX)
|
||||
set(BOARD_TYPE "doit-s3-aibox")
|
||||
elseif(CONFIG_BOARD_TYPE_MIXGO_NOVA)
|
||||
|
||||
@@ -808,7 +808,8 @@ void Application::HandleStateChangedEvent() {
|
||||
case kDeviceStateUnknown:
|
||||
case kDeviceStateIdle:
|
||||
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_.EnableWakeWordDetection(true);
|
||||
break;
|
||||
|
||||
@@ -45,6 +45,10 @@ void Display::SetChatMessage(const char* role, const char* content) {
|
||||
ESP_LOGW(TAG, " %s", content);
|
||||
}
|
||||
|
||||
void Display::ClearChatMessages() {
|
||||
// Default empty implementation, override in subclasses if needed
|
||||
}
|
||||
|
||||
void Display::SetTheme(Theme* theme) {
|
||||
current_theme_ = theme;
|
||||
Settings settings("display", true);
|
||||
|
||||
@@ -35,6 +35,7 @@ public:
|
||||
virtual void ShowNotification(const std::string ¬ification, int duration_ms = 3000);
|
||||
virtual void SetEmotion(const char* emotion);
|
||||
virtual void SetChatMessage(const char* role, const char* content);
|
||||
virtual void ClearChatMessages();
|
||||
virtual void SetTheme(Theme* theme);
|
||||
virtual Theme* GetTheme() { return current_theme_; }
|
||||
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 text_font = lvgl_theme->text_font()->font();
|
||||
|
||||
// Create a message bubble
|
||||
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
|
||||
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
|
||||
void LcdDisplay::SetupUI() {
|
||||
DisplayLockGuard lock(this);
|
||||
@@ -891,29 +910,35 @@ void LcdDisplay::SetupUI() {
|
||||
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 */
|
||||
/* Top layer: Bottom bar - fixed height at bottom */
|
||||
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_size(bottom_bar_, LV_HOR_RES, text_font->line_height + lvgl_theme->spacing(12));
|
||||
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_all(bottom_bar_, 0, 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_set_scrollbar_mode(bottom_bar_, LV_SCROLLBAR_MODE_OFF);
|
||||
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_);
|
||||
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_width(chat_message_label_, LV_HOR_RES - lvgl_theme->spacing(8));
|
||||
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);
|
||||
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);
|
||||
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);
|
||||
}
|
||||
|
||||
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
|
||||
|
||||
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());
|
||||
|
||||
if (gif_controller_->IsLoaded()) {
|
||||
// Set loop delay to 1000ms
|
||||
gif_controller_->SetLoopDelay(3000);
|
||||
// Set up frame update callback
|
||||
gif_controller_->SetFrameCallback([this]() {
|
||||
lv_image_set_src(emoji_image_, gif_controller_->image_dsc());
|
||||
|
||||
@@ -49,6 +49,7 @@ public:
|
||||
~LcdDisplay();
|
||||
virtual void SetEmotion(const char* emotion) override;
|
||||
virtual void SetChatMessage(const char* role, const char* content) override;
|
||||
virtual void ClearChatMessages() override;
|
||||
virtual void SetPreviewImage(std::unique_ptr<LvglImage> image) override;
|
||||
|
||||
// Add theme switching function
|
||||
|
||||
@@ -5,7 +5,8 @@
|
||||
#define TAG "LvglGif"
|
||||
|
||||
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) {
|
||||
ESP_LOGE(TAG, "Invalid image descriptor");
|
||||
return;
|
||||
@@ -66,6 +67,7 @@ void LvglGif::Start() {
|
||||
|
||||
if (timer_) {
|
||||
playing_ = true;
|
||||
loop_waiting_ = false; // Reset loop waiting state
|
||||
last_call_ = lv_tick_get();
|
||||
lv_timer_resume(timer_);
|
||||
lv_timer_reset(timer_);
|
||||
@@ -104,9 +106,15 @@ void LvglGif::Stop() {
|
||||
lv_timer_pause(timer_);
|
||||
}
|
||||
|
||||
// Reset loop waiting state
|
||||
loop_waiting_ = false;
|
||||
|
||||
if (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");
|
||||
}
|
||||
}
|
||||
@@ -134,6 +142,15 @@ void LvglGif::SetLoopCount(int32_t 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 {
|
||||
if (!loaded_ || !gif_) {
|
||||
return 0;
|
||||
@@ -157,6 +174,18 @@ void LvglGif::NextFrame() {
|
||||
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
|
||||
uint32_t elapsed = lv_tick_elaps(last_call_);
|
||||
if (elapsed < gif_->gce.delay * 10) {
|
||||
@@ -165,15 +194,30 @@ void LvglGif::NextFrame() {
|
||||
|
||||
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
|
||||
int has_next = gd_get_frame(gif_);
|
||||
if (has_next == 0) {
|
||||
// Animation finished, pause timer
|
||||
// Animation truly finished (non-infinite loop)
|
||||
playing_ = false;
|
||||
if (timer_) {
|
||||
lv_timer_pause(timer_);
|
||||
}
|
||||
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
|
||||
|
||||
@@ -58,6 +58,17 @@ public:
|
||||
*/
|
||||
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
|
||||
*/
|
||||
@@ -86,6 +97,11 @@ private:
|
||||
bool playing_;
|
||||
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
|
||||
std::function<void()> frame_callback_;
|
||||
|
||||
|
||||
@@ -27,7 +27,7 @@ dependencies:
|
||||
version: ~0.2.1
|
||||
rules:
|
||||
- if: target not in [esp32]
|
||||
78/xiaozhi-fonts: ~1.5.5
|
||||
78/xiaozhi-fonts: ~1.6.0
|
||||
espressif/led_strip: ~3.0.2
|
||||
espressif/esp_codec_dev: ~1.5.4
|
||||
espressif/esp-sr: ~2.3.0
|
||||
|
||||
@@ -693,6 +693,9 @@ def get_text_font_path(builtin_text_font, xiaozhi_fonts_path):
|
||||
|
||||
# Convert from basic to common font name
|
||||
# e.g., font_puhui_basic_16_4 -> font_puhui_common_16_4.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)
|
||||
|
||||
@@ -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
|
||||
|
||||
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)
|
||||
"""
|
||||
if not default_emoji_collection:
|
||||
@@ -729,12 +733,17 @@ def get_emoji_collection_path(default_emoji_collection, xiaozhi_fonts_path, proj
|
||||
print("Warning: project_root not provided, cannot locate otto-gif collection")
|
||||
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)
|
||||
if os.path.exists(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)
|
||||
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
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user