f711130b4c
域5 新增 3 对文件(双端注册 uvprojx + CMakeLists),只做渲染与输入抽象、 不接真实数据源(数据绑定 = 批 2 道E ui_data/prot_set 经回调注入): - ui_w_editor:定点整数(10^decimals)逐位编辑——上下步进(0..9 环绕无进位)、 左右移数字光标(首末环绕)、ENTER 提交经 min/max 范围校验,越界拒绝并 显示"越界"提示留在编辑态;光标反显盒与 ui_page_net_cfg 字节光标同画法 (含"只画单字符防折行"实测坑注释);共享格式化 ui_w_editor_fmt_scaled() 单一规则源(值列显示与编辑光标恒同串) - ui_w_setlist:定值四视图(参数/定值/控制字/软压板)共用列表渲染 + 整定输入——行表(名称<=7字/id/类型/小数位/范围/二态串表) + 回调注入 (get/set/commit/state/bad);查看视图(commit=NULL)与整定视图只差绑定: 整定视图末行固定"保存"行,值随交接态 ENTER/WAIT/OK/ERR(net_cfg 先例), 整表一次提交(一次封存一次校验和,页面补齐规划 §3.2);二态行 ENTER 循环切换;保存失败越界行经 bad 回调反显(页面扩充详细方案 §3.2.7) - ui_page_action:可复用操作确认链——一次确认 -> 二次确认 -> ok_cb 只投递不阻塞 -> done_text 回报(或成功即关页),拒收显示 ERR 可重试; ESC/LEFT 关页取消(实测取消后菜单与基线逐字节一致);单例描述符由 开页装载(内容页框架全局单页前提),不定义 ui_page_action_desc (registration-check 页面模块判据不误拦,批 2 跳板各自出口) 组件文案用字全部落在上一分支预置字集内(font-charset WARN 60->52 印证 确/取/消/越/二/次/返/回 已转源码在用),无需再动字库。 BSP 公开 API 约定合规(首提被 cppcheck/coding-standard/bsp-err 三钩拦下, 按门禁口径修净后复验全绿): - 键分发返回值取 uint8_t 1/0 不用 bool(bsp-err/err-type:bool 只许 is_/has_ 谓词);ui_w_editor_value 直返 int32_t(出参形状无错误通道) - ui_page_action_open 返回 bsp_err_t:BSP_ERR_PARAM 参数无效 / BSP_ERR_STATE 已有页打开 / BSP_ERR_NO_SPACE 框架开页失败(可判分支) - get/state/bad 回调 typedef 各补"返回值:"注释块(bsp-err/return-doc) - cppcheck variableScope 变量收scope 7 处;CODE-RULE-18 Yoda 1 处 (sel+1u==total -> sel==count);提交内已不含 --wdemo/--wact harness 验证: - 模拟器零警告;组件行为经临时 sim-only harness(--wdemo/--wact) 截图 目检 7 态:基线视图/bad 反显/进编辑步进/越界拒绝/二态切换/保存整表 提交 OK/确认链三段/取消回菜单;harness(main.c 4 处 + CMake 1 行 + ui_w_demo.c)已完整还原删除,提交内不含(最终树 main.c 无改动, CMakeLists diff 仅 3 行注册) - 回归:菜单 8 状态 + 内容页 11 页黄金参考逐字节 PASS(ref 无变化, 无基线更新提交);池基线 5368/往返 5448 不变 - 门禁:coding-standard PASS / font-charset PASS / layer-deps PASS (新文件零 App include,基线 6 不变——减基线归 task-6) - @build 0 Error 0 Warning;Code +324 B、RO/RW/ZI 0(组件尚无引用方, ARM 链接器未剥离仅因新 TU;批 2 页面接线后实例 RAM 每页约 +40~60 B) gate: 全钩子正常执行通过,无 --no-verify - pre-commit run --all-files 全绿(含 mem-budget/mem-numbers:本分支基于 回填后台账 d9ab955,RAM 0 变化、RO 不变,仅 Code +324 B 不涉受管区) - 手工自查:6 新文件 BOM=EF BB BF 齐全、全 LF、U+FFFD=0、字面量仅 UTF-8 中文(UI 带 BOM 先例口径:保存/越界/二次确认/告警/跳闸等, 全部已在字体子集);ASCII 串仅 ENTER/WAIT/OK/ERR 状态字
200 lines
7.0 KiB
C
200 lines
7.0 KiB
C
/*
|
||
* 模块名称:UI 动作确认页(操作页共享实现)实现
|
||
* 模块功能:见 ui_page_action.h 头注释(确认链状态机 + 投递回报 + 表单页自绘)
|
||
* 适用平台:STM32F407ZGTx / PC 模拟器(两端共用)
|
||
* 作者:王建锋
|
||
* 创建日期:2026-10-25
|
||
* 修改记录:
|
||
* v1.0 2026-10-25 王建锋 创建(道B task-5 三组件之一)
|
||
*/
|
||
|
||
/* 头文件包含区 */
|
||
#include <string.h>
|
||
|
||
#include "ui_page_action.h"
|
||
|
||
#include "ui_content_page.h"
|
||
|
||
#include "ui_tokens.h"
|
||
|
||
/* 私有宏定义区 */
|
||
|
||
#define AC_PROMPT_Y 44 /* 问句/结果行 放置 y(内容区上三分之一) */
|
||
#define AC_STAGE_Y 66 /* 态标行("二次确认" / "ERR")放置 y */
|
||
#define AC_HINT_Y 110 /* 键提示行 放置 y */
|
||
|
||
/* 固定文案(用字均已入字体子集:二/次/确/认/取/消/返/回/越/界 及 ASCII) */
|
||
#define AC_STAGE2_TXT "二次确认"
|
||
#define AC_HINT_DO "ENTER=确认 ESC=取消"
|
||
#define AC_HINT_BACK "ENTER=返回"
|
||
#define AC_ERR_TXT "ERR"
|
||
|
||
/* 私有变量定义区 */
|
||
|
||
/*
|
||
* 模块单例:内容页框架全局单页(s_open 互斥),同一时刻至多一张操作页,
|
||
* 故钩子经"当前实例指针"取上下文即可成立(头注已载此约束;批 2 各跳板
|
||
* 各自 static 实例、先后开页互不冲突)
|
||
*/
|
||
static ui_page_action_t *s_act; /* 当前实例(NULL = 未在用) */
|
||
static content_page_def_t s_desc; /* 模板描述符(开页时按实例填充) */
|
||
|
||
/* 私有函数声明区 */
|
||
|
||
static void ac_draw_centered(lv_layer_t *a_layer, const char *a_text,
|
||
int32_t a_y, const lv_area_t *a_clip);
|
||
|
||
/* 函数定义区 */
|
||
|
||
/*
|
||
* 函数功能:居中绘制单行文本(框架 cp_draw_centered_text 同法,本文件私有一致拷贝)
|
||
* 入口参数:layer - 绘制层 lv_layer_t *
|
||
* text - 文本(Flash 字面量) const char *
|
||
* y - 放置 y int32_t
|
||
* clip - 重绘区 const lv_area_t *
|
||
* 返回值:无
|
||
* 限定条件:text 非空;条带(整宽 x0..159,高 UI_TEXT_BAND_H)与 clip 相交才绘制
|
||
*/
|
||
static void ac_draw_centered(lv_layer_t *a_layer, const char *a_text,
|
||
int32_t a_y, const lv_area_t *a_clip) {
|
||
lv_area_t t;
|
||
lv_draw_label_dsc_t ld;
|
||
|
||
/* 本 LVGL 版 lv_text_get_width:必须传 strlen(0 = 不测量返回 0,
|
||
* 传 -1 越串尾死循环——框架同款注释警示) */
|
||
t.x1 = 0;
|
||
t.x2 = UI_SCREEN_W - 1;
|
||
t.y1 = a_y;
|
||
t.y2 = a_y + UI_TEXT_BAND_H;
|
||
if (!((t.x1 <= a_clip->x2) && (a_clip->x1 <= t.x2) &&
|
||
(t.y1 <= a_clip->y2) && (a_clip->y1 <= t.y2))) {
|
||
return; /* 不相交(culling) */
|
||
}
|
||
|
||
lv_draw_label_dsc_init(&ld);
|
||
ld.base.layer = a_layer;
|
||
ld.text = a_text;
|
||
ld.text_static = 1u; /* Flash 字面量:不拷内存 */
|
||
ld.color = UI_COL_FG;
|
||
ld.ofs_x = (UI_SCREEN_W -
|
||
(int32_t)lv_text_get_width(a_text, (uint32_t)strlen(a_text), LV_FONT_DEFAULT, 0)) / 2;
|
||
ld.ofs_y = 0;
|
||
lv_draw_label(a_layer, &ld, &t);
|
||
}
|
||
|
||
/*
|
||
* 函数功能:自绘(框架内置区块之后:问句/态标/键提示 三行居中)
|
||
* 入口参数:layer - 绘制层 lv_layer_t *
|
||
* clip - 重绘区 const lv_area_t *
|
||
* 返回值:无
|
||
* 限定条件:经描述符 draw_cb 挂接,页面打开中(s_act 有效)
|
||
*/
|
||
static void ac_draw_cb(lv_layer_t *layer, const lv_area_t *clip) {
|
||
if ((s_act == NULL) || (s_act->prompt == NULL)) {
|
||
return;
|
||
}
|
||
|
||
/* 行 1:问句(stage 2 换成功结果行) */
|
||
if (s_act->stage == 2u) {
|
||
if (s_act->done_text != NULL) {
|
||
ac_draw_centered(layer, s_act->done_text, AC_PROMPT_Y, clip);
|
||
}
|
||
}
|
||
else {
|
||
ac_draw_centered(layer, s_act->prompt, AC_PROMPT_Y, clip);
|
||
}
|
||
|
||
/* 行 2:态标(二次确认 / 投递失败 ERR) */
|
||
if (s_act->stage == 1u) {
|
||
ac_draw_centered(layer, (s_act->err != 0u) ? AC_ERR_TXT : AC_STAGE2_TXT,
|
||
AC_STAGE_Y, clip);
|
||
}
|
||
|
||
/* 行 3:键提示 */
|
||
ac_draw_centered(layer, (s_act->stage == 2u) ? AC_HINT_BACK : AC_HINT_DO,
|
||
AC_HINT_Y, clip);
|
||
}
|
||
|
||
/*
|
||
* 函数功能:按键处理(确认链状态机)
|
||
* 入口参数:key - LVGL 键值 lv_key_t
|
||
* 返回值:true = 已消费;false = 交回框架(其余键忽略)
|
||
* 限定条件:经描述符 key_cb 挂接(key_intercept=0:LEFT 直接关页 = 取消,
|
||
* 不进本函数;ESC 恒关页框架保证,同样不进本函数)
|
||
* 函数说明:ENTER:stage0 -> stage1(二次确认);stage1 -> 投递 ok_cb:
|
||
* 接受且有 done_text -> stage2 回报,接受且无 done_text -> 直接关页,
|
||
* 拒收 -> err=1 留在 stage1;stage2 -> 关页返回
|
||
*/
|
||
static bool ac_key_cb(lv_key_t key) {
|
||
if (key != LV_KEY_ENTER) {
|
||
return false;
|
||
}
|
||
if (s_act == NULL) {
|
||
return false; /* 防御 */
|
||
}
|
||
|
||
if (s_act->stage == 0u) {
|
||
s_act->stage = 1u;
|
||
s_act->err = 0u;
|
||
ui_content_page_invalidate();
|
||
return true;
|
||
}
|
||
|
||
if (s_act->stage == 1u) {
|
||
bool ok = false;
|
||
|
||
if (s_act->ok_cb != NULL) {
|
||
ok = s_act->ok_cb(); /* 只投递请求,不等待执行结果 */
|
||
}
|
||
if (!ok) {
|
||
s_act->err = 1u;
|
||
ui_content_page_invalidate();
|
||
return true; /* ERR 显示,再 ENTER 重试 */
|
||
}
|
||
if (s_act->done_text == NULL) {
|
||
ui_content_page_close(); /* 成功即关页回菜单(无回报行形态) */
|
||
return true;
|
||
}
|
||
s_act->stage = 2u;
|
||
s_act->err = 0u;
|
||
ui_content_page_invalidate();
|
||
return true;
|
||
}
|
||
|
||
/* stage 2:已回报,ENTER 返回 */
|
||
ui_content_page_close();
|
||
return true;
|
||
}
|
||
|
||
/*
|
||
* 函数功能:开操作页(装载实例 + 复位 + 框架开页)
|
||
*/
|
||
bsp_err_t ui_page_action_open(ui_page_action_t *a_inst, uint8_t a_perm) {
|
||
if ((a_inst == NULL) || (a_inst->ok_cb == NULL) || (a_inst->prompt == NULL)) {
|
||
return BSP_ERR_PARAM;
|
||
}
|
||
if (ui_content_page_is_open() != false) {
|
||
return BSP_ERR_STATE; /* 框架单页互斥的显式化(失败原因对跳板可判) */
|
||
}
|
||
|
||
s_act = a_inst;
|
||
s_act->stage = 0u; /* 每次进入固定从一次确认开始 */
|
||
s_act->err = 0u;
|
||
|
||
(void)memset(&s_desc, 0, sizeof(s_desc));
|
||
s_desc.id = "action";
|
||
s_desc.title = (a_inst->title != NULL) ? a_inst->title : a_inst->prompt;
|
||
s_desc.hide_deco = 1u; /* 表单/操作页形态:无浅 V(用户密钥页先例) */
|
||
s_desc.hide_footer = 1u; /* 无页脚页码 */
|
||
s_desc.selectable = 0u; /* 无行列表 */
|
||
s_desc.key_intercept = 0u; /* LEFT 不拦截:关页 = 取消 */
|
||
s_desc.draw_cb = ac_draw_cb;
|
||
s_desc.key_cb = ac_key_cb;
|
||
|
||
if (ui_content_page_open(&s_desc, a_perm) == false) {
|
||
s_act = NULL; /* 开页失败不残留实例(钩子不会再被调) */
|
||
return BSP_ERR_NO_SPACE;
|
||
}
|
||
return BSP_OK;
|
||
}
|