Files
STM32F4-Base/Drivers/BSP/UI/ui_page_password.c
T

274 lines
12 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 Page Password (用户密钥密码页)
* 模块功能:3 位密码输入页(表单/操作页首个实例)——标题由框架自绘(无"浅 V"、
* 无页脚,跟随实机),本模块自绘 3 个数字框:预填 0、首框反显 = 光标位;
* ↑↓调值(9↔0 环绕)、←→移光标(首↔末环绕)、ENTER 确认(修改密码→完成
* 回调授修改权限 / 查看密码→完成回调授只读权限 / 不一致→清空重输)、
* ESC 返回菜单(框架保证);
* 向框架 ui_content_page 提供页面描述符(零 LVGL 对象)
* 适用平台:STM32F407ZGTx / PC 模拟器(两端共用)
* 作者:王建锋
* 创建日期:2026-09-09
* 修改记录:
* 2026-09-09 王建锋 创建:表单/操作页首个实例(内容页架构.md §4.1;
* 实机参考 mockups/密码页面实际机器的示例图片.jpg)
* 2026-09-09 王建锋 改权限模型:arm 只载回调(不再载期望密码);页面比对
* ui_pwd_def.h 两个编译期密码(先查修改),一致时回调携
* 授予的权限(UI_PERM_MODIFY / UI_PERM_VIEW)
* 2026-09-14 王建锋 输错提示:ENTER 不一致显示"密码输入错误",方向键清除后重输
* 2026-09-14 王建锋 修 lv_text_get_width 传 -1 死循环(本 LVGL 版 length≠0 时
* while(i<length) 无 NUL 检查,越串尾读野内存;改传 strlen)
*/
/* 头文件包含区 */
#include <string.h>
#include "ui_page_password.h"
#include "ui_pwd_def.h"
/* 私有宏定义区 */
/*
* 布局常量(设备像素;实机参考 mockups/密码页面实际机器的示例图片.jpg 实测):
* 3 个数字框单行居中于屏中下部(框 14x14、间距 6、行顶 y=78、首框 x=53 →
* 3 框 + 2 距 = 54px,屏中心 80);数字墨迹框内水平居中、垂直贴框顶
* (ASCII 字形顶距 +2 已计入字体度量)
*/
#define PW_DIGITS 3 /* 密码位数 */
#define PW_BOX_W 14 /* 数字框宽(px) */
#define PW_BOX_H 14 /* 数字框高(px) */
#define PW_BOX_GAP 6 /* 数字框间距(px) */
#define PW_BOX_Y 78 /* 数字框行顶 y */
#define PW_BOX_X0 53 /* 首个数字框左 x(居中) */
#define PW_TITLE_Y 44 /* 标题放置 y(中上;0 = 框架默认,实机明显低于其余各页的 y=2) */
#define PW_ERR_Y 104 /* "密码输入错误"提示行顶 y(数字框行之下) */
#define PW_SCREEN_W 160 /* 屏幕宽(px,与 ui_port.h / 模拟器一致;提示文本居中用) */
/* 静态变量定义区 */
/* 单位数字文本(Flash):数字 0..9 各 1 字符(text_static,不拷内存) */
static const char *const s_digit_str[10] = {
"0", "1", "2", "3", "4", "5", "6", "7", "8", "9"
};
/* 页面运行状态(RAM,约 10 B):3 位值 + 光标位 + 错误标志 + 完成回调 */
static uint8_t s_digits[PW_DIGITS]; /* 3 位密码值(0..9) */
static uint8_t s_cur; /* 光标位 0..2(反显框) */
static uint8_t s_err; /* 错误提示标志(1 = 显示"密码输入错误",方向键清除) */
static void (*s_on_ok)(uint8_t); /* 完成回调(逻辑层预装,实参=授予权限;NULL = 一致时仅关页) */
/* 函数定义区 */
/*
* 函数功能:判断两区域是否相交(等价 lv_area_intersect 非空判断;避免依赖 LVGL 私有头)
* 入口参数:a - 区域 1 const lv_area_t *
* b - 区域 2 const lv_area_t *
* 返回值:true - 相交;false - 不相交
* 限定条件:无
* 函数说明:四边取 min/max 成交集,任一边为空即不相交(与 ui_content_page.c 同实现)
*/
static bool pw_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);
}
/*
* 函数功能:绘制数字框内数字(单字符,水平居中)
* 入口参数:layer - 绘制层 lv_layer_t *
* box - 数字框区域 const lv_area_t *
* digit - 数字值(0..9) uint32_t
* color - 文本颜色 lv_color_t
* 返回值:无
* 限定条件:数字框在重绘区内(调用方 culling)
* 函数说明:文本为 Flash 字面量(不拷内存);ofs 相对区域原点
*/
static void pw_draw_digit(lv_layer_t *layer, const lv_area_t *box, uint32_t digit, lv_color_t color) {
lv_draw_label_dsc_t ld;
lv_draw_label_dsc_init(&ld);
ld.base.layer = layer;
ld.text = s_digit_str[digit];
ld.text_static = 1;
ld.color = color;
ld.ofs_x = (PW_BOX_W - lv_text_get_width(s_digit_str[digit], 1, LV_FONT_DEFAULT, 0)) / 2;
ld.ofs_y = 0;
lv_draw_label(layer, &ld, box);
}
/*
* 函数功能:页面自绘(3 个数字框:光标框反显,其余白底黑框)
* 入口参数:layer - 绘制层 lv_layer_t *
* clip - 重绘区 const lv_area_t *
* 返回值:无
* 限定条件:页面打开(框架绘制链末尾调用)
* 函数说明:标题/页脚由框架处理(每页 title_y、"浅 V"/页脚已关);本模块只绘数字框;
* 1bpp 极性:white=暗、black=亮——光标框反显(white 底 black 字),
* 其余框亮底暗字 + 1px 暗边
*/
static void pw_draw_cb(lv_layer_t *layer, const lv_area_t *clip) {
for (uint32_t i = 0u; i < PW_DIGITS; i++) {
lv_area_t box;
lv_draw_rect_dsc_t fd;
box.x1 = (int32_t)(PW_BOX_X0 + i * (PW_BOX_W + PW_BOX_GAP));
box.x2 = (int32_t)(box.x1 + PW_BOX_W - 1u);
box.y1 = PW_BOX_Y;
box.y2 = (int32_t)(PW_BOX_Y + PW_BOX_H - 1u);
if (!pw_areas_intersect(&box, clip)) {
continue; /* 数字框不在重绘区:跳过(culling) */
}
lv_draw_rect_dsc_init(&fd);
fd.base.layer = layer;
fd.bg_opa = LV_OPA_COVER;
if (i == s_cur) {
/* 光标框:反显(1bpp:white=暗底,black=亮字) */
fd.bg_color = lv_color_white();
}
else {
/* 非光标框:亮底暗字 + 1px 暗边(1bpp:black=亮底,white=暗) */
fd.bg_color = lv_color_black();
fd.border_color = lv_color_white();
fd.border_width = 1;
}
lv_draw_rect(layer, &fd, &box);
pw_draw_digit(layer, &box, s_digits[i], (i == s_cur) ? lv_color_black() : lv_color_white());
}
if (s_err != 0u) {
/* "密码输入错误"提示(水平居中;1bpp:white=暗墨) */
lv_draw_label_dsc_t ld;
/* 本 LVGL 版 lv_text_get_width:length=0 = 不测量(返回 0)、length≠0 时
* while(i<length) 无 NUL 检查——必须传 strlen,传 -1 会越串尾死循环 */
int32_t w = lv_text_get_width("密码输入错误", (uint32_t)strlen("密码输入错误"), LV_FONT_DEFAULT, 0);
if (w > 0) {
lv_area_t err_box;
err_box.x1 = (PW_SCREEN_W - w) / 2;
err_box.x2 = (int32_t)(err_box.x1 + w - 1u);
err_box.y1 = PW_ERR_Y;
err_box.y2 = (int32_t)(err_box.y1 + 15u); /* 覆盖字体行高(13)+ 余量(同框架约定) */
if (pw_areas_intersect(&err_box, clip)) {
lv_draw_label_dsc_init(&ld);
ld.base.layer = layer;
ld.text = "密码输入错误";
ld.text_static = 1;
ld.color = lv_color_white();
ld.ofs_x = 0;
ld.ofs_y = 0;
lv_draw_label(layer, &ld, &err_box);
}
}
}
}
/*
* 函数功能:页面按键处理(表单页:键全由页面处理)
* 入口参数:key - LVGL 键值 lv_key_t
* 返回值:true - 页已处理(框架跳过内置语义);false - 未处理
* 限定条件:页面打开;经框架键分发调用(uiTask 上下文)
* 函数说明: 1. ↑/↓:调整光标位数字(+1/-1,9↔0 环绕);错误提示态下先清除提示
* 2. ←/→:移动光标(首↔末环绕;← 键经框架拦截标志转入本函数);同上清提示
* 3. ENTER:修改密码 → 完成回调(授修改权限);查看密码 → 完成回调
* (授只读权限);不一致 → 清空重输 + 显示"密码输入错误"
* 4. ESC 不经本函数(框架最优先恒关页);其余键值未处理
*/
static bool pw_key_cb(lv_key_t key) {
if (key != LV_KEY_ENTER && s_err != 0u) {
s_err = 0u; /* 错误提示态:方向键先清除提示,再正常执行该键(重新输入) */
}
switch (key) {
case LV_KEY_UP:
s_digits[s_cur] = (uint8_t)((s_digits[s_cur] + 1u) % 10u); /* 值 +1,9 环绕 0 */
break;
case LV_KEY_DOWN:
s_digits[s_cur] = (uint8_t)((s_digits[s_cur] + 9u) % 10u); /* 值 -1,0 环绕 9 */
break;
case LV_KEY_LEFT:
s_cur = (uint8_t)((s_cur + PW_DIGITS - 1u) % PW_DIGITS); /* 光标左移,首绕末 */
break;
case LV_KEY_RIGHT:
s_cur = (uint8_t)((s_cur + 1u) % PW_DIGITS); /* 光标右移,末绕首 */
break;
case LV_KEY_ENTER: {
uint16_t input = (uint16_t)((uint32_t)s_digits[0] * 100u
+ (uint32_t)s_digits[1] * 10u
+ (uint32_t)s_digits[2]);
uint8_t perm;
if (input == (uint16_t)UI_PWD_VALUE_MODIFY) {
perm = (uint8_t)UI_PERM_MODIFY; /* 修改密码:授修改权限(先查,两值同设时授高权) */
}
else if (input == (uint16_t)UI_PWD_VALUE_VIEW) {
perm = (uint8_t)UI_PERM_VIEW; /* 查看密码:授只读权限 */
}
else {
/* 不一致:清空重输(3 框回 0、光标回首框)+ 显示错误提示 */
s_digits[0] = 0u;
s_digits[1] = 0u;
s_digits[2] = 0u;
s_cur = 0u;
s_err = 1u;
break;
}
/* 一致:逻辑层接管"关页 + 按权限续进入"(回调内自行关页) */
if (s_on_ok != NULL) {
s_on_ok(perm);
}
else {
ui_content_page_close(); /* 防御:无回调 → 仅关页 */
}
return true; /* 页面已关(或已切走),无需重绘 */
}
default:
return false; /* 未处理:落回框架内置语义 */
}
ui_content_page_invalidate(); /* 状态已变:触发整页重绘 */
return true;
}
/*
* 函数功能:装载完成回调(须在开页前调用)
* 入口参数:on_ok - 完成回调(NULL = 一致时仅关页);一致后以授予的权限为实参
* 返回值:无
* 限定条件:由逻辑层在 uiTask 上下文、开页前调用
* 函数说明:密码页比对 ui_pwd_def.h 的两个编译期密码值(无需载值);
* 每次进入重置为初始态(全 0、光标首框,与实机初始显示一致)
*/
void ui_page_password_arm(void (*on_ok)(uint8_t perm)) {
s_on_ok = on_ok;
s_digits[0] = 0u;
s_digits[1] = 0u;
s_digits[2] = 0u;
s_cur = 0u;
s_err = 0u;
}
/*
* 页面描述符(外部链接:框架描述符表 s_defs[] 直接引用):
* 表单/操作页——selectable=0、无行数据,自绘内容 + 自有键;
* 标题/装饰/页脚跟随实机(无"浅 V"、标题中上、无页脚);
* key_intercept=1:← 键先由 key_cb 处理(移光标),ESC 恒不可拦截
*/
const content_page_def_t ui_page_password_desc = {
.title = "用户密钥",
.title_y = PW_TITLE_Y,
.hide_deco = 1u,
.hide_footer = 1u,
.selectable = 0u,
.key_intercept = 1u,
.draw_cb = pw_draw_cb,
.key_cb = pw_key_cb,
};