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 改同一批文件、文件内
交叠,硬切会产生不可编译的中间态,故整波一笔。
This commit is contained in:
2026-10-20 21:14:14 +08:00
parent 9d06798f2d
commit dafc18f5ed
47 changed files with 1447 additions and 450 deletions
+79 -107
View File
@@ -4,7 +4,9 @@
* 自绘全部内容(浅 V 装饰 + 居中标题 + 行数据 + 页脚页码),无子对象;
* 只持机制:屏/容器按需建毁、焦点、按键分发、描述符驱动通用绘制;
* 不持任何页面数据——各页数据/几何/钩子由 ui_page_xxx 模块经
* content_page_def_t 接入描述符表 s_defs[](架构见 内容页架构.md);
* content_page_def_t 提供,框架按**描述符指针**开页;"哪个叶子进哪一页"
* 的绑定在生成物 ui_page_registry.c(唯一真源 = ui_menu_tree.txt 的
* #page= 注记;架构见 内容页架构.md);
* 进入时切屏并获焦、ESC 返回菜单屏并还原焦点;
* 不持有导航状态——由逻辑层 ui_menu.c 在 ENTER 叶子节点时驱动
* 适用平台:STM32F407ZGTx / PC 模拟器(两端共用)
@@ -26,6 +28,12 @@
* 描述符 init_cb(开页后调一次,页面模块重置运行态)
* 2026-09-13 王建锋 cp_draw_cb 按区块拆为四个绘制子函数(cp_draw_deco/header/rows/footer),
* 主函数仅编排(行数取一次供行窗口/页脚共用),行为零变化
* 2026-10-15 王建锋 U1 设计令牌层:屏幕/骨架/行距/缓冲/1bpp 语义色字面量收进
* ui_tokens.h(纯替换,像素逐位一致)
* 2026-10-15 王建锋 U3 页面原型化:删 content_page_id_t 枚举与 s_defs[],open() 改收
* 描述符指针、s_open 变指针(NULL = 未打开);7 个页面头包含移交
* ui_page_registry.c;日志页身份改打描述符 id(ASCII 页名,比原
* id=%u 更可定位);绘制/按键/几何一律未动
*/
/* 头文件包含区 */
@@ -33,20 +41,6 @@
#include "ui_content_page.h"
#include "ui_page_device_info.h"
#include "ui_page_ac.h"
#include "ui_page_dc.h"
#include "ui_page_password.h"
#include "ui_page_net_cfg.h"
#include "ui_page_runlog.h"
#include "ui_page_selftest.h"
#include "ui_standby.h"
#include "ui_pwd_def.h"
@@ -55,6 +49,10 @@
#include "ui_deco.h"
#include "ui_style.h"
#include "ui_tokens.h"
#include "lvgl.h"
#define DBG_TAG "[UI_CONTENT_PAGE]"
@@ -63,57 +61,32 @@
/* 私有宏定义区 */
/*
* 布局常量(设备像素;参考图 10/20/21 实测,3x 放大图缩回 160x160 测量):
* 标题 y=2(CJK 字形顶距 +1 → 墨迹自 y=3 起,与参考图一致)、页脚 y=143;
* 字形顶距(CJK +1 / ASCII +2)已计入放置 y,墨迹与参考图对齐
* 布局取值全部来自 ui_tokens.h(L0 设计令牌层,本文件不再自带同值字面量):
* 屏幕 UI_SCREEN_W/H、标题 UI_TITLE_Y、页脚 UI_PAGE_LINE_Y、文本条带
* UI_TEXT_BAND_H、行缓冲 UI_BUF_STRLEN、选中带内缩 UI_SEL_BAND_INSET_L/R、
* 页脚左起 UI_FOOTER_X、语义色 UI_COL_FG/BG/SEL_FG/SEL_BG。
* 几何实测来源仍是参考图 10/20/21(3x 放大图缩回 160x160 测量):标题 y=2
* (CJK 字形顶距 +1 → 墨迹自 y=3 起,与参考图一致)、页脚 y=143;字形顶距
* (CJK +1 / ASCII +2)已计入放置 y,墨迹与参考图对齐
*/
#define CP_SCREEN_W 160 /* 屏幕宽(px,与 ui_port.h / 模拟器一致) */
#define CP_SCREEN_H 160 /* 屏幕高(px) */
#define CP_TITLE_Y 2 /* 标题放置 y(居中;墨迹自 y=3 起) */
#define CP_PAGE_LINE_Y 143 /* 页脚"第 X 页,共 Y 页"放置 y(x=6 左对齐) */
#define CP_ROW_BUF_LEN 32 /* 动态行栈缓冲长度(同帧同步光栅化;超出行带即被裁切) */
/* 静态变量定义区 */
/*
* 描述符表:页 ID → 页面模块描述符(每页一行;新增页 = 枚举值 + 此处一行,
* 框架机制不变,见 内容页架构.md §七)
* 当前打开的页:直接持页面描述符指针(NULL = 未打开)。
* 页身份由调用方给出(逻辑层经 ui_page_registry_for / _password 取描述符),
* 框架不再维护"页 ID 枚举 ↔ s_defs 下标 ↔ 菜单节点"三处必须手工对齐的映射
* ——那正是旧 menu_content_page_for() 手写 case 的错源(表序一变即错位)。
*/
static const content_page_def_t *const s_defs[CONTENT_PAGE_COUNT] = {
[CONTENT_PAGE_DEVICE_INFO] = &ui_page_device_info_desc,
[CONTENT_PAGE_AC] = &ui_page_ac_desc,
[CONTENT_PAGE_DC] = &ui_page_dc_desc,
[CONTENT_PAGE_PASSWORD] = &ui_page_password_desc,
[CONTENT_PAGE_NET_CFG] = &ui_page_net_cfg_desc,
[CONTENT_PAGE_RUN_LOG] = &ui_page_runlog_desc,
[CONTENT_PAGE_SELFTEST_LOG] = &ui_page_selftest_desc,
};
static content_page_id_t s_open = CONTENT_PAGE_COUNT; /* 当前打开的页(COUNT = 未打开) */
static const content_page_def_t *s_open = NULL;
static uint16_t s_sel; /* 列表页选中行(全局下标,跨页连续) */
static uint8_t s_perm; /* 本次授予权限(ui_perm_t;开页时装载,关页重置) */
static lv_obj_t *s_scr; /* 页面屏幕(进入时建、返回时毁) */
static lv_obj_t *s_cont; /* 页面全屏容器(焦点/键入口 + 自绘载体) */
/* flat:透明、无边框/圆角/内边距——剥离 mono 主题给普通 lv_obj 附加的 card 样式(同 ui_menu_view.c) */
/* 用常量样式(LV_STYLE_CONST_INIT):属性存静态数组,零 LV_MEM 池分配;
* 动态样式首次 set_prop 会从池分配 values_and_props 且永不释放(x64 实测 88B/个) */
static const lv_style_const_prop_t cp_flat_props[] = {
{ .prop = LV_STYLE_BG_OPA, .value = { .num = LV_OPA_TRANSP } },
{ .prop = LV_STYLE_BORDER_WIDTH, .value = { .num = 0 } },
{ .prop = LV_STYLE_RADIUS, .value = { .num = 0 } },
{ .prop = LV_STYLE_PAD_LEFT, .value = { .num = 0 } },
{ .prop = LV_STYLE_PAD_RIGHT, .value = { .num = 0 } },
{ .prop = LV_STYLE_PAD_TOP, .value = { .num = 0 } },
{ .prop = LV_STYLE_PAD_BOTTOM, .value = { .num = 0 } },
{ .prop = LV_STYLE_PAD_ROW, .value = { .num = 0 } },
{ .prop = LV_STYLE_PAD_COLUMN, .value = { .num = 0 } },
LV_STYLE_CONST_PROPS_END
};
LV_STYLE_CONST_INIT(cp_style_flat, cp_flat_props);
/* 容器样式:flat(透明 + 无边框/圆角/内边距)取自共享样式表 ui_style.c——
* 与 ui_menu_view / ui_standby 共用同一 const 对象(原先三处各定义一份逐字相同
* 的 9 项属性数组);常量样式零 LV_MEM 池分配的纪律见 ui_style.h */
/* 函数定义区 */
@@ -175,18 +148,18 @@ static void cp_draw_centered_text(lv_layer_t *layer, const char *text, int32_t y
lv_draw_label_dsc_t ld;
area.x1 = 0;
area.x2 = CP_SCREEN_W - 1;
area.x2 = UI_SCREEN_W - 1;
area.y1 = y;
area.y2 = y + 15; /* 覆盖字体行高(13)+ 余量 */
area.y2 = y + UI_TEXT_BAND_H; /* 覆盖字体行高(13)+ 余量 */
lv_draw_label_dsc_init(&ld);
ld.base.layer = layer;
ld.text = text;
ld.text_static = 1; /* Flash 字符串:不拷内存 */
ld.color = lv_color_white();
ld.color = UI_COL_FG;
/* 本 LVGL 版 lv_text_get_width:length=0 表示"不测量"(返回 0);
* length≠0 时循环无 NUL 检查,传 -1 会越串尾死循环——必须传 strlen */
ld.ofs_x = (CP_SCREEN_W - lv_text_get_width(text, (uint32_t)strlen(text), LV_FONT_DEFAULT, 0)) / 2;
ld.ofs_x = (UI_SCREEN_W - lv_text_get_width(text, (uint32_t)strlen(text), LV_FONT_DEFAULT, 0)) / 2;
ld.ofs_y = 0;
lv_draw_label(layer, &ld, &area);
}
@@ -211,7 +184,7 @@ static void cp_draw_deco(lv_layer_t *layer, const content_page_def_t *d) {
* d - 页面描述符 const content_page_def_t *
* 返回值:无
* 限定条件:页面已打开
* 函数说明:标题放置 y 每页可配(title_y,0 = 框架默认 CP_TITLE_Y);
* 函数说明:标题放置 y 每页可配(title_y,0 = 框架默认 UI_TITLE_Y);
* 副标题仅信息页(subtitle 非空);重绘区不相交则跳过区块
*/
static void cp_draw_header(lv_layer_t *layer, const content_page_def_t *d) {
@@ -219,12 +192,12 @@ static void cp_draw_header(lv_layer_t *layer, const content_page_def_t *d) {
/* 标题(居中;放置 y 每页可配,0 = 框架默认) */
{
int32_t ty = (d->title_y != 0) ? d->title_y : CP_TITLE_Y;
int32_t ty = (d->title_y != 0) ? d->title_y : UI_TITLE_Y;
t.x1 = 0;
t.x2 = CP_SCREEN_W - 1;
t.x2 = UI_SCREEN_W - 1;
t.y1 = ty;
t.y2 = ty + 15;
t.y2 = ty + UI_TEXT_BAND_H;
if (cp_areas_intersect(&t, &layer->_clip_area)) {
cp_draw_centered_text(layer, d->title, ty);
}
@@ -233,9 +206,9 @@ static void cp_draw_header(lv_layer_t *layer, const content_page_def_t *d) {
/* 副标题(仅信息页) */
if (d->subtitle != NULL) {
t.x1 = 0;
t.x2 = CP_SCREEN_W - 1;
t.x2 = UI_SCREEN_W - 1;
t.y1 = d->subtitle_y;
t.y2 = d->subtitle_y + 15;
t.y2 = d->subtitle_y + UI_TEXT_BAND_H;
if (cp_areas_intersect(&t, &layer->_clip_area)) {
cp_draw_centered_text(layer, d->subtitle, d->subtitle_y);
}
@@ -268,19 +241,19 @@ static void cp_draw_rows(lv_layer_t *layer, const content_page_def_t *d, int32_t
}
bool dynamic = (d->refresh != NULL);
char dyn_name[CP_ROW_BUF_LEN]; /* 动态行栈缓冲(同帧同步光栅化) */
char dyn_value[CP_ROW_BUF_LEN];
char dyn_name[UI_BUF_STRLEN]; /* 动态行栈缓冲(同帧同步光栅化) */
char dyn_value[UI_BUF_STRLEN];
for (int32_t i = 0; i < cnt; i++) {
int32_t row_idx = first + i; /* 数据下标(跨页连续) */
int32_t slot_y = d->top_y + i * d->pitch; /* 行槽顶(窗口内位置固定) */
bool is_sel = (d->selectable != 0u) && (row_idx == (int32_t)s_sel);
lv_color_t color = is_sel ? lv_color_black() : lv_color_white();
lv_color_t color = is_sel ? UI_COL_SEL_FG : UI_COL_FG;
lv_area_t strip;
/* 行条带(整宽)不在重绘区:跳过(culling) */
strip.x1 = 0;
strip.x2 = CP_SCREEN_W - 1;
strip.x2 = UI_SCREEN_W - 1;
strip.y1 = slot_y;
strip.y2 = slot_y + d->pitch - 1;
if (!cp_areas_intersect(&strip, &layer->_clip_area)) {
@@ -292,13 +265,13 @@ static void cp_draw_rows(lv_layer_t *layer, const content_page_def_t *d, int32_t
lv_draw_rect_dsc_t fd;
lv_area_t band;
band.x1 = 2;
band.x2 = CP_SCREEN_W - 3; /* x=156(与参考图实测一致) */
band.x1 = UI_SEL_BAND_INSET_L;
band.x2 = UI_SCREEN_W - UI_SEL_BAND_INSET_R; /* x=156(与参考图实测一致) */
band.y1 = slot_y;
band.y2 = slot_y + d->band_h - 1;
lv_draw_rect_dsc_init(&fd);
fd.base.layer = layer;
fd.bg_color = lv_color_white();
fd.bg_color = UI_COL_SEL_BG;
fd.bg_opa = LV_OPA_COVER;
lv_draw_rect(layer, &fd, &band);
}
@@ -353,10 +326,10 @@ static void cp_draw_rows(lv_layer_t *layer, const content_page_def_t *d, int32_t
* (hide_footer;密码页跟随实机)
*/
static void cp_draw_footer(lv_layer_t *layer, const content_page_def_t *d, int32_t nrows) {
char buf[32]; /* 页行文本缓冲(栈):"第 %u 页,共 %u 页" 最长远不足 32B */
char buf[UI_BUF_STRLEN]; /* 页行文本缓冲(栈):"第 %u 页,共 %u 页" 最长远不足 32B */
uint8_t cur; /* 当前页码(1 基) */
uint8_t pages; /* 总页数 */
lv_area_t t; /* 页脚条带区域(整宽,放置于 CP_PAGE_LINE_Y) */
lv_area_t t; /* 页脚条带区域(整宽,放置于 UI_PAGE_LINE_Y) */
/* 页码计算,两种来源:
* - 列表页(selectable):随选中行现算——当前页 = 选中行/每页行数 + 1
@@ -373,12 +346,12 @@ static void cp_draw_footer(lv_layer_t *layer, const content_page_def_t *d, int32
pages = d->fixed_total_pages;
}
/* 页脚条带:整宽,放置于 CP_PAGE_LINE_Y(143,字形顶距已计入放置 y,
/* 页脚条带:整宽,放置于 UI_PAGE_LINE_Y(143,字形顶距已计入放置 y,
* 墨迹与参考图对齐);高 15 = 字体行高 13 + 余量,整行文字一次覆盖 */
t.x1 = 0;
t.x2 = CP_SCREEN_W - 1;
t.y1 = CP_PAGE_LINE_Y;
t.y2 = CP_PAGE_LINE_Y + 15;
t.x2 = UI_SCREEN_W - 1;
t.y1 = UI_PAGE_LINE_Y;
t.y2 = UI_PAGE_LINE_Y + UI_TEXT_BAND_H;
/* 双重门控:① 本页开启页脚(hide_footer;密码页跟随实机:无页码)
* ② 页脚条带与重绘区相交(culling)——两者皆成立才格式化并绘制 */
@@ -388,13 +361,13 @@ static void cp_draw_footer(lv_layer_t *layer, const content_page_def_t *d, int32
lv_draw_label_dsc_t ld;
/* ofs 相对 coords 原点(t.x1=0,t.y1=CP_PAGE_LINE_Y):
/* ofs 相对 coords 原点(t.x1=0,t.y1=UI_PAGE_LINE_Y):
* 水平 6 = 左对齐、左留白 6px(与实机一致);垂直 0(顶距已计入放置 y) */
lv_draw_label_dsc_init(&ld);
ld.base.layer = layer;
ld.text = buf; /* 栈缓冲:本页码每次现算,同帧内有效(同步光栅化) */
ld.color = lv_color_white(); /* 页码恒白字(不像选中行那样反色) */
ld.ofs_x = 6;
ld.color = UI_COL_FG; /* 页码恒白字(不像选中行那样反色) */
ld.ofs_x = UI_FOOTER_X;
ld.ofs_y = 0;
lv_draw_label(layer, &ld, &t);
}
@@ -410,7 +383,7 @@ static void cp_draw_footer(lv_layer_t *layer, const content_page_def_t *d, int32
* 跳过不在重绘区的部分;末尾调 draw_cb(页自定义内容,在内置区块之后)
*/
static void cp_draw_cb(lv_event_t *e) {
const content_page_def_t *d = s_defs[s_open];
const content_page_def_t *d = s_open;
lv_layer_t *layer = lv_event_get_layer(e);
int32_t nrows = (int32_t)cp_row_total(d); /* 行数取一次:供行窗口与页脚页码共用 */
@@ -438,7 +411,7 @@ static void cp_draw_cb(lv_event_t *e) {
* 信息页不响应;动态行数可能缩小,选中行越界时重置回首行
*/
static void cp_key_cb(lv_event_t *e) {
const content_page_def_t *d = s_defs[s_open];
const content_page_def_t *d = s_open;
lv_key_t key = (lv_key_t)lv_event_get_key(e);
ui_standby_notify_activity(); /* 用户活动:重置待机空闲计时 */
@@ -496,24 +469,23 @@ static void cp_key_cb(lv_event_t *e) {
/*
* 函数功能:打开指定内容页(建屏 + 切屏 + 焦点置页面容器)
* 入口参数:id - 内容页 ID content_page_id_t
* 入口参数:desc - 页面描述符(取自 ui_page_registry_for / ui_page_registry_password)
* const content_page_def_t *
* perm - 本次进入授予的权限 ui_perm_t(UI_PERM_VIEW/MODIFY)
* 返回值:true - 成功;false - 失败(ID 无效/已有页面打开/无描述符/对象创建失败)
* 返回值:true - 成功;false - 失败(desc 为 NULL / 已有页面打开 / 对象创建失败)
* 限定条件:ui_menu_init() 已完成(默认输入组与菜单屏存在)
* 函数说明:1. lv_obj_create(NULL) 新建独立屏幕 + 全屏容器(自绘载体,无子对象)
* 2. 容器入默认输入组并获焦后再切屏——按键立即由本模块接管
* 3. 导航状态不压层:菜单 s_depth/s_sel 原样保留,ESC 返回即还原
* 4. 记录授予权限(页面经 ui_content_page_is_editable 查询)
*/
bool ui_content_page_open(content_page_id_t id, uint8_t perm) {
if (id >= CONTENT_PAGE_COUNT || s_open != CONTENT_PAGE_COUNT) {
return false; /* ID 无效或已有页面打开(防御) */
bool ui_content_page_open(const content_page_def_t *desc, uint8_t perm) {
if (s_open != NULL) {
return false; /* 已有页面打开(防御:页面按需建毁,同一时刻至多一页) */
}
const content_page_def_t *d = s_defs[id];
if (d == NULL) {
DBG_ERROR("page open: id=%u no descriptor (enum/table drift)", (unsigned)id);
return false; /* 枚举/描述符表漂移:拒开并留痕,不崩溃 */
if (desc == NULL) {
DBG_ERROR("page open: NULL descriptor (node has no bound page)");
return false; /* 调用方把未绑定内容页的叶子当页面开了:拒开并留痕,不崩溃 */
}
s_perm = perm; /* 本次授予权限(页面可编辑性 = edit_capable 且修改权限) */
@@ -530,17 +502,17 @@ bool ui_content_page_open(content_page_id_t id, uint8_t perm) {
return false;
}
lv_obj_add_flag(s_cont, LV_OBJ_FLAG_IGNORE_LAYOUT); /* 绝对定位,不参与父级布局 */
lv_obj_set_size(s_cont, CP_SCREEN_W, CP_SCREEN_H); /* 全屏覆盖 */
lv_obj_add_style(s_cont, &cp_style_flat, 0); /* 透明、无边框:剥离主题 card */
lv_obj_set_size(s_cont, UI_SCREEN_W, UI_SCREEN_H); /* 全屏覆盖 */
lv_obj_add_style(s_cont, &ui_style_flat, 0); /* 透明、无边框:剥离主题 card */
lv_obj_add_flag(s_cont, LV_OBJ_FLAG_CLICK_FOCUSABLE); /* 可获焦:键事件入口 */
lv_obj_add_event_cb(s_cont, cp_key_cb, LV_EVENT_KEY, NULL);
lv_obj_add_event_cb(s_cont, cp_draw_cb, LV_EVENT_DRAW_MAIN, NULL);
s_open = id;
s_open = desc;
s_sel = 0u; /* 每次进入从首行开始(页面按需建毁,不跨次保留) */
if (d->init_cb != NULL) {
d->init_cb(); /* 页面模块重置运行态(编辑模式/光标/保存标志等) */
if (desc->init_cb != NULL) {
desc->init_cb(); /* 页面模块重置运行态(编辑模式/光标/保存标志等) */
}
/* 焦点/键入口:入默认输入组并获焦(先于切屏,保证按键立即由本模块接管) */
@@ -551,7 +523,7 @@ bool ui_content_page_open(content_page_id_t id, uint8_t perm) {
}
lv_screen_load(s_scr); /* 切到内容页(菜单屏对象保留、不销毁) */
DBG_INFO("page open: id=%u rows=%u", (unsigned)id, (unsigned)cp_row_total(d));
DBG_INFO("page open: id=%s rows=%u", desc->id, (unsigned)cp_row_total(desc));
return true;
}
@@ -564,7 +536,7 @@ bool ui_content_page_open(content_page_id_t id, uint8_t perm) {
* 2. 切回菜单屏后销毁页面屏幕,LVGL 池内存随即回落
*/
void ui_content_page_close(void) {
if (s_open == CONTENT_PAGE_COUNT || s_cont == NULL) {
if (s_open == NULL || s_cont == NULL) {
return; /* 未打开(防御) */
}
@@ -574,14 +546,14 @@ void ui_content_page_close(void) {
lv_screen_load(lv_obj_get_screen(menu_cont)); /* 切回菜单屏(对象未动,原样还原) */
}
DBG_INFO("page close: id=%u", (unsigned)s_open);
DBG_INFO("page close: id=%s", s_open->id);
lv_obj_delete(s_cont); /* 先删容器 */
lv_obj_delete(s_scr); /* 再删屏 */
s_scr = NULL;
s_cont = NULL;
s_open = CONTENT_PAGE_COUNT;
s_open = NULL;
s_sel = 0u;
s_perm = (uint8_t)UI_PERM_VIEW; /* 权限重置(卫生:不留上次进入的授权) */
}
@@ -594,7 +566,7 @@ void ui_content_page_close(void) {
* 函数说明:供逻辑层/调试判断当前处于菜单屏还是内容页
*/
bool ui_content_page_is_open(void) {
return (s_open != CONTENT_PAGE_COUNT);
return (s_open != NULL);
}
/*
@@ -605,10 +577,10 @@ bool ui_content_page_is_open(void) {
* 函数说明:供页面钩子(key_cb/draw_cb)决定编辑行为;权限由 open 的 perm 实参授予
*/
bool ui_content_page_is_editable(void) {
if (s_open == CONTENT_PAGE_COUNT || s_defs[s_open] == NULL) {
if (s_open == NULL) {
return false; /* 无页打开或无描述符(防御) */
}
return (s_defs[s_open]->edit_capable != 0u && s_perm == (uint8_t)UI_PERM_MODIFY);
return (s_open->edit_capable != 0u && s_perm == (uint8_t)UI_PERM_MODIFY);
}
/*
@@ -619,10 +591,10 @@ bool ui_content_page_is_editable(void) {
* 函数说明:供页面钩子(key_cb/draw_cb)感知当前选中位置
*/
uint16_t ui_content_page_selected_row(void) {
if (s_open == CONTENT_PAGE_COUNT || s_defs[s_open] == NULL) {
if (s_open == NULL) {
return 0u;
}
if (s_defs[s_open]->selectable == 0u) {
if (s_open->selectable == 0u) {
return 0u; /* 信息页/表单页无行选中概念 */
}
return s_sel;