继续优化菜单RAM降低
This commit is contained in:
+104
-83
@@ -2,7 +2,7 @@
|
||||
* 模块名称:UI Menu View (菜单显示模型渲染层)
|
||||
* 模块功能:以 LVGL 基元控件实现《菜单页面设计.md》§四⑧"横向级联子框"显示模型:
|
||||
* 右贴靠(子框左缘=父框右缘、共享边框列)+ 框宽内容自适应 +
|
||||
* 顶对齐/底对齐上移;持有全部 LVGL 对象(样式/4 层容器/框体/根框装饰),
|
||||
* 顶对齐/底对齐上移;持有全部 LVGL 对象(样式/持久容器/框体),
|
||||
* 行不是对象——框体 DRAW_MAIN 自绘(节点下标注册 + GET_SELF_SIZE 虚拟滚动);
|
||||
* 不持有任何导航状态——状态由逻辑层 ui_menu.c 经本接口驱动
|
||||
* 适用平台:STM32F407ZGTx / PC 模拟器(两端共用)
|
||||
@@ -16,6 +16,9 @@
|
||||
* 2026-09-08 王建锋 行对象→自绘重写:删行容器/文本/箭头 label 对象,框体
|
||||
* DRAW_MAIN 自绘行(反色带/文本/▶),GET_SELF_SIZE 虚报
|
||||
* 内容高实现虚拟滚动(省约 2 KB/行 对象开销)
|
||||
* 2026-09-08 王建锋 4 层容器→1 个持久容器(焦点/键唯一入口;帧深度由逻辑层
|
||||
* s_depth 决定,键回调 user_data=depth 冗余);根框装饰
|
||||
* (浅 V)+状态行改容器自绘(删 3 lv_line + 1 label 对象)
|
||||
*/
|
||||
|
||||
/* 头文件包含区 */
|
||||
@@ -69,14 +72,11 @@ typedef struct {
|
||||
|
||||
/* 静态变量定义区 */
|
||||
|
||||
static lv_obj_t *s_cont[MENU_PAGE_MAX]; /* 4 层持久容器(每深度一个;焦点/键入口,永不删) */
|
||||
static lv_obj_t *s_cont; /* 单一持久全屏容器(焦点/键唯一入口;各层框均建其内;永不删) */
|
||||
static lv_obj_t *s_frame_box[MENU_PAGE_MAX]; /* 框体:d=0 持久(根框);d>=1 进入时建、返回时毁(含自绘回调) */
|
||||
static uint8_t s_row_node[MENU_PAGE_MAX][MENU_ITEM_MAX]; /* 行数据:节点下标(名称/箭头绘制时现查,无对象) */
|
||||
static menu_frame_t s_frame[MENU_PAGE_MAX]; /* 每层框几何缓存:位置/尺寸/选中行(现算不落状态,供子框对齐;见上方类型注释) */
|
||||
|
||||
static lv_obj_t *s_root_deco = NULL; /* 根框装饰:顶部"浅 V"水平段(两腿为临时对象,样式同源) */
|
||||
static lv_obj_t *s_root_status = NULL; /* 根框状态行 */
|
||||
|
||||
/* 样式(静态,一次性初始化,常驻) */
|
||||
static lv_style_t s_style_flat; /* 通用:透明、无边框/圆角/内边距(剥离主题 card) */
|
||||
static lv_style_t s_style_frame; /* 框体:透明底 + 1px 白边框 */
|
||||
@@ -163,6 +163,23 @@ static int32_t view_frame_width(const uint8_t *kids, uint8_t cnt) {
|
||||
return w + 2 * MENU_FRAME_BORDER;
|
||||
}
|
||||
|
||||
/*
|
||||
* 函数功能:判断两区域是否相交(等价 lv_area_intersect 非空判断;避免依赖 LVGL 私有头)
|
||||
* 入口参数:a - 区域 1 const lv_area_t *
|
||||
* b - 区域 2 const lv_area_t *
|
||||
* 返回值:true - 相交;false - 不相交
|
||||
* 限定条件:无
|
||||
* 函数说明:四边取 min/max 成交集,任一边为空即不相交(与 lv_area.c 同语义)
|
||||
*/
|
||||
static bool 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);
|
||||
}
|
||||
|
||||
/*
|
||||
* 函数功能:框体虚拟内容高上报(滚动范围 = 内容高 - 内容区高)
|
||||
* 入口参数:e - 事件指针(user_data = 层深度) lv_event_t *
|
||||
@@ -210,7 +227,6 @@ static void view_frame_draw_cb(lv_event_t *e) {
|
||||
for (i = i_first; i <= i_last; i++) {
|
||||
int32_t row_y = area.y1 + i * MENU_ROW_H - sy;
|
||||
lv_area_t row_area;
|
||||
lv_area_t clip;
|
||||
const char *name = ui_menu_tab_name(s_row_node[depth][i]);
|
||||
bool is_sel = (i == (int32_t)f->sel);
|
||||
lv_color_t color = is_sel ? lv_color_black() : lv_color_white();
|
||||
@@ -220,13 +236,9 @@ static void view_frame_draw_cb(lv_event_t *e) {
|
||||
row_area.y1 = row_y;
|
||||
row_area.y2 = row_y + MENU_ROW_H - 1;
|
||||
|
||||
/* 行 ∩ 重绘区(min/max 裁剪,与 lv_area_intersect 等价;空则跳过 culling) */
|
||||
clip.x1 = row_area.x1 > layer->_clip_area.x1 ? row_area.x1 : layer->_clip_area.x1;
|
||||
clip.y1 = row_area.y1 > layer->_clip_area.y1 ? row_area.y1 : layer->_clip_area.y1;
|
||||
clip.x2 = row_area.x2 < layer->_clip_area.x2 ? row_area.x2 : layer->_clip_area.x2;
|
||||
clip.y2 = row_area.y2 < layer->_clip_area.y2 ? row_area.y2 : layer->_clip_area.y2;
|
||||
if (clip.x1 > clip.x2 || clip.y1 > clip.y2) {
|
||||
continue; /* 行不在重绘区:跳过(culling) */
|
||||
/* 行不在重绘区:跳过(culling) */
|
||||
if (!areas_intersect(&row_area, &layer->_clip_area)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
/* 选中行:整行反色(白带) */
|
||||
@@ -383,7 +395,7 @@ void ui_menu_view_frame_build(uint8_t depth, uint8_t node, uint8_t sel, uint16_t
|
||||
f->w = (cnt > 0u) ? view_frame_width(kids, cnt)
|
||||
: (2 * MENU_FRAME_BORDER + MENU_ROW_PAD_L + MENU_ROW_PAD_R);
|
||||
|
||||
box = lv_obj_create(s_cont[depth]);
|
||||
box = lv_obj_create(s_cont);
|
||||
if (box == NULL) {
|
||||
return; /* LVGL 池耗尽:框未建,状态回滚 */
|
||||
}
|
||||
@@ -498,53 +510,93 @@ uint16_t ui_menu_view_get_scroll(uint8_t depth) {
|
||||
}
|
||||
|
||||
/*
|
||||
* 函数功能:焦点移至指定深度的容器
|
||||
* 入口参数:depth - 深度 uint8_t
|
||||
* 函数功能:焦点置于持久容器
|
||||
* 入口参数:无
|
||||
* 返回值:无
|
||||
* 限定条件:ui_menu_view_init() 已完成且容器已加入输入组
|
||||
* 函数说明:焦点=最右框;进/退时由逻辑层调用
|
||||
* 函数说明:焦点=唯一键入口(框深度由逻辑层 s_depth 决定);
|
||||
* 进/退时由逻辑层调用(防御性:保证容器恒为焦点对象)
|
||||
*/
|
||||
void ui_menu_view_focus(uint8_t depth) {
|
||||
if (depth < MENU_PAGE_MAX && s_cont[depth] != NULL) {
|
||||
/* 焦点移到目标深度容器 */
|
||||
lv_group_focus_obj(s_cont[depth]);
|
||||
void ui_menu_view_focus(void) {
|
||||
if (s_cont != NULL) {
|
||||
lv_group_focus_obj(s_cont);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* 函数功能:取指定深度的持久容器
|
||||
* 入口参数:depth - 深度 0..MENU_PAGE_MAX-1 uint8_t
|
||||
* 返回值:容器对象 lv_obj_t * 越界返回 NULL
|
||||
* 函数功能:取持久容器
|
||||
* 入口参数:无
|
||||
* 返回值:容器对象 lv_obj_t * 未初始化返回 NULL
|
||||
* 限定条件:ui_menu_view_init() 已完成
|
||||
* 函数说明:逻辑层在容器上绑定输入组与键事件(显示层不碰输入)
|
||||
*/
|
||||
lv_obj_t *ui_menu_view_container(uint8_t depth) {
|
||||
return (depth < MENU_PAGE_MAX) ? s_cont[depth] : NULL;
|
||||
lv_obj_t *ui_menu_view_container(void) {
|
||||
return s_cont;
|
||||
}
|
||||
|
||||
/*
|
||||
* 函数功能:应用主界面顶部"浅 V"装饰线段的公共样式
|
||||
* 入口参数:line - lv_line 对象;width - 线宽(px)
|
||||
* 返回值:无
|
||||
* 限定条件:line 已创建且已设点集
|
||||
* 函数说明:绝对定位于 (0,0)、忽略布局、白色实线、指定线宽
|
||||
* 根框装饰(浅 V)点集与自绘区域:容器 DRAW_MAIN 自绘(无对象);
|
||||
* culling 区域覆盖原 lv_line 对象范围((0,0) 处 160×20)与状态行文本区
|
||||
*/
|
||||
static void view_deco_line_apply(lv_obj_t *line, int32_t width) {
|
||||
lv_obj_add_flag(line, LV_OBJ_FLAG_IGNORE_LAYOUT);
|
||||
lv_obj_set_pos(line, 0, 0);
|
||||
lv_obj_set_size(line, MENU_SCREEN_W, 20);
|
||||
lv_obj_set_style_line_width(line, width, 0);
|
||||
lv_obj_set_style_line_color(line, lv_color_white(), 0);
|
||||
lv_obj_set_style_line_opa(line, LV_OPA_COVER, 0);
|
||||
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 宽 + 余量) */
|
||||
|
||||
/*
|
||||
* 函数功能:自绘根框装饰 + 状态行(容器 DRAW_MAIN;替代旧 4 个 LVGL 对象)
|
||||
* 入口参数:e - 事件指针 lv_event_t *
|
||||
* 返回值:无
|
||||
* 限定条件:事件为 LV_EVENT_DRAW_MAIN,容器已建
|
||||
* 函数说明: 1. 浅 V:两腿 3px 粗、水平线 2px 细(1bpp 斜线边缘易被阈值吃掉,腿须比水平线粗)
|
||||
* 2. 绘制次序 = 旧对象创建序(左腿、右腿、水平线),重叠像素与旧渲染一致
|
||||
* 3. 状态行:静态字符串、白色,与旧 label 同字体同位置
|
||||
* 4. 重绘区不覆盖对应 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);
|
||||
}
|
||||
}
|
||||
|
||||
/* 状态行:当前模式(静态文本) */
|
||||
if (areas_intersect(&s_status_area, &layer->_clip_area)) {
|
||||
lv_draw_label_dsc_t sd;
|
||||
|
||||
lv_draw_label_dsc_init(&sd);
|
||||
sd.base.layer = layer;
|
||||
sd.text = "当前模式 : 无模式";
|
||||
sd.text_static = 1; /* Flash 字符串:不拷内存 */
|
||||
sd.color = lv_color_white();
|
||||
sd.ofs_x = 0; /* 区域原点即状态行左上 (17,20) */
|
||||
sd.ofs_y = 0;
|
||||
lv_draw_label(layer, &sd, &s_status_area);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* 函数功能:初始化显示层(样式 + 4 层持久容器 + 根框装饰 + 根框框体)
|
||||
* 函数功能:初始化显示层(样式 + 持久容器 + 根框框体;装饰/状态行自绘)
|
||||
* 入口参数:无
|
||||
* 返回值:0 - 成功,其他 - 失败(对象创建失败)
|
||||
* 限定条件:LVGL 已初始化且活动屏幕存在(ui_port_init / sim_port_init 已完成)
|
||||
* 函数说明:1. 4 层容器每深度一个、全屏覆盖、持久不删(焦点/键入口,防悬空)
|
||||
* 2. 根框装饰(浅 V + 状态行)与根框框体只属根层(d=0)
|
||||
* 函数说明:1. 持久容器:全屏覆盖、持久不删(焦点/键唯一入口;各层框均建其内)
|
||||
* 2. 根框装饰(浅 V + 状态行)由容器自绘(无对象);根框框体持久
|
||||
* 3. 行数据由逻辑层经 ui_menu_view_frame_build 注册,框体自绘行
|
||||
*/
|
||||
int ui_menu_view_init(void) {
|
||||
@@ -571,49 +623,18 @@ int ui_menu_view_init(void) {
|
||||
lv_obj_add_style(menu, &s_style_flat, 0); /* 透明、无边框:纯定位层,不可见 */
|
||||
lv_obj_set_size(menu, sw, sh);
|
||||
|
||||
/* 4 层持久容器:每深度一个、全屏覆盖、永不删(焦点/键入口,防悬空) */
|
||||
for (uint8_t d = 0; d < MENU_PAGE_MAX; d++) {
|
||||
s_cont[d] = lv_obj_create(menu);
|
||||
if (s_cont[d] == NULL) {
|
||||
return -1;
|
||||
}
|
||||
lv_obj_add_style(s_cont[d], &s_style_flat, 0); /* 透明、无边框 */
|
||||
lv_obj_add_flag(s_cont[d], LV_OBJ_FLAG_IGNORE_LAYOUT); /* 绝对定位,不参与父级 flex 布局 */
|
||||
lv_obj_set_pos(s_cont[d], 0, 0);
|
||||
lv_obj_set_size(s_cont[d], sw, sh); /* 全屏覆盖 */
|
||||
lv_obj_add_flag(s_cont[d], LV_OBJ_FLAG_CLICK_FOCUSABLE); /* 可获焦:键事件入口 */
|
||||
}
|
||||
|
||||
/* 根框装饰:顶部"开口浅 V"(两腿加粗、水平线细)+ 状态行(绝对定位于 d=0 容器)
|
||||
* 1bpp 下斜线边缘易被阈值吃掉,腿须比水平线粗才接近参考图 */
|
||||
{
|
||||
static const lv_point_precise_t deco_leg_l[] = { {7, 7}, {16, 17} }; /* 左腿:自顶端斜下 */
|
||||
static const lv_point_precise_t deco_hoz[] = { {16, 17}, {145, 17} }; /* 水平线:接两腿底端 */
|
||||
static const lv_point_precise_t deco_leg_r[] = { {158, 7}, {145, 17} }; /* 右腿 */
|
||||
|
||||
lv_obj_t *leg_l = lv_line_create(s_cont[0]);
|
||||
lv_obj_t *leg_r = lv_line_create(s_cont[0]);
|
||||
s_root_deco = lv_line_create(s_cont[0]); /* 仅水平线留存句柄;两腿建后无需再引用 */
|
||||
if (leg_l == NULL || leg_r == NULL || s_root_deco == NULL) {
|
||||
return -1;
|
||||
}
|
||||
lv_line_set_points(leg_l, deco_leg_l, 2);
|
||||
lv_line_set_points(leg_r, deco_leg_r, 2);
|
||||
lv_line_set_points(s_root_deco, deco_hoz, 2);
|
||||
view_deco_line_apply(leg_l, MENU_DECO_LEG_W); /* 腿:3px 粗 */
|
||||
view_deco_line_apply(leg_r, MENU_DECO_LEG_W);
|
||||
view_deco_line_apply(s_root_deco, MENU_DECO_HORIZ_W); /* 水平线:2px 细 */
|
||||
}
|
||||
|
||||
/* 状态行:当前模式(顶部区域,根框上方) */
|
||||
s_root_status = lv_label_create(s_cont[0]);
|
||||
if (s_root_status == NULL) {
|
||||
/* 持久容器:全屏覆盖、永不删(焦点/键唯一入口;各层框均建其内,防悬空) */
|
||||
s_cont = lv_obj_create(menu);
|
||||
if (s_cont == NULL) {
|
||||
return -1;
|
||||
}
|
||||
lv_obj_add_style(s_root_status, &s_style_flat, 0);
|
||||
lv_obj_add_flag(s_root_status, LV_OBJ_FLAG_IGNORE_LAYOUT);
|
||||
lv_obj_set_pos(s_root_status, MENU_ROOT_STATUS_X, MENU_ROOT_STATUS_Y);
|
||||
lv_label_set_text_static(s_root_status, "当前模式 : 无模式"); /* 静态字符串,不拷内存 */
|
||||
lv_obj_add_style(s_cont, &s_style_flat, 0); /* 透明、无边框 */
|
||||
lv_obj_add_flag(s_cont, LV_OBJ_FLAG_IGNORE_LAYOUT); /* 绝对定位,不参与父级 flex 布局 */
|
||||
lv_obj_set_pos(s_cont, 0, 0);
|
||||
lv_obj_set_size(s_cont, sw, sh); /* 全屏覆盖 */
|
||||
lv_obj_add_flag(s_cont, LV_OBJ_FLAG_CLICK_FOCUSABLE); /* 可获焦:键事件入口 */
|
||||
/* 根框装饰(浅 V + 状态行):容器自绘(无对象),回调仅此处挂一次 */
|
||||
lv_obj_add_event_cb(s_cont, view_deco_draw_cb, LV_EVENT_DRAW_MAIN, NULL);
|
||||
|
||||
/* 根框框体:持久;宽 = 内容自适应(主界面 node 0 的子项) */
|
||||
uint8_t kids[MENU_ITEM_MAX];
|
||||
@@ -621,7 +642,7 @@ int ui_menu_view_init(void) {
|
||||
int32_t w = view_frame_width(kids, cnt); /* 宽 = 最宽行内容 + 左右边框(当前 66) */
|
||||
int32_t h = (int32_t)cnt * MENU_ROW_H + 2 * MENU_FRAME_BORDER; /* 高 = 行数×行高 + 上下边框(当前 107) */
|
||||
|
||||
s_frame_box[0] = lv_obj_create(s_cont[0]);
|
||||
s_frame_box[0] = lv_obj_create(s_cont);
|
||||
if (s_frame_box[0] == NULL) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user