498 lines
20 KiB
C
498 lines
20 KiB
C
/*
|
||
* 模块名称:UI Content Page (内容页面显示层)
|
||
* 模块功能:菜单叶子节点的内容页(装置信息/交流量/直流量)——独立 LVGL 屏幕,
|
||
* 全屏容器 DRAW_MAIN 自绘全部内容(浅 V 装饰 + 居中标题 + 行数据 +
|
||
* 页脚页码),无子对象;进入时切屏并获焦、ESC 返回菜单屏并还原焦点
|
||
* 适用平台:STM32F407ZGTx / PC 模拟器(两端共用)
|
||
* 作者:王建锋
|
||
* 创建日期:2026-09-09
|
||
* 修改记录:
|
||
* 2026-09-09 王建锋 创建:装置信息/交流量/直流量三页(参考图 10/20/21 实测几何)
|
||
*/
|
||
|
||
/* 头文件包含区 */
|
||
#include <string.h>
|
||
|
||
#include "ui_content_page.h"
|
||
|
||
#include "ui_menu_view.h"
|
||
|
||
#include "lvgl.h"
|
||
|
||
#define DBG_TAG "[UI_CONTENT_PAGE]"
|
||
#include "dbg_log.h"
|
||
|
||
/* 私有宏定义区 */
|
||
|
||
/*
|
||
* 布局常量(设备像素;参考图 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 / 模拟器一致) */
|
||
#define CP_SCREEN_H 160 /* 屏幕高(px) */
|
||
|
||
#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;
|
||
|
||
/* 静态变量定义区 */
|
||
|
||
/* "浅 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},
|
||
};
|
||
|
||
static content_page_id_t s_open = CONTENT_PAGE_COUNT; /* 当前打开的页(COUNT = 未打开) */
|
||
static uint16_t s_sel; /* 列表页选中行(全局下标,跨页连续) */
|
||
static lv_obj_t *s_scr; /* 页面屏幕(进入时建、返回时毁) */
|
||
static lv_obj_t *s_cont; /* 页面全屏容器(焦点/键入口 + 自绘载体) */
|
||
|
||
/* flat:透明、无边框/圆角/内边距——剥离 mono 主题给普通 lv_obj 附加的 card 样式(同 ui_menu_view.c) */
|
||
/* 用常量样式(LV_STYLE_CONST_INIT):属性存静态数组,零 LV_MEM 池分配;
|
||
* 动态样式首次 set_prop 会从池分配 values_and_props 且永不释放(x64 实测 88B/个) */
|
||
static const lv_style_const_prop_t cp_flat_props[] = {
|
||
{ .prop = LV_STYLE_BG_OPA, .value = { .num = LV_OPA_TRANSP } },
|
||
{ .prop = LV_STYLE_BORDER_WIDTH, .value = { .num = 0 } },
|
||
{ .prop = LV_STYLE_RADIUS, .value = { .num = 0 } },
|
||
{ .prop = LV_STYLE_PAD_LEFT, .value = { .num = 0 } },
|
||
{ .prop = LV_STYLE_PAD_RIGHT, .value = { .num = 0 } },
|
||
{ .prop = LV_STYLE_PAD_TOP, .value = { .num = 0 } },
|
||
{ .prop = LV_STYLE_PAD_BOTTOM, .value = { .num = 0 } },
|
||
{ .prop = LV_STYLE_PAD_ROW, .value = { .num = 0 } },
|
||
{ .prop = LV_STYLE_PAD_COLUMN, .value = { .num = 0 } },
|
||
LV_STYLE_CONST_PROPS_END
|
||
};
|
||
|
||
LV_STYLE_CONST_INIT(cp_style_flat, cp_flat_props);
|
||
|
||
/* 函数定义区 */
|
||
|
||
/*
|
||
* 函数功能:判断两区域是否相交(等价 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 cp_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 *
|
||
* text - 文本(Flash) const char *
|
||
* y - 放置 y int32_t
|
||
* 返回值:无
|
||
* 限定条件:text 非空;重绘区已含该行区域(调用方 culling)
|
||
* 函数说明:x = (屏宽 - 文本实际步进)/2 居中;白色、静态字符串不拷内存
|
||
*/
|
||
static void cp_draw_centered_text(lv_layer_t *layer, const char *text, int32_t y) {
|
||
lv_area_t area;
|
||
lv_draw_label_dsc_t ld;
|
||
|
||
area.x1 = 0;
|
||
area.x2 = CP_SCREEN_W - 1;
|
||
area.y1 = y;
|
||
area.y2 = y + 15; /* 覆盖字体行高(13)+ 余量 */
|
||
|
||
lv_draw_label_dsc_init(&ld);
|
||
ld.base.layer = layer;
|
||
ld.text = text;
|
||
ld.text_static = 1; /* Flash 字符串:不拷内存 */
|
||
ld.color = lv_color_white();
|
||
/* 本 LVGL 版 lv_text_get_width 的 length=0 表示"不测量",必须传 strlen */
|
||
ld.ofs_x = (CP_SCREEN_W - lv_text_get_width(text, (uint32_t)strlen(text), LV_FONT_DEFAULT, 0)) / 2;
|
||
ld.ofs_y = 0;
|
||
lv_draw_label(layer, &ld, &area);
|
||
}
|
||
|
||
/*
|
||
* 函数功能:页面自绘(浅 V 装饰 + 标题/副标题 + 行数据 + 页脚页码)
|
||
* 入口参数:e - 事件指针 lv_event_t *
|
||
* 返回值:无
|
||
* 限定条件:事件为 LV_EVENT_DRAW_MAIN,页面已打开
|
||
* 函数说明: 1. 浅 V 装饰几何与菜单根框一致(3 段线逐段画)
|
||
* 2. 列表页窗口 = 选中行所在页(首可见行 = (sel/每页数)*每页数),
|
||
* 行槽位置固定、数据随页切换;信息页全量静态绘制
|
||
* 3. 选中行整行白带(x=2..156)+ 黑字,非选中白字
|
||
* 4. 各区块按 culling 区跳过不在重绘区的部分
|
||
*/
|
||
static void cp_draw_cb(lv_event_t *e) {
|
||
const cp_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);
|
||
}
|
||
}
|
||
|
||
/* 标题(居中) */
|
||
{
|
||
lv_area_t t;
|
||
|
||
t.x1 = 0;
|
||
t.x2 = CP_SCREEN_W - 1;
|
||
t.y1 = CP_TITLE_Y;
|
||
t.y2 = CP_TITLE_Y + 15;
|
||
if (cp_areas_intersect(&t, &layer->_clip_area)) {
|
||
cp_draw_centered_text(layer, d->title, CP_TITLE_Y);
|
||
}
|
||
}
|
||
|
||
/* 副标题(仅信息页) */
|
||
if (d->subtitle != NULL) {
|
||
lv_area_t t;
|
||
|
||
t.x1 = 0;
|
||
t.x2 = CP_SCREEN_W - 1;
|
||
t.y1 = d->subtitle_y;
|
||
t.y2 = d->subtitle_y + 15;
|
||
if (cp_areas_intersect(&t, &layer->_clip_area)) {
|
||
cp_draw_centered_text(layer, d->subtitle, d->subtitle_y);
|
||
}
|
||
}
|
||
|
||
/* 行数据:列表页窗口跟随选中行所在页,信息页全量 */
|
||
int32_t first = 0;
|
||
int32_t cnt = (int32_t)d->total_rows;
|
||
|
||
if (d->selectable && 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;
|
||
if (cnt > (int32_t)d->rows_per_page) {
|
||
cnt = (int32_t)d->rows_per_page; /* 末页不足整页时截断 */
|
||
}
|
||
}
|
||
|
||
for (int32_t i = 0; i < cnt; i++) {
|
||
int32_t row_idx = first + i; /* 数据下标(跨页连续) */
|
||
int32_t slot_y = d->top_y + i * d->pitch; /* 行槽顶(窗口内位置固定) */
|
||
bool is_sel = (d->selectable != 0u) && (row_idx == (int32_t)s_sel);
|
||
lv_color_t color = is_sel ? lv_color_black() : lv_color_white();
|
||
lv_area_t strip;
|
||
|
||
/* 行条带(整宽)不在重绘区:跳过(culling) */
|
||
strip.x1 = 0;
|
||
strip.x2 = CP_SCREEN_W - 1;
|
||
strip.y1 = slot_y;
|
||
strip.y2 = slot_y + d->pitch - 1;
|
||
if (!cp_areas_intersect(&strip, &layer->_clip_area)) {
|
||
continue;
|
||
}
|
||
|
||
/* 选中行:整行白带(x=2..156,高 band_h) */
|
||
if (is_sel && d->band_h > 0) {
|
||
lv_draw_rect_dsc_t fd;
|
||
lv_area_t band;
|
||
|
||
band.x1 = 2;
|
||
band.x2 = CP_SCREEN_W - 3; /* x=156(与参考图实测一致) */
|
||
band.y1 = slot_y;
|
||
band.y2 = slot_y + d->band_h - 1;
|
||
lv_draw_rect_dsc_init(&fd);
|
||
fd.base.layer = layer;
|
||
fd.bg_color = lv_color_white();
|
||
fd.bg_opa = LV_OPA_COVER;
|
||
lv_draw_rect(layer, &fd, &band);
|
||
}
|
||
|
||
/* 左列(标签/名)与右列(值):固定列 x,垂直偏移 text_dy */
|
||
{
|
||
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.color = color; /* 选中黑 / 非选中白 */
|
||
ld.ofs_x = d->left_x;
|
||
ld.ofs_y = d->text_dy;
|
||
lv_draw_label(layer, &ld, &strip);
|
||
|
||
lv_draw_label_dsc_init(&ld);
|
||
ld.base.layer = layer;
|
||
ld.text = d->rows[row_idx].value;
|
||
ld.text_static = 1;
|
||
ld.color = color;
|
||
ld.ofs_x = d->right_x;
|
||
ld.ofs_y = d->text_dy;
|
||
lv_draw_label(layer, &ld, &strip);
|
||
}
|
||
}
|
||
|
||
/* 页脚"第 X 页,共 Y 页":列表页随选中行现算,信息页按参考图固定值 */
|
||
{
|
||
char buf[32];
|
||
uint8_t cur;
|
||
uint8_t total;
|
||
lv_area_t t;
|
||
|
||
if (d->selectable) {
|
||
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);
|
||
}
|
||
else {
|
||
cur = d->fixed_cur_page;
|
||
total = d->fixed_total_pages;
|
||
}
|
||
|
||
t.x1 = 0;
|
||
t.x2 = CP_SCREEN_W - 1;
|
||
t.y1 = CP_PAGE_LINE_Y;
|
||
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_draw_label_dsc_t ld;
|
||
|
||
/* ofs 相对 coords 原点(t.y1=CP_PAGE_LINE_Y),垂直偏移取 0 */
|
||
lv_draw_label_dsc_init(&ld);
|
||
ld.base.layer = layer;
|
||
ld.text = buf; /* 栈缓冲:本页码每次现算 */
|
||
ld.color = lv_color_white();
|
||
ld.ofs_x = 6;
|
||
ld.ofs_y = 0;
|
||
lv_draw_label(layer, &ld, &t);
|
||
}
|
||
}
|
||
}
|
||
|
||
/*
|
||
* 函数功能:页面按键事件入口(↑↓移选 / ESC·←返回)
|
||
* 入口参数:e - 事件指针 lv_event_t *
|
||
* 返回值:无
|
||
* 限定条件:容器已加入默认输入组并获焦
|
||
* 函数说明:1. ↑/↓:列表页移选(首尾环绕,跨页连续),窗口随页切换后重绘;信息页不响应
|
||
* 2. ESC/←:关闭页面返回菜单(导航状态未压层,原样还原)
|
||
*/
|
||
static void cp_key_cb(lv_event_t *e) {
|
||
const cp_def_t *d = &s_defs[s_open];
|
||
lv_key_t key = (lv_key_t)lv_event_get_key(e);
|
||
|
||
switch (key) {
|
||
case LV_KEY_UP:
|
||
if (d->selectable && d->total_rows > 0u) {
|
||
/* 上移、首尾环绕:先加 total 再减 1 保证无符号下不为负 */
|
||
s_sel = (uint16_t)((s_sel + d->total_rows - 1u) % d->total_rows);
|
||
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); /* 下移、越尾绕回首行 */
|
||
lv_obj_invalidate(s_cont);
|
||
}
|
||
break;
|
||
case LV_KEY_ESC:
|
||
case LV_KEY_LEFT:
|
||
ui_content_page_close(); /* 返回菜单屏(焦点/导航状态还原) */
|
||
break;
|
||
default:
|
||
break; /* 其余键值忽略 */
|
||
}
|
||
}
|
||
|
||
/*
|
||
* 函数功能:打开指定内容页(建屏 + 切屏 + 焦点置页面容器)
|
||
* 入口参数:id - 内容页 ID content_page_id_t
|
||
* 返回值:true - 成功;false - 失败(ID 无效/已有页面打开/对象创建失败)
|
||
* 限定条件:ui_menu_init() 已完成(默认输入组与菜单屏存在)
|
||
* 函数说明:1. lv_obj_create(NULL) 新建独立屏幕 + 全屏容器(自绘载体,无子对象)
|
||
* 2. 容器入默认输入组并获焦后再切屏——按键立即由本模块接管
|
||
* 3. 导航状态不压层:菜单 s_depth/s_sel 原样保留,ESC 返回即还原
|
||
*/
|
||
bool ui_content_page_open(content_page_id_t id) {
|
||
if (id >= CONTENT_PAGE_COUNT || s_open != CONTENT_PAGE_COUNT) {
|
||
return false; /* ID 无效或已有页面打开(防御) */
|
||
}
|
||
|
||
s_scr = lv_obj_create(NULL); /* parent=NULL → 新建屏幕 */
|
||
if (s_scr == NULL) {
|
||
return false; /* LVGL 池耗尽 */
|
||
}
|
||
|
||
s_cont = lv_obj_create(s_scr);
|
||
if (s_cont == NULL) {
|
||
lv_obj_delete(s_scr); /* 回滚:屏已建、容器失败 */
|
||
s_scr = NULL;
|
||
return false;
|
||
}
|
||
lv_obj_add_flag(s_cont, LV_OBJ_FLAG_IGNORE_LAYOUT); /* 绝对定位,不参与父级布局 */
|
||
lv_obj_set_size(s_cont, CP_SCREEN_W, CP_SCREEN_H); /* 全屏覆盖 */
|
||
lv_obj_add_style(s_cont, &cp_style_flat, 0); /* 透明、无边框:剥离主题 card */
|
||
lv_obj_add_flag(s_cont, LV_OBJ_FLAG_CLICK_FOCUSABLE); /* 可获焦:键事件入口 */
|
||
lv_obj_add_event_cb(s_cont, cp_key_cb, LV_EVENT_KEY, NULL);
|
||
lv_obj_add_event_cb(s_cont, cp_draw_cb, LV_EVENT_DRAW_MAIN, NULL);
|
||
|
||
s_open = id;
|
||
s_sel = 0u; /* 每次进入从首行开始(页面按需建毁,不跨次保留) */
|
||
|
||
/* 焦点/键入口:入默认输入组并获焦(先于切屏,保证按键立即由本模块接管) */
|
||
lv_group_t *g = lv_group_get_default();
|
||
if (g != NULL) {
|
||
lv_group_add_obj(g, s_cont);
|
||
lv_group_focus_obj(s_cont);
|
||
}
|
||
|
||
lv_screen_load(s_scr); /* 切到内容页(菜单屏对象保留、不销毁) */
|
||
DBG_INFO("page open: id=%u rows=%u", (unsigned)id, (unsigned)s_defs[id].total_rows);
|
||
return true;
|
||
}
|
||
|
||
/*
|
||
* 函数功能:关闭当前内容页(焦点回菜单容器 + 切回菜单屏 + 销毁页面)
|
||
* 入口参数:无
|
||
* 返回值:无
|
||
* 限定条件:ui_content_page_is_open() 为 true
|
||
* 函数说明:1. 先移焦到菜单容器再删屏——防输入组焦点悬空(删除自动移出组)
|
||
* 2. 切回菜单屏后销毁页面屏幕,LVGL 池内存随即回落
|
||
*/
|
||
void ui_content_page_close(void) {
|
||
if (s_open == CONTENT_PAGE_COUNT || s_cont == NULL) {
|
||
return; /* 未打开(防御) */
|
||
}
|
||
|
||
lv_obj_t *menu_cont = ui_menu_view_container();
|
||
if (menu_cont != NULL) {
|
||
lv_group_focus_obj(menu_cont); /* 焦点先回菜单容器 */
|
||
lv_screen_load(lv_obj_get_screen(menu_cont)); /* 切回菜单屏(对象未动,原样还原) */
|
||
}
|
||
|
||
DBG_INFO("page close: id=%u", (unsigned)s_open);
|
||
|
||
lv_obj_delete(s_cont); /* 先删容器 */
|
||
lv_obj_delete(s_scr); /* 再删屏 */
|
||
|
||
s_scr = NULL;
|
||
s_cont = NULL;
|
||
s_open = CONTENT_PAGE_COUNT;
|
||
s_sel = 0u;
|
||
}
|
||
|
||
/*
|
||
* 函数功能:查询是否有内容页打开
|
||
* 入口参数:无
|
||
* 返回值:true - 有页面打开;false - 无
|
||
* 限定条件:无
|
||
* 函数说明:供逻辑层/调试判断当前处于菜单屏还是内容页
|
||
*/
|
||
bool ui_content_page_is_open(void) {
|
||
return (s_open != CONTENT_PAGE_COUNT);
|
||
}
|