写的页面架构

This commit is contained in:
2026-09-09 17:15:04 +08:00
parent 24c2f57555
commit 856b6f6426
19 changed files with 1091 additions and 189 deletions
+133 -152
View File
@@ -1,13 +1,21 @@
/*
* 模块名称:UI Content Page (内容页面显示层)
* 模块功能:菜单叶子节点的内容页(装置信息/交流量/直流量)——独立 LVGL 屏幕,
* 全屏容器 DRAW_MAIN 自绘全部内容(浅 V 装饰 + 居中标题 + 行数据 +
* 页脚页码),无子对象;进入时切屏并获焦、ESC 返回菜单屏并还原焦点
* 模块名称:UI Content Page (内容页框架)
* 模块功能:菜单叶子节点进入的内容页(独立全屏页面)框架——全屏容器
* 自绘全部内容(浅 V 装饰 + 居中标题 + 行数据 + 页脚页码),无子对象;
* 只持机制:屏/容器按需建毁、焦点、按键分发、描述符驱动通用绘制;
* 不持任何页面数据——各页数据/几何/钩子由 ui_page_xxx 模块经
* content_page_def_t 接入描述符表 s_defs[](架构见 内容页架构.md);
* 进入时切屏并获焦、ESC 返回菜单屏并还原焦点;
* 不持有导航状态——由逻辑层 ui_menu.c 在 ENTER 叶子节点时驱动
* 适用平台:STM32F407ZGTx / PC 模拟器(两端共用)
* 作者:王建锋
* 创建日期:2026-09-09
* 修改记录:
* 2026-09-09 王建锋 创建:装置信息/交流量/直流量三页(参考图 10/20/21 实测几何)
* 2026-09-09 王建锋 "浅 V"装饰几何提取至共享 ui_deco 模块(不再本地重复定义)
* 2026-09-09 王建锋 拆分为"框架 + 页面插件"模型(内容页架构.md):三页数据移入
* ui_page_xxx 模块,本文件只留机制 + 描述符表;绘制/按键改
* 按描述符驱动(静态表/refresh + 4 钩子),行为零变化
*/
/* 头文件包含区 */
@@ -15,8 +23,16 @@
#include "ui_content_page.h"
#include "ui_page_device_info.h"
#include "ui_page_ac.h"
#include "ui_page_dc.h"
#include "ui_menu_view.h"
#include "ui_deco.h"
#include "lvgl.h"
#define DBG_TAG "[UI_CONTENT_PAGE]"
@@ -27,8 +43,6 @@
/*
* 布局常量(设备像素;参考图 10/20/21 实测,3x 放大图缩回 160x160 测量):
* 标题 y=2(CJK 字形顶距 +1 → 墨迹自 y=3 起,与参考图一致)、页脚 y=143;
* 信息页行:首行 y=40、行距 15、标签列 x=6 / 值列 x=86(无选中带);
* 列表页行:槽顶 = 24+14i、选中白带 x=2..156 高 13、名 x=7 / 值 x=101;
* 字形顶距(CJK +1 / ASCII +2)已计入放置 y,墨迹与参考图对齐
*/
#define CP_SCREEN_W 160 /* 屏幕宽(px,与 ui_port.h / 模拟器一致) */
@@ -37,106 +51,18 @@
#define CP_TITLE_Y 2 /* 标题放置 y(居中;墨迹自 y=3 起) */
#define CP_PAGE_LINE_Y 143 /* 页脚"第 X 页,共 Y 页"放置 y(x=6 左对齐) */
#define CP_DECO_LEG_W 3 /* "浅 V"两腿线宽(与菜单根框一致:1bpp 斜线须加粗) */
#define CP_DECO_HORIZ_W 2 /* "浅 V"水平线宽 */
/* 私有类型定义区 */
/* 行数据:左列名 + 右列值(Flash 字面量,不 strdup) */
typedef struct {
const char *name;
const char *value;
} cp_row_t;
/*
* 页面定义(Flash 常量表):标题/行几何/数据表/页脚参数;
* selectable=1 为可移选分页的数据列表页,0 为静态信息页
*/
typedef struct {
const char *title; /* 标题(浅 V 区居中) */
uint8_t selectable; /* 1 = 数据列表页(↑↓移选/分页);0 = 信息页(静态行) */
int32_t top_y; /* 首行槽顶 y */
int32_t pitch; /* 行距(相邻行槽顶间距) */
int32_t band_h; /* 选中白带高(0 = 无选中带) */
int32_t text_dy; /* 文本放置相对行槽顶的垂直偏移 */
int32_t left_x; /* 左列 x(信息页=标签、列表页=名) */
int32_t right_x; /* 右列 x(值) */
uint8_t rows_per_page; /* 每页可见行数(0 = 不分页) */
uint16_t total_rows; /* 数据总行数 */
const cp_row_t *rows; /* 数据表(Flash) */
const char *subtitle; /* 副标题(居中;NULL = 无,仅信息页有) */
int32_t subtitle_y; /* 副标题放置 y(-1 = 无) */
uint8_t fixed_cur_page; /* 页脚"第 X 页"固定显示值(信息页按参考图原样) */
uint8_t fixed_total_pages; /* 页脚"共 Y 页"固定显示值 */
} cp_def_t;
#define CP_ROW_BUF_LEN 32 /* 动态行栈缓冲长度(同帧同步光栅化;超出行带即被裁切) */
/* 静态变量定义区 */
/* "浅 V"装饰点集与线宽:几何与菜单根框一致(ui_menu_view.c s_deco_*),culling 区同范围 */
static const lv_point_precise_t s_deco_leg_l[2] = { {7, 7}, {16, 17} }; /* 左腿 */
static const lv_point_precise_t s_deco_hoz[2] = { {16, 17}, {145, 17} }; /* 水平线:接两腿底端 */
static const lv_point_precise_t s_deco_leg_r[2] = { {158, 7}, {145, 17} }; /* 右腿 */
static const lv_point_precise_t *const s_deco_segs[3] = {s_deco_leg_l, s_deco_leg_r, s_deco_hoz};
static const int32_t s_deco_seg_w[3] = {CP_DECO_LEG_W, CP_DECO_LEG_W, CP_DECO_HORIZ_W};
static const lv_area_t s_deco_band = {0, 0, CP_SCREEN_W - 1, 19}; /* 浅 V culling 区 */
/* 装置信息行(参考图 10;标签含全角冒号,值列固定 x=86) */
static const cp_row_t s_info_rows[] = {
{"终端类型:", "F30"},
{"终端型号:", "F30"},
{"软件版本:", "SV0.010"},
{"硬件版本:", "HW0.010"},
{"软件校验:", "4454"},
{"程序日期:", "2024.08.27"},
};
/* 交流量行(参考图 20 = 第 1 页原样;第 2/3 页为占位数据,可替换) */
static const cp_row_t s_ac_rows[] = {
{"Ia1", "0.000"}, /* 第 1 页(参考图 20) */
{"Ib1", "0.137"},
{"Ic1", "0.274"},
{"Ia2", "0.411"},
{"Ib2", "0.548"},
{"Ic2", "0.685"},
{"Ua1", "0.82"},
{"Ub1", "0.96"},
{"Uc1", "1.03"}, /* 第 2 页(占位数据) */
{"Uab", "1.17"},
{"Ubc", "1.24"},
{"Uca", "1.31"},
{"Hz", "50.00"},
{"P", "1.42"},
{"Q", "0.87"},
{"S", "1.63"},
{"PF", "0.95"}, /* 第 3 页(占位数据) */
{"Iavg","0.35"},
{"Uavg","0.87"},
{"Fmax","50.02"},
{"Imax","1.24"},
{"Pmax","2.10"},
{"Qmax","1.05"},
{"Smax","2.45"},
};
/* 直流量行(参考图 21) */
static const cp_row_t s_dc_rows[] = {
{"ZL01", "3.01"},
{"ZL02", "3.15"},
{"Pa1", "3.288"},
};
/* 页面定义表(顺序与 content_page_id_t 一致) */
static const cp_def_t s_defs[CONTENT_PAGE_COUNT] = {
/* 装置信息:静态键值行 + 副标题;页脚按参考图原样"第 10 页,共 1 页" */
{"装置信息", 0u, 40, 15, 0, 0, 6, 86, 0u, (uint16_t)6, s_info_rows,
"馈线自动化终端", 25, 10u, 1u},
/* 交流量:24 项 / 8 项每页(3 页);text_dy=0:ASCII 字形顶距 +2,墨迹落槽顶+2(与参考图一致) */
{"交流量", 1u, 24, 14, 13, 0, 7, 101, 8u, (uint16_t)24, s_ac_rows,
NULL, -1, 0u, 0u},
/* 直流量:3 项单页 */
{"直流量", 1u, 24, 14, 13, 0, 7, 101, 8u, (uint16_t)3, s_dc_rows,
NULL, -1, 0u, 0u},
/*
* 描述符表:页 ID → 页面模块描述符(每页一行;新增页 = 枚举值 + 此处一行,
* 框架机制不变,见 内容页架构.md §七)
*/
static const content_page_def_t *const s_defs[CONTENT_PAGE_COUNT] = {
[CONTENT_PAGE_DEVICE_INFO] = &ui_page_device_info_desc,
[CONTENT_PAGE_AC] = &ui_page_ac_desc,
[CONTENT_PAGE_DC] = &ui_page_dc_desc,
};
static content_page_id_t s_open = CONTENT_PAGE_COUNT; /* 当前打开的页(COUNT = 未打开) */
@@ -181,6 +107,20 @@ static bool cp_areas_intersect(const lv_area_t *a, const lv_area_t *b) {
return (x1 <= x2 && y1 <= y2);
}
/*
* 函数功能:取页面总行数(静态页 = 描述符 row_count,动态页 = count_cb)
* 入口参数:d - 页面描述符 const content_page_def_t *
* 返回值:总行数 uint16_t
* 限定条件:d 非空
* 函数说明:行数唯一入口;绘制(窗口/页脚)与按键(移选取模)均经此取
*/
static uint16_t cp_row_total(const content_page_def_t *d) {
if (d->count_cb != NULL) {
return d->count_cb();
}
return d->row_count;
}
/*
* 函数功能:绘制居中单行文本(标题/副标题)
* 入口参数:layer - 绘制层 lv_layer_t *
@@ -211,34 +151,25 @@ static void cp_draw_centered_text(lv_layer_t *layer, const char *text, int32_t y
}
/*
* 函数功能:页面自绘(浅 V 装饰 + 标题/副标题 + 行数据 + 页脚页码)
* 函数功能:页面自绘(浅 V 装饰 + 标题/副标题 + 行数据 + 页脚页码 + 页面钩子)
* 入口参数:e - 事件指针 lv_event_t *
* 返回值:无
* 限定条件:事件为 LV_EVENT_DRAW_MAIN,页面已打开
* 函数说明: 1. 浅 V 装饰几何与菜单根框一致(3 段线逐段画)
* 2. 列表页窗口 = 选中行所在页(首可见行 = (sel/每页数)*每页数),
* 函数说明: 1. 浅 V 装饰:共享 ui_deco 模块(与菜单根框同几何)
* 2. 行数:静态页取 row_count,动态页取 count_cb();
* 列表页窗口 = 选中行所在页(首可见行 = (sel/每页数)*每页数),
* 行槽位置固定、数据随页切换;信息页全量静态绘制
* 3. 选中行整行白带(x=2..156)+ 黑字,非选中白字
* 4. 各区块按 culling 区跳过不在重绘区的部分
* 3. 行文本:静态页直绘 Flash 表(不拷内存);动态页经 refresh()
* 逐行取入栈缓冲(同帧同步光栅化,安全)
* 4. 选中行整行白带(x=2..156)+ 黑字,非选中白字
* 5. 各区块按 culling 区跳过不在重绘区的部分;末尾调 draw_cb(页自定义内容)
*/
static void cp_draw_cb(lv_event_t *e) {
const cp_def_t *d = &s_defs[s_open];
const content_page_def_t *d = s_defs[s_open];
lv_layer_t *layer = lv_event_get_layer(e);
/* "浅 V"装饰:3 段线(与菜单根框同几何) */
if (cp_areas_intersect(&s_deco_band, &layer->_clip_area)) {
for (uint8_t i = 0; i < 3; i++) {
lv_draw_line_dsc_t ld;
lv_draw_line_dsc_init(&ld);
ld.base.layer = layer;
ld.color = lv_color_white();
ld.width = s_deco_seg_w[i];
ld.p1 = s_deco_segs[i][0];
ld.p2 = s_deco_segs[i][1];
lv_draw_line(layer, &ld);
}
}
/* "浅 V"装饰:共享 ui_deco 模块(与菜单根框同几何) */
ui_deco_draw(layer, &layer->_clip_area);
/* 标题(居中) */
{
@@ -267,17 +198,22 @@ static void cp_draw_cb(lv_event_t *e) {
}
/* 行数据:列表页窗口跟随选中行所在页,信息页全量 */
int32_t nrows = (int32_t)cp_row_total(d);
int32_t first = 0;
int32_t cnt = (int32_t)d->total_rows;
int32_t cnt = nrows;
if (d->selectable && d->rows_per_page > 0u) {
if (d->selectable != 0u && d->rows_per_page > 0u) {
first = (int32_t)(s_sel / d->rows_per_page) * d->rows_per_page; /* 窗口顶行 */
cnt = (int32_t)d->total_rows - first;
cnt = nrows - first;
if (cnt > (int32_t)d->rows_per_page) {
cnt = (int32_t)d->rows_per_page; /* 末页不足整页时截断 */
}
}
bool dynamic = (d->refresh != NULL);
char dyn_name[CP_ROW_BUF_LEN]; /* 动态行栈缓冲(同帧同步光栅化) */
char dyn_value[CP_ROW_BUF_LEN];
for (int32_t i = 0; i < cnt; i++) {
int32_t row_idx = first + i; /* 数据下标(跨页连续) */
int32_t slot_y = d->top_y + i * d->pitch; /* 行槽顶(窗口内位置固定) */
@@ -310,15 +246,28 @@ static void cp_draw_cb(lv_event_t *e) {
lv_draw_rect(layer, &fd, &band);
}
/* 左列(标签/名)与右列(值):固定列 x,垂直偏移 text_dy */
/* 行文本:静态 = Flash 表(不拷内存);动态 = refresh 逐行取(栈缓冲) */
const char *name;
const char *value;
if (dynamic) {
d->refresh((uint16_t)row_idx, dyn_name, dyn_value);
name = dyn_name;
value = dyn_value;
}
else {
name = d->rows[row_idx].name;
value = d->rows[row_idx].value;
}
{
lv_draw_label_dsc_t ld;
/* ofs_x/ofs_y 相对 coords 原点(strip.y1=slot_y),勿再叠加 slot_y */
lv_draw_label_dsc_init(&ld);
ld.base.layer = layer;
ld.text = d->rows[row_idx].name;
ld.text_static = 1; /* Flash 字符串:不拷内存 */
ld.text = name;
ld.text_static = (uint32_t)(!dynamic); /* 静态不拷;动态缓冲同帧内有效 */
ld.color = color; /* 选中黑 / 非选中白 */
ld.ofs_x = d->left_x;
ld.ofs_y = d->text_dy;
@@ -326,8 +275,8 @@ static void cp_draw_cb(lv_event_t *e) {
lv_draw_label_dsc_init(&ld);
ld.base.layer = layer;
ld.text = d->rows[row_idx].value;
ld.text_static = 1;
ld.text = value;
ld.text_static = (uint32_t)(!dynamic);
ld.color = color;
ld.ofs_x = d->right_x;
ld.ofs_y = d->text_dy;
@@ -339,16 +288,16 @@ static void cp_draw_cb(lv_event_t *e) {
{
char buf[32];
uint8_t cur;
uint8_t total;
uint8_t pages;
lv_area_t t;
if (d->selectable) {
if (d->selectable != 0u) {
cur = (uint8_t)(s_sel / d->rows_per_page + 1u);
total = (uint8_t)((d->total_rows + d->rows_per_page - 1u) / d->rows_per_page);
pages = (uint8_t)((nrows + (int32_t)d->rows_per_page - 1) / d->rows_per_page);
}
else {
cur = d->fixed_cur_page;
total = d->fixed_total_pages;
pages = d->fixed_total_pages;
}
t.x1 = 0;
@@ -357,7 +306,7 @@ static void cp_draw_cb(lv_event_t *e) {
t.y2 = CP_PAGE_LINE_Y + 15;
if (cp_areas_intersect(&t, &layer->_clip_area)) {
/* lv_snprintf 而非 snprintf:MSVC 对 printf 族做格式串扫描,中文字面量会误报 C4819 */
lv_snprintf(buf, sizeof(buf), "第 %u 页,共 %u 页", (unsigned)cur, (unsigned)total);
lv_snprintf(buf, sizeof(buf), "第 %u 页,共 %u 页", (unsigned)cur, (unsigned)pages);
lv_draw_label_dsc_t ld;
@@ -371,38 +320,64 @@ static void cp_draw_cb(lv_event_t *e) {
lv_draw_label(layer, &ld, &t);
}
}
/* 页面钩子:自定义内容(表单/操作页;在内置区块之后绘制) */
if (d->draw_cb != NULL) {
d->draw_cb(layer, &layer->_clip_area);
}
}
/*
* 函数功能:页面按键事件入口(↑↓移选 / ESC·←返回)
* 函数功能:页面按键事件入口(↑↓移选 / ESC·←返回 / 页面钩子)
* 入口参数:e - 事件指针 lv_event_t *
* 返回值:无
* 限定条件:容器已加入默认输入组并获焦
* 函数说明:1. ↑/↓:列表页移选(首尾环绕,跨页连续),窗口随页切换后重绘;信息页不响应
* 2. ESC/←:关闭页面返回菜单(导航状态未压层,原样还原)
* 函数说明: 1. ESC/←:恒关页返回菜单(框架保证,页面钩子不可拦截)
* 2. 其余键:key_cb 钩子先调(返回 true = 页已处理,框架跳过内置)
* 3. 内置:↑/↓ 列表页移选(首尾环绕,跨页连续),窗口随页切换后重绘;
* 信息页不响应;动态行数可能缩小,选中行越界时重置回首行
*/
static void cp_key_cb(lv_event_t *e) {
const cp_def_t *d = &s_defs[s_open];
const content_page_def_t *d = s_defs[s_open];
lv_key_t key = (lv_key_t)lv_event_get_key(e);
/* ESC/←:关闭页面返回菜单屏(焦点/导航状态还原) */
if (key == LV_KEY_ESC || key == LV_KEY_LEFT) {
ui_content_page_close();
return;
}
/* 页面钩子先调:true = 页已处理,框架跳过内置语义 */
if (d->key_cb != NULL && d->key_cb(key)) {
return;
}
switch (key) {
case LV_KEY_UP:
if (d->selectable && d->total_rows > 0u) {
case LV_KEY_UP: {
uint16_t total = cp_row_total(d);
if (d->selectable != 0u && total > 0u) {
if (s_sel >= total) {
s_sel = 0u; /* 动态行数可能缩小:选中行越界重置回首行 */
}
/* 上移、首尾环绕:先加 total 再减 1 保证无符号下不为负 */
s_sel = (uint16_t)((s_sel + d->total_rows - 1u) % d->total_rows);
s_sel = (uint16_t)((s_sel + total - 1u) % total);
lv_obj_invalidate(s_cont); /* 重绘(选中带/窗口/页码可能变化) */
}
break;
case LV_KEY_DOWN:
if (d->selectable && d->total_rows > 0u) {
s_sel = (uint16_t)((s_sel + 1u) % d->total_rows); /* 下移、越尾绕回首行 */
}
case LV_KEY_DOWN: {
uint16_t total = cp_row_total(d);
if (d->selectable != 0u && total > 0u) {
if (s_sel >= total) {
s_sel = 0u; /* 动态行数可能缩小:选中行越界重置回首行 */
}
s_sel = (uint16_t)((s_sel + 1u) % total); /* 下移、越尾绕回首行 */
lv_obj_invalidate(s_cont);
}
break;
case LV_KEY_ESC:
case LV_KEY_LEFT:
ui_content_page_close(); /* 返回菜单屏(焦点/导航状态还原) */
break;
}
default:
break; /* 其余键值忽略 */
}
@@ -411,7 +386,7 @@ static void cp_key_cb(lv_event_t *e) {
/*
* 函数功能:打开指定内容页(建屏 + 切屏 + 焦点置页面容器)
* 入口参数:id - 内容页 ID content_page_id_t
* 返回值:true - 成功;false - 失败(ID 无效/已有页面打开/对象创建失败)
* 返回值:true - 成功;false - 失败(ID 无效/已有页面打开/无描述符/对象创建失败)
* 限定条件:ui_menu_init() 已完成(默认输入组与菜单屏存在)
* 函数说明:1. lv_obj_create(NULL) 新建独立屏幕 + 全屏容器(自绘载体,无子对象)
* 2. 容器入默认输入组并获焦后再切屏——按键立即由本模块接管
@@ -422,6 +397,12 @@ bool ui_content_page_open(content_page_id_t id) {
return false; /* ID 无效或已有页面打开(防御) */
}
const content_page_def_t *d = s_defs[id];
if (d == NULL) {
DBG_ERROR("page open: id=%u no descriptor (enum/table drift)", (unsigned)id);
return false; /* 枚举/描述符表漂移:拒开并留痕,不崩溃 */
}
s_scr = lv_obj_create(NULL); /* parent=NULL → 新建屏幕 */
if (s_scr == NULL) {
return false; /* LVGL 池耗尽 */
@@ -451,7 +432,7 @@ bool ui_content_page_open(content_page_id_t id) {
}
lv_screen_load(s_scr); /* 切到内容页(菜单屏对象保留、不销毁) */
DBG_INFO("page open: id=%u rows=%u", (unsigned)id, (unsigned)s_defs[id].total_rows);
DBG_INFO("page open: id=%u rows=%u", (unsigned)id, (unsigned)cp_row_total(d));
return true;
}
+48 -6
View File
@@ -1,14 +1,21 @@
/*
* 模块名称:UI Content Page (内容页面显示层)
* 模块功能:菜单叶子节点的内容页(装置信息/交流量/直流量)——独立 LVGL 屏幕,
* 全屏容器自绘全部内容(浅 V 装饰 + 居中标题 + 行数据 + 页脚页码),
* 无子对象;进入时切屏并获焦、ESC 返回菜单屏并还原焦点;
* 模块名称:UI Content Page (内容页框架)
* 模块功能:菜单叶子节点进入的内容页(独立全屏页面)框架——
* 屏/容器按需建毁生命周期 + 焦点 + 切屏、按键分发(内置 ↑↓/ESC + key_cb 钩子)、
* 描述符驱动通用绘制(浅 V + 标题/副标题 + 行 + 选中带 + 页脚 + draw_cb);
* 不持任何页面数据:各页数据/几何/钩子由 ui_page_xxx 模块经 content_page_def_t
* 接入框架描述符表(架构见 内容页架构.md);
* 进入时切屏并获焦、ESC 返回菜单屏并还原焦点;
* 不持有导航状态——由逻辑层 ui_menu.c 在 ENTER 叶子节点时驱动
* 适用平台:STM32F407ZGTx / PC 模拟器(两端共用)
* 作者:王建锋
* 创建日期:2026-09-09
* 修改记录:
* 2026-09-09 王建锋 创建:装置信息/交流量/直流量三页(参考图 10/20/21)
* 2026-09-09 王建锋 "浅 V"装饰几何提取至共享 ui_deco 模块(不再本地重复定义)
* 2026-09-09 王建锋 拆分为"框架 + 页面插件"模型(内容页架构.md):三页数据移入
* ui_page_xxx 模块;本文件只留描述类型与 API;描述符新增
* 4 个扩展钩子(refresh/count_cb/draw_cb/key_cb)
*/
#ifndef UI_CONTENT_PAGE_H
#define UI_CONTENT_PAGE_H
@@ -16,6 +23,8 @@
#include <stdint.h>
#include <stdbool.h>
#include "lvgl.h"
/*
* 内容页 ID:与菜单叶子节点一一对应(映射见 ui_menu.c menu_content_page_for);
* CONTENT_PAGE_COUNT 兼作"无内容页/未打开"哨兵值
@@ -27,13 +36,46 @@ typedef enum {
CONTENT_PAGE_COUNT /* 哨兵:无效 ID / 未打开 */
} content_page_id_t;
/* 行数据:左列名 + 右列值(Flash 字面量,不 strdup) */
typedef struct {
const char *name;
const char *value;
} cp_row_t;
/*
* 页面描述符:一页 = 一个 Flash const 结构(几何 + 数据源 + 可选钩子)
* 由各 ui_page_xxx 页面模块提供,框架描述符表 s_defs[] 按页 ID 引用
*/
typedef struct {
const char *title; /* 标题(浅 V 区居中) */
const char *subtitle; /* 副标题;NULL = 无(仅信息页有) */
int32_t subtitle_y; /* 副标题放置 y(仅 subtitle 非空时有效) */
uint8_t selectable; /* 1 = 列表页(↑↓移选+分页);0 = 信息页(静态行) */
int32_t top_y; /* 首行槽顶 y */
int32_t pitch; /* 行距(相邻行槽顶间距) */
int32_t band_h; /* 选中白带高(0 = 无选中带) */
int32_t text_dy; /* 文本放置相对行槽顶的垂直偏移 */
int32_t left_x; /* 左列 x(信息页=标签、列表页=名) */
int32_t right_x; /* 右列 x(值) */
uint8_t rows_per_page; /* 每页可见行数(0 = 不分页) */
uint16_t row_count; /* 总行数(静态页;动态页用 count_cb) */
const cp_row_t *rows; /* 静态数据表(Flash);NULL = 动态页 */
uint8_t fixed_cur_page; /* 信息页页脚"第 X 页"固定显示值 */
uint8_t fixed_total_pages; /* 信息页页脚"共 Y 页"固定显示值 */
/* —— 扩展钩子(全可选;NULL = 纯内置行为;均运行在 uiTask 上下文)—— */
void (*refresh)(uint16_t row, char *name, char *value); /* 动态行:逐行取数(名/值) */
uint16_t (*count_cb)(void); /* 动态行数(记录页) */
void (*draw_cb)(lv_layer_t *layer, const lv_area_t *clip); /* 表单/操作页:自定义内容 */
bool (*key_cb)(lv_key_t key); /* 页面自有键;true = 页已处理 */
} content_page_def_t;
/*
* 函数功能:打开指定内容页(建屏 + 切屏 + 焦点置页面容器)
* 入口参数:id - 内容页 ID content_page_id_t
* 返回值:true - 成功;false - 失败(ID 无效/已有页面打开/对象创建失败)
* 返回值:true - 成功;false - 失败(ID 无效/已有页面打开/无描述符/对象创建失败)
* 限定条件:ui_menu_init() 已完成(默认输入组与菜单屏存在)
* 函数说明:1. 新建独立屏幕,全屏容器自绘全部内容(无子对象,池开销极小)
* 2. 容器入默认输入组并获焦——按键由本模块处理(↑↓移选/ESC返回)
* 2. 容器入默认输入组并获焦——按键由本模块处理(↑↓移选/ESC返回/页面钩子)
* 3. 导航状态不压层:菜单 s_depth/s_sel 原样保留,ESC 返回即还原
*/
bool ui_content_page_open(content_page_id_t id);
+81
View File
@@ -0,0 +1,81 @@
/*
* 模块名称:UI Deco (顶部装饰层)
* 模块功能:顶部"浅 V"装饰(左腿/水平线/右腿 3 段线)共享自绘——
* 菜单根框与内容页共用的几何点集、线宽与 culling 区在本模块唯一定义,
* 各页面经 ui_deco_draw 调用绘制(无 LVGL 对象,DRAW_MAIN 自绘)
* 适用平台:STM32F407ZGTx / PC 模拟器(两端共用)
* 作者:王建锋
* 创建日期:2026-09-09
* 修改记录:
* 2026-09-09 王建锋 创建:提取 ui_menu_view.c / ui_content_page.c 重复的"浅 V"几何
*/
/* 头文件包含区 */
#include <stdbool.h>
#include "ui_deco.h"
/* 私有宏定义区 */
#define DECO_SCREEN_W 160 /* 屏幕宽(px,与 ui_port.h / 模拟器一致) */
#define DECO_LEG_W 3 /* "浅 V"两腿线宽(1bpp 下斜线边缘易被阈值吃掉,需加粗) */
#define DECO_HORIZ_W 2 /* "浅 V"水平线宽 */
/* 静态变量定义区 */
static const lv_point_precise_t s_leg_l[2] = { {7, 7}, {16, 17} }; /* 左腿:自顶端斜下 */
static const lv_point_precise_t s_hoz[2] = { {16, 17}, {145, 17} }; /* 水平线:接两腿底端 */
static const lv_point_precise_t s_leg_r[2] = { {158, 7}, {145, 17} }; /* 右腿 */
/* 绘制次序 = 原对象创建序(左腿、右腿、水平线),重叠像素与原渲染一致 */
static const lv_point_precise_t *const s_segs[3] = {s_leg_l, s_leg_r, s_hoz};
static const int32_t s_seg_w[3] = {DECO_LEG_W, DECO_LEG_W, DECO_HORIZ_W};
static const lv_area_t s_band = {0, 0, DECO_SCREEN_W - 1, 19}; /* "浅 V" culling 区(与原线段对象范围一致) */
/* 函数定义区 */
/*
* 函数功能:判断两区域是否相交(等价 lv_area_intersect 非空判断;避免依赖 LVGL 私有头)
* 入口参数:a - 区域 1 const lv_area_t *
* b - 区域 2 const lv_area_t *
* 返回值:true - 相交;false - 不相交
* 限定条件:无
* 函数说明:四边取 min/max 成交集,任一边为空即不相交(与 ui_menu_view.c 同实现)
*/
static bool deco_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);
}
/*
* 函数功能:绘制顶部"浅 V"装饰(3 段线,线宽各异,白色)
* 入口参数:layer - 绘制层 lv_layer_t *
* clip - 重绘裁剪区 const lv_area_t *
* 返回值:无
* 限定条件:layer/clip 有效(调用方自 LV_EVENT_DRAW_MAIN 事件取得)
* 函数说明:1. 几何共享:两腿 3px 粗、水平线 2px 细(1bpp 斜线边缘易被阈值吃掉,腿须加粗)
* 2. 绘制次序固定(左腿、右腿、水平线),重叠像素与原渲染一致
* 3. 重绘区未覆盖 culling 区(屏顶 20px 带)则整体跳过
*/
void ui_deco_draw(lv_layer_t *layer, const lv_area_t *clip) {
if (!deco_areas_intersect(&s_band, clip)) {
return;
}
for (uint8_t i = 0; i < 3; i++) {
lv_draw_line_dsc_t ld;
lv_draw_line_dsc_init(&ld);
ld.base.layer = layer;
ld.color = lv_color_white();
ld.width = s_seg_w[i];
ld.p1 = s_segs[i][0];
ld.p2 = s_segs[i][1];
lv_draw_line(layer, &ld);
}
}
+29
View File
@@ -0,0 +1,29 @@
/*
* 模块名称:UI Deco (顶部装饰层)
* 模块功能:顶部"浅 V"装饰(左腿/水平线/右腿 3 段线)共享自绘接口——
* 菜单根框(ui_menu_view)与内容页(ui_content_page)共用同一几何
* 与绘制次序;重绘区未覆盖 culling 区则整体跳过
* 适用平台:STM32F407ZGTx / PC 模拟器(两端共用)
* 作者:王建锋
* 创建日期:2026-09-09
* 修改记录:
* 2026-09-09 王建锋 创建:提取 ui_menu_view.c / ui_content_page.c 重复的"浅 V"几何
*/
#ifndef UI_DECO_H
#define UI_DECO_H
#include "lvgl.h"
/*
* 函数功能:绘制顶部"浅 V"装饰(3 段线,线宽各异,白色)
* 入口参数:layer - 绘制层 lv_layer_t *
* clip - 重绘裁剪区 const lv_area_t *
* 返回值:无
* 限定条件:layer/clip 有效(调用方自 LV_EVENT_DRAW_MAIN 事件取得)
* 函数说明:1. 几何共享:两腿 3px 粗、水平线 2px 细(1bpp 斜线边缘易被阈值吃掉,腿须加粗)
* 2. 绘制次序固定(左腿、右腿、水平线),重叠像素与原渲染一致
* 3. 重绘区未覆盖 culling 区(屏顶 20px 带)则整体跳过
*/
void ui_deco_draw(lv_layer_t *layer, const lv_area_t *clip);
#endif /* UI_DECO_H */
+11 -30
View File
@@ -19,6 +19,7 @@
* 2026-09-08 王建锋 4 层容器→1 个持久容器(焦点/键唯一入口;帧深度由逻辑层
* s_depth 决定,键回调 user_data=depth 冗余);根框装饰
* (浅 V)+状态行改容器自绘(删 3 lv_line + 1 label 对象)
* 2026-09-09 王建锋 "浅 V"装饰几何提取至共享 ui_deco 模块(不再本地重复定义)
*/
/* 头文件包含区 */
@@ -28,6 +29,8 @@
#include "ui_menu_tab.h"
#include "ui_deco.h"
#include "lvgl.h"
#define DBG_TAG "[UI_MENU_VIEW]"
@@ -52,8 +55,6 @@
#define MENU_ROOT_BOX_Y 44 /* 根框顶 */
#define MENU_ROOT_STATUS_X 17 /* 状态行左 */
#define MENU_ROOT_STATUS_Y 20 /* 状态行顶 */
#define MENU_DECO_LEG_W 3 /* 顶部"浅 V"两腿线宽(1bpp 下斜线边缘易被阈值吃掉,需加粗) */
#define MENU_DECO_HORIZ_W 2 /* 顶部"浅 V"水平线宽 */
/* 私有类型定义区 */
@@ -542,45 +543,25 @@ lv_obj_t *ui_menu_view_container(void) {
}
/*
* 根框装饰(浅 V)点集与自绘区域:容器 DRAW_MAIN 自绘(无对象);
* culling 区域覆盖原 lv_line 对象范围((0,0) 处 160×20)与状态行文本区
* 根框装饰(浅 V):几何/绘制/culling 由共享 ui_deco 模块承载(无对象,容器自绘);
* 状态行 culling 区为本模块私有(文本约 104px 宽 + 余量)
*/
static const lv_point_precise_t s_deco_leg_l[2] = { {7, 7}, {16, 17} }; /* 左腿:自顶端斜下 */
static const lv_point_precise_t s_deco_hoz[2] = { {16, 17}, {145, 17} }; /* 水平线:接两腿底端 */
static const lv_point_precise_t s_deco_leg_r[2] = { {158, 7}, {145, 17} }; /* 右腿 */
static const lv_point_precise_t *const s_deco_segs[3] = {s_deco_leg_l, s_deco_leg_r, s_deco_hoz};
static const int32_t s_deco_seg_w[3] = {MENU_DECO_LEG_W, MENU_DECO_LEG_W, MENU_DECO_HORIZ_W};
static const lv_area_t s_deco_band = {0, 0, MENU_SCREEN_W - 1, 19}; /* 浅 V culling 区(与原线段对象范围一致) */
static const lv_area_t s_status_area = {MENU_ROOT_STATUS_X, MENU_ROOT_STATUS_Y, 130, 33}; /* 状态行 culling 区(文本约 104px 宽 + 余量) */
static const lv_area_t s_status_area = {MENU_ROOT_STATUS_X, MENU_ROOT_STATUS_Y, 130, 33}; /* 状态行 culling 区 */
/*
* 函数功能:自绘根框装饰 + 状态行(容器 DRAW_MAIN;替代旧 4 个 LVGL 对象)
* 入口参数:e - 事件指针 lv_event_t *
* 返回值:无
* 限定条件:事件为 LV_EVENT_DRAW_MAIN,容器已建
* 函数说明: 1. 浅 V:两腿 3px 粗、水平线 2px 细(1bpp 斜线边缘易被阈值吃掉,腿须比水平线粗)
* 2. 绘制次序 = 旧对象创建序(左腿、右腿、水平线),重叠像素与旧渲染一致
* 3. 状态行:静态字符串、白色,与旧 label 同字体同位置
* 4. 重绘区不覆盖对应 culling 区则跳过(culling)
* 函数说明: 1. 浅 V:共享 ui_deco 模块(两腿 3px / 水平线 2px,绘制次序左腿、右腿、水平线)
* 2. 状态行:静态字符串、白色,与旧 label 同字体同位置
* 3. 重绘区不覆盖对应 culling 区则跳过(culling)
*/
static void view_deco_draw_cb(lv_event_t *e) {
lv_layer_t *layer = lv_event_get_layer(e);
/* "浅 V"装饰:3 段线(线宽各异,逐段画) */
if (areas_intersect(&s_deco_band, &layer->_clip_area)) {
for (uint8_t i = 0; i < 3; i++) {
lv_draw_line_dsc_t ld;
lv_draw_line_dsc_init(&ld);
ld.base.layer = layer;
ld.color = lv_color_white();
ld.width = s_deco_seg_w[i];
ld.p1 = s_deco_segs[i][0];
ld.p2 = s_deco_segs[i][1];
lv_draw_line(layer, &ld);
}
}
/* "浅 V"装饰:共享 ui_deco 模块(几何/绘制次序/culling 均在其内) */
ui_deco_draw(layer, &layer->_clip_area);
/* 状态行:当前模式(静态文本) */
if (areas_intersect(&s_status_area, &layer->_clip_area)) {
+59
View File
@@ -0,0 +1,59 @@
/*
* 模块名称:UI Page AC (交流量内容页)
* 模块功能:交流量叶子节点的内容页数据——数据列表(24 项 / 8 项每页,共 3 页);
* 行槽顶 = 24+14i、选中白带 x=2..156 高 13、名 x=7 / 值 x=101(参考图 20 实测);
* 向框架 ui_content_page 提供页面描述符(零 LVGL 对象)
* 适用平台:STM32F407ZGTx / PC 模拟器(两端共用)
* 作者:王建锋
* 创建日期:2026-09-09
* 修改记录:
* 2026-09-09 王建锋 创建:自 ui_content_page 单体拆出(数据原样迁移,零改动)
*/
/* 头文件包含区 */
#include "ui_page_ac.h"
/* 静态变量定义区 */
/* 交流量行(参考图 20 = 第 1 页原样;第 2/3 页为占位数据,可替换) */
static const cp_row_t s_rows[] = {
{"Ia1", "0.000"}, /* 第 1 页(参考图 20) */
{"Ib1", "0.137"},
{"Ic1", "0.274"},
{"Ia2", "0.411"},
{"Ib2", "0.548"},
{"Ic2", "0.685"},
{"Ua1", "0.82"},
{"Ub1", "0.96"},
{"Uc1", "1.03"}, /* 第 2 页(占位数据) */
{"Uab", "1.17"},
{"Ubc", "1.24"},
{"Uca", "1.31"},
{"Hz", "50.00"},
{"P", "1.42"},
{"Q", "0.87"},
{"S", "1.63"},
{"PF", "0.95"}, /* 第 3 页(占位数据) */
{"Iavg","0.35"},
{"Uavg","0.87"},
{"Fmax","50.02"},
{"Imax","1.24"},
{"Pmax","2.10"},
{"Qmax","1.05"},
{"Smax","2.45"},
};
/* 页面描述符(外部链接:框架描述符表 s_defs[] 直接引用;列表页,钩子全 NULL) */
const content_page_def_t ui_page_ac_desc = {
.title = "交流量",
.selectable = 1u,
.top_y = 24,
.pitch = 14,
.band_h = 13,
.text_dy = 0,
.left_x = 7,
.right_x = 101,
.rows_per_page = 8u,
.row_count = 24,
.rows = s_rows,
};
+23
View File
@@ -0,0 +1,23 @@
/*
* 模块名称:UI Page AC (交流量内容页)
* 模块功能:交流量叶子节点的内容页数据——数据列表(24 项 / 8 项每页,共 3 页;
* 第 1 页参考图 20 原样,第 2/3 页占位数据),可移选分页 + 选中白带;
* 向框架 ui_content_page 提供页面描述符(零 LVGL 对象)
* 适用平台:STM32F407ZGTx / PC 模拟器(两端共用)
* 作者:王建锋
* 创建日期:2026-09-09
* 修改记录:
* 2026-09-09 王建锋 创建:自 ui_content_page 单体拆出(数据原样迁移,零改动)
*/
#ifndef UI_PAGE_AC_H
#define UI_PAGE_AC_H
#include "ui_content_page.h"
/*
* 页面描述符(Flash const):交流量页 = CONTENT_PAGE_AC;
* 接入框架描述符表 s_defs[](定义于 ui_page_ac.c)
*/
extern const content_page_def_t ui_page_ac_desc;
#endif /* UI_PAGE_AC_H */
+37
View File
@@ -0,0 +1,37 @@
/*
* 模块名称:UI Page DC (直流量内容页)
* 模块功能:直流量叶子节点的内容页数据——数据列表(3 项单页);
* 向框架 ui_content_page 提供页面描述符(零 LVGL 对象)
* 适用平台:STM32F407ZGTx / PC 模拟器(两端共用)
* 作者:王建锋
* 创建日期:2026-09-09
* 修改记录:
* 2026-09-09 王建锋 创建:自 ui_content_page 单体拆出(数据原样迁移,零改动)
*/
/* 头文件包含区 */
#include "ui_page_dc.h"
/* 静态变量定义区 */
/* 直流量行(参考图 21) */
static const cp_row_t s_rows[] = {
{"ZL01", "3.01"},
{"ZL02", "3.15"},
{"Pa1", "3.288"},
};
/* 页面描述符(外部链接:框架描述符表 s_defs[] 直接引用;列表页单页,钩子全 NULL) */
const content_page_def_t ui_page_dc_desc = {
.title = "直流量",
.selectable = 1u,
.top_y = 24,
.pitch = 14,
.band_h = 13,
.text_dy = 0,
.left_x = 7,
.right_x = 101,
.rows_per_page = 8u,
.row_count = 3,
.rows = s_rows,
};
+23
View File
@@ -0,0 +1,23 @@
/*
* 模块名称:UI Page DC (直流量内容页)
* 模块功能:直流量叶子节点的内容页数据——数据列表(3 项单页);
* 行几何与交流量页一致(参考图 21 实测);
* 向框架 ui_content_page 提供页面描述符(零 LVGL 对象)
* 适用平台:STM32F407ZGTx / PC 模拟器(两端共用)
* 作者:王建锋
* 创建日期:2026-09-09
* 修改记录:
* 2026-09-09 王建锋 创建:自 ui_content_page 单体拆出(数据原样迁移,零改动)
*/
#ifndef UI_PAGE_DC_H
#define UI_PAGE_DC_H
#include "ui_content_page.h"
/*
* 页面描述符(Flash const):直流量页 = CONTENT_PAGE_DC;
* 接入框架描述符表 s_defs[](定义于 ui_page_dc.c)
*/
extern const content_page_def_t ui_page_dc_desc;
#endif /* UI_PAGE_DC_H */
+45
View File
@@ -0,0 +1,45 @@
/*
* 模块名称:UI Page Device Info (装置信息内容页)
* 模块功能:装置信息叶子节点的内容页数据——静态键值行(参考图 10,值列固定 x=86)
* + 副标题,单页,页脚按参考图原样"第 10 页,共 1 页";
* 向框架 ui_content_page 提供页面描述符(零 LVGL 对象)
* 适用平台:STM32F407ZGTx / PC 模拟器(两端共用)
* 作者:王建锋
* 创建日期:2026-09-09
* 修改记录:
* 2026-09-09 王建锋 创建:自 ui_content_page 单体拆出(数据原样迁移,零改动)
*/
/* 头文件包含区 */
#include "ui_page_device_info.h"
/* 静态变量定义区 */
/* 装置信息行(参考图 10;标签含全角冒号,值列固定 x=86) */
static const cp_row_t s_rows[] = {
{"终端类型:", "F30"},
{"终端型号:", "F30"},
{"软件版本:", "SV0.010"},
{"硬件版本:", "HW0.010"},
{"软件校验:", "4454"},
{"程序日期:", "2024.08.27"},
};
/* 页面描述符(外部链接:框架描述符表 s_defs[] 直接引用;静态信息页,钩子全 NULL) */
const content_page_def_t ui_page_device_info_desc = {
.title = "装置信息",
.subtitle = "馈线自动化终端",
.subtitle_y = 25,
.selectable = 0u,
.top_y = 40,
.pitch = 15,
.band_h = 0,
.text_dy = 0,
.left_x = 6,
.right_x = 86,
.rows_per_page = 0u,
.row_count = 6,
.rows = s_rows,
.fixed_cur_page = 10u,
.fixed_total_pages = 1u,
};
+22
View File
@@ -0,0 +1,22 @@
/*
* 模块名称:UI Page Device Info (装置信息内容页)
* 模块功能:装置信息叶子节点的内容页数据(静态键值行 + 副标题,单页)——
* 向框架 ui_content_page 提供页面描述符(数据/几何/钩子,零 LVGL 对象)
* 适用平台:STM32F407ZGTx / PC 模拟器(两端共用)
* 作者:王建锋
* 创建日期:2026-09-09
* 修改记录:
* 2026-09-09 王建锋 创建:自 ui_content_page 单体拆出(数据原样迁移,零改动)
*/
#ifndef UI_PAGE_DEVICE_INFO_H
#define UI_PAGE_DEVICE_INFO_H
#include "ui_content_page.h"
/*
* 页面描述符(Flash const):装置信息页 = CONTENT_PAGE_DEVICE_INFO;
* 接入框架描述符表 s_defs[](定义于 ui_page_device_info.c)
*/
extern const content_page_def_t ui_page_device_info_desc;
#endif /* UI_PAGE_DEVICE_INFO_H */