Baji_Rtc_Toy_Key/main/dzbj/ai_chat_ui.c
Rdzleo dbdd304905 代码初始化:
本项目为触摸版项目代码复制而来,基于此版本进行按键功能的适配!
2026-03-23 11:14:56 +08:00

251 lines
9.0 KiB
C
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#include "ai_chat_ui.h"
#include "lvgl.h"
#include "esp_lvgl_port.h"
#include "esp_log.h"
#include <string.h>
// lv_gif API 通过 lvgl.h 自动包含LV_USE_GIF=y 时)
static const char *TAG = "AI_CHAT_UI";
// 声明阿里巴巴普惠体 20px 4bppGB2312 简体中文+ASCII
LV_FONT_DECLARE(font_puhui_20_4);
#if LV_USE_GIF
// 需要访问 lv_gif_t 内部结构(暂停/恢复定时器)
#include "extra/libs/gif/lv_gif.h"
// 声明 GIF 表情资源压缩优化200x89
LV_IMG_DECLARE(emotion_angry_200_89);
LV_IMG_DECLARE(emotion_blink1_200_89);
LV_IMG_DECLARE(emotion_blink_fast_200_89);
LV_IMG_DECLARE(emotion_blink_slow_200_89);
LV_IMG_DECLARE(emotion_dizzy_200_89);
LV_IMG_DECLARE(emotion_happy_200_89);
LV_IMG_DECLARE(emotion_sad_200_89);
LV_IMG_DECLARE(emotion_sleep_200_89);
// 声明 GIF 图标资源压缩优化45x45
LV_IMG_DECLARE(icon_emotion_confused_45);
LV_IMG_DECLARE(icon_emotion_sleep_45);
LV_IMG_DECLARE(icon_emotion_thinking_45);
#endif
// 声明旧 PNG 表情资源LV_USE_GIF 未启用时的回退)
LV_IMG_DECLARE(ui_img_neutral_png);
LV_IMG_DECLARE(ui_img_happy_png);
LV_IMG_DECLARE(ui_img_sad_png);
LV_IMG_DECLARE(ui_img_angry_png);
LV_IMG_DECLARE(ui_img_crying_png);
LV_IMG_DECLARE(ui_img_funny_png);
LV_IMG_DECLARE(ui_img_laughing_png);
// 屏幕和控件
static lv_obj_t *ai_screen = NULL;
static lv_obj_t *status_label = NULL;
static lv_obj_t *chat_label = NULL;
#if LV_USE_GIF
static lv_obj_t *gif_emotion = NULL; // GIF 表情对象
static lv_obj_t *gif_icon = NULL; // GIF 图标对象(叠加在表情右上角)
static bool gif_animation_paused = false;
#else
static lv_obj_t *emoji_img = NULL; // 旧 PNG 表情
#endif
// 背景色(纯黑,与 GIF 背景一致避免色差)
#define BG_COLOR 0x000000
// 表情→GIF 映射表
typedef struct {
const char *name;
const lv_img_dsc_t *emotion_gif;
const lv_img_dsc_t *icon_gif; // NULL 表示无叠加图标
} emotion_gif_entry_t;
#if LV_USE_GIF
static const emotion_gif_entry_t emotion_gif_map[] = {
{"neutral", &emotion_blink_slow_200_89, NULL},
{"happy", &emotion_happy_200_89, NULL},
{"laughing", &emotion_happy_200_89, NULL},
{"funny", &emotion_happy_200_89, NULL},
{"loving", &emotion_happy_200_89, NULL},
{"relaxed", &emotion_happy_200_89, NULL},
{"delicious", &emotion_happy_200_89, NULL},
{"kissy", &emotion_happy_200_89, NULL},
{"confident", &emotion_happy_200_89, NULL},
{"sad", &emotion_sad_200_89, NULL},
{"crying", &emotion_sad_200_89, NULL},
{"angry", &emotion_angry_200_89, NULL},
{"surprised", &emotion_blink_fast_200_89, NULL},
{"shocked", &emotion_blink_fast_200_89, NULL},
{"silly", &emotion_blink_fast_200_89, NULL},
{"embarrassed", &emotion_blink_fast_200_89, &icon_emotion_thinking_45},
{"thinking", &emotion_blink_fast_200_89, &icon_emotion_thinking_45},
{"confused", &emotion_blink_fast_200_89, &icon_emotion_confused_45},
{"curious", &emotion_blink_fast_200_89, &icon_emotion_confused_45},
{"dizzy", &emotion_dizzy_200_89, NULL},
{"blink", &emotion_blink1_200_89, NULL},
{"sleepy", &emotion_sleep_200_89, &icon_emotion_sleep_45},
};
#define EMOTION_GIF_MAP_SIZE (sizeof(emotion_gif_map) / sizeof(emotion_gif_map[0]))
#endif
void ai_chat_screen_init(void) {
// 创建 AI 对话屏幕
ai_screen = lv_obj_create(NULL);
lv_obj_set_style_bg_color(ai_screen, lv_color_hex(BG_COLOR), 0);
lv_obj_set_style_bg_opa(ai_screen, 255, 0);
lv_obj_clear_flag(ai_screen, LV_OBJ_FLAG_SCROLLABLE);
#if LV_USE_GIF
// GIF 表情屏幕正中央200x89
gif_emotion = lv_gif_create(ai_screen);
lv_gif_set_src(gif_emotion, &emotion_blink_slow_200_89);
lv_obj_align(gif_emotion, LV_ALIGN_CENTER, 0, 0);
// 降低 GIF 定时器频率10ms→20ms平衡动画流畅度与 CPU 占用
lv_gif_t *gifobj = (lv_gif_t *)gif_emotion;
lv_timer_set_period(gifobj->timer, 20);
// GIF 图标表情上方居中45x45
// 表情高89顶边y=-44.5icon高45中心再上移几像素避免重叠
gif_icon = lv_gif_create(ai_screen);
lv_obj_align(gif_icon, LV_ALIGN_CENTER, 0, -70);
lv_obj_add_flag(gif_icon, LV_OBJ_FLAG_HIDDEN);
#else
// 旧 PNG 表情(回退方案)
emoji_img = lv_img_create(ai_screen);
lv_img_set_src(emoji_img, &ui_img_neutral_png);
lv_obj_align(emoji_img, LV_ALIGN_CENTER, 0, 0);
#endif
// 状态文本暂时隐藏GIF 模式下不需要文字)
status_label = lv_label_create(ai_screen);
lv_obj_set_style_text_font(status_label, &font_puhui_20_4, 0);
lv_obj_set_style_text_color(status_label, lv_color_white(), 0);
lv_obj_set_width(status_label, 300);
lv_obj_set_style_text_align(status_label, LV_TEXT_ALIGN_CENTER, 0);
lv_label_set_text(status_label, "");
lv_obj_align(status_label, LV_ALIGN_CENTER, 0, 90);
#if LV_USE_GIF
lv_obj_add_flag(status_label, LV_OBJ_FLAG_HIDDEN);
#endif
// 聊天消息文本(暂时隐藏,不显示字幕)
chat_label = lv_label_create(ai_screen);
lv_obj_set_style_text_font(chat_label, &font_puhui_20_4, 0);
lv_obj_set_style_text_color(chat_label, lv_color_hex(0xAAAAAA), 0);
lv_obj_set_width(chat_label, 300);
lv_obj_set_style_text_align(chat_label, LV_TEXT_ALIGN_LEFT, 0);
lv_label_set_text(chat_label, "");
lv_obj_align(chat_label, LV_ALIGN_CENTER, 0, 50);
lv_label_set_long_mode(chat_label, LV_LABEL_LONG_WRAP);
lv_obj_add_flag(chat_label, LV_OBJ_FLAG_HIDDEN);
// 加载屏幕
lv_disp_load_scr(ai_screen);
}
void ai_chat_set_status(const char* status) {
if (!status_label) return;
#if LV_USE_GIF
// GIF 模式下隐藏状态文字,仅记录日志
ESP_LOGD(TAG, "状态: %sGIF模式不显示", status);
(void)status;
#else
lvgl_port_lock(50);
lv_label_set_text(status_label, status);
lvgl_port_unlock();
#endif
}
void ai_chat_resume_animation(void) {
#if LV_USE_GIF
if (!gif_emotion || !gif_animation_paused) return;
if (!lvgl_port_lock(200)) return;
lv_gif_t *gifobj = (lv_gif_t *)gif_emotion;
lv_timer_resume(gifobj->timer);
gif_animation_paused = false;
ESP_LOGI(TAG, "GIF动画已恢复播放");
lvgl_port_unlock();
#endif
}
void ai_chat_set_emotion(const char* emotion) {
if (!emotion) return;
#if LV_USE_GIF
if (!gif_emotion) return;
if (!lvgl_port_lock(200)) {
ESP_LOGW(TAG, "LVGL锁超时跳过表情切换: %s", emotion);
return;
}
// 查找映射表
const emotion_gif_entry_t *entry = NULL;
for (int i = 0; i < EMOTION_GIF_MAP_SIZE; i++) {
if (strcmp(emotion, emotion_gif_map[i].name) == 0) {
entry = &emotion_gif_map[i];
break;
}
}
// 未找到映射,默认使用 neutral
if (!entry) {
ESP_LOGW(TAG, "未映射的GIF表情: %s, 使用 neutral", emotion);
entry = &emotion_gif_map[0]; // neutral
}
// 切换表情 GIFlv_gif_set_src 内部已自动启动播放)
lv_gif_set_src(gif_emotion, entry->emotion_gif);
// set_src 内部会重建 10ms 定时器,重新设置为 50ms 降低 CPU 占用
lv_gif_t *gifobj = (lv_gif_t *)gif_emotion;
lv_timer_set_period(gifobj->timer, 20);
gif_animation_paused = false;
// 处理叠加图标
if (entry->icon_gif) {
lv_gif_set_src(gif_icon, entry->icon_gif);
lv_gif_t *icon_gifobj = (lv_gif_t *)gif_icon;
lv_timer_set_period(icon_gifobj->timer, 20);
lv_obj_clear_flag(gif_icon, LV_OBJ_FLAG_HIDDEN);
} else {
// 隐藏图标时暂停其定时器,避免空跑浪费 CPU
lv_gif_t *icon_gifobj = (lv_gif_t *)gif_icon;
if (icon_gifobj->gif) {
lv_timer_pause(icon_gifobj->timer);
}
lv_obj_add_flag(gif_icon, LV_OBJ_FLAG_HIDDEN);
}
ESP_LOGI(TAG, "GIF表情切换: %s%s", emotion, entry->icon_gif ? " +图标" : "");
lvgl_port_unlock();
#else
// 旧 PNG 回退逻辑
if (!emoji_img) return;
lvgl_port_lock(50);
const lv_img_dsc_t *img = &ui_img_neutral_png;
if (strcmp(emotion, "neutral") == 0) img = &ui_img_neutral_png;
else if (strcmp(emotion, "happy") == 0) img = &ui_img_happy_png;
else if (strcmp(emotion, "sad") == 0) img = &ui_img_sad_png;
else if (strcmp(emotion, "angry") == 0) img = &ui_img_angry_png;
else if (strcmp(emotion, "surprised") == 0) img = &ui_img_funny_png;
else if (strcmp(emotion, "crying") == 0) img = &ui_img_crying_png;
else if (strcmp(emotion, "laughing") == 0) img = &ui_img_laughing_png;
lv_img_set_src(emoji_img, img);
lvgl_port_unlock();
#endif
}
void ai_chat_set_chat_message(const char* role, const char* content) {
if (!chat_label) return;
// 字幕暂时隐藏,不更新内容
// 后续恢复时去掉 return 和 ai_chat_screen_init 中的 LV_OBJ_FLAG_HIDDEN
(void)role;
(void)content;
}