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 状态字
399 lines
14 KiB
C
399 lines
14 KiB
C
/*
|
||
* 模块名称: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);
|
||
}
|
||
}
|
||
}
|