UI 菜单初步完成
This commit is contained in:
@@ -0,0 +1,490 @@
|
||||
/*
|
||||
* 模块名称:UI Menu View (菜单显示模型渲染层)
|
||||
* 模块功能:以 LVGL 基元控件实现《菜单页面设计.md》§四⑧"横向级联子框"显示模型:
|
||||
* 右贴靠(子框左缘=父框右缘、共享边框列)+ 框宽内容自适应 +
|
||||
* 顶对齐/底对齐上移;持有全部 LVGL 对象(样式/4 层容器/框/行/根框装饰),
|
||||
* 不持有任何导航状态——状态由逻辑层 ui_menu.c 经本接口驱动
|
||||
* 适用平台:STM32F407ZGTx / PC 模拟器(两端共用)
|
||||
* 作者:王建锋
|
||||
* 创建日期:2026-09-04
|
||||
* 修改记录:
|
||||
* 2026-09-04 王建锋 创建:自旧 ui_menu.c 单页模型拆分出显示层(高内聚低耦合)
|
||||
*/
|
||||
#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,与参考图根框一致);
|
||||
* 行内留白取最小值 2px——最深 4 框级联(根 66 + 42 + 42 + 空框 6 - 3 共享边框)
|
||||
* = 153px,须 < 屏宽 160
|
||||
*/
|
||||
#define MENU_ROW_H 15 /* 行高 */
|
||||
#define MENU_ROW_TXT_TOP 1 /* 行内文字垂直居中偏移((15-13)/2,字体行高 13) */
|
||||
#define MENU_ROW_PAD_L 2 /* 行内文字左留白(紧留白,保证级联总宽 < 160) */
|
||||
#define MENU_ROW_PAD_R 2 /* 行内右侧留白 */
|
||||
#define MENU_ARROW_GAP 2 /* 行内 文本→箭头 间距 */
|
||||
#define MENU_FRAME_BORDER 1 /* 框边框宽(子框与父框共享该边框列) */
|
||||
|
||||
#define MENU_ROOT_BOX_X 0 /* 根框左(左对齐,贴左缘) */
|
||||
#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"水平线宽 */
|
||||
|
||||
/*
|
||||
* 框几何缓存:位置/尺寸是"现算"的(不落导航状态),但子框对齐要用父框刚算出的值,
|
||||
* 故按层缓存一份;sel 为最近一次应用的高亮(子框垂直对齐依据),由逻辑层经
|
||||
* build/refresh 保持同步
|
||||
*/
|
||||
typedef struct {
|
||||
bool built; /* 框已建(框体 + 行对象存在) */
|
||||
uint8_t sel; /* 最近应用的选中行 */
|
||||
uint8_t item_cnt; /* 行数 */
|
||||
int32_t x, y; /* 框左上角(含边框) */
|
||||
int32_t w, h; /* 框外廓尺寸 */
|
||||
} 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_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_selected; /* 选中态:整行反色(白底) */
|
||||
static lv_style_t s_style_frame; /* 框体:透明底 + 1px 白边框 */
|
||||
static lv_style_t s_style_scrollbar; /* 滚动条:白色、4px 宽 */
|
||||
|
||||
/*
|
||||
* 函数功能:初始化显示层样式(静态样式,仅执行一次)
|
||||
* 入口参数:无
|
||||
* 返回值:无
|
||||
* 限定条件:无
|
||||
* 函数说明: 1. flat 剥离主题给对象附加的卡片样式(边框/圆角/内边距)
|
||||
* 2. frame 为所有级联框(含根框)的 1px 白边框
|
||||
* 3. selected 为选中行整行反色(1bpp 白底);文字色在刷新时显式置黑
|
||||
*/
|
||||
static void view_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_frame);
|
||||
lv_style_set_bg_opa(&s_style_frame, LV_OPA_TRANSP);
|
||||
lv_style_set_border_width(&s_style_frame, MENU_FRAME_BORDER);
|
||||
lv_style_set_border_color(&s_style_frame, lv_color_white());
|
||||
lv_style_set_radius(&s_style_frame, 0);
|
||||
lv_style_set_pad_all(&s_style_frame, 0);
|
||||
lv_style_set_pad_gap(&s_style_frame, 0);
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
/*
|
||||
* 函数功能:计算框外廓宽(内容自适应)
|
||||
* 入口参数:kids - 子节点下标数组(各行内容) const uint8_t *
|
||||
* cnt - 条目数 uint8_t
|
||||
* 返回值:框外廓宽 px int32_t = 最宽行内容 + 左右边框
|
||||
* 限定条件:LVGL 已初始化(字体度量可用)
|
||||
* 函数说明:行内容宽 = 左留白 + 文本 + (菜单 ? 间距 + ▶ : 0) + 右留白;
|
||||
* 文本/▶ 宽取字体实际步进(汉字 12px、▶ 10px);
|
||||
* 注意本 LVGL 版 lv_text_get_width 的 length=0 表示"不测量",必须传 strlen
|
||||
*/
|
||||
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]);
|
||||
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;
|
||||
}
|
||||
|
||||
/*
|
||||
* 函数功能:在框体上创建一条条目行(行容器 + 文本 + 可选箭头)
|
||||
* 入口参数:box - 所属框体 lv_obj_t *
|
||||
* d - 框所在深度 uint8_t
|
||||
* i - 行下标 uint8_t
|
||||
* node - 条目节点下标 uint8_t
|
||||
* has_arrow - 是否创建箭头(=条目有子项) bool
|
||||
* 返回值:true - 成功;false - 对象创建失败(LVGL 池耗尽,调用方停止建行)
|
||||
* 限定条件:d < MENU_PAGE_MAX,i < MENU_ITEM_MAX
|
||||
* 函数说明: 1. 行容器整行宽、高 MENU_ROW_H,承载选中整行反色高亮
|
||||
* 2. 文本左留白 MENU_ROW_PAD_L;箭头紧跟文本(间距 MENU_ARROW_GAP,自适应)
|
||||
* 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)
|
||||
{
|
||||
lv_obj_t *row = lv_obj_create(box);
|
||||
if (row == NULL) {
|
||||
return false;
|
||||
}
|
||||
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 false;
|
||||
}
|
||||
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, ui_menu_tab_name(node));
|
||||
|
||||
lv_obj_t *ar = NULL;
|
||||
if (has_arrow) {
|
||||
const char *name = ui_menu_tab_name(node);
|
||||
int32_t tw = lv_text_get_width(name, (uint32_t)strlen(name), LV_FONT_DEFAULT, 0);
|
||||
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_PAD_L + tw + MENU_ARROW_GAP, MENU_ROW_TXT_TOP);
|
||||
lv_label_set_text_static(ar, "▶");
|
||||
}
|
||||
}
|
||||
|
||||
s_row[d][i] = row;
|
||||
s_row_text[d][i] = lbl;
|
||||
s_row_arrow[d][i] = ar;
|
||||
return true;
|
||||
}
|
||||
|
||||
/*
|
||||
* 函数功能:应用指定深度框的选中高亮(整行反色 + 文字/箭头颜色)
|
||||
* 入口参数:depth - 深度 uint8_t
|
||||
* sel - 选中行 uint8_t
|
||||
* 返回值:无
|
||||
* 限定条件:该层行对象已建
|
||||
* 函数说明:1. 选中行整行反色(白底黑字),箭头随反色变黑(参考图实测:选中行保留 ▶)
|
||||
* 2. 非选中行透明底白字、白色箭头
|
||||
*/
|
||||
static void view_apply_sel(uint8_t depth, uint8_t sel)
|
||||
{
|
||||
menu_frame_t *f = &s_frame[depth];
|
||||
|
||||
for (uint8_t i = 0; i < f->item_cnt; i++) {
|
||||
bool is_sel = (i == sel);
|
||||
if (is_sel) {
|
||||
lv_obj_add_state(s_row[depth][i], LV_STATE_CHECKED);
|
||||
}
|
||||
else {
|
||||
lv_obj_clear_state(s_row[depth][i], LV_STATE_CHECKED);
|
||||
}
|
||||
lv_obj_set_style_text_color(s_row_text[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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* 函数功能:配置框体布局(纵向 flex 堆行 + 自动滚动条)
|
||||
* 入口参数:box - 框体 lv_obj_t *
|
||||
* 返回值:无
|
||||
* 限定条件:box 已创建
|
||||
* 函数说明:行内容超框高时框内滚动(本菜单最深 9 行不触发,机制为更长列表保留)
|
||||
*/
|
||||
static void view_box_layout(lv_obj_t *box)
|
||||
{
|
||||
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)
|
||||
{
|
||||
menu_frame_t *f = &s_frame[depth];
|
||||
lv_obj_t *box;
|
||||
uint8_t kids[MENU_ITEM_MAX];
|
||||
uint8_t cnt = ui_menu_tab_children(node, kids, MENU_ITEM_MAX);
|
||||
|
||||
if (depth == 0u) {
|
||||
box = s_frame_box[0]; /* 根框框体持久(view_init 已建) */
|
||||
}
|
||||
else {
|
||||
if (f->built) {
|
||||
ui_menu_view_frame_destroy(depth); /* 防御:重入 = 先毁后建 */
|
||||
}
|
||||
const menu_frame_t *p = &s_frame[depth - 1u];
|
||||
|
||||
/* X:右贴靠——子框左缘 = 父框右缘(共享边框列、零间隙) */
|
||||
f->x = p->x + p->w - MENU_FRAME_BORDER;
|
||||
|
||||
/* Y:顶对齐父框"选中行"行顶;(顶边+框高) 超屏底 → 上移使本框底边
|
||||
* 对齐父框"选中行"行底(顶部不钳制,可叠到状态行区);
|
||||
* 仍超顶(比整屏高)→ 钳制顶部,框高截到可见区、行改框内滚动 */
|
||||
int32_t row_top = p->y + MENU_FRAME_BORDER + (int32_t)p->sel * MENU_ROW_H;
|
||||
int32_t h = (int32_t)cnt * MENU_ROW_H + 2 * MENU_FRAME_BORDER;
|
||||
f->y = row_top;
|
||||
if (f->y + h > MENU_SCREEN_H) {
|
||||
f->y = row_top + MENU_ROW_H - h;
|
||||
}
|
||||
if (f->y < 0) {
|
||||
f->y = 0;
|
||||
}
|
||||
if (f->y + h > MENU_SCREEN_H) {
|
||||
h = MENU_SCREEN_H - f->y;
|
||||
}
|
||||
f->h = h;
|
||||
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]);
|
||||
if (box == NULL) {
|
||||
return; /* LVGL 池耗尽:框未建,状态回滚 */
|
||||
}
|
||||
lv_obj_add_style(box, &s_style_flat, 0);
|
||||
lv_obj_add_style(box, &s_style_frame, 0);
|
||||
lv_obj_add_flag(box, LV_OBJ_FLAG_IGNORE_LAYOUT);
|
||||
lv_obj_set_pos(box, f->x, f->y);
|
||||
lv_obj_set_size(box, f->w, f->h);
|
||||
view_box_layout(box);
|
||||
s_frame_box[depth] = box;
|
||||
}
|
||||
|
||||
f->built = true;
|
||||
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) */
|
||||
}
|
||||
}
|
||||
|
||||
/* 选中高亮 + 滚动位 */
|
||||
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)
|
||||
{
|
||||
menu_frame_t *f = &s_frame[depth];
|
||||
|
||||
if (depth == 0u || !f->built) {
|
||||
return; /* 根框持久不可销毁 */
|
||||
}
|
||||
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->item_cnt = 0u;
|
||||
f->sel = 0u;
|
||||
f->x = 0;
|
||||
f->y = 0;
|
||||
f->w = 0;
|
||||
f->h = 0;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
f->sel = sel;
|
||||
view_apply_sel(depth, 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)
|
||||
{
|
||||
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)
|
||||
{
|
||||
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)
|
||||
{
|
||||
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)
|
||||
{
|
||||
return (depth < MENU_PAGE_MAX) ? s_cont[depth] : NULL;
|
||||
}
|
||||
|
||||
/*
|
||||
* 函数功能:应用主界面顶部"浅 V"装饰线段的公共样式
|
||||
* 入口参数:line - lv_line 对象;width - 线宽(px)
|
||||
* 返回值:无
|
||||
* 限定条件:line 已创建且已设点集
|
||||
* 函数说明:绝对定位于 (0,0)、忽略布局、白色实线、指定线宽
|
||||
*/
|
||||
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);
|
||||
}
|
||||
|
||||
int ui_menu_view_init(void)
|
||||
{
|
||||
lv_obj_t *scr = lv_screen_active();
|
||||
if (scr == NULL) {
|
||||
return -1;
|
||||
}
|
||||
view_styles_init();
|
||||
|
||||
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);
|
||||
|
||||
/* 屏幕根容器:铺满屏幕;子对象全部绝对定位(级联框须同屏叠加,不用 flex 堆叠) */
|
||||
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, 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);
|
||||
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);
|
||||
view_deco_line_apply(leg_r, MENU_DECO_LEG_W);
|
||||
view_deco_line_apply(s_root_deco, MENU_DECO_HORIZ_W);
|
||||
}
|
||||
|
||||
s_root_status = lv_label_create(s_cont[0]);
|
||||
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, "当前模式 : 无模式");
|
||||
|
||||
/* 根框框体:持久;宽 = 内容自适应(主界面 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;
|
||||
|
||||
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_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_size(s_frame_box[0], w, h);
|
||||
view_box_layout(s_frame_box[0]);
|
||||
|
||||
s_frame[0].built = true; /* 框体就绪;行对象由逻辑层 frame_build(0) 创建 */
|
||||
s_frame[0].x = MENU_ROOT_BOX_X;
|
||||
s_frame[0].y = MENU_ROOT_BOX_Y;
|
||||
s_frame[0].w = w;
|
||||
s_frame[0].h = h;
|
||||
|
||||
DBG_INFO("view init: root frame w=%d h=%d", (int)w, (int)h);
|
||||
return 0;
|
||||
}
|
||||
Reference in New Issue
Block a user