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:
@@ -31,6 +31,10 @@
|
||||
|
||||
#include "ui_deco.h"
|
||||
|
||||
#include "ui_style.h"
|
||||
|
||||
#include "ui_tokens.h"
|
||||
|
||||
#include "lvgl.h"
|
||||
|
||||
#define DBG_TAG "[UI_MENU_VIEW]"
|
||||
@@ -79,22 +83,8 @@ static uint8_t s_row_node[MENU_PAGE_MAX][MENU_ITEM_MAX]; /* 行数据:节
|
||||
static menu_frame_t s_frame[MENU_PAGE_MAX]; /* 每层框几何缓存:位置/尺寸/选中行(现算不落状态,供子框对齐;见上方类型注释) */
|
||||
|
||||
/* 样式(静态,一次性初始化,常驻) */
|
||||
/* flat:通用——透明、无边框/圆角/内边距(剥离主题 card)。用常量样式(LV_STYLE_CONST_INIT):
|
||||
* 属性存静态数组、零 LV_MEM 池分配;动态样式首次 set_prop 会从池分配 values_and_props 且永不释放 */
|
||||
static const lv_style_const_prop_t s_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(s_style_flat, s_flat_props);
|
||||
/* flat(透明、无边框/圆角/内边距,剥离主题 card)取自共享样式表 ui_style.c:
|
||||
* 与 ui_content_page / ui_standby 共用同一 const 对象,不再本地重复定义 */
|
||||
static lv_style_t s_style_frame; /* 框体:透明底 + 1px 白边框 */
|
||||
static lv_style_t s_style_scrollbar; /* 滚动条:白色、4px 宽 */
|
||||
|
||||
@@ -122,7 +112,7 @@ static void view_styles_init(void) {
|
||||
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);
|
||||
lv_style_set_border_color(&s_style_frame, lv_color_white());
|
||||
lv_style_set_border_color(&s_style_frame, UI_COL_FG);
|
||||
lv_style_set_radius(&s_style_frame, 0);
|
||||
lv_style_set_pad_all(&s_style_frame, 0);
|
||||
lv_style_set_pad_gap(&s_style_frame, 0);
|
||||
@@ -130,8 +120,8 @@ static void view_styles_init(void) {
|
||||
/* 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());
|
||||
lv_style_set_width(&s_style_scrollbar, 4);
|
||||
lv_style_set_bg_color(&s_style_scrollbar, UI_COL_FG);
|
||||
lv_style_set_width(&s_style_scrollbar, UI_SCROLLBAR_W);
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -237,7 +227,7 @@ static void view_frame_draw_cb(lv_event_t *e) {
|
||||
lv_area_t row_area;
|
||||
const char *name = ui_menu_tab_name(s_row_node[depth][i]);
|
||||
bool is_sel = (i == (int32_t)f->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;
|
||||
|
||||
row_area.x1 = area.x1;
|
||||
row_area.x2 = area.x2;
|
||||
@@ -254,7 +244,7 @@ static void view_frame_draw_cb(lv_event_t *e) {
|
||||
lv_draw_rect_dsc_t fd;
|
||||
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, &row_area);
|
||||
}
|
||||
@@ -407,7 +397,7 @@ void ui_menu_view_frame_build(uint8_t depth, uint8_t node, uint8_t sel, uint16_t
|
||||
if (box == NULL) {
|
||||
return; /* LVGL 池耗尽:框未建,状态回滚 */
|
||||
}
|
||||
lv_obj_add_style(box, &s_style_flat, 0);
|
||||
lv_obj_add_style(box, &ui_style_flat, 0);
|
||||
lv_obj_add_style(box, &s_style_frame, 0);
|
||||
lv_obj_add_flag(box, LV_OBJ_FLAG_IGNORE_LAYOUT);
|
||||
lv_obj_set_pos(box, f->x, f->y);
|
||||
@@ -571,7 +561,7 @@ static void view_deco_draw_cb(lv_event_t *e) {
|
||||
sd.base.layer = layer;
|
||||
sd.text = "当前模式 : 无模式";
|
||||
sd.text_static = 1; /* Flash 字符串:不拷内存 */
|
||||
sd.color = lv_color_white();
|
||||
sd.color = UI_COL_FG;
|
||||
sd.ofs_x = 0; /* 区域原点即状态行左上 (17,20) */
|
||||
sd.ofs_y = 0;
|
||||
lv_draw_label(layer, &sd, &s_status_area);
|
||||
@@ -608,7 +598,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, &ui_style_flat, 0); /* 透明、无边框:纯定位层,不可见 */
|
||||
lv_obj_set_size(menu, sw, sh);
|
||||
|
||||
/* 持久容器:全屏覆盖、永不删(焦点/键唯一入口;各层框均建其内,防悬空) */
|
||||
@@ -616,7 +606,7 @@ int ui_menu_view_init(void) {
|
||||
if (s_cont == NULL) {
|
||||
return -1;
|
||||
}
|
||||
lv_obj_add_style(s_cont, &s_style_flat, 0); /* 透明、无边框 */
|
||||
lv_obj_add_style(s_cont, &ui_style_flat, 0); /* 透明、无边框 */
|
||||
lv_obj_add_flag(s_cont, LV_OBJ_FLAG_IGNORE_LAYOUT); /* 绝对定位,不参与父级 flex 布局 */
|
||||
lv_obj_set_pos(s_cont, 0, 0);
|
||||
lv_obj_set_size(s_cont, sw, sh); /* 全屏覆盖 */
|
||||
@@ -634,7 +624,7 @@ int ui_menu_view_init(void) {
|
||||
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], &ui_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); /* 左上 (0,44):装饰/状态行之下 */
|
||||
|
||||
Reference in New Issue
Block a user