Update to 2.0.1
This commit is contained in:
@@ -47,24 +47,9 @@ void Display::SetChatMessage(const char* role, const char* content) {
|
||||
|
||||
void Display::SetTheme(Theme* theme) {
|
||||
current_theme_ = theme;
|
||||
Settings settings("display", true);
|
||||
settings.SetString("theme", theme->name());
|
||||
}
|
||||
|
||||
void Display::SetMusicInfo(const char* song_name) {
|
||||
// 默认实现:对于非微信模式,将歌名显示在聊天消息标签中
|
||||
DisplayLockGuard lock(this);
|
||||
if (chat_message_label_ == nullptr) {
|
||||
return;
|
||||
}
|
||||
if (song_name != nullptr && strlen(song_name) > 0) {
|
||||
std::string music_text = "";
|
||||
music_text += song_name;
|
||||
lv_label_set_text(chat_message_label_, music_text.c_str());
|
||||
SetEmotion(FONT_AWESOME_MUSIC);
|
||||
} else {
|
||||
lv_label_set_text(chat_message_label_, "");
|
||||
SetEmotion(FONT_AWESOME_NEUTRAL);
|
||||
if (theme != nullptr) {
|
||||
Settings settings("display", true);
|
||||
settings.SetString("theme", theme->name());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -38,11 +38,12 @@ public:
|
||||
virtual void SetTheme(Theme* theme);
|
||||
virtual Theme* GetTheme() { return current_theme_; }
|
||||
virtual void UpdateStatusBar(bool update_all = false);
|
||||
virtual void SetMusicInfo(const char* song_name);
|
||||
virtual void SetPowerSaveMode(bool on);
|
||||
|
||||
// 音乐播放相关方法
|
||||
virtual void SetMusicInfo(const char* info) {}
|
||||
virtual void start() {}
|
||||
virtual void clearScreen() {} // 清除FFT显示,默认为空实现
|
||||
virtual void stopFft() {} // 停止FFT显示,默认为空实现
|
||||
virtual void stopFft() {}
|
||||
|
||||
inline int width() const { return width_; }
|
||||
inline int height() const { return height_; }
|
||||
@@ -56,8 +57,6 @@ protected:
|
||||
friend class DisplayLockGuard;
|
||||
virtual bool Lock(int timeout_ms = 0) = 0;
|
||||
virtual void Unlock() = 0;
|
||||
lv_obj_t* chat_message_label_ = nullptr;
|
||||
lv_obj_t *emotion_label_ = nullptr;
|
||||
};
|
||||
|
||||
|
||||
|
||||
57
main/display/esplog_display.cc
Normal file
57
main/display/esplog_display.cc
Normal file
@@ -0,0 +1,57 @@
|
||||
#include "esplog_display.h"
|
||||
|
||||
#include "esp_log.h"
|
||||
|
||||
#define TAG "Display2Log"
|
||||
|
||||
|
||||
EspLogDisplay::EspLogDisplay()
|
||||
{}
|
||||
|
||||
EspLogDisplay::~EspLogDisplay()
|
||||
{}
|
||||
|
||||
void EspLogDisplay::SetStatus(const char* status)
|
||||
{
|
||||
ESP_LOGW(TAG, "SetStatus: %s", status);
|
||||
}
|
||||
|
||||
void EspLogDisplay::ShowNotification(const char* notification, int duration_ms)
|
||||
{
|
||||
ESP_LOGW(TAG, "ShowNotification: %s", notification);
|
||||
}
|
||||
void EspLogDisplay::ShowNotification(const std::string ¬ification, int duration_ms)
|
||||
{
|
||||
ShowNotification(notification.c_str(), duration_ms);
|
||||
}
|
||||
|
||||
|
||||
void EspLogDisplay::SetEmotion(const char* emotion)
|
||||
{
|
||||
ESP_LOGW(TAG, "SetEmotion: %s", emotion);
|
||||
}
|
||||
|
||||
void EspLogDisplay::SetChatMessage(const char* role, const char* content)
|
||||
{
|
||||
ESP_LOGW(TAG, "Role:%s", role);
|
||||
ESP_LOGW(TAG, " %s", content);
|
||||
}
|
||||
|
||||
// 音乐播放相关(用日志模拟UI行为)
|
||||
// 显示当前播放的歌曲信息
|
||||
void EspLogDisplay::SetMusicInfo(const char* info)
|
||||
{
|
||||
ESP_LOGW(TAG, "MusicInfo: %s", info ? info : "");
|
||||
}
|
||||
|
||||
// 启动频谱显示(此处仅打印日志)
|
||||
void EspLogDisplay::start()
|
||||
{
|
||||
ESP_LOGW(TAG, "Spectrum start");
|
||||
}
|
||||
|
||||
// 停止频谱显示(此处仅打印日志)
|
||||
void EspLogDisplay::stopFft()
|
||||
{
|
||||
ESP_LOGW(TAG, "Spectrum stop");
|
||||
}
|
||||
33
main/display/esplog_display.h
Normal file
33
main/display/esplog_display.h
Normal file
@@ -0,0 +1,33 @@
|
||||
#ifndef ESPLOG_DISPLAY_H_
|
||||
#define ESPLOG_DISPLAY_H_
|
||||
|
||||
#include "display.h"
|
||||
|
||||
#include <string>
|
||||
|
||||
class EspLogDisplay : public Display {
|
||||
public:
|
||||
EspLogDisplay();
|
||||
~EspLogDisplay();
|
||||
|
||||
virtual void SetStatus(const char* status);
|
||||
virtual void ShowNotification(const char* notification, int duration_ms = 3000);
|
||||
virtual void ShowNotification(const std::string ¬ification, int duration_ms = 3000);
|
||||
virtual void SetEmotion(const char* emotion) override;
|
||||
virtual void SetChatMessage(const char* role, const char* content) override;
|
||||
|
||||
// 音乐播放相关(无屏版本用日志模拟)
|
||||
virtual void SetMusicInfo(const char* info) override;
|
||||
virtual void start() override;
|
||||
virtual void stopFft() override;
|
||||
|
||||
virtual inline void SetPreviewImage(const lv_img_dsc_t* image) {}
|
||||
virtual inline void SetTheme(const std::string& theme_name) {}
|
||||
virtual inline void UpdateStatusBar(bool update_all = false) override {}
|
||||
|
||||
protected:
|
||||
virtual inline bool Lock(int timeout_ms = 0) override { return true; }
|
||||
virtual inline void Unlock() override {}
|
||||
};
|
||||
|
||||
#endif
|
||||
@@ -28,30 +28,30 @@ void LcdDisplay::InitializeLcdThemes() {
|
||||
|
||||
// light theme
|
||||
auto light_theme = new LvglTheme("light");
|
||||
light_theme->set_background_color(lv_color_white());
|
||||
light_theme->set_text_color(lv_color_black());
|
||||
light_theme->set_chat_background_color(lv_color_hex(0xE0E0E0));
|
||||
light_theme->set_user_bubble_color(lv_color_hex(0x95EC69));
|
||||
light_theme->set_assistant_bubble_color(lv_color_white());
|
||||
light_theme->set_system_bubble_color(lv_color_hex(0xE0E0E0));
|
||||
light_theme->set_system_text_color(lv_color_hex(0x666666));
|
||||
light_theme->set_border_color(lv_color_hex(0xE0E0E0));
|
||||
light_theme->set_low_battery_color(lv_color_black());
|
||||
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_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(0x121212));
|
||||
dark_theme->set_text_color(lv_color_white());
|
||||
dark_theme->set_chat_background_color(lv_color_hex(0x1E1E1E));
|
||||
dark_theme->set_user_bubble_color(lv_color_hex(0x1A6C37));
|
||||
dark_theme->set_assistant_bubble_color(lv_color_hex(0x333333));
|
||||
dark_theme->set_system_bubble_color(lv_color_hex(0x2A2A2A));
|
||||
dark_theme->set_system_text_color(lv_color_hex(0xAAAAAA));
|
||||
dark_theme->set_border_color(lv_color_hex(0x333333));
|
||||
dark_theme->set_low_battery_color(lv_color_hex(0xFF0000));
|
||||
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_text_font(text_font);
|
||||
dark_theme->set_icon_font(icon_font);
|
||||
dark_theme->set_large_icon_font(large_icon_font);
|
||||
@@ -120,6 +120,9 @@ SpiLcdDisplay::SpiLcdDisplay(esp_lcd_panel_io_handle_t panel_io, esp_lcd_panel_h
|
||||
ESP_LOGI(TAG, "Initialize LVGL port");
|
||||
lvgl_port_cfg_t port_cfg = ESP_LVGL_PORT_INIT_CONFIG();
|
||||
port_cfg.task_priority = 1;
|
||||
#if CONFIG_SOC_CPU_CORES_NUM > 1
|
||||
port_cfg.task_affinity = 1;
|
||||
#endif
|
||||
lvgl_port_init(&port_cfg);
|
||||
|
||||
ESP_LOGI(TAG, "Adding LCD display");
|
||||
@@ -451,7 +454,7 @@ void LcdDisplay::SetupUI() {
|
||||
lv_obj_add_flag(low_battery_popup_, LV_OBJ_FLAG_HIDDEN);
|
||||
|
||||
emoji_image_ = lv_img_create(screen);
|
||||
lv_obj_align(emoji_image_, LV_ALIGN_TOP_MID, 0, text_font->line_height + lvgl_theme->spacing(2));
|
||||
lv_obj_align(emoji_image_, LV_ALIGN_TOP_MID, 0, text_font->line_height + lvgl_theme->spacing(8));
|
||||
|
||||
// Display AI logo while booting
|
||||
emoji_label_ = lv_label_create(screen);
|
||||
@@ -521,8 +524,7 @@ void LcdDisplay::SetChatMessage(const char* role, const char* content) {
|
||||
lv_obj_t* msg_bubble = lv_obj_create(content_);
|
||||
lv_obj_set_style_radius(msg_bubble, 8, 0);
|
||||
lv_obj_set_scrollbar_mode(msg_bubble, LV_SCROLLBAR_MODE_OFF);
|
||||
lv_obj_set_style_border_width(msg_bubble, 1, 0);
|
||||
lv_obj_set_style_border_color(msg_bubble, lvgl_theme->border_color(), 0);
|
||||
lv_obj_set_style_border_width(msg_bubble, 0, 0);
|
||||
lv_obj_set_style_pad_all(msg_bubble, lvgl_theme->spacing(4), 0);
|
||||
|
||||
// Create the message text
|
||||
@@ -561,6 +563,7 @@ void LcdDisplay::SetChatMessage(const char* role, const char* content) {
|
||||
if (strcmp(role, "user") == 0) {
|
||||
// User messages are right-aligned with green background
|
||||
lv_obj_set_style_bg_color(msg_bubble, lvgl_theme->user_bubble_color(), 0);
|
||||
lv_obj_set_style_bg_opa(msg_bubble, LV_OPA_70, 0);
|
||||
// Set text color for contrast
|
||||
lv_obj_set_style_text_color(msg_text, lvgl_theme->text_color(), 0);
|
||||
|
||||
@@ -576,6 +579,7 @@ void LcdDisplay::SetChatMessage(const char* role, const char* content) {
|
||||
} else if (strcmp(role, "assistant") == 0) {
|
||||
// Assistant messages are left-aligned with white background
|
||||
lv_obj_set_style_bg_color(msg_bubble, lvgl_theme->assistant_bubble_color(), 0);
|
||||
lv_obj_set_style_bg_opa(msg_bubble, LV_OPA_70, 0);
|
||||
// Set text color for contrast
|
||||
lv_obj_set_style_text_color(msg_text, lvgl_theme->text_color(), 0);
|
||||
|
||||
@@ -591,6 +595,7 @@ void LcdDisplay::SetChatMessage(const char* role, const char* content) {
|
||||
} else if (strcmp(role, "system") == 0) {
|
||||
// System messages are center-aligned with light gray background
|
||||
lv_obj_set_style_bg_color(msg_bubble, lvgl_theme->system_bubble_color(), 0);
|
||||
lv_obj_set_style_bg_opa(msg_bubble, LV_OPA_70, 0);
|
||||
// Set text color for contrast
|
||||
lv_obj_set_style_text_color(msg_text, lvgl_theme->system_text_color(), 0);
|
||||
|
||||
@@ -657,84 +662,88 @@ void LcdDisplay::SetChatMessage(const char* role, const char* content) {
|
||||
chat_message_label_ = msg_text;
|
||||
}
|
||||
|
||||
void LcdDisplay::SetPreviewImage(const lv_img_dsc_t* img_dsc) {
|
||||
void LcdDisplay::SetPreviewImage(std::unique_ptr<LvglImage> image) {
|
||||
DisplayLockGuard lock(this);
|
||||
if (content_ == nullptr) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (image == nullptr) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto lvgl_theme = static_cast<LvglTheme*>(current_theme_);
|
||||
if (img_dsc != nullptr) {
|
||||
// Create a message bubble for image preview
|
||||
lv_obj_t* img_bubble = lv_obj_create(content_);
|
||||
lv_obj_set_style_radius(img_bubble, 8, 0);
|
||||
lv_obj_set_scrollbar_mode(img_bubble, LV_SCROLLBAR_MODE_OFF);
|
||||
lv_obj_set_style_border_width(img_bubble, 1, 0);
|
||||
lv_obj_set_style_border_color(img_bubble, lvgl_theme->border_color(), 0);
|
||||
lv_obj_set_style_pad_all(img_bubble, lvgl_theme->spacing(4), 0);
|
||||
|
||||
// Set image bubble background color (similar to system message)
|
||||
lv_obj_set_style_bg_color(img_bubble, lvgl_theme->assistant_bubble_color(), 0);
|
||||
|
||||
// 设置自定义属性标记气泡类型
|
||||
lv_obj_set_user_data(img_bubble, (void*)"image");
|
||||
// Create a message bubble for image preview
|
||||
lv_obj_t* img_bubble = lv_obj_create(content_);
|
||||
lv_obj_set_style_radius(img_bubble, 8, 0);
|
||||
lv_obj_set_scrollbar_mode(img_bubble, LV_SCROLLBAR_MODE_OFF);
|
||||
lv_obj_set_style_border_width(img_bubble, 0, 0);
|
||||
lv_obj_set_style_pad_all(img_bubble, lvgl_theme->spacing(4), 0);
|
||||
|
||||
// Set image bubble background color (similar to system message)
|
||||
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);
|
||||
|
||||
// 设置自定义属性标记气泡类型
|
||||
lv_obj_set_user_data(img_bubble, (void*)"image");
|
||||
|
||||
// Create the image object inside the bubble
|
||||
lv_obj_t* preview_image = lv_image_create(img_bubble);
|
||||
|
||||
// Calculate appropriate size for the image
|
||||
lv_coord_t max_width = LV_HOR_RES * 70 / 100; // 70% of screen width
|
||||
lv_coord_t max_height = LV_VER_RES * 50 / 100; // 50% of screen height
|
||||
|
||||
// Calculate zoom factor to fit within maximum dimensions
|
||||
lv_coord_t img_width = img_dsc->header.w;
|
||||
lv_coord_t img_height = img_dsc->header.h;
|
||||
if (img_width == 0 || img_height == 0) {
|
||||
img_width = max_width;
|
||||
img_height = max_height;
|
||||
ESP_LOGW(TAG, "Invalid image dimensions: %ld x %ld, using default dimensions: %ld x %ld", img_width, img_height, max_width, max_height);
|
||||
}
|
||||
|
||||
lv_coord_t zoom_w = (max_width * 256) / img_width;
|
||||
lv_coord_t zoom_h = (max_height * 256) / img_height;
|
||||
lv_coord_t zoom = (zoom_w < zoom_h) ? zoom_w : zoom_h;
|
||||
|
||||
// Ensure zoom doesn't exceed 256 (100%)
|
||||
if (zoom > 256) zoom = 256;
|
||||
|
||||
// Set image properties
|
||||
lv_image_set_src(preview_image, img_dsc);
|
||||
lv_image_set_scale(preview_image, zoom);
|
||||
|
||||
// Add event handler to clean up copied data when image is deleted
|
||||
lv_obj_add_event_cb(preview_image, [](lv_event_t* e) {
|
||||
lv_img_dsc_t* img_dsc = (lv_img_dsc_t*)lv_event_get_user_data(e);
|
||||
if (img_dsc != nullptr) {
|
||||
heap_caps_free((void*)img_dsc->data);
|
||||
heap_caps_free(img_dsc);
|
||||
}
|
||||
}, LV_EVENT_DELETE, (void*)img_dsc);
|
||||
|
||||
// Calculate actual scaled image dimensions
|
||||
lv_coord_t scaled_width = (img_width * zoom) / 256;
|
||||
lv_coord_t scaled_height = (img_height * zoom) / 256;
|
||||
|
||||
// Set bubble size to be 16 pixels larger than the image (8 pixels on each side)
|
||||
lv_obj_set_width(img_bubble, scaled_width + 16);
|
||||
lv_obj_set_height(img_bubble, scaled_height + 16);
|
||||
|
||||
// Don't grow in flex layout
|
||||
lv_obj_set_style_flex_grow(img_bubble, 0, 0);
|
||||
|
||||
// Center the image within the bubble
|
||||
lv_obj_center(preview_image);
|
||||
|
||||
// Left align the image bubble like assistant messages
|
||||
lv_obj_align(img_bubble, LV_ALIGN_LEFT_MID, 0, 0);
|
||||
|
||||
// Auto-scroll to the image bubble
|
||||
lv_obj_scroll_to_view_recursive(img_bubble, LV_ANIM_ON);
|
||||
// Create the image object inside the bubble
|
||||
lv_obj_t* preview_image = lv_image_create(img_bubble);
|
||||
|
||||
// Calculate appropriate size for the image
|
||||
lv_coord_t max_width = LV_HOR_RES * 70 / 100; // 70% of screen width
|
||||
lv_coord_t max_height = LV_VER_RES * 50 / 100; // 50% of screen height
|
||||
|
||||
// Calculate zoom factor to fit within maximum dimensions
|
||||
auto img_dsc = image->image_dsc();
|
||||
lv_coord_t img_width = img_dsc->header.w;
|
||||
lv_coord_t img_height = img_dsc->header.h;
|
||||
if (img_width == 0 || img_height == 0) {
|
||||
img_width = max_width;
|
||||
img_height = max_height;
|
||||
ESP_LOGW(TAG, "Invalid image dimensions: %ld x %ld, using default dimensions: %ld x %ld", img_width, img_height, max_width, max_height);
|
||||
}
|
||||
|
||||
lv_coord_t zoom_w = (max_width * 256) / img_width;
|
||||
lv_coord_t zoom_h = (max_height * 256) / img_height;
|
||||
lv_coord_t zoom = (zoom_w < zoom_h) ? zoom_w : zoom_h;
|
||||
|
||||
// Ensure zoom doesn't exceed 256 (100%)
|
||||
if (zoom > 256) zoom = 256;
|
||||
|
||||
// Set image properties
|
||||
lv_image_set_src(preview_image, img_dsc);
|
||||
lv_image_set_scale(preview_image, zoom);
|
||||
|
||||
// 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(); // 释放智能指针的所有权
|
||||
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 对象来正确释放内存
|
||||
}
|
||||
}, LV_EVENT_DELETE, (void*)raw_image);
|
||||
|
||||
// Calculate actual scaled image dimensions
|
||||
lv_coord_t scaled_width = (img_width * zoom) / 256;
|
||||
lv_coord_t scaled_height = (img_height * zoom) / 256;
|
||||
|
||||
// Set bubble size to be 16 pixels larger than the image (8 pixels on each side)
|
||||
lv_obj_set_width(img_bubble, scaled_width + 16);
|
||||
lv_obj_set_height(img_bubble, scaled_height + 16);
|
||||
|
||||
// Don't grow in flex layout
|
||||
lv_obj_set_style_flex_grow(img_bubble, 0, 0);
|
||||
|
||||
// Center the image within the bubble
|
||||
lv_obj_center(preview_image);
|
||||
|
||||
// Left align the image bubble like assistant messages
|
||||
lv_obj_align(img_bubble, LV_ALIGN_LEFT_MID, 0, 0);
|
||||
|
||||
// Auto-scroll to the image bubble
|
||||
lv_obj_scroll_to_view_recursive(img_bubble, LV_ANIM_ON);
|
||||
}
|
||||
#else
|
||||
void LcdDisplay::SetupUI() {
|
||||
@@ -858,37 +867,35 @@ void LcdDisplay::SetupUI() {
|
||||
lv_obj_add_flag(low_battery_popup_, LV_OBJ_FLAG_HIDDEN);
|
||||
}
|
||||
|
||||
void LcdDisplay::SetPreviewImage(const lv_img_dsc_t* img_dsc) {
|
||||
void LcdDisplay::SetPreviewImage(std::unique_ptr<LvglImage> image) {
|
||||
DisplayLockGuard lock(this);
|
||||
if (preview_image_ == nullptr) {
|
||||
ESP_LOGE(TAG, "Preview image is not initialized");
|
||||
return;
|
||||
}
|
||||
|
||||
auto old_src = (const lv_img_dsc_t*)lv_image_get_src(preview_image_);
|
||||
if (old_src != nullptr) {
|
||||
lv_image_set_src(preview_image_, nullptr);
|
||||
heap_caps_free((void*)old_src->data);
|
||||
heap_caps_free((void*)old_src);
|
||||
}
|
||||
|
||||
if (img_dsc != nullptr) {
|
||||
// 设置图片源并显示预览图片
|
||||
lv_image_set_src(preview_image_, img_dsc);
|
||||
if (img_dsc->header.w > 0 && img_dsc->header.h > 0) {
|
||||
// zoom factor 0.5
|
||||
lv_image_set_scale(preview_image_, 128 * width_ / img_dsc->header.w);
|
||||
}
|
||||
// Hide emoji_box_
|
||||
lv_obj_add_flag(emoji_box_, LV_OBJ_FLAG_HIDDEN);
|
||||
lv_obj_remove_flag(preview_image_, LV_OBJ_FLAG_HIDDEN);
|
||||
esp_timer_stop(preview_timer_);
|
||||
ESP_ERROR_CHECK(esp_timer_start_once(preview_timer_, PREVIEW_IMAGE_DURATION_MS * 1000));
|
||||
} else {
|
||||
if (image == nullptr) {
|
||||
esp_timer_stop(preview_timer_);
|
||||
lv_obj_remove_flag(emoji_box_, LV_OBJ_FLAG_HIDDEN);
|
||||
lv_obj_add_flag(preview_image_, LV_OBJ_FLAG_HIDDEN);
|
||||
preview_image_cached_.reset();
|
||||
return;
|
||||
}
|
||||
|
||||
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
|
||||
lv_image_set_scale(preview_image_, 128 * width_ / img_dsc->header.w);
|
||||
}
|
||||
|
||||
// Hide emoji_box_
|
||||
lv_obj_add_flag(emoji_box_, LV_OBJ_FLAG_HIDDEN);
|
||||
lv_obj_remove_flag(preview_image_, LV_OBJ_FLAG_HIDDEN);
|
||||
esp_timer_stop(preview_timer_);
|
||||
ESP_ERROR_CHECK(esp_timer_start_once(preview_timer_, PREVIEW_IMAGE_DURATION_MS * 1000));
|
||||
}
|
||||
|
||||
void LcdDisplay::SetChatMessage(const char* role, const char* content) {
|
||||
@@ -955,7 +962,8 @@ void LcdDisplay::SetEmotion(const char* emotion) {
|
||||
|
||||
#if CONFIG_USE_WECHAT_MESSAGE_STYLE
|
||||
// Wechat message style中,如果emotion是neutral,则不显示
|
||||
if (strcmp(emotion, "neutral") == 0) {
|
||||
uint32_t child_count = lv_obj_get_child_cnt(content_);
|
||||
if (strcmp(emotion, "neutral") == 0 && child_count > 0) {
|
||||
// Stop GIF animation if running
|
||||
if (gif_controller_) {
|
||||
gif_controller_->Stop();
|
||||
@@ -1101,33 +1109,3 @@ void LcdDisplay::SetTheme(Theme* theme) {
|
||||
// No errors occurred. Save theme to settings
|
||||
Display::SetTheme(lvgl_theme);
|
||||
}
|
||||
|
||||
void LcdDisplay::SetMusicInfo(const char* song_name) {
|
||||
#if CONFIG_USE_WECHAT_MESSAGE_STYLE
|
||||
// 微信模式下不显示歌名,保持原有聊天功能
|
||||
return;
|
||||
#else
|
||||
// 非微信模式:在表情下方显示歌名
|
||||
DisplayLockGuard lock(this);
|
||||
if (chat_message_label_ == nullptr) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (song_name != nullptr && strlen(song_name) > 0) {
|
||||
std::string music_text = "";
|
||||
music_text += song_name;
|
||||
lv_label_set_text(chat_message_label_, music_text.c_str());
|
||||
|
||||
// 确保显示 emotion_label_ 和 chat_message_label_,隐藏 preview_image_
|
||||
if (emotion_label_ != nullptr) {
|
||||
lv_obj_clear_flag(emotion_label_, LV_OBJ_FLAG_HIDDEN);
|
||||
}
|
||||
if (preview_image_ != nullptr) {
|
||||
lv_obj_add_flag(preview_image_, LV_OBJ_FLAG_HIDDEN);
|
||||
}
|
||||
} else {
|
||||
// 清空歌名显示
|
||||
lv_label_set_text(chat_message_label_, "");
|
||||
}
|
||||
#endif
|
||||
}
|
||||
@@ -31,6 +31,7 @@ protected:
|
||||
lv_obj_t* emoji_box_ = nullptr;
|
||||
lv_obj_t* chat_message_label_ = nullptr;
|
||||
esp_timer_handle_t preview_timer_ = nullptr;
|
||||
std::unique_ptr<LvglImage> preview_image_cached_ = nullptr;
|
||||
|
||||
void InitializeLcdThemes();
|
||||
void SetupUI();
|
||||
@@ -44,14 +45,11 @@ protected:
|
||||
public:
|
||||
~LcdDisplay();
|
||||
virtual void SetEmotion(const char* emotion) override;
|
||||
virtual void SetPreviewImage(const lv_img_dsc_t* img_dsc) override;
|
||||
virtual void SetChatMessage(const char* role, const char* content) override;
|
||||
virtual void SetPreviewImage(std::unique_ptr<LvglImage> image) override;
|
||||
|
||||
// Add theme switching function
|
||||
virtual void SetTheme(Theme* theme) override;
|
||||
|
||||
// Add set music info function
|
||||
virtual void SetMusicInfo(const char* song_name) override;
|
||||
};
|
||||
|
||||
// SPI LCD显示器
|
||||
|
||||
@@ -3,6 +3,9 @@
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <stdbool.h>
|
||||
#include <esp_log.h>
|
||||
|
||||
#define TAG "GIF"
|
||||
|
||||
#define MIN(A, B) ((A) < (B) ? (A) : (B))
|
||||
#define MAX(A, B) ((A) > (B) ? (A) : (B))
|
||||
@@ -80,13 +83,13 @@ static gd_GIF * gif_open(gd_GIF * gif_base)
|
||||
/* Header */
|
||||
f_gif_read(gif_base, sigver, 3);
|
||||
if(memcmp(sigver, "GIF", 3) != 0) {
|
||||
LV_LOG_WARN("invalid signature");
|
||||
ESP_LOGW(TAG, "invalid signature");
|
||||
goto fail;
|
||||
}
|
||||
/* Version */
|
||||
f_gif_read(gif_base, sigver, 3);
|
||||
if(memcmp(sigver, "89a", 3) != 0) {
|
||||
LV_LOG_WARN("invalid version");
|
||||
if(memcmp(sigver, "89a", 3) != 0 && memcmp(sigver, "87a", 3) != 0) {
|
||||
ESP_LOGW(TAG, "invalid version");
|
||||
goto fail;
|
||||
}
|
||||
/* Width x Height */
|
||||
@@ -96,7 +99,7 @@ static gd_GIF * gif_open(gd_GIF * gif_base)
|
||||
f_gif_read(gif_base, &fdsz, 1);
|
||||
/* Presence of GCT */
|
||||
if(!(fdsz & 0x80)) {
|
||||
LV_LOG_WARN("no global color table");
|
||||
ESP_LOGW(TAG, "no global color table");
|
||||
goto fail;
|
||||
}
|
||||
/* Color Space's Depth */
|
||||
@@ -110,18 +113,18 @@ static gd_GIF * gif_open(gd_GIF * gif_base)
|
||||
f_gif_read(gif_base, &aspect, 1);
|
||||
/* Create gd_GIF Structure. */
|
||||
if(0 == width || 0 == height){
|
||||
LV_LOG_WARN("Zero size image");
|
||||
ESP_LOGW(TAG, "Zero size image");
|
||||
goto fail;
|
||||
}
|
||||
#if LV_GIF_CACHE_DECODE_DATA
|
||||
if(0 == (INT_MAX - sizeof(gd_GIF) - LZW_CACHE_SIZE) / width / height / 5){
|
||||
LV_LOG_WARN("Image dimensions are too large");
|
||||
ESP_LOGW(TAG, "Image dimensions are too large");
|
||||
goto fail;
|
||||
}
|
||||
gif = lv_malloc(sizeof(gd_GIF) + 5 * width * height + LZW_CACHE_SIZE);
|
||||
#else
|
||||
if(0 == (INT_MAX - sizeof(gd_GIF)) / width / height / 5){
|
||||
LV_LOG_WARN("Image dimensions are too large");
|
||||
ESP_LOGW(TAG, "Image dimensions are too large");
|
||||
goto fail;
|
||||
}
|
||||
gif = lv_malloc(sizeof(gd_GIF) + 5 * width * height);
|
||||
@@ -292,7 +295,7 @@ read_ext(gd_GIF * gif)
|
||||
read_application_ext(gif);
|
||||
break;
|
||||
default:
|
||||
LV_LOG_WARN("unknown extension: %02X\n", label);
|
||||
ESP_LOGW(TAG, "unknown extension: %02X\n", label);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -386,7 +389,7 @@ read_image_data(gd_GIF *gif, int interlace)
|
||||
/* copy data to frame buffer */
|
||||
while (sp > p_stack) {
|
||||
if(frm_off >= frm_size){
|
||||
LV_LOG_WARN("LZW table token overflows the frame buffer");
|
||||
ESP_LOGW(TAG, "LZW table token overflows the frame buffer");
|
||||
return -1;
|
||||
}
|
||||
*ptr++ = *(--sp);
|
||||
@@ -593,7 +596,7 @@ read_image_data(gd_GIF * gif, int interlace)
|
||||
entry = table->entries[key];
|
||||
str_len = entry.length;
|
||||
if(frm_off + str_len > frm_size){
|
||||
LV_LOG_WARN("LZW table token overflows the frame buffer");
|
||||
ESP_LOGW(TAG, "LZW table token overflows the frame buffer");
|
||||
lv_free(table);
|
||||
return -1;
|
||||
}
|
||||
@@ -635,7 +638,7 @@ read_image(gd_GIF * gif)
|
||||
gif->fw = read_num(gif);
|
||||
gif->fh = read_num(gif);
|
||||
if(gif->fx + (uint32_t)gif->fw > gif->width || gif->fy + (uint32_t)gif->fh > gif->height){
|
||||
LV_LOG_WARN("Frame coordinates out of image bounds");
|
||||
ESP_LOGW(TAG, "Frame coordinates out of image bounds");
|
||||
return -1;
|
||||
}
|
||||
f_gif_read(gif, &fisrz, 1);
|
||||
|
||||
@@ -14,6 +14,7 @@ LvglGif::LvglGif(const lv_img_dsc_t* img_dsc)
|
||||
gif_ = gd_open_gif_data(img_dsc->data);
|
||||
if (!gif_) {
|
||||
ESP_LOGE(TAG, "Failed to open GIF from image descriptor");
|
||||
return;
|
||||
}
|
||||
|
||||
// Setup LVGL image descriptor
|
||||
@@ -33,7 +34,7 @@ LvglGif::LvglGif(const lv_img_dsc_t* img_dsc)
|
||||
}
|
||||
|
||||
loaded_ = true;
|
||||
ESP_LOGI(TAG, "GIF loaded from image descriptor: %dx%d", gif_->width, gif_->height);
|
||||
ESP_LOGD(TAG, "GIF loaded from image descriptor: %dx%d", gif_->width, gif_->height);
|
||||
}
|
||||
|
||||
// Destructor
|
||||
@@ -72,7 +73,7 @@ void LvglGif::Start() {
|
||||
// Render first frame
|
||||
NextFrame();
|
||||
|
||||
ESP_LOGI(TAG, "GIF animation started");
|
||||
ESP_LOGD(TAG, "GIF animation started");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -80,7 +81,7 @@ void LvglGif::Pause() {
|
||||
if (timer_) {
|
||||
playing_ = false;
|
||||
lv_timer_pause(timer_);
|
||||
ESP_LOGI(TAG, "GIF animation paused");
|
||||
ESP_LOGD(TAG, "GIF animation paused");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -93,7 +94,7 @@ void LvglGif::Resume() {
|
||||
if (timer_) {
|
||||
playing_ = true;
|
||||
lv_timer_resume(timer_);
|
||||
ESP_LOGI(TAG, "GIF animation resumed");
|
||||
ESP_LOGD(TAG, "GIF animation resumed");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -106,7 +107,7 @@ void LvglGif::Stop() {
|
||||
if (gif_) {
|
||||
gd_rewind(gif_);
|
||||
NextFrame();
|
||||
ESP_LOGI(TAG, "GIF animation stopped and rewound");
|
||||
ESP_LOGD(TAG, "GIF animation stopped and rewound");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -172,7 +173,7 @@ void LvglGif::NextFrame() {
|
||||
if (timer_) {
|
||||
lv_timer_pause(timer_);
|
||||
}
|
||||
ESP_LOGI(TAG, "GIF animation completed");
|
||||
ESP_LOGD(TAG, "GIF animation completed");
|
||||
}
|
||||
|
||||
// Render current frame
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
#include <cstdlib>
|
||||
#include <cstring>
|
||||
#include <font_awesome.h>
|
||||
#include <img_converters.h>
|
||||
|
||||
#include "lvgl_display.h"
|
||||
#include "board.h"
|
||||
@@ -201,12 +202,7 @@ void LvglDisplay::UpdateStatusBar(bool update_all) {
|
||||
esp_pm_lock_release(pm_lock_);
|
||||
}
|
||||
|
||||
void LvglDisplay::SetPreviewImage(const lv_img_dsc_t* image) {
|
||||
// Do nothing but free the image
|
||||
if (image != nullptr) {
|
||||
heap_caps_free((void*)image->data);
|
||||
heap_caps_free((void*)image);
|
||||
}
|
||||
void LvglDisplay::SetPreviewImage(std::unique_ptr<LvglImage> image) {
|
||||
}
|
||||
|
||||
void LvglDisplay::SetPowerSaveMode(bool on) {
|
||||
@@ -218,3 +214,29 @@ void LvglDisplay::SetPowerSaveMode(bool on) {
|
||||
SetEmotion("neutral");
|
||||
}
|
||||
}
|
||||
|
||||
bool LvglDisplay::SnapshotToJpeg(uint8_t*& jpeg_output_data, size_t& jpeg_output_data_size, int quality) {
|
||||
DisplayLockGuard lock(this);
|
||||
|
||||
lv_obj_t* screen = lv_screen_active();
|
||||
lv_draw_buf_t* draw_buffer = lv_snapshot_take(screen, LV_COLOR_FORMAT_RGB565);
|
||||
if (draw_buffer == nullptr) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// swap bytes
|
||||
uint16_t* data = (uint16_t*)draw_buffer->data;
|
||||
size_t pixel_count = draw_buffer->data_size / 2;
|
||||
for (size_t i = 0; i < pixel_count; i++) {
|
||||
data[i] = __builtin_bswap16(data[i]);
|
||||
}
|
||||
|
||||
if (!fmt2jpg(draw_buffer->data, draw_buffer->data_size, draw_buffer->header.w, draw_buffer->header.h,
|
||||
PIXFORMAT_RGB565, quality, &jpeg_output_data, &jpeg_output_data_size)) {
|
||||
lv_draw_buf_destroy(draw_buffer);
|
||||
return false;
|
||||
}
|
||||
|
||||
lv_draw_buf_destroy(draw_buffer);
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
#define LVGL_DISPLAY_H
|
||||
|
||||
#include "display.h"
|
||||
#include "lvgl_image.h"
|
||||
|
||||
#include <lvgl.h>
|
||||
#include <esp_timer.h>
|
||||
@@ -19,9 +20,10 @@ public:
|
||||
virtual void SetStatus(const char* status);
|
||||
virtual void ShowNotification(const char* notification, int duration_ms = 3000);
|
||||
virtual void ShowNotification(const std::string ¬ification, int duration_ms = 3000);
|
||||
virtual void SetPreviewImage(const lv_img_dsc_t* image);
|
||||
virtual void SetPreviewImage(std::unique_ptr<LvglImage> image);
|
||||
virtual void UpdateStatusBar(bool update_all = false);
|
||||
virtual void SetPowerSaveMode(bool on);
|
||||
virtual bool SnapshotToJpeg(uint8_t*& jpeg_output_data, size_t& jpeg_output_size, int quality = 80);
|
||||
|
||||
protected:
|
||||
esp_pm_lock_handle_t pm_lock_ = nullptr;
|
||||
|
||||
@@ -2,19 +2,21 @@
|
||||
#include <cbin_font.h>
|
||||
|
||||
#include <esp_log.h>
|
||||
#include <stdexcept>
|
||||
#include <cstring>
|
||||
#include <esp_heap_caps.h>
|
||||
|
||||
#define TAG "LvglImage"
|
||||
|
||||
|
||||
LvglRawImage::LvglRawImage(void* data, size_t size) {
|
||||
bzero(&image_dsc_, sizeof(image_dsc_));
|
||||
image_dsc_.data_size = size;
|
||||
image_dsc_.data = static_cast<uint8_t*>(data);
|
||||
image_dsc_.header.magic = LV_IMAGE_HEADER_MAGIC;
|
||||
image_dsc_.header.cf = LV_COLOR_FORMAT_RAW_ALPHA;
|
||||
image_dsc_.header.w = 0;
|
||||
image_dsc_.header.h = 0;
|
||||
image_dsc_.data_size = size;
|
||||
image_dsc_.data = static_cast<uint8_t*>(data);
|
||||
}
|
||||
|
||||
bool LvglRawImage::IsGif() const {
|
||||
@@ -31,3 +33,32 @@ LvglCBinImage::~LvglCBinImage() {
|
||||
cbin_img_dsc_delete(image_dsc_);
|
||||
}
|
||||
}
|
||||
|
||||
LvglAllocatedImage::LvglAllocatedImage(void* data, size_t size) {
|
||||
bzero(&image_dsc_, sizeof(image_dsc_));
|
||||
image_dsc_.data_size = size;
|
||||
image_dsc_.data = static_cast<uint8_t*>(data);
|
||||
|
||||
if (lv_image_decoder_get_info(&image_dsc_, &image_dsc_.header) != LV_RESULT_OK) {
|
||||
ESP_LOGE(TAG, "Failed to get image info, data: %p size: %u", data, size);
|
||||
throw std::runtime_error("Failed to get image info");
|
||||
}
|
||||
}
|
||||
|
||||
LvglAllocatedImage::LvglAllocatedImage(void* data, size_t size, int width, int height, int stride, int color_format) {
|
||||
bzero(&image_dsc_, sizeof(image_dsc_));
|
||||
image_dsc_.data_size = size;
|
||||
image_dsc_.data = static_cast<uint8_t*>(data);
|
||||
image_dsc_.header.magic = LV_IMAGE_HEADER_MAGIC;
|
||||
image_dsc_.header.cf = color_format;
|
||||
image_dsc_.header.w = width;
|
||||
image_dsc_.header.h = height;
|
||||
image_dsc_.header.stride = stride;
|
||||
}
|
||||
|
||||
LvglAllocatedImage::~LvglAllocatedImage() {
|
||||
if (image_dsc_.data) {
|
||||
heap_caps_free((void*)image_dsc_.data);
|
||||
image_dsc_.data = nullptr;
|
||||
}
|
||||
}
|
||||
@@ -39,4 +39,15 @@ public:
|
||||
|
||||
private:
|
||||
const lv_img_dsc_t* image_dsc_;
|
||||
};
|
||||
|
||||
class LvglAllocatedImage : public LvglImage {
|
||||
public:
|
||||
LvglAllocatedImage(void* data, size_t size);
|
||||
LvglAllocatedImage(void* data, size_t size, int width, int height, int stride, int color_format);
|
||||
virtual ~LvglAllocatedImage();
|
||||
virtual const lv_img_dsc_t* image_dsc() const override { return &image_dsc_; }
|
||||
|
||||
private:
|
||||
lv_img_dsc_t image_dsc_;
|
||||
};
|
||||
@@ -40,6 +40,9 @@ OledDisplay::OledDisplay(esp_lcd_panel_io_handle_t panel_io, esp_lcd_panel_handl
|
||||
lvgl_port_cfg_t port_cfg = ESP_LVGL_PORT_INIT_CONFIG();
|
||||
port_cfg.task_priority = 1;
|
||||
port_cfg.task_stack = 6144;
|
||||
#if CONFIG_SOC_CPU_CORES_NUM > 1
|
||||
port_cfg.task_affinity = 1;
|
||||
#endif
|
||||
lvgl_port_init(&port_cfg);
|
||||
|
||||
ESP_LOGI(TAG, "Adding OLED display");
|
||||
|
||||
Reference in New Issue
Block a user