增加网络配置界面

This commit is contained in:
2026-09-10 11:36:33 +08:00
parent 020278d4e3
commit 8e1fd3b2b8
44 changed files with 2488 additions and 332 deletions
+231
View File
@@ -0,0 +1,231 @@
/*
* 模块名称: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)
*/
/* 头文件包含区 */
#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) */
/* 静态变量定义区 */
/* 单位数字文本(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,约 9 B):3 位值 + 光标位 + 完成回调 */
static uint8_t s_digits[PW_DIGITS]; /* 3 位密码值(0..9) */
static uint8_t s_cur; /* 光标位 0..2(反显框) */
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());
}
}
/*
* 函数功能:页面按键处理(表单页:键全由页面处理)
* 入口参数: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) {
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;
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_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,
};