a5142c6d9d
- ui_content_page:描述符尾部追加 auto_ms/auto_seq 可选字段(缺省=0/NULL= 旧行为逐位不变);页打开期间框架持一只 lv_timer,仅当发布序号变过才整页 失效(零空转失效),关页即销;新 API ui_content_page_auto_poll - ui_page_ac.c:.auto_ms=300(≥200ms 发布节拍、避 400=2×200 锁相、300= 15×20ms LVGL 整刷周期;最坏屏上滞后 500ms;完整取值账目在代码注释) - simulator:rt 帧装配收口 sim_fill_rt_frame(n=0 冻结帧保 74 张黄金逐字节 一致,n≥1 确定性斜坡);新增 --rtlive 两态自检 + 窗口模式 200ms 节拍发布; check_content_pages.py 增 [rtlive] 零按键 A≠B 落屏断言 - docs/UI设计:供给说明新增 §5.5 自动刷新口径;内容页架构 §3.1/§四/§九 同步 (并修正 s_open 判定旧描述) - 台账:Keil 构建后按固定顺序回填(Code=256,588 / RW_IRAM1 +8B / UI +8B) 验证:模拟器 0错0警;菜单 8 状态 + 内容页 74 张 + rtlive(A≠B 30 字节)PASS (lead 独立复跑 RC=0);Keil 0错0警;mem-budget --gate / mem-numbers --scan 绿。
184 lines
12 KiB
C
184 lines
12 KiB
C
/*
|
||
* 模块名称:UI Content Page (内容页框架)
|
||
* 模块功能:菜单叶子节点进入的内容页(独立全屏页面)框架——
|
||
* 屏/容器按需建毁生命周期 + 焦点 + 切屏、按键分发(内置 ↑↓/ESC + key_cb 钩子)、
|
||
* 描述符驱动通用绘制(浅 V + 标题/副标题 + 行 + 选中带 + 页脚 + draw_cb);
|
||
* 不持任何页面数据:各页数据/几何/钩子由 ui_page_xxx 模块经 content_page_def_t
|
||
* 提供,框架按**描述符指针**开页;"哪个叶子进哪一页"的绑定在生成物
|
||
* ui_page_registry.c(真源 = 菜单树 ui_menu_tree.txt 的 #page= 注记),
|
||
* 框架既无页 ID 枚举也无节点→页手写 case(架构见 内容页架构.md);
|
||
* 进入时切屏并获焦、ESC 返回菜单屏并还原焦点;
|
||
* 不持有导航状态——由逻辑层 ui_menu.c 在 ENTER 叶子节点时驱动
|
||
* 适用平台:STM32F407ZGTx / PC 模拟器(两端共用)
|
||
* 作者:王建锋
|
||
* 创建日期:2026-09-09
|
||
* 修改记录:
|
||
* 2026-09-09 王建锋 创建:装置信息/交流量/直流量三页(参考图 10/20/21)
|
||
* 2026-09-09 王建锋 "浅 V"装饰几何提取至共享 ui_deco 模块(不再本地重复定义)
|
||
* 2026-09-09 王建锋 拆分为"框架 + 页面插件"模型(内容页架构.md):三页数据移入
|
||
* ui_page_xxx 模块;本文件只留描述类型与 API;描述符新增
|
||
* 4 个扩展钩子(refresh/count_cb/draw_cb/key_cb)
|
||
* 2026-09-09 王建锋 密码页(用户密钥):描述符新增 title_y/hide_deco/hide_footer/key_intercept
|
||
* 4 字段 + 新 API ui_content_page_invalidate;← 键可每页拦截
|
||
* (ESC 恒不可拦截)
|
||
* 2026-09-09 王建锋 权限模型:open() 增授予权限实参(ui_perm_t,逻辑层按
|
||
* 是否过密码页/所输密码决定);描述符新增 edit_capable(页面
|
||
* 是否支持编辑模式)+ 新 API ui_content_page_is_editable
|
||
* (页面钩子据以决定编辑行为:edit_capable 且修改权限)
|
||
* 2026-10-15 王建锋 UI 页面原型化:删 content_page_id_t 枚举与框架侧 s_defs[],
|
||
* open() 改收描述符指针;节点→页面绑定移交给生成物
|
||
* ui_page_registry.c(菜单树 #page= 注记为唯一真源),
|
||
* 逻辑层手写 menu_content_page_for() 随之删除;
|
||
* 描述符新增 id 字段(ASCII 页名,供日志/诊断定位是哪一页)
|
||
* 2026-11-13 DTU 项目组 实时页自动刷新:描述符新增 auto_ms/auto_seq 两字段,
|
||
* 新 API ui_content_page_auto_poll——框架在页面打开期间持
|
||
* 一只轮询定时器,发布序号变过才整页失效,数据没变零重绘;
|
||
* 页面模块不再各自造定时器(周期账见 ui_page_ac.c 的
|
||
* AC_AUTO_MS 注与 UI_数据供给设计说明.md §5.4)
|
||
*/
|
||
#ifndef UI_CONTENT_PAGE_H
|
||
#define UI_CONTENT_PAGE_H
|
||
|
||
#include <stdint.h>
|
||
#include <stdbool.h>
|
||
|
||
#include "lvgl.h"
|
||
|
||
/* 行数据:左列名 + 右列值(Flash 字面量,不 strdup) */
|
||
typedef struct {
|
||
const char *name;
|
||
const char *value;
|
||
} cp_row_t;
|
||
|
||
/*
|
||
* 页面描述符:一页 = 一个 Flash const 结构(身份 + 几何 + 数据源 + 可选钩子)
|
||
* 由各 ui_page_xxx 页面模块提供;"哪个叶子进哪一页"的绑定不在本头,也不在框架 .c,
|
||
* 而在生成物 ui_page_registry.c(唯一真源 = 菜单树 ui_menu_tree.txt 的 #page= 注记)。
|
||
* 框架按描述符**指针**开页,不再有页 ID 枚举与 s_defs[] 下标表——
|
||
* 少一处"枚举/表/节点下标三处必须对齐"的手工同步点。
|
||
*/
|
||
typedef struct {
|
||
const char *id; /* 页面模块名(ASCII,日志/诊断用;= ui_page_<id> 的 <id>) */
|
||
const char *title; /* 标题(浅 V 区居中) */
|
||
const char *subtitle; /* 副标题;NULL = 无(仅信息页有) */
|
||
int32_t subtitle_y; /* 副标题放置 y(仅 subtitle 非空时有效) */
|
||
int32_t title_y; /* 标题放置 y;0 = 框架默认(UI_TITLE_Y) */
|
||
uint8_t hide_deco; /* 1 = 不绘"浅 V"装饰(密码页跟随实机) */
|
||
uint8_t hide_footer; /* 1 = 不绘页脚页码(密码页跟随实机:无页脚) */
|
||
uint8_t selectable; /* 1 = 列表页(↑↓移选+分页);0 = 信息页(静态行) */
|
||
uint8_t key_intercept; /* 1 = LV_KEY_LEFT 先交 key_cb(true = 页已处理不关页);ESC 恒不可拦截 */
|
||
uint8_t edit_capable; /* 1 = 本页支持编辑模式(可编辑还须 UI_PERM_MODIFY 权限) */
|
||
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 row_count; /* 总行数(静态页;动态页用 count_cb) */
|
||
const cp_row_t *rows; /* 静态数据表(Flash);NULL = 动态页 */
|
||
uint8_t fixed_cur_page; /* 信息页页脚"第 X 页"固定显示值 */
|
||
uint8_t fixed_total_pages; /* 信息页页脚"共 Y 页"固定显示值 */
|
||
/* —— 扩展钩子(全可选;NULL = 纯内置行为;均运行在 uiTask 上下文)—— */
|
||
void (*init_cb)(void); /* 开页后调一次(页面模块重置运行态) */
|
||
void (*refresh)(uint16_t row, char *name, char *value); /* 动态行:逐行取数(名/值) */
|
||
uint16_t (*count_cb)(void); /* 动态行数(记录页) */
|
||
void (*draw_cb)(lv_layer_t *layer, const lv_area_t *clip); /* 表单/操作页:自定义内容 */
|
||
bool (*key_cb)(lv_key_t key); /* 页面自有键;true = 页已处理 */
|
||
/* —— 自动失效轮询(全可选;auto_ms=0 = 只在按键重绘,未订阅页行为逐位不变)——
|
||
* auto_ms>0:本页打开期间框架持一只 lv_timer(周期 auto_ms ms,uiTask 上下文随
|
||
* lv_timer_handler 走),每拍取发布序号(auto_seq(),NULL = ui_data_rt_seq()
|
||
* 即通道一),序号与上一拍不同才整页失效——取数门控仍是页面 refresh 里的
|
||
* seq 判断(UI_数据供给设计说明.md §5.4 的页面私有缓存约定原样有效)。
|
||
* 关页即销定时器;页面模块禁止自造定时器。周期取值账见 ui_page_ac.c 的
|
||
* AC_AUTO_MS 注释(发布者 rt 每 200ms 一帧,见同文 §5.2)。 */
|
||
uint16_t auto_ms; /* 轮询周期(ms);0 = 不启用自动刷新 */
|
||
uint32_t (*auto_seq)(void); /* 新鲜度序号源;NULL = ui_data_rt_seq() */
|
||
} content_page_def_t;
|
||
|
||
/*
|
||
* 函数功能:打开指定内容页(建屏 + 切屏 + 焦点置页面容器)
|
||
* 入口参数:desc - 页面描述符(取自 ui_page_registry_for / ui_page_registry_password)
|
||
* const content_page_def_t *
|
||
* perm - 本次进入授予的权限 ui_perm_t(UI_PERM_VIEW/MODIFY;
|
||
* 由逻辑层按是否过密码页/所输密码决定,页面经
|
||
* ui_content_page_is_editable 查询)
|
||
* 返回值:true - 成功;false - 失败(desc 为 NULL / 已有页面打开 / 对象创建失败)
|
||
* 限定条件:ui_menu_init() 已完成(默认输入组与菜单屏存在)
|
||
* 函数说明:1. 新建独立屏幕,全屏容器自绘全部内容(无子对象,池开销极小)
|
||
* 2. 容器入默认输入组并获焦——按键由本模块处理(↑↓移选/ESC返回/页面钩子)
|
||
* 3. 导航状态不压层:菜单 s_depth/s_sel 原样保留,ESC 返回即还原
|
||
*/
|
||
bool ui_content_page_open(const content_page_def_t *desc, uint8_t perm);
|
||
|
||
/*
|
||
* 函数功能:关闭当前内容页(焦点回菜单容器 + 切回菜单屏 + 销毁页面)
|
||
* 入口参数:无
|
||
* 返回值:无
|
||
* 限定条件:ui_content_page_is_open() 为 true
|
||
* 函数说明:先移焦再删屏,防输入组焦点悬空;LVGL 池内存随即回落
|
||
*/
|
||
void ui_content_page_close(void);
|
||
|
||
/*
|
||
* 函数功能:失效当前打开的页面容器(触发整页重绘)
|
||
* 入口参数:无
|
||
* 返回值:无
|
||
* 限定条件:有页面打开时生效
|
||
* 函数说明:供页面模块(表单/操作页)自身状态变化后调用;无页打开时空操作
|
||
*/
|
||
void ui_content_page_invalidate(void);
|
||
|
||
/*
|
||
* 函数功能:查询是否有内容页打开
|
||
* 入口参数:无
|
||
* 返回值:true - 有页面打开;false - 无
|
||
* 限定条件:无
|
||
* 函数说明:供逻辑层/调试判断当前处于菜单屏还是内容页
|
||
*/
|
||
bool ui_content_page_is_open(void);
|
||
|
||
/*
|
||
* 函数功能:查询当前打开页面的描述符(身份查询/调试消费,Issue #69)
|
||
* 入口参数:无
|
||
* 返回值:当前页面描述符指针(Flash 常量,只读);无页打开 = NULL
|
||
* 限定条件:与框架同上下文(uiTask)读取才有一致性语义
|
||
* 函数说明:消费方(推流 STATUS)据 desc->id 回答"开着哪一页";零副作用、不打日志
|
||
*/
|
||
const content_page_def_t *ui_content_page_current_desc(void);
|
||
|
||
/*
|
||
* 函数功能:查询当前打开的页面是否可编辑
|
||
* 入口参数:无
|
||
* 返回值:true - 页面支持编辑模式且被授予修改权限;false - 其余(无页打开/
|
||
* 页面不支持编辑/仅只读权限)
|
||
* 限定条件:无
|
||
* 函数说明:供页面钩子(key_cb/draw_cb)决定编辑行为;权限由 ui_content_page_open
|
||
* 的 perm 实参授予(逻辑层按密码页结果/无密码默认只读传入)
|
||
*/
|
||
bool ui_content_page_is_editable(void);
|
||
|
||
/*
|
||
* 函数功能:查询当前列表页的选中行(全局下标,跨页连续)
|
||
* 入口参数:无
|
||
* 返回值:选中行下标;无页打开/非列表页 = 0 uint16_t
|
||
* 限定条件:无
|
||
* 函数说明:供页面钩子(key_cb/draw_cb)感知当前选中位置(编辑模式以
|
||
* 选中行作用、绘制钩子定位选中行带)
|
||
*/
|
||
uint16_t ui_content_page_selected_row(void);
|
||
|
||
/*
|
||
* 函数功能:自动刷新轮询一拍(生产由框架定时器回调调用;无头模拟器 --rtlive 自检
|
||
* 没有定时器节拍、直调本函数顶替——两个入口共用同一份判定,规则不会两套)
|
||
* 入口参数:无
|
||
* 返回值:无
|
||
* 限定条件:uiTask 上下文(lv_timer_handler 内或其等价串行点)
|
||
* 函数说明:1. 无页打开 / 本页 auto_ms=0:空操作;
|
||
* 2. 序号与上一拍相同:不失效——数据没变的一次整屏重绘是纯支出(屏带宽/功耗);
|
||
* 3. 序号变了:记下新基准并整页失效,真正取数仍走页面 refresh 的 seq 门控
|
||
*/
|
||
void ui_content_page_auto_poll(void);
|
||
|
||
#endif /* UI_CONTENT_PAGE_H */
|