Files
STM32F4-Base/Drivers/BSP/UI/ui_content_page.h
T
wandering1 dafc18f5ed feat(UI): W6 第一波——设计令牌层、页面注册表生成器与 UI 门禁补强
启动 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 改同一批文件、文件内
交叠,硬切会产生不可编译的中间态,故整波一笔。
2026-10-20 21:14:14 +08:00

149 lines
9.1 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.
/*
* 模块名称:UI Content Page (内容页框架)
* 模块功能:菜单叶子节点进入的内容页(独立全屏页面)框架——
* 屏/容器按需建毁生命周期 + 焦点 + 切屏、按键分发(内置 ↑↓/ESC + key_cb 钩子)、
* 描述符驱动通用绘制(浅 V + 标题/副标题 + 行 + 选中带 + 页脚 + draw_cb);
* 不持任何页面数据:各页数据/几何/钩子由 ui_page_xxx 模块经 content_page_def_t
* 提供,框架按**描述符指针**开页;"哪个叶子进哪一页"的绑定在生成物
* ui_page_registry.c(真源 = 菜单树 ui_menu_tree.txt 的 #page= 注记),
* 框架既无页 ID 枚举也无节点→页手写 case(架构见 内容页架构.md);
* 进入时切屏并获焦、ESC 返回菜单屏并还原焦点;
* 不持有导航状态——由逻辑层 ui_menu.c 在 ENTER 叶子节点时驱动
* 适用平台:STM32F407ZGTx / PC 模拟器(两端共用)
* 作者:王建锋
* 创建日期:2026-09-09
* 修改记录:
* 2026-09-09 王建锋 创建:装置信息/交流量/直流量三页(参考图 10/20/21)
* 2026-09-09 王建锋 "浅 V"装饰几何提取至共享 ui_deco 模块(不再本地重复定义)
* 2026-09-09 王建锋 拆分为"框架 + 页面插件"模型(内容页架构.md):三页数据移入
* ui_page_xxx 模块;本文件只留描述类型与 API;描述符新增
* 4 个扩展钩子(refresh/count_cb/draw_cb/key_cb)
* 2026-09-09 王建锋 密码页(用户密钥):描述符新增 title_y/hide_deco/hide_footer/key_intercept
* 4 字段 + 新 API ui_content_page_invalidate;← 键可每页拦截
* (ESC 恒不可拦截)
* 2026-09-09 王建锋 权限模型:open() 增授予权限实参(ui_perm_t,逻辑层按
* 是否过密码页/所输密码决定);描述符新增 edit_capable(页面
* 是否支持编辑模式)+ 新 API ui_content_page_is_editable
* (页面钩子据以决定编辑行为:edit_capable 且修改权限)
* 2026-10-15 王建锋 U3 页面原型化:删 content_page_id_t 枚举与框架侧 s_defs[],
* open() 改收描述符指针;节点→页面绑定移交给生成物
* ui_page_registry.c(菜单树 #page= 注记为唯一真源),
* 逻辑层手写 menu_content_page_for() 随之删除;
* 描述符新增 id 字段(ASCII 页名,供日志/诊断定位是哪一页)
*/
#ifndef UI_CONTENT_PAGE_H
#define UI_CONTENT_PAGE_H
#include <stdint.h>
#include <stdbool.h>
#include "lvgl.h"
/* 行数据:左列名 + 右列值(Flash 字面量,不 strdup) */
typedef struct {
const char *name;
const char *value;
} cp_row_t;
/*
* 页面描述符:一页 = 一个 Flash const 结构(身份 + 几何 + 数据源 + 可选钩子)
* 由各 ui_page_xxx 页面模块提供;"哪个叶子进哪一页"的绑定不在本头,也不在框架 .c,
* 而在生成物 ui_page_registry.c(唯一真源 = 菜单树 ui_menu_tree.txt 的 #page= 注记)。
* 框架按描述符**指针**开页,不再有页 ID 枚举与 s_defs[] 下标表——
* 少一处"枚举/表/节点下标三处必须对齐"的手工同步点。
*/
typedef struct {
const char *id; /* 页面模块名(ASCII,日志/诊断用;= ui_page_<id> 的 <id>) */
const char *title; /* 标题(浅 V 区居中) */
const char *subtitle; /* 副标题;NULL = 无(仅信息页有) */
int32_t subtitle_y; /* 副标题放置 y(仅 subtitle 非空时有效) */
int32_t title_y; /* 标题放置 y;0 = 框架默认(UI_TITLE_Y) */
uint8_t hide_deco; /* 1 = 不绘"浅 V"装饰(密码页跟随实机) */
uint8_t hide_footer; /* 1 = 不绘页脚页码(密码页跟随实机:无页脚) */
uint8_t selectable; /* 1 = 列表页(↑↓移选+分页);0 = 信息页(静态行) */
uint8_t key_intercept; /* 1 = LV_KEY_LEFT 先交 key_cb(true = 页已处理不关页);ESC 恒不可拦截 */
uint8_t edit_capable; /* 1 = 本页支持编辑模式(可编辑还须 UI_PERM_MODIFY 权限) */
int32_t top_y; /* 首行槽顶 y */
int32_t pitch; /* 行距(相邻行槽顶间距) */
int32_t band_h; /* 选中白带高(0 = 无选中带) */
int32_t text_dy; /* 文本放置相对行槽顶的垂直偏移 */
int32_t left_x; /* 左列 x(信息页=标签、列表页=名) */
int32_t right_x; /* 右列 x(值) */
uint8_t rows_per_page; /* 每页可见行数(0 = 不分页) */
uint16_t row_count; /* 总行数(静态页;动态页用 count_cb) */
const cp_row_t *rows; /* 静态数据表(Flash);NULL = 动态页 */
uint8_t fixed_cur_page; /* 信息页页脚"第 X 页"固定显示值 */
uint8_t fixed_total_pages; /* 信息页页脚"共 Y 页"固定显示值 */
/* —— 扩展钩子(全可选;NULL = 纯内置行为;均运行在 uiTask 上下文)—— */
void (*init_cb)(void); /* 开页后调一次(页面模块重置运行态) */
void (*refresh)(uint16_t row, char *name, char *value); /* 动态行:逐行取数(名/值) */
uint16_t (*count_cb)(void); /* 动态行数(记录页) */
void (*draw_cb)(lv_layer_t *layer, const lv_area_t *clip); /* 表单/操作页:自定义内容 */
bool (*key_cb)(lv_key_t key); /* 页面自有键;true = 页已处理 */
} content_page_def_t;
/*
* 函数功能:打开指定内容页(建屏 + 切屏 + 焦点置页面容器)
* 入口参数:desc - 页面描述符(取自 ui_page_registry_for / ui_page_registry_password)
* const content_page_def_t *
* perm - 本次进入授予的权限 ui_perm_t(UI_PERM_VIEW/MODIFY;
* 由逻辑层按是否过密码页/所输密码决定,页面经
* ui_content_page_is_editable 查询)
* 返回值:true - 成功;false - 失败(desc 为 NULL / 已有页面打开 / 对象创建失败)
* 限定条件:ui_menu_init() 已完成(默认输入组与菜单屏存在)
* 函数说明:1. 新建独立屏幕,全屏容器自绘全部内容(无子对象,池开销极小)
* 2. 容器入默认输入组并获焦——按键由本模块处理(↑↓移选/ESC返回/页面钩子)
* 3. 导航状态不压层:菜单 s_depth/s_sel 原样保留,ESC 返回即还原
*/
bool ui_content_page_open(const content_page_def_t *desc, uint8_t perm);
/*
* 函数功能:关闭当前内容页(焦点回菜单容器 + 切回菜单屏 + 销毁页面)
* 入口参数:无
* 返回值:无
* 限定条件:ui_content_page_is_open() 为 true
* 函数说明:先移焦再删屏,防输入组焦点悬空;LVGL 池内存随即回落
*/
void ui_content_page_close(void);
/*
* 函数功能:失效当前打开的页面容器(触发整页重绘)
* 入口参数:无
* 返回值:无
* 限定条件:有页面打开时生效
* 函数说明:供页面模块(表单/操作页)自身状态变化后调用;无页打开时空操作
*/
void ui_content_page_invalidate(void);
/*
* 函数功能:查询是否有内容页打开
* 入口参数:无
* 返回值:true - 有页面打开;false - 无
* 限定条件:无
* 函数说明:供逻辑层/调试判断当前处于菜单屏还是内容页
*/
bool ui_content_page_is_open(void);
/*
* 函数功能:查询当前打开的页面是否可编辑
* 入口参数:无
* 返回值:true - 页面支持编辑模式且被授予修改权限;false - 其余(无页打开/
* 页面不支持编辑/仅只读权限)
* 限定条件:无
* 函数说明:供页面钩子(key_cb/draw_cb)决定编辑行为;权限由 ui_content_page_open
* 的 perm 实参授予(逻辑层按密码页结果/无密码默认只读传入)
*/
bool ui_content_page_is_editable(void);
/*
* 函数功能:查询当前列表页的选中行(全局下标,跨页连续)
* 入口参数:无
* 返回值:选中行下标;无页打开/非列表页 = 0 uint16_t
* 限定条件:无
* 函数说明:供页面钩子(key_cb/draw_cb)感知当前选中位置(编辑模式以
* 选中行作用、绘制钩子定位选中行带)
*/
uint16_t ui_content_page_selected_row(void);
#endif /* UI_CONTENT_PAGE_H */