增加UI 代码的注释
This commit is contained in:
+40
-32
@@ -7,24 +7,27 @@
|
||||
* 作者:王建锋
|
||||
* 创建日期:2026-08-29
|
||||
* 修改记录:
|
||||
* 2026-08-29 王建锋 创建初始版本
|
||||
* 2026-09-02 王建锋 弃用已废弃的 lv_menu 控件,改为基元控件页面导航
|
||||
* 2026-09-02 王建锋 修复选中行文字不可见(白底白字)
|
||||
* 2026-09-03 王建锋 主界面按设计稿重构(根页 7 项/装饰/状态行)
|
||||
* 2026-09-04 王建锋 重构为"横向级联框"模型(菜单页面设计.md):拆出显示层
|
||||
* ui_menu_view 与数据层 ui_menu_tab;本层只留导航状态机
|
||||
* 2026-09-08 王建锋 按代码规范 V1.0 补全分区注释与关键行注释;函数定义括号改 K&R 风格(同行)
|
||||
*/
|
||||
|
||||
/* 头文件包含区 */
|
||||
#include <stdint.h>
|
||||
|
||||
#include "ui_menu.h"
|
||||
|
||||
#include "ui_menu_view.h"
|
||||
|
||||
#include "ui_menu_tab.h"
|
||||
|
||||
#include <stdint.h>
|
||||
#include "lvgl.h"
|
||||
|
||||
#define DBG_TAG "[UI_MENU]"
|
||||
#include "dbg_log.h"
|
||||
|
||||
/* 静态变量定义区 */
|
||||
|
||||
/*
|
||||
* 导航状态:唯一"必须持久"的导航历史(共 17 B,见设计 §7.2);
|
||||
* 条目列表/框几何均可由 g_menu_tab + 本状态随时重算,不落 RAM
|
||||
@@ -34,6 +37,8 @@ static uint8_t s_sel[MENU_PAGE_MAX]; /* 各层选中下标(返回时还原
|
||||
static uint8_t s_depth; /* 当前深度(0=根框)= 焦点所在(最右)框 */
|
||||
static uint16_t s_scroll[MENU_PAGE_MAX]; /* 各框滚动偏移(px);框销毁再进入时还原 */
|
||||
|
||||
/* 函数定义区 */
|
||||
|
||||
/*
|
||||
* 函数功能:移选后的层刷新(高亮 + 跟随滚动 + 滚动位回读持久化)
|
||||
* 入口参数:depth - 层深度 uint8_t
|
||||
@@ -41,8 +46,7 @@ static uint16_t s_scroll[MENU_PAGE_MAX]; /* 各框滚动偏移(px);框销
|
||||
* 限定条件:该层框已建
|
||||
* 函数说明:显示层跟随滚动后,滚动位写回 s_scroll 持久(框重建时还原)
|
||||
*/
|
||||
static void menu_refresh_layer(uint8_t depth)
|
||||
{
|
||||
static void menu_refresh_layer(uint8_t depth) {
|
||||
ui_menu_view_frame_refresh(depth, s_sel[depth]);
|
||||
s_scroll[depth] = ui_menu_view_get_scroll(depth);
|
||||
}
|
||||
@@ -55,8 +59,7 @@ static void menu_refresh_layer(uint8_t depth)
|
||||
* 函数说明: 1. 叶子(无子项)同样压层,生成空白终点框(占位,无条目)
|
||||
* 2. 同节点再次进入:还原上次选中与滚动位;换节点则清零
|
||||
*/
|
||||
static void menu_open(uint8_t child)
|
||||
{
|
||||
static void menu_open(uint8_t child) {
|
||||
uint8_t d = s_depth;
|
||||
bool same = (s_path[d + 1u] == child);
|
||||
|
||||
@@ -77,8 +80,7 @@ static void menu_open(uint8_t child)
|
||||
* 函数说明: 1. 销毁最右框行对象(既有屏上框不动、无需重建)
|
||||
* 2. 焦点回父框;父层选中/滚动原样保留(选中不跳)
|
||||
*/
|
||||
static void menu_back(void)
|
||||
{
|
||||
static void menu_back(void) {
|
||||
ui_menu_view_frame_destroy(s_depth);
|
||||
s_depth--;
|
||||
ui_menu_view_focus(s_depth);
|
||||
@@ -95,38 +97,40 @@ static void menu_back(void)
|
||||
* 3. ESC/LEFT:关最右框(根框不响应)
|
||||
* 4. 空框(终点页 0 条目)只响应返回
|
||||
*/
|
||||
static void menu_key_handle(uint8_t depth, lv_key_t key)
|
||||
{
|
||||
uint8_t kids[MENU_ITEM_MAX];
|
||||
uint8_t cnt = ui_menu_tab_children(s_path[depth], kids, MENU_ITEM_MAX);
|
||||
static void menu_key_handle(uint8_t depth, lv_key_t key) {
|
||||
uint8_t kids[MENU_ITEM_MAX]; /* 本框条目列表:由 g_menu_tab 按 parent 现算,不落 RAM */
|
||||
uint8_t cnt = ui_menu_tab_children(s_path[depth], kids, MENU_ITEM_MAX); /* 条目数;0 = 空白终点框(进入叶子) */
|
||||
|
||||
switch (key) {
|
||||
case LV_KEY_UP:
|
||||
if (cnt > 0u) {
|
||||
if (cnt > 0u) { /* 空框(0 条目)不移选;同时防对 0 取模 */
|
||||
/* 移选上移、首尾环绕:先加 cnt 再减 1 保证无符号下不为负,% cnt 绕回首行 */
|
||||
s_sel[depth] = (uint8_t)((s_sel[depth] + (uint8_t)cnt - 1u) % cnt);
|
||||
menu_refresh_layer(depth);
|
||||
menu_refresh_layer(depth); /* 刷新高亮 + 跟随滚动 + 滚动位回读持久化 */
|
||||
}
|
||||
break;
|
||||
case LV_KEY_DOWN:
|
||||
if (cnt > 0u) {
|
||||
if (cnt > 0u) { /* 空框(0 条目)不移选;同时防对 0 取模 */
|
||||
/* 移选下移、首尾环绕:% cnt 越尾自动绕回首行 */
|
||||
s_sel[depth] = (uint8_t)((s_sel[depth] + 1u) % cnt);
|
||||
menu_refresh_layer(depth);
|
||||
menu_refresh_layer(depth); /* 刷新高亮 + 跟随滚动 + 滚动位回读持久化 */
|
||||
}
|
||||
break;
|
||||
case LV_KEY_ENTER:
|
||||
case LV_KEY_RIGHT:
|
||||
if (cnt > 0u && depth + 1u < MENU_PAGE_MAX) {
|
||||
menu_open(kids[s_sel[depth]]);
|
||||
/* 进子框:空框无条目可进;最深层(depth=MAX-1)无子层可再开 */
|
||||
if (cnt > 0u && depth < MENU_PAGE_MAX - 1u) {
|
||||
menu_open(kids[s_sel[depth]]); /* 压层:建新框 + 焦点移到新框 */
|
||||
}
|
||||
break;
|
||||
case LV_KEY_ESC:
|
||||
case LV_KEY_LEFT:
|
||||
if (depth > 0u) {
|
||||
menu_back();
|
||||
if (depth > 0u) { /* 根框(d=0)不响应返回 */
|
||||
menu_back(); /* 弹层:毁最右框,焦点回父框(选中/滚动不跳) */
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
break; /* 其余键值忽略 */
|
||||
}
|
||||
}
|
||||
|
||||
@@ -137,8 +141,7 @@ static void menu_key_handle(uint8_t depth, lv_key_t key)
|
||||
* 限定条件:容器已加入默认输入组并获焦
|
||||
* 函数说明:键值经默认输入组送达焦点容器;统一分发到 menu_key_handle
|
||||
*/
|
||||
static void menu_key_cb(lv_event_t *e)
|
||||
{
|
||||
static void menu_key_cb(lv_event_t *e) {
|
||||
uint8_t depth = (uint8_t)(uintptr_t)lv_event_get_user_data(e);
|
||||
|
||||
if (depth < MENU_PAGE_MAX) {
|
||||
@@ -157,26 +160,29 @@ static void menu_key_cb(lv_event_t *e)
|
||||
* 4. 建根框(主界面 node 0 子项)并置焦点
|
||||
* 5. 构建前后监控 LVGL 池内存并打印
|
||||
*/
|
||||
int ui_menu_init(void)
|
||||
{
|
||||
int ui_menu_init(void) {
|
||||
/* 数据表自检:各节点 parent 下标必须有效,提前捕获表内笔误(失败即返回、不建任何对象) */
|
||||
if (!ui_menu_tab_valid()) {
|
||||
DBG_ERROR("menu table: INVALID (parent index out of range)");
|
||||
return -1;
|
||||
}
|
||||
|
||||
/* 重置导航状态:各层从主界面(node 0)第 0 行开始、无滚动 */
|
||||
for (uint8_t d = 0u; d < MENU_PAGE_MAX; d++) {
|
||||
s_path[d] = 0u;
|
||||
s_sel[d] = 0u;
|
||||
s_scroll[d] = 0u;
|
||||
}
|
||||
s_depth = 0u;
|
||||
s_depth = 0u; /* 起始于根框 */
|
||||
|
||||
/* 取 LVGL 池内存基线(与构建后对照,量化菜单对象开销) */
|
||||
lv_mem_monitor_t mon;
|
||||
lv_mem_monitor(&mon);
|
||||
DBG_INFO("menu build start: total=%u free=%u big=%u",
|
||||
(unsigned)mon.total_size,
|
||||
(unsigned)mon.free_size, (unsigned)mon.free_biggest_size);
|
||||
|
||||
/* 初始化显示层:样式 + 4 层持久容器 + 根框框体/装饰(失败 = 对象创建失败、池耗尽) */
|
||||
if (ui_menu_view_init() != 0) {
|
||||
return -1;
|
||||
}
|
||||
@@ -187,14 +193,16 @@ int ui_menu_init(void)
|
||||
if (cont == NULL) {
|
||||
return -1;
|
||||
}
|
||||
lv_group_add_obj(lv_group_get_default(), cont);
|
||||
lv_group_add_obj(lv_group_get_default(), cont); /* 容器入组:焦点可轮到它、能收键事件 */
|
||||
/* 容器挂键回调;层深度 d 装入 user_data 指针(menu_key_cb 内拆回) */
|
||||
lv_obj_add_event_cb(cont, menu_key_cb, LV_EVENT_KEY, (void *)(uintptr_t)d);
|
||||
}
|
||||
|
||||
/* 根框:主界面(node 0)子项;建一次、持久 */
|
||||
ui_menu_view_frame_build(0u, 0u, 0u, 0u);
|
||||
ui_menu_view_focus(0u);
|
||||
ui_menu_view_frame_build(0u, 0u, 0u, 0u); /* depth=0、node=0(主界面)、sel=0、scroll=0 */
|
||||
ui_menu_view_focus(0u); /* 焦点置根框:按键首先进根框 */
|
||||
|
||||
/* 读构建后内存、打印增量(对照上方基线) */
|
||||
lv_mem_monitor(&mon);
|
||||
DBG_INFO("menu build done: total=%u free=%u big=%u used=%u",
|
||||
(unsigned)mon.total_size,
|
||||
|
||||
+176
-71
@@ -9,18 +9,25 @@
|
||||
* 创建日期:2026-09-04
|
||||
* 修改记录:
|
||||
* 2026-09-04 王建锋 创建:自旧 ui_menu.c 单页模型拆分出显示层(高内聚低耦合)
|
||||
* 2026-09-08 王建锋 按代码规范 V1.0 补全:分区注释、公共函数注释、关键行注释
|
||||
* 2026-09-08 王建锋 menu_frame_t 瘦身(uint8_t/int16_t,96→48 B/层);
|
||||
* s_row_text/s_row_arrow 并入 s_row(省 512 B,行 label 颜色继承)
|
||||
*/
|
||||
|
||||
/* 头文件包含区 */
|
||||
#include <string.h>
|
||||
|
||||
#include "ui_menu_view.h"
|
||||
|
||||
#include "ui_menu_tab.h"
|
||||
|
||||
#include "lvgl.h"
|
||||
|
||||
#include <string.h>
|
||||
|
||||
#define DBG_TAG "[UI_MENU_VIEW]"
|
||||
#include "dbg_log.h"
|
||||
|
||||
/* 私有宏定义区 */
|
||||
|
||||
/*
|
||||
* 布局常量(设备像素;参考图 02~07 实测 + 字体度量速查推导):
|
||||
* 汉字步进 12px/字、▶ 步进 10px、行高 15(7 行×15+2 边框=107,与参考图根框一致);
|
||||
@@ -41,25 +48,27 @@
|
||||
#define MENU_DECO_LEG_W 3 /* 顶部"浅 V"两腿线宽(1bpp 下斜线边缘易被阈值吃掉,需加粗) */
|
||||
#define MENU_DECO_HORIZ_W 2 /* 顶部"浅 V"水平线宽 */
|
||||
|
||||
/* 私有类型定义区 */
|
||||
|
||||
/*
|
||||
* 框几何缓存:位置/尺寸是"现算"的(不落导航状态),但子框对齐要用父框刚算出的值,
|
||||
* 故按层缓存一份;sel 为最近一次应用的高亮(子框垂直对齐依据),由逻辑层经
|
||||
* build/refresh 保持同步
|
||||
*/
|
||||
typedef struct {
|
||||
bool built; /* 框已建(框体 + 行对象存在) */
|
||||
uint8_t built; /* 框已建(框体 + 行对象存在):0/1(uint8_t 与系统存储一致) */
|
||||
uint8_t sel; /* 最近应用的选中行 */
|
||||
uint8_t item_cnt; /* 行数 */
|
||||
int32_t x, y; /* 框左上角(含边框) */
|
||||
int32_t w, h; /* 框外廓尺寸 */
|
||||
int16_t x, y; /* 框左上角(含边框;16 位够:y 钳制前可为负) */
|
||||
int16_t w, h; /* 框外廓尺寸(16 位够:最大 16×15+2=242) */
|
||||
} menu_frame_t;
|
||||
|
||||
/* 静态变量定义区 */
|
||||
|
||||
static lv_obj_t *s_cont[MENU_PAGE_MAX]; /* 4 层持久容器(每深度一个;焦点/键入口,永不删) */
|
||||
static lv_obj_t *s_frame_box[MENU_PAGE_MAX]; /* 框体:d=0 持久(根框);d>=1 进入时建、返回时毁 */
|
||||
static lv_obj_t *s_row[MENU_PAGE_MAX][MENU_ITEM_MAX]; /* 行容器(整行反色高亮的载体) */
|
||||
static lv_obj_t *s_row_text[MENU_PAGE_MAX][MENU_ITEM_MAX]; /* 行文本 label */
|
||||
static lv_obj_t *s_row_arrow[MENU_PAGE_MAX][MENU_ITEM_MAX]; /* 行箭头 label(叶子=NULL) */
|
||||
static menu_frame_t s_frame[MENU_PAGE_MAX];
|
||||
static lv_obj_t *s_row[MENU_PAGE_MAX][MENU_ITEM_MAX]; /* 行容器(整行反色高亮的载体;文本/箭头是其子对象,不单独存) */
|
||||
static menu_frame_t s_frame[MENU_PAGE_MAX]; /* 每层框几何缓存:位置/尺寸/选中行(现算不落状态,供子框对齐;见上方类型注释) */
|
||||
|
||||
static lv_obj_t *s_root_deco = NULL; /* 根框装饰:顶部"浅 V"水平段(两腿为临时对象,样式同源) */
|
||||
static lv_obj_t *s_root_status = NULL; /* 根框状态行 */
|
||||
@@ -70,6 +79,8 @@ static lv_style_t s_style_selected; /* 选中态:整行反色(白底)
|
||||
static lv_style_t s_style_frame; /* 框体:透明底 + 1px 白边框 */
|
||||
static lv_style_t s_style_scrollbar; /* 滚动条:白色、4px 宽 */
|
||||
|
||||
/* 函数定义区 */
|
||||
|
||||
/*
|
||||
* 函数功能:初始化显示层样式(静态样式,仅执行一次)
|
||||
* 入口参数:无
|
||||
@@ -79,14 +90,16 @@ static lv_style_t s_style_scrollbar; /* 滚动条:白色、4px 宽 */
|
||||
* 2. frame 为所有级联框(含根框)的 1px 白边框
|
||||
* 3. selected 为选中行整行反色(1bpp 白底);文字色在刷新时显式置黑
|
||||
*/
|
||||
static void view_styles_init(void)
|
||||
{
|
||||
static void view_styles_init(void) {
|
||||
static bool inited = false;
|
||||
|
||||
/* 一次性初始化:已初始化则直接返回 */
|
||||
if (inited) {
|
||||
return;
|
||||
}
|
||||
inited = true;
|
||||
|
||||
/* flat:透明、无边框/圆角/内边距(剥离主题附加的卡片样式) */
|
||||
lv_style_init(&s_style_flat);
|
||||
lv_style_set_bg_opa(&s_style_flat, LV_OPA_TRANSP);
|
||||
lv_style_set_border_width(&s_style_flat, 0);
|
||||
@@ -94,11 +107,13 @@ static void view_styles_init(void)
|
||||
lv_style_set_pad_all(&s_style_flat, 0);
|
||||
lv_style_set_pad_gap(&s_style_flat, 0);
|
||||
|
||||
/* selected:选中行整行反色(白底黑字) */
|
||||
lv_style_init(&s_style_selected);
|
||||
lv_style_set_bg_opa(&s_style_selected, LV_OPA_COVER);
|
||||
lv_style_set_bg_color(&s_style_selected, lv_color_white());
|
||||
lv_style_set_text_color(&s_style_selected, lv_color_black());
|
||||
|
||||
/* frame:透明底 + 1px 白边框(所有级联框含根框) */
|
||||
lv_style_init(&s_style_frame);
|
||||
lv_style_set_bg_opa(&s_style_frame, LV_OPA_TRANSP);
|
||||
lv_style_set_border_width(&s_style_frame, MENU_FRAME_BORDER);
|
||||
@@ -107,6 +122,7 @@ static void view_styles_init(void)
|
||||
lv_style_set_pad_all(&s_style_frame, 0);
|
||||
lv_style_set_pad_gap(&s_style_frame, 0);
|
||||
|
||||
/* scrollbar:白色 4px 宽(行超框高时自动出现) */
|
||||
lv_style_init(&s_style_scrollbar);
|
||||
lv_style_set_bg_opa(&s_style_scrollbar, LV_OPA_COVER);
|
||||
lv_style_set_bg_color(&s_style_scrollbar, lv_color_white());
|
||||
@@ -123,22 +139,30 @@ static void view_styles_init(void)
|
||||
* 文本/▶ 宽取字体实际步进(汉字 12px、▶ 10px);
|
||||
* 注意本 LVGL 版 lv_text_get_width 的 length=0 表示"不测量",必须传 strlen
|
||||
*/
|
||||
static int32_t view_frame_width(const uint8_t *kids, uint8_t cnt)
|
||||
{
|
||||
static int32_t view_frame_width(const uint8_t *kids, uint8_t cnt) {
|
||||
const lv_font_t *font = LV_FONT_DEFAULT;
|
||||
int32_t w = 0;
|
||||
|
||||
/* 遍历各行,取内容宽最大值 */
|
||||
for (uint8_t i = 0; i < cnt; i++) {
|
||||
const char *name = ui_menu_tab_name(kids[i]);
|
||||
|
||||
/* 量文本实际步进(本 LVGL 版 length=0 意为"不测量",须传 strlen) */
|
||||
int32_t tw = lv_text_get_width(name, (uint32_t)strlen(name), font, 0);
|
||||
int32_t cw = MENU_ROW_PAD_L + tw + MENU_ROW_PAD_R;
|
||||
|
||||
/* 菜单行(有子项):加箭头间距 + ▶ 宽 */
|
||||
if (ui_menu_tab_has_children(kids[i])) {
|
||||
cw += MENU_ARROW_GAP + lv_text_get_width("▶", (uint32_t)strlen("▶"), font, 0);
|
||||
}
|
||||
|
||||
/* 取最宽行 */
|
||||
if (cw > w) {
|
||||
w = cw;
|
||||
}
|
||||
}
|
||||
|
||||
/* 加左右边框 */
|
||||
return w + 2 * MENU_FRAME_BORDER;
|
||||
}
|
||||
|
||||
@@ -153,19 +177,22 @@ static int32_t view_frame_width(const uint8_t *kids, uint8_t cnt)
|
||||
* 限定条件:d < MENU_PAGE_MAX,i < MENU_ITEM_MAX
|
||||
* 函数说明: 1. 行容器整行宽、高 MENU_ROW_H,承载选中整行反色高亮
|
||||
* 2. 文本左留白 MENU_ROW_PAD_L;箭头紧跟文本(间距 MENU_ARROW_GAP,自适应)
|
||||
* 3. 文本/箭头子 label 加 IGNORE_LAYOUT 做行内绝对定位
|
||||
* 3. 文本/箭头子 label 加 IGNORE_LAYOUT 做行内绝对定位,是行的子对象、不单独存
|
||||
*/
|
||||
static bool view_add_row(lv_obj_t *box, uint8_t d, uint8_t i, uint8_t node, bool has_arrow)
|
||||
{
|
||||
static bool view_add_row(lv_obj_t *box, uint8_t d, uint8_t i, uint8_t node, bool has_arrow) {
|
||||
/* 行容器:整行宽、行高 MENU_ROW_H(整行反色高亮的载体) */
|
||||
lv_obj_t *row = lv_obj_create(box);
|
||||
if (row == NULL) {
|
||||
return false;
|
||||
}
|
||||
lv_obj_add_style(row, &s_style_flat, 0);
|
||||
|
||||
/* 挂选中行反色样式(CHECKED 态时触发白底) */
|
||||
lv_obj_add_style(row, &s_style_selected, LV_STATE_CHECKED);
|
||||
lv_obj_set_width(row, lv_pct(100));
|
||||
lv_obj_set_height(row, MENU_ROW_H);
|
||||
|
||||
/* 文本 label:左留白,行内绝对定位(IGNORE_LAYOUT) */
|
||||
lv_obj_t *lbl = lv_label_create(row);
|
||||
if (lbl == NULL) {
|
||||
lv_obj_delete(row);
|
||||
@@ -174,11 +201,16 @@ static bool view_add_row(lv_obj_t *box, uint8_t d, uint8_t i, uint8_t node, bool
|
||||
lv_obj_add_style(lbl, &s_style_flat, 0);
|
||||
lv_obj_add_flag(lbl, LV_OBJ_FLAG_IGNORE_LAYOUT);
|
||||
lv_obj_set_pos(lbl, MENU_ROW_PAD_L, MENU_ROW_TXT_TOP);
|
||||
|
||||
/* 静态字符串:直接指向 ROM 表,不拷贝 */
|
||||
lv_label_set_text_static(lbl, ui_menu_tab_name(node));
|
||||
|
||||
/* 箭头:仅菜单行(有子项),紧跟文本(间距 MENU_ARROW_GAP) */
|
||||
lv_obj_t *ar = NULL;
|
||||
if (has_arrow) {
|
||||
const char *name = ui_menu_tab_name(node);
|
||||
|
||||
/* 箭头 x = 左留白 + 实测文本宽 + 间距 */
|
||||
int32_t tw = lv_text_get_width(name, (uint32_t)strlen(name), LV_FONT_DEFAULT, 0);
|
||||
ar = lv_label_create(row);
|
||||
if (ar != NULL) {
|
||||
@@ -189,9 +221,8 @@ static bool view_add_row(lv_obj_t *box, uint8_t d, uint8_t i, uint8_t node, bool
|
||||
}
|
||||
}
|
||||
|
||||
/* 只存行容器:文本/箭头是其子对象(颜色在行上设置、子对象继承) */
|
||||
s_row[d][i] = row;
|
||||
s_row_text[d][i] = lbl;
|
||||
s_row_arrow[d][i] = ar;
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -203,11 +234,12 @@ static bool view_add_row(lv_obj_t *box, uint8_t d, uint8_t i, uint8_t node, bool
|
||||
* 限定条件:该层行对象已建
|
||||
* 函数说明:1. 选中行整行反色(白底黑字),箭头随反色变黑(参考图实测:选中行保留 ▶)
|
||||
* 2. 非选中行透明底白字、白色箭头
|
||||
* 3. 文本/箭头颜色设在行容器上,子 label 继承(text_color 为可继承属性)
|
||||
*/
|
||||
static void view_apply_sel(uint8_t depth, uint8_t sel)
|
||||
{
|
||||
static void view_apply_sel(uint8_t depth, uint8_t sel) {
|
||||
menu_frame_t *f = &s_frame[depth];
|
||||
|
||||
/* 逐行置/清 CHECKED 态(选中行经样式整行反色) */
|
||||
for (uint8_t i = 0; i < f->item_cnt; i++) {
|
||||
bool is_sel = (i == sel);
|
||||
if (is_sel) {
|
||||
@@ -216,12 +248,10 @@ static void view_apply_sel(uint8_t depth, uint8_t sel)
|
||||
else {
|
||||
lv_obj_clear_state(s_row[depth][i], LV_STATE_CHECKED);
|
||||
}
|
||||
lv_obj_set_style_text_color(s_row_text[depth][i],
|
||||
|
||||
/* 文本色设在行上:文本/箭头子对象继承(选中黑、非选中白,箭头随反色) */
|
||||
lv_obj_set_style_text_color(s_row[depth][i],
|
||||
is_sel ? lv_color_black() : lv_color_white(), 0);
|
||||
if (s_row_arrow[depth][i] != NULL) {
|
||||
lv_obj_set_style_text_color(s_row_arrow[depth][i],
|
||||
is_sel ? lv_color_black() : lv_color_white(), 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -232,16 +262,30 @@ static void view_apply_sel(uint8_t depth, uint8_t sel)
|
||||
* 限定条件:box 已创建
|
||||
* 函数说明:行内容超框高时框内滚动(本菜单最深 9 行不触发,机制为更长列表保留)
|
||||
*/
|
||||
static void view_box_layout(lv_obj_t *box)
|
||||
{
|
||||
static void view_box_layout(lv_obj_t *box) {
|
||||
/* 纵向 flex 堆行、顶对齐(行自框顶依次排列) */
|
||||
lv_obj_set_flex_flow(box, LV_FLEX_FLOW_COLUMN);
|
||||
lv_obj_set_flex_align(box, LV_FLEX_ALIGN_START, LV_FLEX_ALIGN_START, LV_FLEX_ALIGN_START);
|
||||
|
||||
/* 自动滚动条:仅当行内容超框高时出现(框内滚动) */
|
||||
lv_obj_set_scrollbar_mode(box, LV_SCROLLBAR_MODE_AUTO);
|
||||
lv_obj_add_style(box, &s_style_scrollbar, LV_PART_SCROLLBAR);
|
||||
}
|
||||
|
||||
void ui_menu_view_frame_build(uint8_t depth, uint8_t node, uint8_t sel, uint16_t scroll)
|
||||
{
|
||||
/*
|
||||
* 函数功能:构建指定深度的框(几何现算 + 行对象创建 + 选中/滚动应用)
|
||||
* 入口参数:depth - 深度(0=根框) uint8_t
|
||||
* node - 本框展示节点(行 = 其子项) uint8_t
|
||||
* sel - 本框选中行 uint8_t
|
||||
* scroll - 本框滚动偏移(px,0=顶部) uint16_t
|
||||
* 返回值:无
|
||||
* 限定条件:depth>0 时 depth-1 层框已建(父框几何是其对齐依据)
|
||||
* 函数说明: 1. d=0:根框框体已存在,仅(重)建行对象
|
||||
* 2. d>0:右贴靠父框右缘;顶边=父框选中行行顶,超屏底则
|
||||
* 上移使本框底边对齐父框选中行行底,仍超顶则钳制顶部并改框内滚动
|
||||
* 3. 框宽 = 最宽行(文本 + ▶ + 左右留白)+ 边框;条目现算不落地
|
||||
*/
|
||||
void ui_menu_view_frame_build(uint8_t depth, uint8_t node, uint8_t sel, uint16_t scroll) {
|
||||
menu_frame_t *f = &s_frame[depth];
|
||||
lv_obj_t *box;
|
||||
uint8_t kids[MENU_ITEM_MAX];
|
||||
@@ -291,17 +335,20 @@ void ui_menu_view_frame_build(uint8_t depth, uint8_t node, uint8_t sel, uint16_t
|
||||
s_frame_box[depth] = box;
|
||||
}
|
||||
|
||||
f->built = true;
|
||||
/* 更新框几何缓存(子框对齐的依据) */
|
||||
f->built = 1u;
|
||||
f->item_cnt = cnt;
|
||||
f->sel = sel;
|
||||
|
||||
/* 行对象:条目现算(= parent==node 过滤),进时建、退时毁 */
|
||||
lv_obj_clean(box);
|
||||
|
||||
/* 清空旧行指针(旧对象已随框体删除) */
|
||||
for (uint8_t i = 0; i < MENU_ITEM_MAX; i++) {
|
||||
s_row[depth][i] = NULL;
|
||||
s_row_text[depth][i] = NULL;
|
||||
s_row_arrow[depth][i] = NULL;
|
||||
}
|
||||
|
||||
/* 逐行建行对象 */
|
||||
for (uint8_t i = 0; i < cnt; i++) {
|
||||
if (!view_add_row(box, depth, i, kids[i], ui_menu_tab_has_children(kids[i]))) {
|
||||
break; /* LVGL 池耗尽:停止建行(池有界,见设计 §7.5) */
|
||||
@@ -311,15 +358,23 @@ void ui_menu_view_frame_build(uint8_t depth, uint8_t node, uint8_t sel, uint16_t
|
||||
/* 选中高亮 + 滚动位 */
|
||||
view_apply_sel(depth, sel);
|
||||
if (scroll > 0u) {
|
||||
/* 有保存的滚动位:还原 */
|
||||
lv_obj_scroll_to_y(box, (int32_t)scroll, LV_ANIM_OFF);
|
||||
}
|
||||
else if (cnt > 0u) {
|
||||
/* 无保存滚动位:选中行对齐到可见区 */
|
||||
lv_obj_scroll_to_view(s_row[depth][sel], LV_ANIM_OFF);
|
||||
}
|
||||
}
|
||||
|
||||
void ui_menu_view_frame_destroy(uint8_t depth)
|
||||
{
|
||||
/*
|
||||
* 函数功能:销毁指定深度的框(框体连同行对象,容器保留)
|
||||
* 入口参数:depth - 深度 uint8_t
|
||||
* 返回值:无
|
||||
* 限定条件:depth>0(根框持久不可销毁)
|
||||
* 函数说明:返回(关最右框)时调用,LVGL 池内存随即回落
|
||||
*/
|
||||
void ui_menu_view_frame_destroy(uint8_t depth) {
|
||||
menu_frame_t *f = &s_frame[depth];
|
||||
|
||||
if (depth == 0u || !f->built) {
|
||||
@@ -327,12 +382,12 @@ void ui_menu_view_frame_destroy(uint8_t depth)
|
||||
}
|
||||
lv_obj_delete(s_frame_box[depth]); /* 框体连行对象整体删除(容器保留) */
|
||||
s_frame_box[depth] = NULL;
|
||||
|
||||
/* 清空行指针与几何缓存 */
|
||||
for (uint8_t i = 0; i < MENU_ITEM_MAX; i++) {
|
||||
s_row[depth][i] = NULL;
|
||||
s_row_text[depth][i] = NULL;
|
||||
s_row_arrow[depth][i] = NULL;
|
||||
}
|
||||
f->built = false;
|
||||
f->built = 0u;
|
||||
f->item_cnt = 0u;
|
||||
f->sel = 0u;
|
||||
f->x = 0;
|
||||
@@ -341,10 +396,18 @@ void ui_menu_view_frame_destroy(uint8_t depth)
|
||||
f->h = 0;
|
||||
}
|
||||
|
||||
void ui_menu_view_frame_refresh(uint8_t depth, uint8_t sel)
|
||||
{
|
||||
/*
|
||||
* 函数功能:刷新指定深度框的选中高亮
|
||||
* 入口参数:depth - 深度 uint8_t
|
||||
* sel - 新选中行 uint8_t
|
||||
* 返回值:无
|
||||
* 限定条件:ui_menu_view_frame_build(depth,…) 已完成
|
||||
* 函数说明:只改行状态与文字色 + 跟随滚动,不重建对象
|
||||
*/
|
||||
void ui_menu_view_frame_refresh(uint8_t depth, uint8_t sel) {
|
||||
menu_frame_t *f = &s_frame[depth];
|
||||
|
||||
/* 未建或越界直接返回 */
|
||||
if (!f->built || depth >= MENU_PAGE_MAX || sel >= f->item_cnt) {
|
||||
return;
|
||||
}
|
||||
@@ -354,31 +417,59 @@ void ui_menu_view_frame_refresh(uint8_t depth, uint8_t sel)
|
||||
lv_obj_scroll_to_view(s_row[depth][sel], LV_ANIM_OFF);
|
||||
}
|
||||
|
||||
void ui_menu_view_set_scroll(uint8_t depth, uint16_t scroll)
|
||||
{
|
||||
/*
|
||||
* 函数功能:应用指定深度框的滚动偏移
|
||||
* 入口参数:depth - 深度 uint8_t
|
||||
* scroll - 滚动偏移(px) uint16_t
|
||||
* 返回值:无
|
||||
* 限定条件:该层框已建
|
||||
* 函数说明:行对象重建后按此还原用户滚动位置
|
||||
*/
|
||||
void ui_menu_view_set_scroll(uint8_t depth, uint16_t scroll) {
|
||||
/* 未建框直接返回 */
|
||||
if (depth >= MENU_PAGE_MAX || !s_frame[depth].built || s_frame_box[depth] == NULL) {
|
||||
return;
|
||||
}
|
||||
lv_obj_scroll_to_y(s_frame_box[depth], (int32_t)scroll, LV_ANIM_OFF);
|
||||
}
|
||||
|
||||
uint16_t ui_menu_view_get_scroll(uint8_t depth)
|
||||
{
|
||||
/*
|
||||
* 函数功能:读取指定深度框当前滚动偏移
|
||||
* 入口参数:depth - 深度 uint8_t
|
||||
* 返回值:滚动偏移(px) uint16_t 未建框返回 0
|
||||
* 限定条件:无
|
||||
* 函数说明:跟随滚动后由逻辑层读回,写入导航状态持久化
|
||||
*/
|
||||
uint16_t ui_menu_view_get_scroll(uint8_t depth) {
|
||||
/* 未建框返回 0 */
|
||||
if (depth >= MENU_PAGE_MAX || !s_frame[depth].built || s_frame_box[depth] == NULL) {
|
||||
return 0u;
|
||||
}
|
||||
return (uint16_t)lv_obj_get_scroll_y(s_frame_box[depth]);
|
||||
}
|
||||
|
||||
void ui_menu_view_focus(uint8_t depth)
|
||||
{
|
||||
/*
|
||||
* 函数功能:焦点移至指定深度的容器
|
||||
* 入口参数:depth - 深度 uint8_t
|
||||
* 返回值:无
|
||||
* 限定条件:ui_menu_view_init() 已完成且容器已加入输入组
|
||||
* 函数说明:焦点=最右框;进/退时由逻辑层调用
|
||||
*/
|
||||
void ui_menu_view_focus(uint8_t depth) {
|
||||
if (depth < MENU_PAGE_MAX && s_cont[depth] != NULL) {
|
||||
/* 焦点移到目标深度容器 */
|
||||
lv_group_focus_obj(s_cont[depth]);
|
||||
}
|
||||
}
|
||||
|
||||
lv_obj_t *ui_menu_view_container(uint8_t depth)
|
||||
{
|
||||
/*
|
||||
* 函数功能:取指定深度的持久容器
|
||||
* 入口参数:depth - 深度 0..MENU_PAGE_MAX-1 uint8_t
|
||||
* 返回值:容器对象 lv_obj_t * 越界返回 NULL
|
||||
* 限定条件:ui_menu_view_init() 已完成
|
||||
* 函数说明:逻辑层在容器上绑定输入组与键事件(显示层不碰输入)
|
||||
*/
|
||||
lv_obj_t *ui_menu_view_container(uint8_t depth) {
|
||||
return (depth < MENU_PAGE_MAX) ? s_cont[depth] : NULL;
|
||||
}
|
||||
|
||||
@@ -389,8 +480,7 @@ lv_obj_t *ui_menu_view_container(uint8_t depth)
|
||||
* 限定条件:line 已创建且已设点集
|
||||
* 函数说明:绝对定位于 (0,0)、忽略布局、白色实线、指定线宽
|
||||
*/
|
||||
static void view_deco_line_apply(lv_obj_t *line, int32_t width)
|
||||
{
|
||||
static void view_deco_line_apply(lv_obj_t *line, int32_t width) {
|
||||
lv_obj_add_flag(line, LV_OBJ_FLAG_IGNORE_LAYOUT);
|
||||
lv_obj_set_pos(line, 0, 0);
|
||||
lv_obj_set_size(line, MENU_SCREEN_W, 20);
|
||||
@@ -399,14 +489,27 @@ static void view_deco_line_apply(lv_obj_t *line, int32_t width)
|
||||
lv_obj_set_style_line_opa(line, LV_OPA_COVER, 0);
|
||||
}
|
||||
|
||||
int ui_menu_view_init(void)
|
||||
{
|
||||
/*
|
||||
* 函数功能:初始化显示层(样式 + 4 层持久容器 + 根框装饰 + 根框框体)
|
||||
* 入口参数:无
|
||||
* 返回值:0 - 成功,其他 - 失败(对象创建失败)
|
||||
* 限定条件:LVGL 已初始化且活动屏幕存在(ui_port_init / sim_port_init 已完成)
|
||||
* 函数说明:1. 4 层容器每深度一个、全屏覆盖、持久不删(焦点/键入口,防悬空)
|
||||
* 2. 根框装饰(浅 V + 状态行)与根框框体只属根层(d=0)
|
||||
* 3. 行对象不在此建,由逻辑层调 ui_menu_view_frame_build 按需建
|
||||
*/
|
||||
int ui_menu_view_init(void) {
|
||||
/* 取活动屏幕:NULL = ui_port_init/sim_port_init 未完成、LVGL 未就绪 */
|
||||
lv_obj_t *scr = lv_screen_active();
|
||||
|
||||
if (scr == NULL) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
/* 样式一次性初始化(静态样式、常驻,仅此处建一次) */
|
||||
view_styles_init();
|
||||
|
||||
/* 读屏幕实际分辨率(不硬编码,MCU/模拟器两端共用本代码) */
|
||||
lv_display_t *disp = lv_display_get_default();
|
||||
int32_t sw = lv_display_get_horizontal_resolution(disp);
|
||||
int32_t sh = lv_display_get_vertical_resolution(disp);
|
||||
@@ -416,7 +519,7 @@ int ui_menu_view_init(void)
|
||||
if (menu == NULL) {
|
||||
return -1;
|
||||
}
|
||||
lv_obj_add_style(menu, &s_style_flat, 0);
|
||||
lv_obj_add_style(menu, &s_style_flat, 0); /* 透明、无边框:纯定位层,不可见 */
|
||||
lv_obj_set_size(menu, sw, sh);
|
||||
|
||||
/* 4 层持久容器:每深度一个、全屏覆盖、永不删(焦点/键入口,防悬空) */
|
||||
@@ -425,34 +528,35 @@ int ui_menu_view_init(void)
|
||||
if (s_cont[d] == NULL) {
|
||||
return -1;
|
||||
}
|
||||
lv_obj_add_style(s_cont[d], &s_style_flat, 0);
|
||||
lv_obj_add_flag(s_cont[d], LV_OBJ_FLAG_IGNORE_LAYOUT);
|
||||
lv_obj_add_style(s_cont[d], &s_style_flat, 0); /* 透明、无边框 */
|
||||
lv_obj_add_flag(s_cont[d], LV_OBJ_FLAG_IGNORE_LAYOUT); /* 绝对定位,不参与父级 flex 布局 */
|
||||
lv_obj_set_pos(s_cont[d], 0, 0);
|
||||
lv_obj_set_size(s_cont[d], sw, sh);
|
||||
lv_obj_add_flag(s_cont[d], LV_OBJ_FLAG_CLICK_FOCUSABLE);
|
||||
lv_obj_set_size(s_cont[d], sw, sh); /* 全屏覆盖 */
|
||||
lv_obj_add_flag(s_cont[d], LV_OBJ_FLAG_CLICK_FOCUSABLE); /* 可获焦:键事件入口 */
|
||||
}
|
||||
|
||||
/* 根框装饰:顶部"开口浅 V"(两腿加粗、水平线细)+ 状态行(绝对定位于 d=0 容器)
|
||||
* 1bpp 下斜线边缘易被阈值吃掉,腿须比水平线粗才接近参考图 */
|
||||
{
|
||||
static const lv_point_precise_t deco_leg_l[] = { {7, 7}, {16, 17} };
|
||||
static const lv_point_precise_t deco_hoz[] = { {16, 17}, {145, 17} };
|
||||
static const lv_point_precise_t deco_leg_r[] = { {158, 7}, {145, 17} };
|
||||
static const lv_point_precise_t deco_leg_l[] = { {7, 7}, {16, 17} }; /* 左腿:自顶端斜下 */
|
||||
static const lv_point_precise_t deco_hoz[] = { {16, 17}, {145, 17} }; /* 水平线:接两腿底端 */
|
||||
static const lv_point_precise_t deco_leg_r[] = { {158, 7}, {145, 17} }; /* 右腿 */
|
||||
|
||||
lv_obj_t *leg_l = lv_line_create(s_cont[0]);
|
||||
lv_obj_t *leg_r = lv_line_create(s_cont[0]);
|
||||
s_root_deco = lv_line_create(s_cont[0]);
|
||||
s_root_deco = lv_line_create(s_cont[0]); /* 仅水平线留存句柄;两腿建后无需再引用 */
|
||||
if (leg_l == NULL || leg_r == NULL || s_root_deco == NULL) {
|
||||
return -1;
|
||||
}
|
||||
lv_line_set_points(leg_l, deco_leg_l, 2);
|
||||
lv_line_set_points(leg_r, deco_leg_r, 2);
|
||||
lv_line_set_points(s_root_deco, deco_hoz, 2);
|
||||
view_deco_line_apply(leg_l, MENU_DECO_LEG_W);
|
||||
view_deco_line_apply(leg_l, MENU_DECO_LEG_W); /* 腿:3px 粗 */
|
||||
view_deco_line_apply(leg_r, MENU_DECO_LEG_W);
|
||||
view_deco_line_apply(s_root_deco, MENU_DECO_HORIZ_W);
|
||||
view_deco_line_apply(s_root_deco, MENU_DECO_HORIZ_W); /* 水平线:2px 细 */
|
||||
}
|
||||
|
||||
/* 状态行:当前模式(顶部区域,根框上方) */
|
||||
s_root_status = lv_label_create(s_cont[0]);
|
||||
if (s_root_status == NULL) {
|
||||
return -1;
|
||||
@@ -460,26 +564,27 @@ int ui_menu_view_init(void)
|
||||
lv_obj_add_style(s_root_status, &s_style_flat, 0);
|
||||
lv_obj_add_flag(s_root_status, LV_OBJ_FLAG_IGNORE_LAYOUT);
|
||||
lv_obj_set_pos(s_root_status, MENU_ROOT_STATUS_X, MENU_ROOT_STATUS_Y);
|
||||
lv_label_set_text_static(s_root_status, "当前模式 : 无模式");
|
||||
lv_label_set_text_static(s_root_status, "当前模式 : 无模式"); /* 静态字符串,不拷内存 */
|
||||
|
||||
/* 根框框体:持久;宽 = 内容自适应(主界面 node 0 的子项) */
|
||||
uint8_t kids[MENU_ITEM_MAX];
|
||||
uint8_t cnt = ui_menu_tab_children(0u, kids, MENU_ITEM_MAX);
|
||||
int32_t w = view_frame_width(kids, cnt);
|
||||
int32_t h = (int32_t)cnt * MENU_ROW_H + 2 * MENU_FRAME_BORDER;
|
||||
uint8_t cnt = ui_menu_tab_children(0u, kids, MENU_ITEM_MAX); /* 主界面子项(7 个 L1 条目) */
|
||||
int32_t w = view_frame_width(kids, cnt); /* 宽 = 最宽行内容 + 左右边框(当前 66) */
|
||||
int32_t h = (int32_t)cnt * MENU_ROW_H + 2 * MENU_FRAME_BORDER; /* 高 = 行数×行高 + 上下边框(当前 107) */
|
||||
|
||||
s_frame_box[0] = lv_obj_create(s_cont[0]);
|
||||
if (s_frame_box[0] == NULL) {
|
||||
return -1;
|
||||
}
|
||||
lv_obj_add_style(s_frame_box[0], &s_style_flat, 0);
|
||||
lv_obj_add_style(s_frame_box[0], &s_style_frame, 0);
|
||||
lv_obj_add_style(s_frame_box[0], &s_style_flat, 0); /* 透明底 */
|
||||
lv_obj_add_style(s_frame_box[0], &s_style_frame, 0); /* 1px 白边框 */
|
||||
lv_obj_add_flag(s_frame_box[0], LV_OBJ_FLAG_IGNORE_LAYOUT);
|
||||
lv_obj_set_pos(s_frame_box[0], MENU_ROOT_BOX_X, MENU_ROOT_BOX_Y);
|
||||
lv_obj_set_pos(s_frame_box[0], MENU_ROOT_BOX_X, MENU_ROOT_BOX_Y); /* 左上 (0,44):装饰/状态行之下 */
|
||||
lv_obj_set_size(s_frame_box[0], w, h);
|
||||
view_box_layout(s_frame_box[0]);
|
||||
view_box_layout(s_frame_box[0]); /* 纵向 flex 堆行 + 自动滚动条(行对象后由 frame_build 添加) */
|
||||
|
||||
s_frame[0].built = true; /* 框体就绪;行对象由逻辑层 frame_build(0) 创建 */
|
||||
/* 写根框几何缓存:子框右贴靠其右缘、顶对齐其选中行,均以此为准 */
|
||||
s_frame[0].built = 1u; /* 框体就绪;行对象由逻辑层 frame_build(0) 创建 */
|
||||
s_frame[0].x = MENU_ROOT_BOX_X;
|
||||
s_frame[0].y = MENU_ROOT_BOX_Y;
|
||||
s_frame[0].w = w;
|
||||
|
||||
Reference in New Issue
Block a user