Rdzleo f9dc7d4861 feat: 触屏版迁移到按键版,两键实现全部交互功能
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>
2026-03-12 14:57:14 +08:00

536 lines
18 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 "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;
}