1. 按键驱动重构:GPIO中断+手动去抖 → iot_button组件(单击/双击/长按) 2. 新增key_nav按键导航管理器:上下文状态机 + Set界面焦点蓝色边框高亮 3. 移除所有触摸手势/点击事件(ScreenHome/ScreenImg/ScreenSet) 4. 应援灯颜色切换优化:DISPOFF→直接写GRAM→DISPON,消除分band刷新 5. 亮度调节按键化:BOOT +10% / KEY -10% / KEY长按退出 6. 休眠管理适配:按键唤醒统一由key_nav处理 7. 新增迁移总结文档 docs/touch-to-button-migration.md Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
536 lines
18 KiB
C
536 lines
18 KiB
C
#include "key_nav.h"
|
||
#include "button.h"
|
||
#include "sleep_mgr.h"
|
||
#include "pages.h"
|
||
#include "esp_log.h"
|
||
#include "esp_lvgl_port.h"
|
||
#include "freertos/FreeRTOS.h"
|
||
#include "freertos/task.h"
|
||
#include "../ui/ui.h"
|
||
#include "../ui/screens/ui_ScreenSet.h"
|
||
#include "../ui/screens/ui_ScreenImg.h"
|
||
|
||
static const char *TAG = "KEY_NAV";
|
||
|
||
// 当前导航上下文
|
||
static nav_context_t current_ctx = NAV_CTX_HOME;
|
||
|
||
// Set界面焦点状态
|
||
static set_focus_item_t set_focus = SET_FOCUS_NONE;
|
||
|
||
// Set界面可聚焦的LVGL对象(在Set界面init后有效)
|
||
static lv_obj_t *set_focus_objects[SET_FOCUS_COUNT] = {NULL};
|
||
|
||
// 蓝色边框样式常量
|
||
#define FOCUS_BORDER_COLOR 0x2196F3 // Material Blue
|
||
#define FOCUS_BORDER_WIDTH 3
|
||
|
||
// ==================== Set界面焦点管理 ====================
|
||
|
||
// 更新焦点对象指针(Set界面初始化后调用)
|
||
static void update_focus_objects(void)
|
||
{
|
||
set_focus_objects[SET_FOCUS_LOW_POWER] = ui_ImgLowPower;
|
||
set_focus_objects[SET_FOCUS_FLASHLIGHT] = ui_ImgFlashlight;
|
||
set_focus_objects[SET_FOCUS_DELETE] = ui_ImgDelete;
|
||
set_focus_objects[SET_FOCUS_BRIGHTNESS] = ui_SliderBrightness;
|
||
}
|
||
|
||
// 清除所有焦点边框
|
||
static void clear_all_focus_borders(void)
|
||
{
|
||
if (!lvgl_port_lock(50)) return;
|
||
for (int i = 0; i < SET_FOCUS_COUNT; i++) {
|
||
if (set_focus_objects[i]) {
|
||
lv_obj_set_style_border_width(set_focus_objects[i], 0, LV_PART_MAIN | LV_STATE_DEFAULT);
|
||
}
|
||
}
|
||
lvgl_port_unlock();
|
||
}
|
||
|
||
// 设置指定对象的焦点边框
|
||
static void set_focus_border(int index)
|
||
{
|
||
if (index < 0 || index >= SET_FOCUS_COUNT) return;
|
||
if (!set_focus_objects[index]) return;
|
||
|
||
if (!lvgl_port_lock(50)) return;
|
||
lv_obj_set_style_border_color(set_focus_objects[index],
|
||
lv_color_hex(FOCUS_BORDER_COLOR),
|
||
LV_PART_MAIN | LV_STATE_DEFAULT);
|
||
lv_obj_set_style_border_width(set_focus_objects[index],
|
||
FOCUS_BORDER_WIDTH,
|
||
LV_PART_MAIN | LV_STATE_DEFAULT);
|
||
lv_obj_set_style_border_opa(set_focus_objects[index],
|
||
LV_OPA_COVER,
|
||
LV_PART_MAIN | LV_STATE_DEFAULT);
|
||
lvgl_port_unlock();
|
||
}
|
||
|
||
// Set焦点移动到下一个
|
||
static void set_focus_next(void)
|
||
{
|
||
update_focus_objects();
|
||
clear_all_focus_borders();
|
||
|
||
// 从当前焦点移到下一个(循环:NONE→0→1→2→3→NONE)
|
||
set_focus++;
|
||
if (set_focus >= SET_FOCUS_COUNT) {
|
||
set_focus = SET_FOCUS_NONE;
|
||
ESP_LOGI(TAG, "Set焦点:无选中");
|
||
return;
|
||
}
|
||
|
||
set_focus_border(set_focus);
|
||
ESP_LOGI(TAG, "Set焦点:%d", set_focus);
|
||
}
|
||
|
||
// ==================== 全局返回Home ====================
|
||
|
||
// 派发到独立任务执行(iot_button回调在esp_timer中,不能vTaskDelay)
|
||
typedef struct {
|
||
int gpio_num;
|
||
btn_event_type_t event;
|
||
} nav_evt_t;
|
||
|
||
static void nav_task_go_home(void *arg)
|
||
{
|
||
ESP_LOGI(TAG, "双击BOOT:返回Home");
|
||
|
||
// 如果手电筒活跃,先退出
|
||
if (flashlight_is_active()) {
|
||
uint8_t saved_br = flashlight_get_saved_brightness();
|
||
flashlight_exit();
|
||
vTaskDelay(pdMS_TO_TICKS(80));
|
||
|
||
// 切换到Home
|
||
if (lvgl_port_lock(100)) {
|
||
_ui_screen_change(&ui_ScreenHome, LV_SCR_LOAD_ANIM_NONE, 0, 0, &ui_ScreenHome_screen_init);
|
||
lvgl_port_unlock();
|
||
}
|
||
|
||
vTaskDelay(pdMS_TO_TICKS(150));
|
||
pwm_set_brightness(saved_br);
|
||
} else {
|
||
// 隐藏Img删除容器(如果在Img界面)
|
||
if (lvgl_port_lock(100)) {
|
||
lv_obj_t *scr = lv_scr_act();
|
||
if (scr == ui_ScreenImg) {
|
||
ui_ScreenImg_hide_delete_container();
|
||
}
|
||
_ui_screen_change(&ui_ScreenHome, LV_SCR_LOAD_ANIM_NONE, 0, 0, &ui_ScreenHome_screen_init);
|
||
lvgl_port_unlock();
|
||
}
|
||
}
|
||
|
||
current_ctx = NAV_CTX_HOME;
|
||
set_focus = SET_FOCUS_NONE;
|
||
vTaskDelete(NULL);
|
||
}
|
||
|
||
// ==================== Home界面按键 ====================
|
||
|
||
static void nav_task_home_boot_click(void *arg)
|
||
{
|
||
// BOOT单击:切换到Set
|
||
ESP_LOGI(TAG, "Home: BOOT单击→Set");
|
||
if (lvgl_port_lock(100)) {
|
||
ui_ScreenSet_set_previous(&ui_ScreenHome, &ui_ScreenHome_screen_init);
|
||
_ui_screen_change(&ui_ScreenSet, LV_SCR_LOAD_ANIM_NONE, 0, 0, &ui_ScreenSet_screen_init);
|
||
lvgl_port_unlock();
|
||
}
|
||
current_ctx = NAV_CTX_SET;
|
||
set_focus = SET_FOCUS_NONE;
|
||
vTaskDelete(NULL);
|
||
}
|
||
|
||
static void nav_task_home_key_click(void *arg)
|
||
{
|
||
// KEY单击:切换到Img
|
||
ESP_LOGI(TAG, "Home: KEY单击→Img");
|
||
if (lvgl_port_lock(100)) {
|
||
_ui_screen_change(&ui_ScreenImg, LV_SCR_LOAD_ANIM_NONE, 0, 0, &ui_ScreenImg_screen_init);
|
||
lvgl_port_unlock();
|
||
}
|
||
current_ctx = NAV_CTX_IMG;
|
||
vTaskDelete(NULL);
|
||
}
|
||
|
||
// ==================== Img界面按键 ====================
|
||
|
||
static void nav_task_img_key_click(void *arg)
|
||
{
|
||
if (current_ctx == NAV_CTX_IMG_DELETE) {
|
||
// 删除模式:KEY单击→取消删除
|
||
ESP_LOGI(TAG, "Img删除模式: KEY单击→取消");
|
||
if (lvgl_port_lock(100)) {
|
||
ui_ScreenImg_hide_delete_container();
|
||
lvgl_port_unlock();
|
||
}
|
||
current_ctx = NAV_CTX_IMG;
|
||
} else {
|
||
// 正常浏览:KEY单击→下一张图片
|
||
ESP_LOGI(TAG, "Img: KEY单击→下一张");
|
||
const char *next = get_next_image();
|
||
if (next) {
|
||
if (lvgl_port_lock(100)) {
|
||
update_ui_ImgBle(next);
|
||
lvgl_port_unlock();
|
||
}
|
||
}
|
||
}
|
||
vTaskDelete(NULL);
|
||
}
|
||
|
||
static void nav_task_img_boot_click(void *arg)
|
||
{
|
||
if (current_ctx == NAV_CTX_IMG_DELETE) {
|
||
// 删除模式:BOOT单击→确认删除
|
||
ESP_LOGI(TAG, "Img删除模式: BOOT单击→删除");
|
||
if (lvgl_port_lock(100)) {
|
||
if (delete_current_image()) {
|
||
const char *next = get_current_image();
|
||
if (next) {
|
||
update_ui_ImgBle(next);
|
||
}
|
||
}
|
||
// 删除后隐藏容器
|
||
ui_ScreenImg_hide_delete_container();
|
||
lvgl_port_unlock();
|
||
}
|
||
current_ctx = NAV_CTX_IMG;
|
||
} else {
|
||
// 正常浏览:BOOT单击→返回Home
|
||
ESP_LOGI(TAG, "Img: BOOT单击→Home");
|
||
if (lvgl_port_lock(100)) {
|
||
ui_ScreenImg_hide_delete_container();
|
||
_ui_screen_change(&ui_ScreenHome, LV_SCR_LOAD_ANIM_NONE, 0, 0, &ui_ScreenHome_screen_init);
|
||
lvgl_port_unlock();
|
||
}
|
||
current_ctx = NAV_CTX_HOME;
|
||
}
|
||
vTaskDelete(NULL);
|
||
}
|
||
|
||
// ==================== Set界面按键 ====================
|
||
|
||
static void nav_task_set_key_click(void *arg)
|
||
{
|
||
if (current_ctx == NAV_CTX_SET_BRIGHTNESS) {
|
||
// 亮度调节模式:KEY单击→亮度-10%
|
||
uint8_t br = pwm_get_brightness();
|
||
if (br > 10) br -= 10; else br = 10;
|
||
pwm_set_brightness(br);
|
||
// 同步更新UI
|
||
if (lvgl_port_lock(100)) {
|
||
if (ui_SliderBrightness) {
|
||
lv_slider_set_value(ui_SliderBrightness, br, LV_ANIM_OFF);
|
||
}
|
||
if (ui_LabelBrightness) {
|
||
char buf[8];
|
||
snprintf(buf, sizeof(buf), "%d%%", br);
|
||
lv_label_set_text(ui_LabelBrightness, buf);
|
||
}
|
||
lvgl_port_unlock();
|
||
}
|
||
ESP_LOGI(TAG, "亮度-10%%: %d%%", br);
|
||
} else {
|
||
// 焦点导航:KEY单击→下一个图标
|
||
set_focus_next();
|
||
}
|
||
vTaskDelete(NULL);
|
||
}
|
||
|
||
static void nav_task_set_boot_click(void *arg)
|
||
{
|
||
if (current_ctx == NAV_CTX_SET_BRIGHTNESS) {
|
||
// 亮度调节模式:BOOT单击→亮度+10%
|
||
uint8_t br = pwm_get_brightness();
|
||
if (br < 100) br += 10; else br = 100;
|
||
pwm_set_brightness(br);
|
||
if (lvgl_port_lock(100)) {
|
||
if (ui_SliderBrightness) {
|
||
lv_slider_set_value(ui_SliderBrightness, br, LV_ANIM_OFF);
|
||
}
|
||
if (ui_LabelBrightness) {
|
||
char buf[8];
|
||
snprintf(buf, sizeof(buf), "%d%%", br);
|
||
lv_label_set_text(ui_LabelBrightness, buf);
|
||
}
|
||
lvgl_port_unlock();
|
||
}
|
||
ESP_LOGI(TAG, "亮度+10%%: %d%%", br);
|
||
} else if (set_focus == SET_FOCUS_NONE) {
|
||
// 无焦点:BOOT单击→返回前一界面
|
||
ESP_LOGI(TAG, "Set: BOOT单击→返回前一界面");
|
||
clear_all_focus_borders();
|
||
if (lvgl_port_lock(100)) {
|
||
// 尝试返回前一界面,默认Home
|
||
_ui_screen_change(&ui_ScreenHome, LV_SCR_LOAD_ANIM_NONE, 0, 0, &ui_ScreenHome_screen_init);
|
||
lvgl_port_unlock();
|
||
}
|
||
current_ctx = NAV_CTX_HOME;
|
||
set_focus = SET_FOCUS_NONE;
|
||
} else {
|
||
// 有焦点:BOOT单击→激活选中功能
|
||
ESP_LOGI(TAG, "Set: BOOT激活焦点 %d", set_focus);
|
||
switch (set_focus) {
|
||
case SET_FOCUS_LOW_POWER: {
|
||
// 切换节能模式
|
||
bool enabled = sleep_mgr_is_enabled();
|
||
sleep_mgr_set_enabled(!enabled);
|
||
// 更新imgbtn的checked状态
|
||
if (lvgl_port_lock(100)) {
|
||
if (ui_ImgLowPower) {
|
||
if (!enabled) {
|
||
lv_obj_add_state(ui_ImgLowPower, LV_STATE_CHECKED);
|
||
} else {
|
||
lv_obj_clear_state(ui_ImgLowPower, LV_STATE_CHECKED);
|
||
}
|
||
// 强制重绘,确保imgbtn图片立即切换
|
||
lv_obj_invalidate(ui_ImgLowPower);
|
||
}
|
||
lvgl_port_unlock();
|
||
}
|
||
ESP_LOGI(TAG, "节能模式: %s", !enabled ? "开启" : "关闭");
|
||
break;
|
||
}
|
||
case SET_FOCUS_FLASHLIGHT: {
|
||
// 进入应援灯模式
|
||
ESP_LOGI(TAG, "进入应援灯模式");
|
||
if (lvgl_port_lock(100)) {
|
||
clear_all_focus_borders();
|
||
lvgl_port_unlock();
|
||
}
|
||
// show_flashlight是static的,需要通过模拟点击触发
|
||
// 直接调用外部接口
|
||
if (lvgl_port_lock(100)) {
|
||
lv_event_send(ui_ImgFlashlight, LV_EVENT_CLICKED, NULL);
|
||
lvgl_port_unlock();
|
||
}
|
||
current_ctx = NAV_CTX_FLASHLIGHT;
|
||
break;
|
||
}
|
||
case SET_FOCUS_DELETE: {
|
||
// 跳转Img删除模式
|
||
ESP_LOGI(TAG, "进入删除模式");
|
||
clear_all_focus_borders();
|
||
if (lvgl_port_lock(100)) {
|
||
_ui_screen_change(&ui_ScreenImg, LV_SCR_LOAD_ANIM_NONE, 0, 0, &ui_ScreenImg_screen_init);
|
||
ui_ScreenImg_show_delete_container();
|
||
// 高亮确认删除图标(提示用户BOOT=删除)
|
||
if (ui_ImageDel) {
|
||
lv_obj_set_style_border_color(ui_ImageDel,
|
||
lv_color_hex(FOCUS_BORDER_COLOR),
|
||
LV_PART_MAIN | LV_STATE_DEFAULT);
|
||
lv_obj_set_style_border_width(ui_ImageDel,
|
||
FOCUS_BORDER_WIDTH,
|
||
LV_PART_MAIN | LV_STATE_DEFAULT);
|
||
lv_obj_set_style_border_opa(ui_ImageDel,
|
||
LV_OPA_COVER,
|
||
LV_PART_MAIN | LV_STATE_DEFAULT);
|
||
}
|
||
lvgl_port_unlock();
|
||
}
|
||
current_ctx = NAV_CTX_IMG_DELETE;
|
||
set_focus = SET_FOCUS_NONE;
|
||
break;
|
||
}
|
||
case SET_FOCUS_BRIGHTNESS: {
|
||
// 进入亮度调节模式并直接增加10%(无需二次按键激活)
|
||
current_ctx = NAV_CTX_SET_BRIGHTNESS;
|
||
uint8_t br = pwm_get_brightness();
|
||
if (br <= 90) br += 10; else br = 100;
|
||
pwm_set_brightness(br);
|
||
if (lvgl_port_lock(100)) {
|
||
if (ui_SliderBrightness) {
|
||
lv_slider_set_value(ui_SliderBrightness, br, LV_ANIM_OFF);
|
||
}
|
||
if (ui_LabelBrightness) {
|
||
char buf[8];
|
||
snprintf(buf, sizeof(buf), "%d%%", br);
|
||
lv_label_set_text(ui_LabelBrightness, buf);
|
||
}
|
||
lvgl_port_unlock();
|
||
}
|
||
ESP_LOGI(TAG, "进入亮度调节+10%%: %d%%", br);
|
||
break;
|
||
}
|
||
default:
|
||
break;
|
||
}
|
||
}
|
||
vTaskDelete(NULL);
|
||
}
|
||
|
||
// Set界面KEY长按:退出亮度调节模式
|
||
static void nav_task_set_key_long(void *arg)
|
||
{
|
||
if (current_ctx == NAV_CTX_SET_BRIGHTNESS) {
|
||
ESP_LOGI(TAG, "长按KEY:退出亮度调节");
|
||
current_ctx = NAV_CTX_SET;
|
||
// 焦点移到下一个(循环回无选中)
|
||
set_focus_next();
|
||
}
|
||
vTaskDelete(NULL);
|
||
}
|
||
|
||
// ==================== 应援灯模式按键 ====================
|
||
|
||
static void nav_task_flashlight_boot_click(void *arg)
|
||
{
|
||
// BOOT单击:切换颜色
|
||
// flashlight_switch_color 使用 DISPOFF→直接写GRAM→DISPON,无需操作PWM
|
||
ESP_LOGI(TAG, "应援灯: BOOT单击→切换颜色");
|
||
|
||
flashlight_switch_color();
|
||
|
||
// DISPOFF/DISPON已处理显示切换,只需重启闪烁
|
||
flashlight_restart_blink();
|
||
|
||
vTaskDelete(NULL);
|
||
}
|
||
|
||
static void nav_task_flashlight_key_click(void *arg)
|
||
{
|
||
// KEY单击:退出应援灯,返回Set
|
||
ESP_LOGI(TAG, "应援灯: KEY单击→退出到Set");
|
||
uint8_t saved_br = flashlight_get_saved_brightness();
|
||
flashlight_exit();
|
||
vTaskDelay(pdMS_TO_TICKS(80));
|
||
|
||
if (lvgl_port_lock(100)) {
|
||
// 不切换界面,Set界面还在下面
|
||
lvgl_port_unlock();
|
||
}
|
||
|
||
vTaskDelay(pdMS_TO_TICKS(100));
|
||
pwm_set_brightness(saved_br);
|
||
|
||
current_ctx = NAV_CTX_SET;
|
||
set_focus = SET_FOCUS_NONE;
|
||
vTaskDelete(NULL);
|
||
}
|
||
|
||
// ==================== 按键事件总调度 ====================
|
||
|
||
// 创建任务执行按键处理(避免在esp_timer上下文中vTaskDelay)
|
||
#define NAV_TASK_STACK 3072
|
||
|
||
static void dispatch_task(TaskFunction_t func, const char *name)
|
||
{
|
||
xTaskCreate(func, name, NAV_TASK_STACK, NULL, 5, NULL);
|
||
}
|
||
|
||
// BOOT按键事件处理
|
||
static void boot_event_handler(int gpio_num, btn_event_type_t event, void *usr_data)
|
||
{
|
||
// 先处理屏幕唤醒
|
||
if (sleep_mgr_is_screen_off()) {
|
||
sleep_mgr_notify_activity();
|
||
return; // 仅唤醒,不触发业务
|
||
}
|
||
|
||
// 通知活动(重置休眠计时器)
|
||
sleep_mgr_notify_activity();
|
||
|
||
// 双击BOOT:全局返回Home
|
||
if (event == BTN_EVT_DOUBLE_CLICK) {
|
||
dispatch_task(nav_task_go_home, "go_home");
|
||
return;
|
||
}
|
||
|
||
// 单击BOOT:根据当前上下文分发
|
||
if (event == BTN_EVT_CLICK) {
|
||
switch (current_ctx) {
|
||
case NAV_CTX_HOME:
|
||
dispatch_task(nav_task_home_boot_click, "h_boot");
|
||
break;
|
||
case NAV_CTX_IMG:
|
||
case NAV_CTX_IMG_DELETE:
|
||
dispatch_task(nav_task_img_boot_click, "i_boot");
|
||
break;
|
||
case NAV_CTX_SET:
|
||
case NAV_CTX_SET_BRIGHTNESS:
|
||
dispatch_task(nav_task_set_boot_click, "s_boot");
|
||
break;
|
||
case NAV_CTX_FLASHLIGHT:
|
||
dispatch_task(nav_task_flashlight_boot_click, "f_boot");
|
||
break;
|
||
}
|
||
}
|
||
}
|
||
|
||
// KEY2按键事件处理
|
||
static void key2_event_handler(int gpio_num, btn_event_type_t event, void *usr_data)
|
||
{
|
||
// 先处理屏幕唤醒
|
||
if (sleep_mgr_is_screen_off()) {
|
||
sleep_mgr_notify_activity();
|
||
return;
|
||
}
|
||
|
||
sleep_mgr_notify_activity();
|
||
|
||
if (event == BTN_EVT_CLICK) {
|
||
switch (current_ctx) {
|
||
case NAV_CTX_HOME:
|
||
dispatch_task(nav_task_home_key_click, "h_key");
|
||
break;
|
||
case NAV_CTX_IMG:
|
||
case NAV_CTX_IMG_DELETE:
|
||
dispatch_task(nav_task_img_key_click, "i_key");
|
||
break;
|
||
case NAV_CTX_SET:
|
||
case NAV_CTX_SET_BRIGHTNESS:
|
||
dispatch_task(nav_task_set_key_click, "s_key");
|
||
break;
|
||
case NAV_CTX_FLASHLIGHT:
|
||
dispatch_task(nav_task_flashlight_key_click, "f_key");
|
||
break;
|
||
}
|
||
} else if (event == BTN_EVT_LONG_PRESS) {
|
||
switch (current_ctx) {
|
||
case NAV_CTX_SET_BRIGHTNESS:
|
||
dispatch_task(nav_task_set_key_long, "s_klong");
|
||
break;
|
||
default:
|
||
break;
|
||
}
|
||
}
|
||
}
|
||
|
||
// ==================== 初始化 ====================
|
||
|
||
void key_nav_init(void)
|
||
{
|
||
current_ctx = NAV_CTX_HOME;
|
||
set_focus = SET_FOCUS_NONE;
|
||
|
||
button_on_boot_event(boot_event_handler, NULL);
|
||
button_on_key2_event(key2_event_handler, NULL);
|
||
|
||
ESP_LOGI(TAG, "按键导航管理器初始化完成");
|
||
}
|
||
|
||
nav_context_t key_nav_get_context(void)
|
||
{
|
||
return current_ctx;
|
||
}
|
||
|
||
void key_nav_set_context(nav_context_t ctx)
|
||
{
|
||
current_ctx = ctx;
|
||
}
|
||
|
||
set_focus_item_t key_nav_get_set_focus(void)
|
||
{
|
||
return set_focus;
|
||
}
|
||
|
||
void key_nav_reset_set_focus(void)
|
||
{
|
||
clear_all_focus_borders();
|
||
set_focus = SET_FOCUS_NONE;
|
||
}
|