增加UI 代码的注释

This commit is contained in:
2026-09-08 18:36:01 +08:00
parent 2b41bdccb5
commit 891c9ef0d9
7 changed files with 1334 additions and 489 deletions
+176 -71
View File
@@ -9,18 +9,25 @@
* 创建日期:2026-09-04
* 修改记录:
* 2026-09-04 王建锋 创建:自旧 ui_menu.c 单页模型拆分出显示层(高内聚低耦合)
* 2026-09-08 王建锋 按代码规范 V1.0 补全:分区注释、公共函数注释、关键行注释
* 2026-09-08 王建锋 menu_frame_t 瘦身(uint8_t/int16_t,96→48 B/层);
* s_row_text/s_row_arrow 并入 s_row(省 512 B,行 label 颜色继承)
*/
/* 头文件包含区 */
#include <string.h>
#include "ui_menu_view.h"
#include "ui_menu_tab.h"
#include "lvgl.h"
#include <string.h>
#define DBG_TAG "[UI_MENU_VIEW]"
#include "dbg_log.h"
/* 私有宏定义区 */
/*
* 布局常量(设备像素;参考图 02~07 实测 + 字体度量速查推导):
* 汉字步进 12px/字、▶ 步进 10px、行高 15(7 行×15+2 边框=107,与参考图根框一致);
@@ -41,25 +48,27 @@
#define MENU_DECO_LEG_W 3 /* 顶部"浅 V"两腿线宽(1bpp 下斜线边缘易被阈值吃掉,需加粗) */
#define MENU_DECO_HORIZ_W 2 /* 顶部"浅 V"水平线宽 */
/* 私有类型定义区 */
/*
* 框几何缓存:位置/尺寸是"现算"的(不落导航状态),但子框对齐要用父框刚算出的值,
* 故按层缓存一份;sel 为最近一次应用的高亮(子框垂直对齐依据),由逻辑层经
* build/refresh 保持同步
*/
typedef struct {
bool built; /* 框已建(框体 + 行对象存在) */
uint8_t built; /* 框已建(框体 + 行对象存在):0/1(uint8_t 与系统存储一致) */
uint8_t sel; /* 最近应用的选中行 */
uint8_t item_cnt; /* 行数 */
int32_t x, y; /* 框左上角(含边框) */
int32_t w, h; /* 框外廓尺寸 */
int16_t x, y; /* 框左上角(含边框;16 位够:y 钳制前可为负) */
int16_t w, h; /* 框外廓尺寸(16 位够:最大 16×15+2=242) */
} menu_frame_t;
/* 静态变量定义区 */
static lv_obj_t *s_cont[MENU_PAGE_MAX]; /* 4 层持久容器(每深度一个;焦点/键入口,永不删) */
static lv_obj_t *s_frame_box[MENU_PAGE_MAX]; /* 框体:d=0 持久(根框);d>=1 进入时建、返回时毁 */
static lv_obj_t *s_row[MENU_PAGE_MAX][MENU_ITEM_MAX]; /* 行容器(整行反色高亮的载体) */
static lv_obj_t *s_row_text[MENU_PAGE_MAX][MENU_ITEM_MAX]; /* 行文本 label */
static lv_obj_t *s_row_arrow[MENU_PAGE_MAX][MENU_ITEM_MAX]; /* 行箭头 label(叶子=NULL) */
static menu_frame_t s_frame[MENU_PAGE_MAX];
static lv_obj_t *s_row[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; /* 根框状态行 */
@@ -70,6 +79,8 @@ static lv_style_t s_style_selected; /* 选中态:整行反色(白底)
static lv_style_t s_style_frame; /* 框体:透明底 + 1px 白边框 */
static lv_style_t s_style_scrollbar; /* 滚动条:白色、4px 宽 */
/* 函数定义区 */
/*
* 函数功能:初始化显示层样式(静态样式,仅执行一次)
* 入口参数:无
@@ -79,14 +90,16 @@ static lv_style_t s_style_scrollbar; /* 滚动条:白色、4px 宽 */
* 2. frame 为所有级联框(含根框)的 1px 白边框
* 3. selected 为选中行整行反色(1bpp 白底);文字色在刷新时显式置黑
*/
static void view_styles_init(void)
{
static void view_styles_init(void) {
static bool inited = false;
/* 一次性初始化:已初始化则直接返回 */
if (inited) {
return;
}
inited = true;
/* flat:透明、无边框/圆角/内边距(剥离主题附加的卡片样式) */
lv_style_init(&s_style_flat);
lv_style_set_bg_opa(&s_style_flat, LV_OPA_TRANSP);
lv_style_set_border_width(&s_style_flat, 0);
@@ -94,11 +107,13 @@ static void view_styles_init(void)
lv_style_set_pad_all(&s_style_flat, 0);
lv_style_set_pad_gap(&s_style_flat, 0);
/* selected:选中行整行反色(白底黑字) */
lv_style_init(&s_style_selected);
lv_style_set_bg_opa(&s_style_selected, LV_OPA_COVER);
lv_style_set_bg_color(&s_style_selected, lv_color_white());
lv_style_set_text_color(&s_style_selected, lv_color_black());
/* frame:透明底 + 1px 白边框(所有级联框含根框) */
lv_style_init(&s_style_frame);
lv_style_set_bg_opa(&s_style_frame, LV_OPA_TRANSP);
lv_style_set_border_width(&s_style_frame, MENU_FRAME_BORDER);
@@ -107,6 +122,7 @@ static void view_styles_init(void)
lv_style_set_pad_all(&s_style_frame, 0);
lv_style_set_pad_gap(&s_style_frame, 0);
/* scrollbar:白色 4px 宽(行超框高时自动出现) */
lv_style_init(&s_style_scrollbar);
lv_style_set_bg_opa(&s_style_scrollbar, LV_OPA_COVER);
lv_style_set_bg_color(&s_style_scrollbar, lv_color_white());
@@ -123,22 +139,30 @@ static void view_styles_init(void)
* 文本/▶ 宽取字体实际步进(汉字 12px、▶ 10px);
* 注意本 LVGL 版 lv_text_get_width 的 length=0 表示"不测量",必须传 strlen
*/
static int32_t view_frame_width(const uint8_t *kids, uint8_t cnt)
{
static int32_t view_frame_width(const uint8_t *kids, uint8_t cnt) {
const lv_font_t *font = LV_FONT_DEFAULT;
int32_t w = 0;
/* 遍历各行,取内容宽最大值 */
for (uint8_t i = 0; i < cnt; i++) {
const char *name = ui_menu_tab_name(kids[i]);
/* 量文本实际步进(本 LVGL 版 length=0 意为"不测量",须传 strlen) */
int32_t tw = lv_text_get_width(name, (uint32_t)strlen(name), font, 0);
int32_t cw = MENU_ROW_PAD_L + tw + MENU_ROW_PAD_R;
/* 菜单行(有子项):加箭头间距 + ▶ 宽 */
if (ui_menu_tab_has_children(kids[i])) {
cw += MENU_ARROW_GAP + lv_text_get_width("▶", (uint32_t)strlen("▶"), font, 0);
}
/* 取最宽行 */
if (cw > w) {
w = cw;
}
}
/* 加左右边框 */
return w + 2 * MENU_FRAME_BORDER;
}
@@ -153,19 +177,22 @@ static int32_t view_frame_width(const uint8_t *kids, uint8_t cnt)
* 限定条件:d < MENU_PAGE_MAX,i < MENU_ITEM_MAX
* 函数说明: 1. 行容器整行宽、高 MENU_ROW_H,承载选中整行反色高亮
* 2. 文本左留白 MENU_ROW_PAD_L;箭头紧跟文本(间距 MENU_ARROW_GAP,自适应)
* 3. 文本/箭头子 label 加 IGNORE_LAYOUT 做行内绝对定位
* 3. 文本/箭头子 label 加 IGNORE_LAYOUT 做行内绝对定位,是行的子对象、不单独存
*/
static bool view_add_row(lv_obj_t *box, uint8_t d, uint8_t i, uint8_t node, bool has_arrow)
{
static bool view_add_row(lv_obj_t *box, uint8_t d, uint8_t i, uint8_t node, bool has_arrow) {
/* 行容器:整行宽、行高 MENU_ROW_H(整行反色高亮的载体) */
lv_obj_t *row = lv_obj_create(box);
if (row == NULL) {
return false;
}
lv_obj_add_style(row, &s_style_flat, 0);
/* 挂选中行反色样式(CHECKED 态时触发白底) */
lv_obj_add_style(row, &s_style_selected, LV_STATE_CHECKED);
lv_obj_set_width(row, lv_pct(100));
lv_obj_set_height(row, MENU_ROW_H);
/* 文本 label:左留白,行内绝对定位(IGNORE_LAYOUT) */
lv_obj_t *lbl = lv_label_create(row);
if (lbl == NULL) {
lv_obj_delete(row);
@@ -174,11 +201,16 @@ static bool view_add_row(lv_obj_t *box, uint8_t d, uint8_t i, uint8_t node, bool
lv_obj_add_style(lbl, &s_style_flat, 0);
lv_obj_add_flag(lbl, LV_OBJ_FLAG_IGNORE_LAYOUT);
lv_obj_set_pos(lbl, MENU_ROW_PAD_L, MENU_ROW_TXT_TOP);
/* 静态字符串:直接指向 ROM 表,不拷贝 */
lv_label_set_text_static(lbl, ui_menu_tab_name(node));
/* 箭头:仅菜单行(有子项),紧跟文本(间距 MENU_ARROW_GAP) */
lv_obj_t *ar = NULL;
if (has_arrow) {
const char *name = ui_menu_tab_name(node);
/* 箭头 x = 左留白 + 实测文本宽 + 间距 */
int32_t tw = lv_text_get_width(name, (uint32_t)strlen(name), LV_FONT_DEFAULT, 0);
ar = lv_label_create(row);
if (ar != NULL) {
@@ -189,9 +221,8 @@ static bool view_add_row(lv_obj_t *box, uint8_t d, uint8_t i, uint8_t node, bool
}
}
/* 只存行容器:文本/箭头是其子对象(颜色在行上设置、子对象继承) */
s_row[d][i] = row;
s_row_text[d][i] = lbl;
s_row_arrow[d][i] = ar;
return true;
}
@@ -203,11 +234,12 @@ static bool view_add_row(lv_obj_t *box, uint8_t d, uint8_t i, uint8_t node, bool
* 限定条件:该层行对象已建
* 函数说明:1. 选中行整行反色(白底黑字),箭头随反色变黑(参考图实测:选中行保留 ▶)
* 2. 非选中行透明底白字、白色箭头
* 3. 文本/箭头颜色设在行容器上,子 label 继承(text_color 为可继承属性)
*/
static void view_apply_sel(uint8_t depth, uint8_t sel)
{
static void view_apply_sel(uint8_t depth, uint8_t sel) {
menu_frame_t *f = &s_frame[depth];
/* 逐行置/清 CHECKED 态(选中行经样式整行反色) */
for (uint8_t i = 0; i < f->item_cnt; i++) {
bool is_sel = (i == sel);
if (is_sel) {
@@ -216,12 +248,10 @@ static void view_apply_sel(uint8_t depth, uint8_t sel)
else {
lv_obj_clear_state(s_row[depth][i], LV_STATE_CHECKED);
}
lv_obj_set_style_text_color(s_row_text[depth][i],
/* 文本色设在行上:文本/箭头子对象继承(选中黑、非选中白,箭头随反色) */
lv_obj_set_style_text_color(s_row[depth][i],
is_sel ? lv_color_black() : lv_color_white(), 0);
if (s_row_arrow[depth][i] != NULL) {
lv_obj_set_style_text_color(s_row_arrow[depth][i],
is_sel ? lv_color_black() : lv_color_white(), 0);
}
}
}
@@ -232,16 +262,30 @@ static void view_apply_sel(uint8_t depth, uint8_t sel)
* 限定条件:box 已创建
* 函数说明:行内容超框高时框内滚动(本菜单最深 9 行不触发,机制为更长列表保留)
*/
static void view_box_layout(lv_obj_t *box)
{
static void view_box_layout(lv_obj_t *box) {
/* 纵向 flex 堆行、顶对齐(行自框顶依次排列) */
lv_obj_set_flex_flow(box, LV_FLEX_FLOW_COLUMN);
lv_obj_set_flex_align(box, LV_FLEX_ALIGN_START, LV_FLEX_ALIGN_START, LV_FLEX_ALIGN_START);
/* 自动滚动条:仅当行内容超框高时出现(框内滚动) */
lv_obj_set_scrollbar_mode(box, LV_SCROLLBAR_MODE_AUTO);
lv_obj_add_style(box, &s_style_scrollbar, LV_PART_SCROLLBAR);
}
void ui_menu_view_frame_build(uint8_t depth, uint8_t node, uint8_t sel, uint16_t scroll)
{
/*
* 函数功能:构建指定深度的框(几何现算 + 行对象创建 + 选中/滚动应用)
* 入口参数:depth - 深度(0=根框) uint8_t
* node - 本框展示节点(行 = 其子项) uint8_t
* sel - 本框选中行 uint8_t
* scroll - 本框滚动偏移(px,0=顶部) uint16_t
* 返回值:无
* 限定条件:depth>0 时 depth-1 层框已建(父框几何是其对齐依据)
* 函数说明: 1. d=0:根框框体已存在,仅(重)建行对象
* 2. d>0:右贴靠父框右缘;顶边=父框选中行行顶,超屏底则
* 上移使本框底边对齐父框选中行行底,仍超顶则钳制顶部并改框内滚动
* 3. 框宽 = 最宽行(文本 + ▶ + 左右留白)+ 边框;条目现算不落地
*/
void ui_menu_view_frame_build(uint8_t depth, uint8_t node, uint8_t sel, uint16_t scroll) {
menu_frame_t *f = &s_frame[depth];
lv_obj_t *box;
uint8_t kids[MENU_ITEM_MAX];
@@ -291,17 +335,20 @@ void ui_menu_view_frame_build(uint8_t depth, uint8_t node, uint8_t sel, uint16_t
s_frame_box[depth] = box;
}
f->built = true;
/* 更新框几何缓存(子框对齐的依据) */
f->built = 1u;
f->item_cnt = cnt;
f->sel = sel;
/* 行对象:条目现算(= parent==node 过滤),进时建、退时毁 */
lv_obj_clean(box);
/* 清空旧行指针(旧对象已随框体删除) */
for (uint8_t i = 0; i < MENU_ITEM_MAX; i++) {
s_row[depth][i] = NULL;
s_row_text[depth][i] = NULL;
s_row_arrow[depth][i] = NULL;
}
/* 逐行建行对象 */
for (uint8_t i = 0; i < cnt; i++) {
if (!view_add_row(box, depth, i, kids[i], ui_menu_tab_has_children(kids[i]))) {
break; /* LVGL 池耗尽:停止建行(池有界,见设计 §7.5) */
@@ -311,15 +358,23 @@ void ui_menu_view_frame_build(uint8_t depth, uint8_t node, uint8_t sel, uint16_t
/* 选中高亮 + 滚动位 */
view_apply_sel(depth, sel);
if (scroll > 0u) {
/* 有保存的滚动位:还原 */
lv_obj_scroll_to_y(box, (int32_t)scroll, LV_ANIM_OFF);
}
else if (cnt > 0u) {
/* 无保存滚动位:选中行对齐到可见区 */
lv_obj_scroll_to_view(s_row[depth][sel], LV_ANIM_OFF);
}
}
void ui_menu_view_frame_destroy(uint8_t depth)
{
/*
* 函数功能:销毁指定深度的框(框体连同行对象,容器保留)
* 入口参数:depth - 深度 uint8_t
* 返回值:无
* 限定条件:depth>0(根框持久不可销毁)
* 函数说明:返回(关最右框)时调用,LVGL 池内存随即回落
*/
void ui_menu_view_frame_destroy(uint8_t depth) {
menu_frame_t *f = &s_frame[depth];
if (depth == 0u || !f->built) {
@@ -327,12 +382,12 @@ void ui_menu_view_frame_destroy(uint8_t depth)
}
lv_obj_delete(s_frame_box[depth]); /* 框体连行对象整体删除(容器保留) */
s_frame_box[depth] = NULL;
/* 清空行指针与几何缓存 */
for (uint8_t i = 0; i < MENU_ITEM_MAX; i++) {
s_row[depth][i] = NULL;
s_row_text[depth][i] = NULL;
s_row_arrow[depth][i] = NULL;
}
f->built = false;
f->built = 0u;
f->item_cnt = 0u;
f->sel = 0u;
f->x = 0;
@@ -341,10 +396,18 @@ void ui_menu_view_frame_destroy(uint8_t depth)
f->h = 0;
}
void ui_menu_view_frame_refresh(uint8_t depth, uint8_t sel)
{
/*
* 函数功能:刷新指定深度框的选中高亮
* 入口参数:depth - 深度 uint8_t
* sel - 新选中行 uint8_t
* 返回值:无
* 限定条件:ui_menu_view_frame_build(depth,…) 已完成
* 函数说明:只改行状态与文字色 + 跟随滚动,不重建对象
*/
void ui_menu_view_frame_refresh(uint8_t depth, uint8_t sel) {
menu_frame_t *f = &s_frame[depth];
/* 未建或越界直接返回 */
if (!f->built || depth >= MENU_PAGE_MAX || sel >= f->item_cnt) {
return;
}
@@ -354,31 +417,59 @@ void ui_menu_view_frame_refresh(uint8_t depth, uint8_t sel)
lv_obj_scroll_to_view(s_row[depth][sel], LV_ANIM_OFF);
}
void ui_menu_view_set_scroll(uint8_t depth, uint16_t scroll)
{
/*
* 函数功能:应用指定深度框的滚动偏移
* 入口参数:depth - 深度 uint8_t
* scroll - 滚动偏移(px) uint16_t
* 返回值:无
* 限定条件:该层框已建
* 函数说明:行对象重建后按此还原用户滚动位置
*/
void ui_menu_view_set_scroll(uint8_t depth, uint16_t scroll) {
/* 未建框直接返回 */
if (depth >= MENU_PAGE_MAX || !s_frame[depth].built || s_frame_box[depth] == NULL) {
return;
}
lv_obj_scroll_to_y(s_frame_box[depth], (int32_t)scroll, LV_ANIM_OFF);
}
uint16_t ui_menu_view_get_scroll(uint8_t depth)
{
/*
* 函数功能:读取指定深度框当前滚动偏移
* 入口参数:depth - 深度 uint8_t
* 返回值:滚动偏移(px) uint16_t 未建框返回 0
* 限定条件:无
* 函数说明:跟随滚动后由逻辑层读回,写入导航状态持久化
*/
uint16_t ui_menu_view_get_scroll(uint8_t depth) {
/* 未建框返回 0 */
if (depth >= MENU_PAGE_MAX || !s_frame[depth].built || s_frame_box[depth] == NULL) {
return 0u;
}
return (uint16_t)lv_obj_get_scroll_y(s_frame_box[depth]);
}
void ui_menu_view_focus(uint8_t depth)
{
/*
* 函数功能:焦点移至指定深度的容器
* 入口参数:depth - 深度 uint8_t
* 返回值:无
* 限定条件:ui_menu_view_init() 已完成且容器已加入输入组
* 函数说明:焦点=最右框;进/退时由逻辑层调用
*/
void ui_menu_view_focus(uint8_t depth) {
if (depth < MENU_PAGE_MAX && s_cont[depth] != NULL) {
/* 焦点移到目标深度容器 */
lv_group_focus_obj(s_cont[depth]);
}
}
lv_obj_t *ui_menu_view_container(uint8_t depth)
{
/*
* 函数功能:取指定深度的持久容器
* 入口参数:depth - 深度 0..MENU_PAGE_MAX-1 uint8_t
* 返回值:容器对象 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;
}
@@ -389,8 +480,7 @@ lv_obj_t *ui_menu_view_container(uint8_t depth)
* 限定条件:line 已创建且已设点集
* 函数说明:绝对定位于 (0,0)、忽略布局、白色实线、指定线宽
*/
static void view_deco_line_apply(lv_obj_t *line, int32_t width)
{
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);
@@ -399,14 +489,27 @@ static void view_deco_line_apply(lv_obj_t *line, int32_t width)
lv_obj_set_style_line_opa(line, LV_OPA_COVER, 0);
}
int ui_menu_view_init(void)
{
/*
* 函数功能:初始化显示层(样式 + 4 层持久容器 + 根框装饰 + 根框框体)
* 入口参数:无
* 返回值:0 - 成功,其他 - 失败(对象创建失败)
* 限定条件:LVGL 已初始化且活动屏幕存在(ui_port_init / sim_port_init 已完成)
* 函数说明:1. 4 层容器每深度一个、全屏覆盖、持久不删(焦点/键入口,防悬空)
* 2. 根框装饰(浅 V + 状态行)与根框框体只属根层(d=0)
* 3. 行对象不在此建,由逻辑层调 ui_menu_view_frame_build 按需建
*/
int ui_menu_view_init(void) {
/* 取活动屏幕:NULL = ui_port_init/sim_port_init 未完成、LVGL 未就绪 */
lv_obj_t *scr = lv_screen_active();
if (scr == NULL) {
return -1;
}
/* 样式一次性初始化(静态样式、常驻,仅此处建一次) */
view_styles_init();
/* 读屏幕实际分辨率(不硬编码,MCU/模拟器两端共用本代码) */
lv_display_t *disp = lv_display_get_default();
int32_t sw = lv_display_get_horizontal_resolution(disp);
int32_t sh = lv_display_get_vertical_resolution(disp);
@@ -416,7 +519,7 @@ int ui_menu_view_init(void)
if (menu == NULL) {
return -1;
}
lv_obj_add_style(menu, &s_style_flat, 0);
lv_obj_add_style(menu, &s_style_flat, 0); /* 透明、无边框:纯定位层,不可见 */
lv_obj_set_size(menu, sw, sh);
/* 4 层持久容器:每深度一个、全屏覆盖、永不删(焦点/键入口,防悬空) */
@@ -425,34 +528,35 @@ int ui_menu_view_init(void)
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);
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);
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} };
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]);
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);
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);
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) {
return -1;
@@ -460,26 +564,27 @@ int ui_menu_view_init(void)
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_label_set_text_static(s_root_status, "当前模式 : 无模式"); /* 静态字符串,不拷内存 */
/* 根框框体:持久;宽 = 内容自适应(主界面 node 0 的子项) */
uint8_t kids[MENU_ITEM_MAX];
uint8_t cnt = ui_menu_tab_children(0u, kids, MENU_ITEM_MAX);
int32_t w = view_frame_width(kids, cnt);
int32_t h = (int32_t)cnt * MENU_ROW_H + 2 * MENU_FRAME_BORDER;
uint8_t cnt = ui_menu_tab_children(0u, kids, MENU_ITEM_MAX); /* 主界面子项(7 个 L1 条目) */
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]);
if (s_frame_box[0] == NULL) {
return -1;
}
lv_obj_add_style(s_frame_box[0], &s_style_flat, 0);
lv_obj_add_style(s_frame_box[0], &s_style_frame, 0);
lv_obj_add_style(s_frame_box[0], &s_style_flat, 0); /* 透明底 */
lv_obj_add_style(s_frame_box[0], &s_style_frame, 0); /* 1px 白边框 */
lv_obj_add_flag(s_frame_box[0], LV_OBJ_FLAG_IGNORE_LAYOUT);
lv_obj_set_pos(s_frame_box[0], MENU_ROOT_BOX_X, MENU_ROOT_BOX_Y);
lv_obj_set_pos(s_frame_box[0], MENU_ROOT_BOX_X, MENU_ROOT_BOX_Y); /* 左上 (0,44):装饰/状态行之下 */
lv_obj_set_size(s_frame_box[0], w, h);
view_box_layout(s_frame_box[0]);
view_box_layout(s_frame_box[0]); /* 纵向 flex 堆行 + 自动滚动条(行对象后由 frame_build 添加) */
s_frame[0].built = true; /* 框体就绪;行对象由逻辑层 frame_build(0) 创建 */
/* 写根框几何缓存:子框右贴靠其右缘、顶对齐其选中行,均以此为准 */
s_frame[0].built = 1u; /* 框体就绪;行对象由逻辑层 frame_build(0) 创建 */
s_frame[0].x = MENU_ROOT_BOX_X;
s_frame[0].y = MENU_ROOT_BOX_Y;
s_frame[0].w = w;