dafc18f5ed
启动 docs/任务规划/README.md 的 W6 UI 扩展簇(用户裁定,覆盖 2026-10-11
的"本轮不排期")。四个工作包,全部验证通过。
## U1 设计令牌层(零行为变化)
新建 Drivers/BSP/UI/ui_tokens.h(20 个宏),框架 + 7 个页面 + App/task/
ui_task.c 全部接入。屏幕几何**复用 ui_port.h 既有 UI_SCREEN_W/H**,不重复
定义;令牌头自身只 include ui_port.h,不把 LVGL 拖进全部消费者。
补收了报告没列但代码里真重复的量:UI_TEXT_BAND_H 15(7 次)、列表左右边界
与每页行数(各 4 个页面逐字复制)、推流协议几何与显示几何同源(此前硬编码
3200 且注释写着"须与 UI_FB_SIZE 一致"——典型漂移隐患)、UI_TASK_TICK_MS
(ui_port_step 与 osDelay 必须同值,原先靠注释维系)。
故意不收页面私有几何与"同形不同义"的值(device_info 的 pitch=15 与列表
UI_ROW_PITCH=14 合并即改像素)。
## U3 页面注册表与生成器(删手工分发)
ui_page_registry.{c,h} 由生成器产出,**手工 menu_content_page_for() 已删
除**(仓库内仅存 2 处历史说明注释)。密码页按代码实况**单独出口**
(ui_page_registry_password())——它非节点绑定,任何 #P 叶子进同一页。
顺带落地此前判定"U1 阶段做不了"的共享 flat 样式 ui_style.{c,h}。
生成器除产注册表外**自动把新 .c 写进 uvprojx 与 CMakeLists.txt**,且
**双向对账**:加页打印 registered、删页打印 pruned,不留死条目;并双向
校验(#page 找不到实例报错、实例未被任何 #page 引用也报错)。
## U4 门禁补强(35 个钩子)
新增 font-charset 钩子(scripts/check_font_charset.py):复用
tools/font_gen 的同一套提取与 cmap 解析规则(importlib 直载,零第二套
规则),A 段查源码在用 vs 字符集、B 段查字符集 vs 字体 cmap。
registration-check 补第 ④ 类:实测原规则对"进了两端构建但没接描述符表"
的页面**全绿**——desc 被链接器剥离、编译链接全过、菜单永远打不开的死代码。
仍照不出的两项已如实写进文档(枚举漏加归 @build、菜单树漏挂归 golden)。
## 字体子集瘦身(D 案,4 留 3 删)
ui_cn_chars.txt 122→119 字(恰删 上/电/重,零新增)。异/常/片/芯 进
KEEP_EXTRA 带理由白名单:它们曾在 9eb1bd63 被写进中文自检描述落盘
/log/selftest.log(定长 64B 记录跨固件持久),删字形会使历史故障记录
渲染空白且不可逆。保留字按**必需**处理,反向有牙——谁只跑 extract_cn
把它们静默洗掉,钩子直接 FAIL 并逐字打印理由。
--strict 口径为"未知冗余 = 0"而非"冗余 = 0"。
## 验证
@build 0 错 0 警;ER_IROM1 232,052 B(较 HEAD −228 B)、RW_IRAM1
75,232 B **零增量**、RW_CCM 87.9% 未变。golden:菜单 8 状态全像素一致 +
内容页 11 页逐字节一致,**参考图 sha256 差异 0 条**(未用 --update 刷
基线);池水位菜单基线 5368 B、开页-关页往返 5448 B 与 U1 时相同。
38 个在途修改文件逐个核对 BOM/CRLF/U+FFFD 全部与 HEAD 一致。
L0 33 钩子 + L0.5 16 runner / 1104 断言全绿;check_doc_links --all
142 篇、无法判定 0;mem-budget / mem-numbers 已回填。
## 单位成本实测(U5 排期依据)
临时页 zzprobe 绑到"遥信量"叶子 → 只跑生成器 → 带页 @build 0/0 →
注册表出现 {5u, &ui_page_zzprobe_desc} → 回滚后 6 个产出文件 zzprobe
残留计数均 0、重编回基线。
**结论:新增一页 = 3 处人工(新 .c + 新 .h + 菜单树那一行加
#page=<名>)+ 1 条生成器命令,手工编辑注册文件 0 处。**
据此修正报告原口径"8 处降到 1 处"——那个 1 只在"1 处对既有文件的修改"
意义下成立,页面自身 2 个文件本就要写,真实处数是 3。
## 文档同步与两处自我修正
§0.1 UI 行 B → B+;§6.1「内容页」「设计令牌」两行按实况回填;§6.5 U1/U3
状态更新;§6 顶部加"建议栏不可照抄"总警示(本轮四次证伪:ui_port.h 已有
同名宏、LV_COLOR_BLACK/WHITE 不存在且极性反了、27 页实为目标页数现存 7
个、生成器草图漏密码页非节点绑定)。
两处改口:① §6.5 U4 从我上一版**误写的 3/3 改回 2/3**——"golden 脚本早已
存在"不等于"已进自动门禁",两个 golden 脚本至今未挂 pre-commit,该缺口
单列为待办;② 原计划"分三笔提交"**作废**,U1 与 U3 改同一批文件、文件内
交叠,硬切会产生不可编译的中间态,故整波一笔。
117 lines
5.0 KiB
C
117 lines
5.0 KiB
C
/*
|
||
* 模块名称:UI Menu View (菜单显示模型渲染层)
|
||
* 模块功能:以 LVGL 基元控件实现《菜单页面设计.md》§四⑧"横向级联子框"显示模型:
|
||
* 右贴靠(子框左缘=父框右缘、共享边框列)+ 框宽内容自适应 +
|
||
* 顶对齐/底对齐上移;持有全部 LVGL 对象(样式/持久容器/框),
|
||
* 不持有任何导航状态——状态由逻辑层 ui_menu.c 经本接口驱动
|
||
* 适用平台:STM32F407ZGTx / PC 模拟器(两端共用)
|
||
* 作者:王建锋
|
||
* 创建日期:2026-09-04
|
||
* 修改记录:
|
||
* 2026-09-04 王建锋 创建:自旧 ui_menu.c 单页模型拆分出显示层(高内聚低耦合)
|
||
* 2026-09-08 王建锋 行改自绘(节点下标注册 + 框体 DRAW_MAIN 自绘 + 虚拟滚动),接口不变
|
||
* 2026-09-08 王建锋 4 层容器→单一持久容器(container/focus 去 depth 参数);
|
||
* 根框装饰/状态行改容器自绘
|
||
*/
|
||
#ifndef UI_MENU_VIEW_H
|
||
#define UI_MENU_VIEW_H
|
||
|
||
#include <stdint.h>
|
||
#include <stdbool.h>
|
||
|
||
#include "lvgl.h"
|
||
|
||
#include "ui_tokens.h"
|
||
|
||
#define MENU_PAGE_MAX 4 /* 页面深度上限(根框 + 3 级子框) */
|
||
#define MENU_ITEM_MAX 16 /* 单框最大条目数 */
|
||
|
||
/* 屏幕尺寸单一来源 = ui_tokens.h(权威定义在 ui_port.h,两端一致);
|
||
* 保留 MENU_SCREEN_* 旧名作为别名,兼容既有引用与本模块文档 */
|
||
#define MENU_SCREEN_W UI_SCREEN_W /* 屏幕宽(px) */
|
||
#define MENU_SCREEN_H UI_SCREEN_H /* 屏幕高(px) */
|
||
|
||
/*
|
||
* 函数功能:初始化显示层(样式 + 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);
|
||
|
||
/*
|
||
* 函数功能:取持久容器
|
||
* 入口参数:无
|
||
* 返回值:容器对象 lv_obj_t * 未初始化返回 NULL
|
||
* 限定条件:ui_menu_view_init() 已完成
|
||
* 函数说明:逻辑层在容器上绑定输入组与键事件(显示层不碰输入)
|
||
*/
|
||
lv_obj_t *ui_menu_view_container(void);
|
||
|
||
/*
|
||
* 函数功能:构建指定深度的框(几何现算 + 行数据注册 + 选中/滚动应用)
|
||
* 入口参数: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);
|
||
|
||
/*
|
||
* 函数功能:销毁指定深度的框(框体连自绘回调整体删除,容器保留)
|
||
* 入口参数:depth - 深度 uint8_t
|
||
* 返回值:无
|
||
* 限定条件:depth>0(根框持久不可销毁)
|
||
* 函数说明:返回(关最右框)时调用,LVGL 池内存随即回落
|
||
*/
|
||
void ui_menu_view_frame_destroy(uint8_t depth);
|
||
|
||
/*
|
||
* 函数功能:刷新指定深度框的选中高亮
|
||
* 入口参数:depth - 深度 uint8_t
|
||
* sel - 新选中行 uint8_t
|
||
* 返回值:无
|
||
* 限定条件:ui_menu_view_frame_build(depth,…) 已完成
|
||
* 函数说明:只改选中数据 + 跟随滚动、重绘框体,不重建对象
|
||
*/
|
||
void ui_menu_view_frame_refresh(uint8_t depth, uint8_t sel);
|
||
|
||
/*
|
||
* 函数功能:应用指定深度框的滚动偏移
|
||
* 入口参数:depth - 深度 uint8_t
|
||
* scroll - 滚动偏移(px) uint16_t
|
||
* 返回值:无
|
||
* 限定条件:该层框已建
|
||
* 函数说明:行重建后按此还原用户滚动位置
|
||
*/
|
||
void ui_menu_view_set_scroll(uint8_t depth, uint16_t scroll);
|
||
|
||
/*
|
||
* 函数功能:读取指定深度框当前滚动偏移
|
||
* 入口参数:depth - 深度 uint8_t
|
||
* 返回值:滚动偏移(px) uint16_t 未建框返回 0
|
||
* 限定条件:无
|
||
* 函数说明:跟随滚动后由逻辑层读回,写入导航状态持久化
|
||
*/
|
||
uint16_t ui_menu_view_get_scroll(uint8_t depth);
|
||
|
||
/*
|
||
* 函数功能:焦点置于持久容器
|
||
* 入口参数:无
|
||
* 返回值:无
|
||
* 限定条件:ui_menu_view_init() 已完成且容器已加入输入组
|
||
* 函数说明:焦点=唯一键入口(框深度由逻辑层 s_depth 决定);进/退时由逻辑层调用
|
||
*/
|
||
void ui_menu_view_focus(void);
|
||
|
||
#endif /* UI_MENU_VIEW_H */
|