Files
STM32F4-Base/Drivers/BSP/UI/ui_w_editor.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

399 lines
14 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_editor)实现
* 模块功能:见 ui_w_editor.h 头注释(逐位步进编辑 + 范围校验 + 光标反显绘制)
* 适用平台:STM32F407ZGTx / PC 模拟器(两端共用)
* 作者:王建锋
* 创建日期:2026-10-25
* 修改记录:
* v1.0 2026-10-25 王建锋 创建(道B task-5 三组件之一;编辑交互对齐
* ui_page_net_cfg 的"反显光标 + 键全消费"模式,
* 差异:字节 -> 十进制数字位、0..255 环绕 -> 0..9
* 环绕、新增 min/max 提交把关)
*/
/* 头文件包含区 */
#include <string.h>
#include "ui_w_editor.h"
#include "ui_tokens.h"
/* 私有宏定义区 */
#define ED_MAX_DECIMALS 3u /* 定点小数位上限(decimals 有效域) */
#define ED_HINT_GAP 4 /* "越界"提示与值串末尾的水平间隙(px) */
#define ED_SAFE_POWER 7u /* 参与步进的数字位最大权值指数(10^7;再高
环绕步进可致 int32 溢出,该位锁定不动——
工程定值值串最长 6 位,正常编辑不触发) */
/* 函数定义区 */
/*
* 函数功能:10^n 幂(n <= ED_SAFE_POWER+1,调用侧已限定;用循环不用查表越界)
* 入口参数:n - 指数 uint8_t
* 返回值:10^n(n 超界时饱和返回最大值) uint32_t
* 限定条件:无
*/
static uint32_t ed_pow10(uint8_t a_n) {
uint32_t p = 1u;
uint8_t i;
for (i = 0u; (i < a_n) && (i < 9u); i++) {
p *= 10u;
}
return p;
}
/*
* 函数功能:工作值 -> 显示串("500" / "10.24" / "0.001")
* 入口参数:inst - 编辑器实例 const ui_w_editor_t *
* buf - 输出缓冲 char *
* len - 缓冲长度 uint32_t
* 返回值:无
* 限定条件:buf 非空;值非负(min >= 0 使用前提);decimals <= ED_MAX_DECIMALS
* 函数说明:不用 %*d 变宽(MicroLIB 支持性存疑),小数位 0..3 逐档定式;
* lv_snprintf 而非 snprintf(工程口径,且避开 MSVC C4819 格式串扫描)
*/
void ui_w_editor_fmt_scaled(int32_t a_value, uint8_t a_decimals, char *a_buf, uint32_t a_len) {
uint32_t scale = ed_pow10(a_decimals);
int32_t whole = a_value / (int32_t)scale;
int32_t frac = a_value % (int32_t)scale;
switch (a_decimals) {
case 1u:
(void)lv_snprintf(a_buf, a_len, "%d.%d", (int)whole, (int)frac);
break;
case 2u:
(void)lv_snprintf(a_buf, a_len, "%d.%02d", (int)whole, (int)frac);
break;
case 3u:
(void)lv_snprintf(a_buf, a_len, "%d.%03d", (int)whole, (int)frac);
break;
default:
(void)lv_snprintf(a_buf, a_len, "%d", (int)whole);
break;
}
}
/*
* 函数功能:工作值 -> 显示串(实例壳:转调 ui_w_editor_fmt_scaled)
*/
static void ed_format(const ui_w_editor_t *a_inst, char *a_buf, uint32_t a_len) {
ui_w_editor_fmt_scaled(a_inst->cur, a_inst->decimals, a_buf, a_len);
}
/*
* 函数功能:扫描值串,定位第 inst->digit 个数字字符并给出该位的定点权值
* 入口参数:inst - 编辑器实例(digit/decimals 有效) const ui_w_editor_t *
* buf - 值串(与 inst->cur 同步,ed_format 产物) const char *
* step_out - 输出:该位 +1 对 cur 的增量(= 10^权值指数) uint32_t *
* 返回值:该数字字符在串中的下标(无数字时 0) uint32_t
* 限定条件:inst->digit < inst->ndigits(调用方经 begin/recount 保证)
* 函数说明:权值指数 = 该位右侧的小数位数 + 更低的整数位数:
* 整数位:dec + (整数位数 - 1 - 位序);小数位:dec - 1 - 小数位序。
* 例 dec=2 值串 "12.34":'1'->1000 '2'->100 '3'->10 '4'->1
*/
static uint32_t ed_digit_info(const ui_w_editor_t *a_inst, const char *a_buf,
uint32_t *a_step_out) {
uint32_t total = (uint32_t)strlen(a_buf);
uint32_t dot = total; /* 小数点下标(total = 无小数点) */
uint32_t i;
uint32_t seen = 0u; /* 已扫过的数字字符数(= 当前数字的位序) */
uint32_t int_pos = 0u; /* 整数位内位序 */
uint32_t frac_pos = 0u; /* 小数位内位序 */
if (a_step_out != NULL) {
*a_step_out = 1u;
}
for (i = 0u; i < total; i++) {
if (a_buf[i] == '.') {
dot = i;
break;
}
}
for (i = 0u; i < total; i++) {
char c = a_buf[i];
if ((c < '0') || (c > '9')) {
continue;
}
if (seen == (uint32_t)a_inst->digit) {
uint32_t power;
if (i < dot) {
uint32_t n_int = 0u; /* 整数位数字个数(求该位权值用) */
uint32_t j;
for (j = 0u; j < dot; j++) {
if ((a_buf[j] >= '0') && (a_buf[j] <= '9')) {
n_int++;
}
}
power = (uint32_t)a_inst->decimals + (n_int - 1u) - int_pos;
}
else {
power = ((uint32_t)a_inst->decimals - 1u) - frac_pos;
}
if (a_step_out != NULL) {
*a_step_out = (power > ED_SAFE_POWER) ? 0u : ed_pow10((uint8_t)power);
}
return i;
}
if (i < dot) {
int_pos++;
}
else {
frac_pos++;
}
seen++;
}
return 0u;
}
/*
* 函数功能:重算 ndigits 并把数字光标夹回有效域(值位数随步进变化时防指空)
* 入口参数:inst - 编辑器实例 ui_w_editor_t *
* 返回值:无
* 限定条件:inst->cur 已更新
*/
static void ed_recount(ui_w_editor_t *a_inst) {
char buf[UI_BUF_STRLEN];
uint32_t i;
uint8_t n = 0u;
ed_format(a_inst, buf, sizeof(buf));
for (i = 0u; i < (uint32_t)strlen(buf); i++) {
if ((buf[i] >= '0') && (buf[i] <= '9')) {
n++;
}
}
a_inst->ndigits = n;
if (a_inst->digit >= n) {
a_inst->digit = (n > 0u) ? (uint8_t)(n - 1u) : 0u;
}
}
/*
* 函数功能:复位实例(清除编辑态与提示)
*/
void ui_w_editor_reset(ui_w_editor_t *a_inst) {
a_inst->editing = 0u;
a_inst->bad = 0u;
a_inst->digit = 0u;
a_inst->ndigits = 0u;
}
/*
* 函数功能:进入编辑(载入初始值与范围,光标落在个位)
*/
void ui_w_editor_begin(ui_w_editor_t *a_inst, int32_t a_value,
int32_t a_min_v, int32_t a_max_v, uint8_t a_decimals) {
char buf[UI_BUF_STRLEN];
uint32_t i;
uint8_t int_digits = 0u;
ui_w_editor_reset(a_inst);
a_inst->cur = (a_value < 0) ? 0 : a_value; /* 非负前提(防御钳 0) */
a_inst->min = (a_min_v < 0) ? 0 : a_min_v;
a_inst->max = (a_max_v < a_inst->min) ? a_inst->min : a_max_v;
a_inst->decimals = (a_decimals > ED_MAX_DECIMALS) ? (uint8_t)ED_MAX_DECIMALS : a_decimals;
if (a_inst->cur > a_inst->max) {
a_inst->cur = a_inst->max; /* 起点必须合法:个位光标才落在实位上 */
}
if (a_inst->cur < a_inst->min) {
a_inst->cur = a_inst->min;
}
/* 光标落个位 = 第 (整数位数 - 1) 个数字字符 */
ed_format(a_inst, buf, sizeof(buf));
for (i = 0u; i < (uint32_t)strlen(buf); i++) {
if (buf[i] == '.') {
break;
}
if ((buf[i] >= '0') && (buf[i] <= '9')) {
int_digits++;
}
}
a_inst->digit = (int_digits > 0u) ? (uint8_t)(int_digits - 1u) : 0u;
ed_recount(a_inst);
a_inst->editing = 1u;
}
/*
* 函数功能:查询是否处于编辑态
*/
bool ui_w_editor_is_editing(const ui_w_editor_t *a_inst) {
return (a_inst != NULL) && (a_inst->editing != 0u);
}
/*
* 函数功能:编辑态按键处理
*/
uint8_t ui_w_editor_key(ui_w_editor_t *a_inst, lv_key_t a_key) {
if ((a_inst == NULL) || (a_inst->editing == 0u)) {
return 0u;
}
a_inst->bad = 0u; /* 任意编辑键先清"越界"提示 */
switch (a_key) {
case LV_KEY_UP:
case LV_KEY_DOWN: {
char buf[UI_BUF_STRLEN];
uint32_t step;
int32_t dcur;
int32_t dnew;
ed_format(a_inst, buf, sizeof(buf));
(void)ed_digit_info(a_inst, buf, &step);
if (step == 0u) {
return 1u; /* 超安全权值位(ED_SAFE_POWER 之上):锁定该位 */
}
dcur = (a_inst->cur / (int32_t)step) % 10;
if (dcur < 0) {
dcur += 10; /* 非负前提下不触发;双保险 */
}
dnew = (a_key == LV_KEY_UP) ? ((dcur + 1) % 10) : ((dcur + 9) % 10);
a_inst->cur += (dnew - dcur) * (int32_t)step;
if (a_inst->cur < 0) {
a_inst->cur = 0; /* 双保险(0 减 1 走 dnew=9 环绕,本不应为负) */
}
ed_recount(a_inst);
return 1u;
}
case LV_KEY_LEFT:
if (a_inst->ndigits > 0u) {
a_inst->digit = (uint8_t)((a_inst->digit + a_inst->ndigits - 1u) % a_inst->ndigits);
}
return 1u;
case LV_KEY_RIGHT:
if (a_inst->ndigits > 0u) {
a_inst->digit = (uint8_t)((a_inst->digit + 1u) % a_inst->ndigits);
}
return 1u;
case LV_KEY_ENTER:
if ((a_inst->cur < a_inst->min) || (a_inst->cur > a_inst->max)) {
a_inst->bad = 1u; /* 越界:拒绝提交,留在编辑态显示"越界" */
return 1u;
}
a_inst->editing = 0u; /* 提交:调用方经 ui_w_editor_value 取回写 RAM 副本 */
return 1u;
default:
return 0u; /* ESC 到不了这里(框架恒拦截关页 = 取消编辑) */
}
}
/*
* 函数功能:取工作值
*/
int32_t ui_w_editor_value(const ui_w_editor_t *a_inst) {
return (a_inst != NULL) ? a_inst->cur : 0;
}
/*
* 函数功能:把工作值格式化为显示串(对外壳,供 refresh 值列复用)
*/
void ui_w_editor_format(const ui_w_editor_t *a_inst, char *a_buf, uint32_t a_len) {
if ((a_inst != NULL) && (a_buf != NULL) && (a_len > 0u)) {
ed_format(a_inst, a_buf, a_len);
}
}
/*
* 函数功能:判断两区域相交(与本目录各绘制文件同实现:static 局部、不跨文件共享)
* 入口参数:a - 区域 1 const lv_area_t *
* b - 区域 2 const lv_area_t *
* 返回值:true = 相交 bool
* 限定条件:无
*/
static bool ed_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);
}
/*
* 函数功能:编辑态自绘(数字光标反显盒 + "越界"提示)
*/
void ui_w_editor_draw(ui_w_editor_t *a_inst, lv_layer_t *a_layer,
const lv_area_t *a_clip, int32_t a_row_top,
int32_t a_text_dy, int32_t a_band_h, int32_t a_value_x) {
char buf[UI_BUF_STRLEN];
uint32_t step;
uint32_t cidx;
int32_t x0;
int32_t w;
lv_area_t box;
if ((a_inst == NULL) || (a_inst->editing == 0u) || (a_inst->ndigits == 0u)) {
return;
}
ed_format(a_inst, buf, sizeof(buf));
cidx = ed_digit_info(a_inst, buf, &step);
/* 光标反显盒:框住第 digit 个数字字符(左右各外扩 1px,net_cfg 同法) */
x0 = a_value_x + (int32_t)lv_text_get_width(buf, cidx, LV_FONT_DEFAULT, 0);
w = (int32_t)lv_text_get_width(buf + cidx, 1u, LV_FONT_DEFAULT, 0);
box.x1 = x0 - 1;
box.x2 = x0 + w;
box.y1 = a_row_top;
box.y2 = a_row_top + a_band_h - 1;
if (ed_areas_intersect(&box, a_clip)) {
lv_draw_rect_dsc_t fd;
lv_draw_label_dsc_t ld;
char seg[2];
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);
/* 只画光标单字符(整串会按盒宽折行溢出邻行——net_cfg 实测坑);
* 编辑行恒为选中行(白带),反显字色取黑 UI_COL_SEL_FG */
seg[0] = buf[cidx];
seg[1] = '\0';
lv_draw_label_dsc_init(&ld);
ld.base.layer = a_layer;
ld.text = seg;
ld.text_static = 0u; /* 栈缓冲(同帧有效) */
ld.color = UI_COL_SEL_FG;
ld.ofs_x = 1; /* box.x1 = x0-1,+1 回 x0(与框架行文本原点一致) */
ld.ofs_y = a_text_dy;
lv_draw_label(a_layer, &ld, &box);
}
/* "越界"提示:值串右侧(编辑行 = 选中白带行 -> 黑字可见) */
if (a_inst->bad != 0u) {
lv_area_t hb;
lv_draw_label_dsc_t ld;
hb.x1 = a_value_x +
(int32_t)lv_text_get_width(buf, (uint32_t)strlen(buf), LV_FONT_DEFAULT, 0) +
ED_HINT_GAP;
hb.x2 = UI_SCREEN_W - 1;
hb.y1 = a_row_top;
hb.y2 = a_row_top + a_band_h - 1;
if (ed_areas_intersect(&hb, a_clip)) {
lv_draw_label_dsc_init(&ld);
ld.base.layer = a_layer;
ld.text = "越界";
ld.text_static = 1u;
ld.color = UI_COL_SEL_FG; /* 白带内黑字(1bpp:black = 亮墨底反) */
ld.ofs_x = 0;
ld.ofs_y = a_text_dy;
lv_draw_label(a_layer, &ld, &hb);
}
}
}