Files
STM32F4-Base/Drivers/BSP/UI/ui_w_setlist.c
T
wandering1 45393be3d5 feat(ui-pages): 参数定值控制字软压板四个查看页接 par 快照
四个空叶子(参数定值 → 查看 → 参数/定值/控制字/软压板)接上 par 通道。页面薄、模型厚:
新建 Drivers/BSP/UI/ui_w_setview.{c,h} 收四张视图表 + Q10 换算 + 越界反显,四个页面各是
一份实例 + 四个跳板钩子;渲染与分页复用批 1 的 ui_w_setlist(查看态 commit=NULL:无保存行、
无编辑通路,§3.2.6 查看列口径)。

行表三项口径全部有真源,一项都不在这份文件里拟定
- id = App/algo/prot_set.h 的枚举序。BSP 不 include App 头(分层门禁),所以行表里是裸数字
  + 行尾注枚举名——这份对不上就没人能在 BSP 侧编译期发现,于是从外面钉:见下面的新脚本
- min/max = App/algo/prot_set.c 的 g_prot_set_meta Q10 上下限按该行小数位换算的结果。整定页
  下一批复用同一批表,界内界外一处一份,不会出现「查看看着合法、整定说越界」两套边界
- 显示名/小数位/单位 = docs/UI设计/页面扩充详细方案.md §3.2.2~§3.2.5 四张表

一处偏离文档(不是静默偏离,写进三处:代码注释 / 文档落地注 / 本正文)
- 四个返回系数按 2 位小数显示,文档写 3 位。理由是量值真实性不是排版:Q10 存不下 0.95
  (0.95×1024 截成 972 → 真存 0.94921875),按 3 位显示就多打一位根本不存在的数字;整定单
  上写 0.95 的人看到屏上 0.949 会怀疑装置、甚至反复重输(重输还是 0.949)。2 位才是这份存储
  在自身可分辨精度上的正确读数——文档的 3 位来自「把 Q10 当成十进制存储」这个误会
- 单位挂在值串尾(10.00A / 500ms)而不是塞进显示名:值列只有数字会让 60000 既是 ms 又是 A。
  为此给 ui_w_setlist_row_t 加了 unit 字段(该结构此前无任何消费者,扩展零成本);只挂在
  非编辑态——编辑中是逐位输入串,挂单位会让位光标算错列宽。A/V/ms/Hz 全 ASCII 不占字库

新增可重跑核验:tools/setview_check/check_setview_limits.py
- 从外面把三件事钉住:四张表的 id 恰好覆盖 0..39 各一次;数值行 min/max 等于 meta 上下限的
  换算;kind 与 meta.is_ctl 一致、dec 查表与行表小数位一致、单位只允许 ASCII。改 meta 不改
  行表就红。当前输出:行表 40 行 / dec 40 项 / meta 40 项 -> OK
- 写这个脚本时自己踩的两个坑也留在脚本注释里:① 正则用 \w+ 吃不下带引号的单位,会让整行
  **静默跳过**(少检一行比检查错更难发现)→ 改 \S+ 并加「行数必须等于 40」硬闸;② 脚本要
  打印中文,Windows 控制台按 CP936 写 stdout 会崩在半路(崩在半路 = 一条都没检查)→ 开头
  把 stdout/stderr 锁到 UTF-8

两个只有画面才会暴露的错(都不是静态检查抓到的,都记进了注释)
- ui_data_par() 的通道是 0=成功 / -1=尚无首帧,我按 bool 反义用了:真机与模拟器上都会「永远
  读不到」,整页值列恒 --,而页面看起来完全正常、编译与钩子全绿。看像素图才发现满屏横杠
- 肉眼把 CT 一次额定读成 1000A,字形匹配器证明值列只有 4 个字符位(命中 x=101/107/113/119):
  实际是 100A。断言因此按匹配器能证明的写。顺带一个自洽佐证:PT 10000/100 V 配 CT 100/5 A
  正是 10 kV 馈线终端的变比;控制字页默认值天然两两一组(Ⅰ/Ⅱ 段跳闸、Ⅲ 与过负荷段告警),
  行表若整体错位就不可能复现这个组合——这条已进像素断言,成为 id 对齐的机械证据

无数据时的显示口径
- 参数帧未就位:名字照打、值列打 --(不把 0 当定值显示,也不给整页空白让人以为菜单坏了)
- 越界反显只在 bad_valid=1 时参与:把「没数据」画成「这行有毛病」是假告警,比不画更糟
- Q10 换算不用浮点也不用 64 位除:整数段 + 小数段拆分四舍五入,满位进位(MicroLIB 软浮点与
  __aeabi_ldiv 一进镜像几百字节,模拟器(宿主 CRT)永远照不出这笔代价)

批 1 那个 KEEP_EXTRA 决策在这里第一次兑现
- 本批行表用到的「延时 / 电流 / 压力 / 总 / 配 / 重 / 间 / 未」等字,正是当时成组并入 KEEP_EXTRA
  的 35 个「未知冗余」字里的一批;若当时按 extract 输出重写字符集,这些字形已被洗掉,本批
  上屏就是白框,而 font-charset 只会报「字在清单里没有」——它报不出「字曾被删掉过」
- 本批 font-charset 直接 Passed,未扩字库、未重生成字体

验证
- @build 0 错误 0 警告;模拟器 0 警告;内容页 19 项 PASS(16 页像素 + 四页共 39 条文本断言),
  其余 12 页逐字节不变;菜单 8 状态 PASS;ui-data 83/83、soe 32/32
- 四页出图逐张肉眼确认:值列右缘不越屏(10000V 最宽)、名值不重叠、第 1 页行槽与页脚不撞;
  分页页码正确(定值 18 行 = 3 页、软压板 10 行 = 2 页)
- check_setview_limits.py OK;ui_menu_tab.h 等生成物全部由 gen_menu.py 重生成(手改生成物 = 一定
  会被覆盖,见批 2 那条),uvprojx/CMakeLists 注册由生成器写入,另手工登记 ui_w_setview.c 两处
- 全量钩子 --all-files:见下一行 gate;红项只有同族三条(台账与同码凭证回填归单写者)

体积(相对 b746a1f 台账 217204 / 21504 / 1256 / 132680)
- 实测 Code 221856 / RO 23508 / RW 1300 / ZI 134660
  → Code +4652、RO +2004、RW +44、ZI +1980
- Flash 合计 +6700 B、RAM +2024 B(三批累计)。本批 RO +2120 主要是 40 行行表与名称/单位串,
  ZI +388 是 par 帧缓存(184 B)+ 四个组件实例 + 注册表增量;这是把 40 项真定值上屏的代价,
  不是可省开销

待台架实测:装载真定值后的四页读数(含真机 bad_mask 非 0 时的反显行)、翻页节拍、
PT/CT 变比与 104 上位的定值读回(C_RD)一致性。

gate: only mem-budget/mem-numbers/size-gate pending lead ledger backfill
(同族三条,root cause 一条:台账与同码凭证回填归单写者)
像素基线单独提交(下一笔新增 4 个 shot_v*.bin):本笔单独 checkout 时 ui-golden 会报缺参考,
属预期拆分;上面的钩子结论取自「本笔 + 下一笔」的合并树。
自查:12 个新增/改动 UI 源文件 BOM 与 HEAD 对齐(本批 edit 又剥掉 4 次,逐次补回);CR = 0;
U+FFFD = 0;C 字面量纯 ASCII(中文只在带 BOM 文件里)。
2026-10-25 14:44:47 +08:00

333 lines
12 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 定值表共享组件(ui_w_setlist)实现
* 模块功能:见 ui_w_setlist.h 头注释(四视图共用列表渲染 + 整定输入抽象,
* 数据经回调注入,数值行走 ui_w_editor,二态行 ENTER 循环切换)
* 适用平台:STM32F407ZGTx / PC 模拟器(两端共用)
* 作者:王建锋
* 创建日期:2026-10-25
* 修改记录:
* v1.0 2026-10-25 王建锋 创建(道B task-5 三组件之一)
*/
/* 头文件包含区 */
#include <string.h>
#include "ui_w_setlist.h"
#include "ui_tokens.h"
/* 私有宏定义区 */
#define SL_SAVE_NAME "保存" /* 保存行名(字体子集已含 保/存) */
#define SL_WIN_ROWS UI_LIST_ROWS /* 窗口行数(与框架 rows_per_page 同令牌) */
/* 函数定义区 */
/*
* 函数功能:判断两区域相交(与本目录各绘制文件同实现:static 局部)
* 入口参数:a - 区域 1 const lv_area_t *
* b - 区域 2 const lv_area_t *
* 返回值:true = 相交 bool
* 限定条件:无
*/
static bool sl_areas_intersect(const lv_area_t *a, const lv_area_t *b) {
int32_t x1 = (a->x1 > b->x1) ? a->x1 : b->x1;
int32_t y1 = (a->y1 > b->y1) ? a->y1 : b->y1;
int32_t x2 = (a->x2 < b->x2) ? a->x2 : b->x2;
int32_t y2 = (a->y2 < b->y2) ? a->y2 : b->y2;
return (x1 <= x2 && y1 <= y2);
}
/*
* 函数功能:整定视图判定(挂了提交回调 = 有保存行、可编辑)
* 入口参数:inst - 定值表实例 const ui_w_setlist_t *
* 返回值:true = 整定视图 bool
* 限定条件:inst 非空由调用链保证
*/
static bool sl_is_rw(const ui_w_setlist_t *a_inst) {
return (a_inst->commit != NULL);
}
/*
* 函数功能:二态行串表长度(NULL 结尾)
* 入口参数:opts - 串表 const char *const *
* 返回值:条目数(>=1;空表防御返回 1) uint8_t
* 限定条件:无
*/
static uint8_t sl_opts_count(const char *const *a_opts) {
uint8_t n = 0u;
if (a_opts == NULL) {
return 1u;
}
while (a_opts[n] != NULL) {
n++;
}
return (n > 0u) ? n : 1u;
}
/*
* 函数功能:当前页窗口顶行下标(与框架 cp_draw_rows / cp_draw_footer 同式)
* 入口参数:inst - 定值表实例 const ui_w_setlist_t *
* 返回值:窗口顶行(全局下标) uint16_t
* 限定条件:编辑中行恒在选中行所在页(编辑态 ↑↓ 被编辑器消费,选中不移)
*/
static uint16_t sl_window_first(const ui_w_setlist_t *a_inst) {
uint16_t sel = ui_content_page_selected_row();
(void)a_inst;
return (uint16_t)((sel / SL_WIN_ROWS) * SL_WIN_ROWS);
}
/*
* 函数功能:行槽顶 y(窗口内)
* 入口参数:inst - 定值表实例 const ui_w_setlist_t *
* row - 全局行下标 uint16_t
* 返回值:行槽顶 y int32_t
* 限定条件:row 在当前窗口内(绘制侧由 bad/ed 行可见性决定,越窗行不绘)
*/
static int32_t sl_slot_y(const ui_w_setlist_t *a_inst, uint16_t a_row) {
return UI_LIST_TOP + ((int32_t)a_row - (int32_t)sl_window_first(a_inst)) * UI_ROW_PITCH;
}
/*
* 函数功能:开页复位(清编辑态)
*/
void ui_w_setlist_reset(ui_w_setlist_t *a_inst) {
ui_w_editor_reset(&a_inst->ed);
a_inst->ed_row = 0u;
}
/*
* 函数功能:总行数(数据行 + 保存行[整定视图])
*/
uint16_t ui_w_setlist_count(const ui_w_setlist_t *a_inst) {
if (a_inst->rows == NULL) {
return 0u;
}
return (uint16_t)(a_inst->count + (sl_is_rw(a_inst) ? 1u : 0u));
}
/*
* 函数功能:逐行取名/值(框架 refresh 转发)
*/
void ui_w_setlist_refresh(ui_w_setlist_t *a_inst, uint16_t a_row,
char *a_name, char *a_value) {
uint16_t total = ui_w_setlist_count(a_inst);
a_name[0] = '\0';
a_value[0] = '\0';
if ((a_inst->rows == NULL) || (a_row >= total)) {
return; /* 防御:空表/行越界(动态收缩瞬间) */
}
if (a_row < a_inst->count) {
const ui_w_setlist_row_t *r = &a_inst->rows[a_row];
(void)lv_snprintf(a_name, UI_BUF_STRLEN, "%s", r->name);
if (r->kind == UI_W_SETLIST_KIND_ENUM) {
int32_t idx = a_inst->get(r->id);
uint8_t n = sl_opts_count(r->opts);
if (idx < 0) {
idx = 0;
}
if (idx >= (int32_t)n) {
idx = (int32_t)n - 1;
}
(void)lv_snprintf(a_value, UI_BUF_STRLEN, "%s", r->opts[idx]);
return;
}
if ((a_inst->ed.editing != 0u) && (a_inst->ed_row == a_row)) {
ui_w_editor_format(&a_inst->ed, a_value, UI_BUF_STRLEN); /* 编辑实时串 */
}
else {
/* 单位后缀(r->unit NULL = 不挂):先格式化到临时串再合成,一次界内写回,
* 不用 strcat 类函数(代码规范 7.5;且长度不足时会静默截半,屏幕上更难查) */
char fmt_buf[UI_BUF_STRLEN];
ui_w_editor_fmt_scaled(a_inst->get(r->id), r->decimals, fmt_buf, UI_BUF_STRLEN);
(void)lv_snprintf(a_value, UI_BUF_STRLEN, "%s%s", fmt_buf,
(r->unit != NULL) ? r->unit : "");
}
return;
}
/* 保存行(整定视图最后一行;名"保存",值随交接态——net_cfg 保存行先例) */
(void)lv_snprintf(a_name, UI_BUF_STRLEN, "%s", SL_SAVE_NAME);
{
ui_w_setlist_st_t st = (a_inst->state != NULL) ? a_inst->state()
: UI_W_SETLIST_ST_IDLE;
switch (st) {
case UI_W_SETLIST_ST_WAIT:
(void)lv_snprintf(a_value, UI_BUF_STRLEN, "%s", "WAIT");
break;
case UI_W_SETLIST_ST_OK:
(void)lv_snprintf(a_value, UI_BUF_STRLEN, "%s", "OK");
break;
case UI_W_SETLIST_ST_FAIL:
(void)lv_snprintf(a_value, UI_BUF_STRLEN, "%s", "ERR");
break;
default:
(void)lv_snprintf(a_value, UI_BUF_STRLEN, "%s", "ENTER");
break;
}
}
}
/*
* 函数功能:按键处理(框架 key_cb 转发)
*/
uint8_t ui_w_setlist_key(ui_w_setlist_t *a_inst, lv_key_t a_key) {
uint16_t sel = ui_content_page_selected_row();
if (a_inst->rows == NULL) {
return 0u;
}
/* 编辑态:键先交编辑器 */
if (a_inst->ed.editing != 0u) {
if ((sel != a_inst->ed_row) || (sel >= a_inst->count)) {
/* 防御:编辑中选中被外部改动(正常不会——编辑态 ↑↓ 被消费)→ 弃编辑 */
ui_w_editor_reset(&a_inst->ed);
return 0u;
}
if (ui_w_editor_key(&a_inst->ed, a_key) == 0u) {
return 0u; /* 未消费键(本组件编辑态只接方向/ENTER) */
}
if (a_inst->ed.editing == 0u) {
/* ENTER 提交成功:值写 RAM 工作副本,落盘统一走保存行整表提交 */
a_inst->set(a_inst->rows[sel].id, ui_w_editor_value(&a_inst->ed));
}
ui_content_page_invalidate();
return 1u;
}
if (a_key == LV_KEY_ENTER) {
if (ui_content_page_is_editable() == false) {
return 0u; /* 只读权限 ENTER 一律忽略(框架内置也无 ENTER 语义) */
}
if (sel < a_inst->count) {
const ui_w_setlist_row_t *r = &a_inst->rows[sel];
if (r->kind == UI_W_SETLIST_KIND_NUM) {
ui_w_editor_begin(&a_inst->ed, a_inst->get(r->id),
r->min, r->max, r->decimals);
a_inst->ed_row = sel;
ui_content_page_invalidate();
return 1u;
}
/* 二态行:循环切换(控制字 告警<->跳闸、软压板 投入<->退出) */
{
int32_t cur = a_inst->get(r->id);
uint8_t n = sl_opts_count(r->opts);
int32_t nxt = cur + 1;
if ((cur < 0) || (cur >= (int32_t)n)) {
nxt = 0;
}
a_inst->set(r->id, nxt);
ui_content_page_invalidate();
return 1u;
}
}
if ((sel == a_inst->count) && sl_is_rw(a_inst)) {
/* 保存行(数据行之后的最后一行):整表提交投递(一次封存一次校验和,
* 显示由 state 回读) */
a_inst->commit();
ui_content_page_invalidate();
return 1u;
}
}
return 0u; /* 其余键交回框架:↑↓ 移选、LEFT 关页(编辑态才在上面消费) */
}
/*
* 函数功能:反显一个名区(保存失败越界行:白底黑字覆盖名文本区)
* 入口参数:inst - 定值表实例 const ui_w_setlist_t *
* layer - 绘制层 lv_layer_t *
* clip - 重绘区 const lv_area_t *
* row - 数据行下标 uint16_t
* 返回值:无
* 限定条件:行在当前窗口内(调用方已筛);编辑行不重复反显(选中带已反)
*/
static void sl_invert_name(const ui_w_setlist_t *a_inst, lv_layer_t *a_layer,
const lv_area_t *a_clip, uint16_t a_row) {
lv_area_t box;
int32_t top = sl_slot_y(a_inst, a_row);
box.x1 = UI_SEL_BAND_INSET_L;
box.x2 = UI_LIST_RIGHT_X - 3; /* 名区止于值列前留 3px 缝 */
box.y1 = top;
box.y2 = top + UI_ROW_BAND_H - 1;
if (!sl_areas_intersect(&box, a_clip)) {
return;
}
{
lv_draw_rect_dsc_t fd;
lv_draw_label_dsc_t ld;
char name[UI_BUF_STRLEN];
lv_draw_rect_dsc_init(&fd);
fd.base.layer = a_layer;
fd.bg_color = UI_COL_SEL_BG;
fd.bg_opa = LV_OPA_COVER;
lv_draw_rect(a_layer, &fd, &box);
(void)lv_snprintf(name, sizeof(name), "%s", a_inst->rows[a_row].name);
lv_draw_label_dsc_init(&ld);
ld.base.layer = a_layer;
ld.text = name;
ld.text_static = 0u; /* 栈缓冲(同帧有效) */
ld.color = UI_COL_SEL_FG;
ld.ofs_x = UI_LIST_LEFT_X - UI_SEL_BAND_INSET_L; /* ofs 相对 box.x1 */
ld.ofs_y = 0;
lv_draw_label(a_layer, &ld, &box);
}
}
/*
* 函数功能:自绘(编辑光标反显 + 越界行反显;框架内置区块之后)
*/
void ui_w_setlist_draw(ui_w_setlist_t *a_inst, lv_layer_t *a_layer,
const lv_area_t *a_clip) {
uint16_t first;
if (a_inst->rows == NULL) {
return;
}
first = sl_window_first(a_inst);
/* 1. 保存失败越界行反显(编辑行跳过:选中带已反,避免双重叠绘歧义) */
if (a_inst->bad != NULL) {
uint16_t total = ui_w_setlist_count(a_inst);
for (uint16_t r = first; (r < a_inst->count) && (r < total); r++) {
if ((r == a_inst->ed_row) && (a_inst->ed.editing != 0u)) {
continue;
}
if (r == ui_content_page_selected_row()) {
continue;
}
if (a_inst->bad(r)) {
sl_invert_name(a_inst, a_layer, a_clip, r);
}
}
}
/* 2. 数值编辑光标(含"越界"提示;几何交编辑器按传入行槽绘制) */
if (a_inst->ed.editing != 0u) {
if (a_inst->ed_row >= first) {
ui_w_editor_draw(&a_inst->ed, a_layer, a_clip,
sl_slot_y(a_inst, a_inst->ed_row),
0, UI_ROW_BAND_H, UI_LIST_RIGHT_X);
}
}
}