/* * 模块名称:UI Menu (菜单页面构建) * 模块功能:基于 LVGL 基元控件(页面导航)复现 DTU-HMI 树形菜单,支持方向键导航与返回 * 适用平台:STM32F407ZGTx * 作者:王建锋 * 创建日期:2026-08-29 * 修改记录: * 2026-08-29 王建锋 创建初始版本 * 2026-09-02 王建锋 弃用已废弃的 lv_menu 控件,改为基元控件页面导航 * 2026-09-02 王建锋 修复选中行文字不可见(白底白字):行改用 label 承载,选中态样式直接覆盖文字色 * 2026-09-03 王建锋 主界面按设计稿重构:顶部开口浅 V 装饰 + 状态行 + 左对齐带框 7 行列表(选中行整行反色、无箭头,其余行带 ▶); * 根页条目收敛为一级 7 项(排除"厂家设置"子树);重命名 定值参数→参数定值、遥测量→三遥设置、通讯设置→通讯参数 */ #include "ui_menu.h" #include "lvgl.h" #define DBG_TAG "[UI_MENU]" #include "dbg_log.h" /* 菜单节点表(简化自 DTU-HMI menuTab):层级 + 名称 */ typedef struct { uint8_t level; /* 0=根页面, 1..3=子层次 */ const char *name; } ui_menu_node_t; static const ui_menu_node_t g_menu_tab[] = { {0, "主界面"}, {1, "装置信息"}, {1, "实时数据"}, {2, "交流量"}, {2, "直流量"}, {2, "开入量"}, {1, "参数定值"}, {2, "定值"}, {3, "定值"}, {3, "遥调"}, {3, "零压"}, {3, "电压"}, {2, "查看"}, {3, "定值"}, {3, "遥调"}, {3, "零压"}, {3, "电压"}, {1, "三遥设置"}, {2, "测量值"}, {2, "遥调系数"}, {2, "遥测系数"}, {2, "双字节"}, {2, "装置遥调"}, {1, "装置维护"}, {2, "时间设置"}, {2, "强制复归"}, {2, "手动录波"}, {2, "自动录波"}, {1, "通讯参数"}, {2, "通讯参数"}, {2, "网络配置"}, {2, "SNTP设置"}, {1, "记录查询"}, {2, "SOE记录"}, {2, "事故记录"}, {2, "操作记录"}, {2, "告警记录"}, {2, "变位告警"}, {2, "遥信记录"}, {2, "自检记录"}, {2, "定值记录"}, {2, "录波记录"}, {0, "厂家设置"}, {1, "元件设置"}, {1, "恢复默认"}, {2, "全部恢复"}, {2, "默认参数"}, {2, "默认定值"}, {2, "默认系数"}, {2, "电压"}, {2, "元件参数"}, {1, "版本显示"}, {1, "装置遥调"}, {2, "测量遥调"}, {2, "装置遥调"}, {2, "直流遥调"}, {2, "开出遥调"}, {2, "装置记录"}, {2, "告警记录"}, {2, "遥调记录"}, {2, "信号复归"}, {1, "版本信息"}, }; #define MENU_NODE_COUNT (sizeof(g_menu_tab) / sizeof(g_menu_tab[0])) #define MENU_NODE_ROOT 0xFFFF /* 虚拟根节点(根页面) */ #define MENU_PAGE_MAX 4 /* 页面深度上限(根页 + 3 级子页) */ #define MENU_ITEM_MAX 16 /* 单页最大条目数 */ #define MENU_HEADER_H 18 /* 子页页头高度(px) */ /* 主界面(根页)布局(设备像素,依据设计稿 mockup 实测) */ #define MENU_ROW_H 15 /* 行高 */ #define MENU_ROW_TXT_TOP 1 /* 行内文字垂直居中偏移((15-13)/2,字体行高 13) */ #define MENU_ROW_PAD_L 8 /* 行内文字左留白 */ #define MENU_ROW_ARROW_X 58 /* 行内箭头 x(紧跟 4 字文本之后) */ #define MENU_ROOT_BOX_X 0 /* 列表框左(左对齐,贴左缘) */ #define MENU_ROOT_BOX_Y 44 /* 列表框顶 */ #define MENU_ROOT_BOX_W 99 /* 列表框宽 */ #define MENU_ROOT_BOX_H 107 /* 列表框高(7 行 × 15 + 上下边框 2) */ #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"水平线宽(≈原图 ~2px) */ /* * 页面:持久容器 + 按导航动态建毁的条目 * 容器常驻对象树(焦点对象不可删除,避免输入设备悬空指针), * 条目按钮进入页面时创建、返回时销毁,内存占用 = 当前路径 * * 每条目 = 行容器(整行背景)+ 文本 label + 可选箭头 label: * - 行容器承载整行反色高亮(选中态白底) * - 文本/箭头为子 label(绝对定位);选中态文字色须显式置黑 * (主题对 label 直接给白字,父级继承不可靠,见 2026-09-02 记录) */ typedef struct { lv_obj_t *cont; /* 页面容器(持久) */ lv_obj_t *row[MENU_ITEM_MAX]; /* 行容器(动态) */ lv_obj_t *row_text[MENU_ITEM_MAX]; /* 行文本 label */ lv_obj_t *row_arrow[MENU_ITEM_MAX]; /* 行箭头 label(NULL=无) */ uint16_t item_node[MENU_ITEM_MAX]; /* 各条目对应 g_menu_tab 下标 */ uint16_t node; /* 本页展示节点(MENU_NODE_ROOT=虚拟根) */ uint8_t item_cnt; /* 条目数 */ uint8_t sel; /* 当前选中条目下标 */ uint8_t depth; /* 页面深度(0=根页) */ } ui_menu_page_t; static ui_menu_page_t s_pages[MENU_PAGE_MAX]; static ui_menu_page_t *s_cur = NULL; /* 当前显示页面 */ static lv_obj_t *s_header = NULL; /* 子页页头容器(返回按钮 + 标题);根页隐藏 */ static lv_obj_t *s_back_btn = NULL; /* 页头返回按钮 */ static lv_obj_t *s_title = NULL; /* 页头标题 */ static lv_obj_t *s_root_deco = NULL; /* 主界面顶部装饰(开口浅 V) */ static lv_obj_t *s_root_status = NULL; /* 主界面状态行 */ static lv_obj_t *s_root_box = NULL; /* 主界面列表框(1px 边框) */ /* 样式(静态,一次性初始化) */ static lv_style_t s_style_flat; /* 通用:透明、无边框/圆角/内边距 */ static lv_style_t s_style_selected; /* 选中态:整行反色(白底) */ static lv_style_t s_style_back; /* 返回按钮:细边框、小内边距 */ static lv_style_t s_style_scrollbar; /* 滚动条:白色、4px 宽 */ static lv_style_t s_style_root_box; /* 主界面列表框:透明底 + 1px 白边框 */ /* * 函数功能:初始化菜单样式(静态样式,仅执行一次) * 入口参数:无 * 返回值:无 * 限定条件:无 * 函数说明: 1. flat 用于剥离主题给对象附加的卡片样式(边框/圆角/内边距) * 2. selected 为选中条目整行反色高亮(1bpp:白底;文字色在刷新时显式置黑) * 3. 样式后加优先,可覆盖主题在创建时附加的样式 */ static void menu_styles_init(void) { static bool inited = false; if (inited) { return; } inited = true; 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); lv_style_set_radius(&s_style_flat, 0); lv_style_set_pad_all(&s_style_flat, 0); lv_style_set_pad_gap(&s_style_flat, 0); 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()); lv_style_init(&s_style_back); lv_style_set_bg_opa(&s_style_back, LV_OPA_TRANSP); lv_style_set_border_width(&s_style_back, 1); lv_style_set_border_color(&s_style_back, lv_color_white()); lv_style_set_radius(&s_style_back, 0); lv_style_set_pad_all(&s_style_back, 1); 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()); lv_style_set_width(&s_style_scrollbar, 4); lv_style_init(&s_style_root_box); lv_style_set_bg_opa(&s_style_root_box, LV_OPA_TRANSP); lv_style_set_border_width(&s_style_root_box, 1); lv_style_set_border_color(&s_style_root_box, lv_color_white()); lv_style_set_radius(&s_style_root_box, 0); lv_style_set_pad_all(&s_style_root_box, 0); /* mono 主题 card 样式自带 pad_gap=PAD_DEF(4),须显式清零,否则行间距被撑大导致溢出 */ lv_style_set_pad_gap(&s_style_root_box, 0); } /* * 函数功能:向页面添加一个条目(行容器 + 文本 + 可选箭头) * 入口参数:page - 页面结构指针 ui_menu_page_t * * parent - 条目父容器 lv_obj_t * 根页=s_root_box,子页=page->cont * node - g_menu_tab 节点下标 uint16_t * has_arrow - 是否创建箭头 label bool * 返回值:无 * 限定条件:page->item_cnt < MENU_ITEM_MAX * 函数说明:1. 行容器整行宽、高 MENU_ROW_H,承载选中反色高亮 * 2. 文本 label 左留白 MENU_ROW_PAD_L;箭头 label 位于 MENU_ROW_ARROW_X * 3. 文本/箭头子 label 加 IGNORE_LAYOUT 做行内绝对定位 */ static void menu_page_add_item(ui_menu_page_t *page, lv_obj_t *parent, uint16_t node, bool has_arrow) { if (page->item_cnt >= MENU_ITEM_MAX) { return; } uint8_t idx = page->item_cnt; lv_obj_t *row = lv_obj_create(parent); if (row == NULL) { return; } lv_obj_add_style(row, &s_style_flat, 0); 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); lv_obj_t *lbl = lv_label_create(row); if (lbl == NULL) { lv_obj_delete(row); return; } 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); lv_label_set_text_static(lbl, g_menu_tab[node].name); lv_obj_t *ar = NULL; if (has_arrow) { ar = lv_label_create(row); if (ar != NULL) { lv_obj_add_style(ar, &s_style_flat, 0); lv_obj_add_flag(ar, LV_OBJ_FLAG_IGNORE_LAYOUT); lv_obj_set_pos(ar, MENU_ROW_ARROW_X, MENU_ROW_TXT_TOP); lv_label_set_text_static(ar, "▶"); } } page->row[idx] = row; page->row_text[idx] = lbl; page->row_arrow[idx] = ar; page->item_node[idx] = node; page->item_cnt++; } /* 前向声明(menu_page_build 先于其定义调用) */ static void menu_page_refresh_sel(ui_menu_page_t *page); /* * 函数功能:构建页面条目列表 * 入口参数:page - 页面结构指针 ui_menu_page_t * * node - 展示节点 uint16_t MENU_NODE_ROOT 表示虚拟根 * 返回值:无 * 限定条件:根页时 s_root_box 已创建 * 函数说明:1. 根页:一级节点 7 项(至第二个根"厂家设置"前截断),带箭头,父容器=s_root_box * 2. 子页:node+1 起 level 为 (level+1) 的连续段,无箭头,父容器=page->cont * 3. 先清空父容器旧条目再重建,默认选中首项 */ static void menu_page_build(ui_menu_page_t *page, uint16_t node) { bool is_root = (node == MENU_NODE_ROOT); lv_obj_t *parent = is_root ? s_root_box : page->cont; bool has_arrow = is_root; lv_obj_clean(parent); page->node = node; page->item_cnt = 0; page->sel = 0; if (is_root) { for (uint32_t i = 1; i < MENU_NODE_COUNT; i++) { if (g_menu_tab[i].level == 0) { break; /* 抵达第二个根(厂家设置),主界面仅取其之前的一级项 */ } if (g_menu_tab[i].level == 1) { menu_page_add_item(page, parent, (uint16_t)i, has_arrow); } } } else { uint8_t lvl = (uint8_t)(g_menu_tab[node].level + 1); for (uint32_t i = node + 1; i < MENU_NODE_COUNT; i++) { if (g_menu_tab[i].level != lvl) { break; } menu_page_add_item(page, parent, (uint16_t)i, has_arrow); } } if (page->item_cnt > 0) { menu_page_refresh_sel(page); } } /* * 函数功能:更新选中高亮、文字色与箭头显隐 * 入口参数:page - 页面结构指针 ui_menu_page_t * * 返回值:无 * 限定条件:page->item_cnt > 0 * 函数说明:1. 选中行整行反色(白底),文字/箭头显式置黑并隐藏箭头 * 2. 非选中行透明底,文字/箭头置白并显示箭头 * 3. 滚动使选中条目进入可视区 */ static void menu_page_refresh_sel(ui_menu_page_t *page) { if (page->item_cnt == 0) { return; } for (uint8_t i = 0; i < page->item_cnt; i++) { bool sel = (i == page->sel); if (sel) { lv_obj_add_state(page->row[i], LV_STATE_CHECKED); } else { lv_obj_clear_state(page->row[i], LV_STATE_CHECKED); } lv_obj_set_style_text_color(page->row_text[i], sel ? lv_color_black() : lv_color_white(), 0); if (page->row_arrow[i] != NULL) { if (sel) { lv_obj_add_flag(page->row_arrow[i], LV_OBJ_FLAG_HIDDEN); } else { lv_obj_remove_flag(page->row_arrow[i], LV_OBJ_FLAG_HIDDEN); } lv_obj_set_style_text_color(page->row_arrow[i], sel ? lv_color_black() : lv_color_white(), 0); } } lv_obj_scroll_to_view(page->row[page->sel], LV_ANIM_OFF); } /* * 函数功能:切换显示页面 * 入口参数:page - 目标页面 ui_menu_page_t * * 返回值:无 * 限定条件:ui_menu_init() 已完成 * 函数说明:1. 隐藏旧页、显示新页(仅切换 HIDDEN 标志) * 2. 页头仅子页显示;根页隐藏页头(让位顶部装饰 + 状态行,内容区撑满 160) * 3. 焦点移至新页容器(按键输入入口) */ static void menu_show_page(ui_menu_page_t *page) { if (s_cur != NULL && s_cur != page) { lv_obj_add_flag(s_cur->cont, LV_OBJ_FLAG_HIDDEN); } s_cur = page; lv_obj_clear_flag(page->cont, LV_OBJ_FLAG_HIDDEN); if (page->depth == 0) { lv_obj_add_flag(s_header, LV_OBJ_FLAG_HIDDEN); } else { lv_obj_remove_flag(s_header, LV_OBJ_FLAG_HIDDEN); lv_label_set_text_static(s_title, g_menu_tab[page->node].name); } lv_group_focus_obj(page->cont); } /* * 函数功能:打开选中条目的子页面 * 入口参数:page - 当前页面 ui_menu_page_t * * 返回值:无 * 限定条件:page->item_cnt > 0 * 函数说明:1. 在下一深度页面容器上重建条目并显示 * 2. 叶子节点(无子节点)打开空白占位页 * 3. 深度保护:超过 MENU_PAGE_MAX 不再下钻 */ static void menu_open_selected(ui_menu_page_t *page) { if (page->item_cnt == 0) { return; } if (page->depth + 1 >= MENU_PAGE_MAX) { return; } ui_menu_page_t *child = &s_pages[page->depth + 1]; menu_page_build(child, page->item_node[page->sel]); menu_show_page(child); } /* * 函数功能:返回上级页面 * 入口参数:page - 当前页面 ui_menu_page_t * * 返回值:无 * 限定条件:page->depth > 0 * 函数说明:1. 销毁本页条目释放 LVGL 池内存(容器保留) * 2. 显示父页并恢复焦点(父页选中状态保持不变) */ static void menu_go_back(ui_menu_page_t *page) { if (page->depth == 0) { return; } lv_obj_clean(page->cont); menu_show_page(&s_pages[page->depth - 1]); } /* * 函数功能:页面容器按键事件处理 * 入口参数:e - 事件指针 lv_event_t * * 返回值:无 * 限定条件:容器已加入默认输入组并获焦 * 函数说明:1. UP/DOWN:移动选中(环绕) * 2. ENTER/RIGHT:打开选中条目子页 * 3. ESC/LEFT:返回上级页面 */ static void menu_page_key_cb(lv_event_t *e) { ui_menu_page_t *page = (ui_menu_page_t *)lv_event_get_user_data(e); lv_key_t key = (lv_key_t)lv_event_get_key(e); switch (key) { case LV_KEY_UP: if (page->item_cnt > 0) { page->sel = (uint8_t)((page->sel + page->item_cnt - 1) % page->item_cnt); menu_page_refresh_sel(page); } break; case LV_KEY_DOWN: if (page->item_cnt > 0) { page->sel = (uint8_t)((page->sel + 1) % page->item_cnt); menu_page_refresh_sel(page); } break; case LV_KEY_ENTER: case LV_KEY_RIGHT: menu_open_selected(page); break; case LV_KEY_ESC: case LV_KEY_LEFT: menu_go_back(page); break; default: break; } } /* * 函数功能:返回按钮点击回调(预留触摸输入) * 入口参数:e - 事件指针 lv_event_t * * 返回值:无 * 限定条件:ui_menu_init() 已完成 * 函数说明:返回上级页面;按键输入由页面容器 ESC/LEFT 处理 */ static void menu_back_btn_cb(lv_event_t *e) { (void)e; if (s_cur != NULL) { menu_go_back(s_cur); } } /* * 函数功能:应用主界面顶部"浅 V"装饰线段的公共样式 * 入口参数:line - lv_line 对象;width - 线宽(px) * 返回值:无 * 限定条件:line 已创建且已设点集 * 函数说明:绝对定位于 (0,0)、忽略布局、白色实线、指定线宽 */ static void 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, 160, 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); } /* * 函数功能:构建菜单(基元控件页面导航) * 入口参数:无 * 返回值:0 - 成功,其他 - 失败(对象创建失败) * 限定条件:ui_port_init()/sim_port_init() 已成功调用,活动屏幕存在 * 函数说明: 1. 子页页头(返回按钮 + 标题)+ 内容区,4 个页面容器(每深度一个,持久) * 2. 主界面(根页):顶部开口浅 V 装饰 + 状态行 + 左对齐带框 7 行列表 * 3. 页面按需构建:ENTER 时建、返回时毁,内存 = 当前路径 * 4. 页面容器加入默认输入组,按键由容器统一处理 * 5. 构建前后监控 LVGL 池内存并打印 */ int ui_menu_init(void) { lv_obj_t *scr = lv_screen_active(); if (scr == NULL) { return -1; } menu_styles_init(); lv_mem_monitor_t mon; lv_mem_monitor(&mon); DBG_INFO("menu build start: total=%u free=%u big=%u", (unsigned)mon.total_size, (unsigned)mon.free_size, (unsigned)mon.free_biggest_size); /* 根容器:铺满屏幕,纵向布局 */ lv_display_t *disp = lv_display_get_default(); lv_obj_t *menu = lv_obj_create(scr); if (menu == NULL) { return -1; } lv_obj_add_style(menu, &s_style_flat, 0); lv_obj_set_size(menu, lv_display_get_horizontal_resolution(disp), lv_display_get_vertical_resolution(disp)); lv_obj_set_flex_flow(menu, LV_FLEX_FLOW_COLUMN); /* 子页页头:返回按钮 + 标题(根页隐藏) */ s_header = lv_obj_create(menu); if (s_header == NULL) { return -1; } lv_obj_add_style(s_header, &s_style_flat, 0); lv_obj_set_width(s_header, lv_pct(100)); lv_obj_set_height(s_header, MENU_HEADER_H); lv_obj_set_flex_flow(s_header, LV_FLEX_FLOW_ROW); lv_obj_set_flex_align(s_header, LV_FLEX_ALIGN_START, LV_FLEX_ALIGN_CENTER, LV_FLEX_ALIGN_CENTER); lv_obj_set_style_pad_gap(s_header, 4, 0); s_back_btn = lv_button_create(s_header); if (s_back_btn == NULL) { return -1; } lv_obj_add_style(s_back_btn, &s_style_back, 0); lv_obj_set_size(s_back_btn, 28, 16); lv_obj_set_flex_flow(s_back_btn, LV_FLEX_FLOW_ROW); lv_obj_set_flex_align(s_back_btn, LV_FLEX_ALIGN_CENTER, LV_FLEX_ALIGN_CENTER, LV_FLEX_ALIGN_CENTER); lv_obj_t *back_lbl = lv_label_create(s_back_btn); if (back_lbl == NULL) { return -1; } lv_obj_add_style(back_lbl, &s_style_flat, 0); lv_label_set_text_static(back_lbl, "返回"); lv_obj_add_event_cb(s_back_btn, menu_back_btn_cb, LV_EVENT_CLICKED, NULL); s_title = lv_label_create(s_header); if (s_title == NULL) { return -1; } lv_obj_add_style(s_title, &s_style_flat, 0); lv_label_set_text_static(s_title, "主界面"); lv_obj_add_flag(s_header, LV_OBJ_FLAG_HIDDEN); /* 默认根页:隐藏页头 */ /* 内容区:占满剩余高度(根页隐藏页头时撑满 160) */ lv_obj_t *content = lv_obj_create(menu); if (content == NULL) { return -1; } lv_obj_add_style(content, &s_style_flat, 0); lv_obj_set_width(content, lv_pct(100)); lv_obj_set_flex_grow(content, 1); lv_obj_set_flex_flow(content, LV_FLEX_FLOW_COLUMN); /* 页面容器:每深度一个,持久存在(条目按导航建毁) */ for (uint8_t i = 0; i < MENU_PAGE_MAX; i++) { s_pages[i].cont = lv_obj_create(content); if (s_pages[i].cont == NULL) { return -1; } lv_obj_add_style(s_pages[i].cont, &s_style_flat, 0); lv_obj_set_size(s_pages[i].cont, lv_pct(100), lv_pct(100)); lv_obj_add_flag(s_pages[i].cont, LV_OBJ_FLAG_CLICK_FOCUSABLE); lv_group_add_obj(lv_group_get_default(), s_pages[i].cont); lv_obj_add_event_cb(s_pages[i].cont, menu_page_key_cb, LV_EVENT_KEY, &s_pages[i]); s_pages[i].depth = i; s_pages[i].node = MENU_NODE_ROOT; if (i > 0) { /* 子页容器:纵向堆叠条目 + 自动滚动条 */ lv_obj_set_flex_flow(s_pages[i].cont, LV_FLEX_FLOW_COLUMN); lv_obj_set_flex_align(s_pages[i].cont, LV_FLEX_ALIGN_START, LV_FLEX_ALIGN_START, LV_FLEX_ALIGN_START); lv_obj_set_scrollbar_mode(s_pages[i].cont, LV_SCROLLBAR_MODE_AUTO); lv_obj_add_style(s_pages[i].cont, &s_style_scrollbar, LV_PART_SCROLLBAR); lv_obj_add_flag(s_pages[i].cont, LV_OBJ_FLAG_HIDDEN); } } /* 主界面(根页):顶部"开口浅 V"装饰 + 状态行 + 列表框(绝对定位于 s_pages[0].cont) * 拆 3 段:两腿加粗(1bpp 下斜线边缘易被阈值吃掉,需比水平线粗才接近原图 ~4px),水平线保持细线 */ { static const lv_point_precise_t g_deco_leg_l[] = { {7, 7}, {16, 17} }; static const lv_point_precise_t g_deco_hoz[] = { {16, 17}, {145, 17} }; static const lv_point_precise_t g_deco_leg_r[] = { {158, 7}, {145, 17} }; lv_obj_t * leg_l = lv_line_create(s_pages[0].cont); lv_obj_t * leg_r = lv_line_create(s_pages[0].cont); s_root_deco = lv_line_create(s_pages[0].cont); if (leg_l == NULL || leg_r == NULL || s_root_deco == NULL) { return -1; } lv_line_set_points(leg_l, g_deco_leg_l, 2); lv_line_set_points(leg_r, g_deco_leg_r, 2); lv_line_set_points(s_root_deco, g_deco_hoz, 2); deco_line_apply(leg_l, MENU_DECO_LEG_W); deco_line_apply(leg_r, MENU_DECO_LEG_W); deco_line_apply(s_root_deco, MENU_DECO_HORIZ_W); } s_root_status = lv_label_create(s_pages[0].cont); if (s_root_status == 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, "当前模式 : 无模式"); s_root_box = lv_obj_create(s_pages[0].cont); if (s_root_box == NULL) { return -1; } lv_obj_add_style(s_root_box, &s_style_root_box, 0); lv_obj_add_flag(s_root_box, LV_OBJ_FLAG_IGNORE_LAYOUT); lv_obj_set_pos(s_root_box, MENU_ROOT_BOX_X, MENU_ROOT_BOX_Y); lv_obj_set_size(s_root_box, MENU_ROOT_BOX_W, MENU_ROOT_BOX_H); lv_obj_set_flex_flow(s_root_box, LV_FLEX_FLOW_COLUMN); lv_obj_set_flex_align(s_root_box, LV_FLEX_ALIGN_START, LV_FLEX_ALIGN_START, LV_FLEX_ALIGN_START); /* 构建根页并显示 */ menu_page_build(&s_pages[0], MENU_NODE_ROOT); menu_show_page(&s_pages[0]); lv_mem_monitor(&mon); DBG_INFO("menu build done: total=%u free=%u big=%u used=%u", (unsigned)mon.total_size, (unsigned)mon.free_size, (unsigned)mon.free_biggest_size, (unsigned)(mon.total_size - mon.free_size)); return 0; }