Files
STM32F4-Base/Drivers/BSP/UI/ui_page_action.c
T
wandering1 f711130b4c feat(UI组件): 新增数值编辑器/定值表/动作确认页三组件(道B task-5)
域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 状态字
2026-10-25 04:34:43 +08:00

200 lines
7.0 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_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;
}