/* * 模块名称: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 #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); } } }