将菜单的架构改成 MVP,并且进一步优化视图层和模型层的逻辑
This commit is contained in:
@@ -30,14 +30,13 @@ add_executable(DTU-HMI
|
||||
src/thread_utils.c
|
||||
src/remoteDisplay.c
|
||||
src/Drv/menu/app/menu.c
|
||||
src/Drv/menu/model/display.c
|
||||
src/Drv/menu/model/menu_model.c
|
||||
src/Drv/menu/view/menu_view.c
|
||||
src/Drv/menu/presenter/menu_presenter.c
|
||||
src/Drv/menu/model/menu_tree_builder.c
|
||||
src/Drv/menu/view/menu_layout.c
|
||||
src/Drv/menu/presenter/menu_navigator.c
|
||||
src/Drv/menu/view/menu_renderer_lcd.c
|
||||
src/Drv/pages/page_manager.c
|
||||
src/Drv/pages/global/global_state.c
|
||||
src/Drv/pages/global/renderer_lcd.c
|
||||
src/Drv/pages/menu/page.c
|
||||
src/Drv/pages/menu/model.c
|
||||
src/Drv/pages/menu/presenter.c
|
||||
src/Drv/pages/menu/view.c
|
||||
src/Drv/lcd/lcd.c
|
||||
src/Drv/lcd/lcd_draw.c
|
||||
src/Drv/lcd/lcd_text.c
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
---
|
||||
562
docs/系统架构设计文档.md
562
docs/系统架构设计文档.md
@@ -77,13 +77,535 @@ columns 1
|
||||
- 处理按键驱动的菜单状态迁移与路径重建
|
||||
- 执行菜单显示坐标计算与多级菜单渲染调度
|
||||
|
||||
### 4.3 显示层
|
||||
### 4.3 多页面管理层设计
|
||||
**设计思想:**
|
||||
基于经典的 MVP 范式,彻底解耦**数据、视图、控制逻辑**,解决复杂 UI 与业务逻辑的协同维护问题。
|
||||
- **Model(模型层)**:纯业务数据与状态管理,负责系统参数、设备状态、采集数据的读写、校验、存储,与 UI 完全无关。
|
||||
- **View(视图层)**:纯渲染显示,仅根据 Model 的数据绘制菜单界面、焦点高亮、弹窗、动画,不处理任何业务逻辑。
|
||||
- **Presenter(控制层)**:核心调度中枢,接收输入事件、更新 Model 数据、控制 View 刷新、处理菜单跳转逻辑,是 Model 与 View 的唯一桥梁。
|
||||
目录结构:
|
||||
`src/Drv/pages`: 这个文件夹下面放不同的页面
|
||||
`src/Drv/pages/global`: 全局状态管理器,页面之间交互的中间件
|
||||
`src/Drv/pages/global/renderer_lcd.c`: 这个文件是抽象底层的lcd,给所有页面提供一个统一的调用接口。
|
||||
`src/Drv/pages/global/renderer_lcd.h`: 这个文件是抽象底层的lcd,给所有页面提供一个统一的调用接口。
|
||||
`src/Drv/pages/menu`: 项目的菜单逻辑
|
||||
`src/Drv/pages/menu/model`: 菜单的模型层
|
||||
`src/Drv/pages/menu/view`: 菜单的视图层
|
||||
`src/Drv/pages/menu/Presenter`: 菜单的控制层
|
||||
`src/Drv/pages/menu/page.c`: 菜单的页面逻辑
|
||||
`src/Drv/pages/menu/page.h`: 菜单的页面逻辑
|
||||
|
||||
- 文件:`src/Drv/lcd/lcd.c`、`src/Drv/lcd/lcd_draw.c`、`src/Drv/lcd/lcd_text.c`、`src/Drv/lcd/text_codec.c`、`src/Drv/lcd/ascii.c`、`src/Drv/menu/model/display.c`
|
||||
- 职责:
|
||||
- 管理 LCD 显存 `g_tCVsr` 与像素绘制
|
||||
- 提供 ASCII/UTF-8 字符显示能力
|
||||
- 提供静态菜单模型定义 `g_tMenuModelTab`
|
||||
`src/Drv/pages/...`: 其他页面
|
||||
|
||||
整个多页面管理系统分为 5 个核心模块,从上到下形成完整的调度闭环,与底层 MVP 架构无缝衔接
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────────┐
|
||||
│ 全局状态管理器(Global Model) │
|
||||
│ (跨页面共享数据、持久化配置、系统全局状态、观察者通知) │
|
||||
├─────────────────────────────────────────────────────────────┤
|
||||
│ 页面管理器核心(Page Manager) │
|
||||
│ ┌─────────────────────────────────────────────────────────┐│
|
||||
│ │ 页面栈(Page Stack 静态数组) ││
|
||||
│ │ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ ││
|
||||
│ │ │ 栈顶页面 │ │ 后台页面1 │ │ 后台页面2 │ ... ││
|
||||
│ │ │(前台显示) │ │(暂停状态) │ │(暂停状态) │ ││
|
||||
│ │ └─────────────┘ └─────────────┘ └─────────────┘ ││
|
||||
│ └─────────────────────────────────────────────────────────┘│
|
||||
│ 生命周期调度器 | 事件分发器 | 页面跳转核心接口 │
|
||||
├─────────────────────────────────────────────────────────────┤
|
||||
│ 标准化页面抽象(Page 接口层) │
|
||||
│ 每个页面独立封装:生命周期钩子 + 专属MVP三元组 │
|
||||
│ ┌─────────────────────────────────────────────────────────┐│
|
||||
│ │ Page 基础属性:页面ID、缓存标志、创建状态 ││
|
||||
│ │ 生命周期钩子:on_create/on_enter/on_exit/on_destroy ││
|
||||
│ │ 运行入口:on_event(事件处理)、on_loop(主循环) ││
|
||||
│ │ 专属MVP三元组:Model(私有) → Presenter → View ││
|
||||
│ └─────────────────────────────────────────────────────────┘│
|
||||
├─────────────────────────────────────────────────────────────┤
|
||||
│ 底层支撑层(与MVP架构无缝衔接) │
|
||||
│ 显示HAL | 输入HAL | 基础图形库 | 硬件驱动 │
|
||||
└─────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
#### 4.3.1 标准化页面抽象(Page 接口层)
|
||||
这是整个系统的基础单元,**每个独立业务页面对应一个 Page 实例,每个 Page 内部封装一套完全独立的 MVP 三元组**,对外仅暴露标准接口,页面管理器仅通过标准接口与页面交互,完全不关心页面内部的 MVP 实现,实现页面间的彻底解耦。
|
||||
```c
|
||||
// 编译期静态配置(根据MCU资源调整)
|
||||
#define MAX_PAGE_STACK_DEPTH 5 // 最大页面栈深度,防止栈溢出
|
||||
#define MAX_PAGE_COUNT 8 // 系统支持的最大页面总数
|
||||
#define EVENT_QUEUE_LENGTH 8 // 输入事件队列长度
|
||||
|
||||
// 页面唯一ID枚举(所有页面必须在此定义,用于页面查找、防重复入栈)
|
||||
typedef enum {
|
||||
PAGE_ID_NONE = 0,
|
||||
PAGE_ID_HOME, // 首页(主页面,栈底常驻)
|
||||
PAGE_ID_SETTINGS, // 设置页面
|
||||
PAGE_ID_TEMP_DETAIL, // 温度详情页面
|
||||
PAGE_ID_ABOUT, // 关于页面
|
||||
PAGE_ID_MAX
|
||||
} page_id_t;
|
||||
|
||||
// 事件处理结果枚举
|
||||
typedef enum {
|
||||
EVENT_UNHANDLED = 0, // 事件未被消费,继续分发
|
||||
EVENT_HANDLED = 1 // 事件已被消费,终止分发
|
||||
} event_result_t;
|
||||
|
||||
// 页面结构体前置声明
|
||||
typedef struct page_t page_t;
|
||||
|
||||
```
|
||||
#### 4.3.2 页面标准接口与生命周期定义
|
||||
嵌入式场景下,生命周期钩子的设计必须严格对应资源的申请 / 释放时机,避免 RAM 浪费和低功耗异常,每个钩子的执行时机、职责完全固定,禁止越权操作。
|
||||
```c
|
||||
// 页面标准接口结构体
|
||||
struct page_t {
|
||||
// ===================== 页面基础属性 =====================
|
||||
page_id_t page_id; // 页面唯一ID,不可重复
|
||||
bool is_cached; // 缓存标志:true=出栈时不销毁,仅暂停;false=出栈时彻底销毁
|
||||
bool is_created; // 内部状态标志:页面是否已执行on_create,外部只读
|
||||
|
||||
// ===================== 生命周期钩子函数 =====================
|
||||
/**
|
||||
* @brief 页面第一次创建时执行,仅执行1次
|
||||
* @note 职责:MVP三元组初始化、静态资源申请(字体/图标缓冲区)、回调绑定
|
||||
* @note 禁止:耗时操作、屏幕绘制、动态内存申请
|
||||
*/
|
||||
void (*on_create)(page_t *page);
|
||||
|
||||
/**
|
||||
* @brief 页面进入前台(成为栈顶)时执行,每次进入都执行
|
||||
* @note 职责:从Model拉取最新数据、全量UI绘制、开启定时器/传感器、注册事件回调
|
||||
* @note 禁止:资源销毁、MVP初始化操作
|
||||
*/
|
||||
void (*on_enter)(page_t *page);
|
||||
|
||||
/**
|
||||
* @brief 页面离开前台(被覆盖/出栈)时执行,每次离开都执行
|
||||
* @note 职责:关闭定时器/传感器、保存临时数据、注销事件回调、低功耗准备
|
||||
* @note 禁止:屏幕绘制、MVP初始化、耗时操作
|
||||
*/
|
||||
void (*on_exit)(page_t *page);
|
||||
|
||||
/**
|
||||
* @brief 页面彻底销毁时执行,仅执行1次
|
||||
* @note 职责:释放所有申请的资源、MVP实例销毁、回调注销
|
||||
* @note 仅非缓存页面出栈时会触发,缓存页面不会触发
|
||||
*/
|
||||
void (*on_destroy)(page_t *page);
|
||||
|
||||
// ===================== 运行时入口 =====================
|
||||
/**
|
||||
* @brief 事件处理入口,页面管理器仅将事件分发给栈顶页面的此函数
|
||||
* @note 职责:将事件转发给内部View,由View判断命中并转发给Presenter
|
||||
* @return 事件是否被消费
|
||||
*/
|
||||
event_result_t (*on_event)(page_t *page, input_event_t *event);
|
||||
|
||||
/**
|
||||
* @brief 主循环入口,页面管理器仅调用栈顶页面的此函数
|
||||
* @note 职责:执行页面Presenter的主循环逻辑、UI动画刷新、状态机轮询
|
||||
* @note 禁止:阻塞操作、耗时计算
|
||||
*/
|
||||
void (*on_loop)(page_t *page);
|
||||
|
||||
// ===================== 页面专属MVP三元组 =====================
|
||||
// 每个页面独立拥有,与其他页面完全隔离,页面管理器不直接访问
|
||||
void *presenter; // 页面对应的Presenter实例指针
|
||||
void *view; // 页面对应的View实例指针
|
||||
void *model; // 页面对应的私有Model实例(仅当前页面使用)
|
||||
};
|
||||
```
|
||||
#### 4.3.3 页面 MVP 三元组的绑定规则
|
||||
- **完全隔离**:每个页面的 Model 仅管理当前页面的私有数据,禁止跨页面访问;View 仅负责当前页面的 UI 绘制;Presenter 仅调度当前页面的业务逻辑
|
||||
- **绑定时机**:MVP 三元组的初始化、绑定,必须在页面的`on_create`钩子中完成,保证页面创建时 MVP 已就绪
|
||||
- **销毁时机**:MVP 实例的资源释放,必须在页面的`on_destroy`钩子中完成,避免资源泄漏
|
||||
- **跨页面交互**:禁止页面 A 的 Presenter 直接调用页面 B 的任何接口,所有跨页面数据同步必须通过全局状态管理器完成
|
||||
#### 4.3.4 栈式页面管理器(核心调度中枢)
|
||||
这是整个系统的核心,通过静态数组实现的栈结构管理所有页面,负责页面跳转的核心逻辑、生命周期的精准调度、输入事件的统一分发、主循环的统一调度。
|
||||
**页面管理器结构体定义**
|
||||
```c
|
||||
// 页面管理器结构体
|
||||
typedef struct {
|
||||
page_t *page_stack[MAX_PAGE_STACK_DEPTH]; // 页面栈,静态数组实现
|
||||
int8_t stack_top; // 栈顶指针,初始值为-1(空栈)
|
||||
} page_manager_t;
|
||||
|
||||
// 全局页面管理器单例(整个系统仅一个实例)
|
||||
extern page_manager_t g_page_manager;
|
||||
```
|
||||
**基础接口:初始化与栈状态查询**
|
||||
```c
|
||||
// 全局页面管理器实例
|
||||
page_manager_t g_page_manager;
|
||||
|
||||
/**
|
||||
* @brief 页面管理器初始化,系统启动时调用1次
|
||||
*/
|
||||
void page_manager_init(page_manager_t *manager) {
|
||||
memset(manager, 0, sizeof(page_manager_t));
|
||||
manager->stack_top = -1; // 初始化为空栈
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 获取当前栈顶页面(前台显示页面)
|
||||
* @return 栈顶页面指针,空栈返回NULL
|
||||
*/
|
||||
page_t* page_manager_get_top(page_manager_t *manager) {
|
||||
if (manager->stack_top < 0) {
|
||||
return NULL;
|
||||
}
|
||||
return manager->page_stack[manager->stack_top];
|
||||
}
|
||||
```
|
||||
**页面入栈(Push,页面跳转)**
|
||||
- 入参校验:检查栈是否已满、新页面是否合法、是否重复入栈
|
||||
- 触发旧栈顶页面的`on_exit`:当前页面被覆盖,执行暂停逻辑
|
||||
- 新页面入栈,栈顶指针 + 1
|
||||
- 若新页面未创建,触发`on_create`:执行 MVP 初始化,仅执行 1 次
|
||||
- 触发新页面的`on_enter`:新页面进入前台,执行 UI 绘制与业务启动
|
||||
- 新页面成为前台页面,接收事件与主循环调度
|
||||
```c
|
||||
/**
|
||||
* @brief 页面入栈(跳转到新页面)
|
||||
* @param manager 页面管理器实例
|
||||
* @param new_page 待入栈的新页面实例
|
||||
* @return 0=成功,负数=错误码
|
||||
*/
|
||||
int page_manager_push(page_manager_t *manager, page_t *new_page) {
|
||||
// 入参合法性校验
|
||||
if (manager == NULL || new_page == NULL) {
|
||||
return -1;
|
||||
}
|
||||
// 栈满校验,防止栈溢出
|
||||
if (manager->stack_top >= MAX_PAGE_STACK_DEPTH - 1) {
|
||||
return -2;
|
||||
}
|
||||
// 防重复入栈:当前栈顶已是该页面,无需重复操作
|
||||
page_t *current_top = page_manager_get_top(manager);
|
||||
if (current_top != NULL && current_top->page_id == new_page->page_id) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
// ===================== 生命周期执行 =====================
|
||||
// 1. 触发当前栈顶页面的on_exit(离开前台)
|
||||
if (current_top != NULL && current_top->on_exit != NULL) {
|
||||
current_top->on_exit(current_top);
|
||||
}
|
||||
|
||||
// 2. 新页面入栈
|
||||
manager->stack_top++;
|
||||
manager->page_stack[manager->stack_top] = new_page;
|
||||
|
||||
// 3. 页面首次创建,触发on_create(仅执行1次)
|
||||
if (!new_page->is_created) {
|
||||
if (new_page->on_create != NULL) {
|
||||
new_page->on_create(new_page);
|
||||
}
|
||||
new_page->is_created = true;
|
||||
}
|
||||
|
||||
// 4. 触发新页面的on_enter(进入前台)
|
||||
if (new_page->on_enter != NULL) {
|
||||
new_page->on_enter(new_page);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
```
|
||||
**页面出栈(Pop,页面返回)**
|
||||
- 入参校验:检查栈是否为空、是否为栈底首页(禁止出栈)
|
||||
- 触发当前栈顶页面的`on_exit`:当前页面离开前台,执行退出准备
|
||||
- 若当前页面为非缓存页面,触发`on_destroy`:彻底销毁页面,释放资源
|
||||
- 栈顶指针 - 1,上一个页面恢复为栈顶
|
||||
- 触发新栈顶页面的`on_enter`:页面恢复前台,执行数据刷新与 UI 重绘
|
||||
- 新栈顶页面成为前台页面,接收事件与主循环调度
|
||||
```c
|
||||
/**
|
||||
* @brief 页面出栈(返回上一页)
|
||||
* @param manager 页面管理器实例
|
||||
* @return 0=成功,负数=错误码
|
||||
*/
|
||||
int page_manager_pop(page_manager_t *manager) {
|
||||
// 入参校验
|
||||
if (manager == NULL) {
|
||||
return -1;
|
||||
}
|
||||
// 禁止空栈出栈、禁止栈底首页出栈(保证首页常驻)
|
||||
if (manager->stack_top <= 0) {
|
||||
return -2;
|
||||
}
|
||||
|
||||
// ===================== 生命周期执行 =====================
|
||||
// 1. 获取当前栈顶页面(待出栈页面)
|
||||
page_t *current_page = manager->page_stack[manager->stack_top];
|
||||
|
||||
// 2. 触发当前页面的on_exit(离开前台)
|
||||
if (current_page->on_exit != NULL) {
|
||||
current_page->on_exit(current_page);
|
||||
}
|
||||
|
||||
// 3. 非缓存页面:触发on_destroy,彻底销毁,释放资源
|
||||
if (!current_page->is_cached) {
|
||||
if (current_page->on_destroy != NULL) {
|
||||
current_page->on_destroy(current_page);
|
||||
}
|
||||
current_page->is_created = false;
|
||||
}
|
||||
|
||||
// 4. 栈顶指针减1,页面出栈
|
||||
manager->stack_top--;
|
||||
|
||||
// 5. 触发新栈顶页面的on_enter(恢复前台)
|
||||
page_t *new_top = manager->page_stack[manager->stack_top];
|
||||
if (new_top->on_enter != NULL) {
|
||||
new_top->on_enter(new_top);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
```
|
||||
|
||||
**统一事件分发机制**
|
||||
嵌入式多页面 GUI 的核心痛点之一是事件误响应,必须保证**仅当前前台页面(栈顶)可接收输入事件**,后台页面完全无法接收事件,避免出现「设置页面按键触发首页逻辑」的 bug
|
||||
- 底层 Input HAL 在中断中采集输入事件(按键、触摸、编码器),投递到全局静态环形事件队列
|
||||
- 页面管理器在主循环中从事件队列取出事件
|
||||
- 获取当前栈顶前台页面,将事件分发给页面的`on_event`入口
|
||||
- 页面将事件转发给内部 View,View 判断事件命中,转发给 Presenter 处理
|
||||
- 若页面返回`EVENT_UNHANDLED`(事件未被消费),则执行全局事件处理(例如全局返回键、电源键
|
||||
```c
|
||||
// 全局事件处理(示例:全局返回键)
|
||||
static void global_event_handle(input_event_t *event) {
|
||||
// 全局返回键:无论哪个页面,按返回键都执行出栈
|
||||
if (event->type == EVENT_TYPE_KEY_DOWN && event->key_id == KEY_ID_BACK) {
|
||||
page_manager_pop(&g_page_manager);
|
||||
}
|
||||
}
|
||||
|
||||
// 页面管理器事件分发逻辑(集成在主循环中)
|
||||
static void page_manager_dispatch_event(page_manager_t *manager) {
|
||||
input_event_t event;
|
||||
// 从事件队列取出事件
|
||||
if (!input_event_get(&event)) {
|
||||
return;
|
||||
}
|
||||
// 获取当前栈顶页面
|
||||
page_t *current_page = page_manager_get_top(manager);
|
||||
if (current_page == NULL) {
|
||||
return;
|
||||
}
|
||||
// 分发给当前页面
|
||||
event_result_t result = EVENT_UNHANDLED;
|
||||
if (current_page->on_event != NULL) {
|
||||
result = current_page->on_event(current_page, &event);
|
||||
}
|
||||
// 页面未消费,执行全局事件处理
|
||||
if (result == EVENT_UNHANDLED) {
|
||||
global_event_handle(&event);
|
||||
}
|
||||
}
|
||||
```
|
||||
#### 4.3.4 全局状态管理器(跨页面数据同步)
|
||||
多页面场景下,必然存在跨页面数据共享的需求(例如设置页修改温度单位,首页、详情页都要同步更新),如果直接让页面间互相调用,会彻底破坏 MVP 的解耦原则,导致代码耦合混乱。
|
||||
|
||||
全局状态管理器的核心设计思路是:**实现一个全局单例的 Model(GlobalModel),作为跨页面数据的唯一可信源,通过观察者模式实现数据变化的同步通知,完全不破坏各页面 MVP 的独立性**
|
||||
#### 4.3.5 完整页面实现示例
|
||||
以下是一个完整的页面实现,包含生命周期钩子、MVP 三元组绑定、页面跳转逻辑,可直接落地使用。
|
||||
**设置页面 MVP 定义**
|
||||
```c
|
||||
// ===================== 设置页面Model =====================
|
||||
typedef struct {
|
||||
float temp_offset; // 温度校准偏移
|
||||
uint8_t temp_unit; // 温度单位
|
||||
bool backlight_auto; // 自动背光开关
|
||||
// 数据变化回调
|
||||
void (*on_data_change)(void *context);
|
||||
void *context;
|
||||
} settings_model_t;
|
||||
|
||||
// ===================== 设置页面View接口 =====================
|
||||
typedef struct {
|
||||
void (*update_temp_offset)(void *view, float offset);
|
||||
void (*update_temp_unit)(void *view, uint8_t unit);
|
||||
void (*update_backlight_state)(void *view, bool auto_on);
|
||||
void (*draw)(void *view);
|
||||
} settings_view_interface_t;
|
||||
|
||||
// ===================== 设置页面Presenter =====================
|
||||
typedef struct {
|
||||
page_t *page;
|
||||
settings_model_t *model;
|
||||
settings_view_interface_t *view;
|
||||
} settings_presenter_t;
|
||||
```
|
||||
**设置页面实例与生命周期实现**
|
||||
```c
|
||||
// 静态页面实例与MVP实例(静态内存,无动态分配)
|
||||
static page_t settings_page;
|
||||
static settings_model_t settings_model;
|
||||
static settings_view_interface_t settings_view;
|
||||
static settings_presenter_t settings_presenter;
|
||||
|
||||
// ===================== 生命周期钩子实现 =====================
|
||||
// 页面创建:MVP初始化,仅执行1次
|
||||
static void settings_page_on_create(page_t *page) {
|
||||
// 1. 初始化Model
|
||||
memset(&settings_model, 0, sizeof(settings_model_t));
|
||||
// 从Flash读取持久化配置
|
||||
settings_model.temp_unit = global_model_get_temp_unit();
|
||||
settings_model.temp_offset = flash_read_temp_offset();
|
||||
settings_model.backlight_auto = flash_read_backlight_auto();
|
||||
// 绑定Model回调
|
||||
settings_model.context = &settings_presenter;
|
||||
settings_model.on_data_change = settings_presenter_on_model_change;
|
||||
|
||||
// 2. 初始化View接口
|
||||
memset(&settings_view, 0, sizeof(settings_view_interface_t));
|
||||
settings_view.update_temp_offset = settings_view_update_temp_offset;
|
||||
settings_view.update_temp_unit = settings_view_update_temp_unit;
|
||||
settings_view.update_backlight_state = settings_view_update_backlight_state;
|
||||
settings_view.draw = settings_view_draw;
|
||||
|
||||
// 3. 初始化Presenter,绑定MVP
|
||||
memset(&settings_presenter, 0, sizeof(settings_presenter_t));
|
||||
settings_presenter.page = page;
|
||||
settings_presenter.model = &settings_model;
|
||||
settings_presenter.view = &settings_view;
|
||||
|
||||
// 4. 绑定MVP到页面
|
||||
page->model = &settings_model;
|
||||
page->view = &settings_view;
|
||||
page->presenter = &settings_presenter;
|
||||
}
|
||||
|
||||
// 页面进入前台:刷新UI,开启定时器
|
||||
static void settings_page_on_enter(page_t *page) {
|
||||
settings_presenter_t *presenter = (settings_presenter_t *)page->presenter;
|
||||
// 从Model拉取数据,刷新UI
|
||||
presenter->view->update_temp_offset(presenter->view, presenter->model->temp_offset);
|
||||
presenter->view->update_temp_unit(presenter->view, presenter->model->temp_unit);
|
||||
presenter->view->update_backlight_state(presenter->view, presenter->model->backlight_auto);
|
||||
// 全量绘制页面
|
||||
presenter->view->draw(presenter->view);
|
||||
// 开启背光定时器
|
||||
backlight_timer_start();
|
||||
}
|
||||
|
||||
// 页面离开前台:保存数据,关闭定时器
|
||||
static void settings_page_on_exit(page_t *page) {
|
||||
settings_model_t *model = (settings_model_t *)page->model;
|
||||
// 关闭背光定时器
|
||||
backlight_timer_stop();
|
||||
// 保存配置到Flash
|
||||
flash_write_temp_offset(model->temp_offset);
|
||||
flash_write_backlight_auto(model->backlight_auto);
|
||||
// 同步全局状态
|
||||
global_model_set_temp_unit(model->temp_unit);
|
||||
}
|
||||
|
||||
// 页面销毁:释放资源
|
||||
static void settings_page_on_destroy(page_t *page) {
|
||||
memset(page->model, 0, sizeof(settings_model_t));
|
||||
memset(page->presenter, 0, sizeof(settings_presenter_t));
|
||||
page->model = NULL;
|
||||
page->view = NULL;
|
||||
page->presenter = NULL;
|
||||
}
|
||||
|
||||
// 事件处理入口
|
||||
static event_result_t settings_page_on_event(page_t *page, input_event_t *event) {
|
||||
settings_view_interface_t *view = (settings_view_interface_t *)page->view;
|
||||
// 转发事件给View处理
|
||||
return settings_view_handle_event(view, event);
|
||||
}
|
||||
|
||||
// 主循环入口
|
||||
static void settings_page_on_loop(page_t *page) {
|
||||
settings_presenter_t *presenter = (settings_presenter_t *)page->presenter;
|
||||
// 执行Presenter主循环逻辑(长按检测、动画刷新等)
|
||||
settings_presenter_loop(presenter);
|
||||
}
|
||||
|
||||
// ===================== 页面实例获取接口 =====================
|
||||
page_t* settings_page_get_instance(void) {
|
||||
memset(&settings_page, 0, sizeof(page_t));
|
||||
settings_page.page_id = PAGE_ID_SETTINGS;
|
||||
settings_page.is_cached = true; // 缓存页面,出栈不销毁
|
||||
// 绑定生命周期钩子
|
||||
settings_page.on_create = settings_page_on_create;
|
||||
settings_page.on_enter = settings_page_on_enter;
|
||||
settings_page.on_exit = settings_page_on_exit;
|
||||
settings_page.on_destroy = settings_page_on_destroy;
|
||||
settings_page.on_event = settings_page_on_event;
|
||||
settings_page.on_loop = settings_page_on_loop;
|
||||
return &settings_page;
|
||||
}
|
||||
```
|
||||
**页面跳转调用示例**
|
||||
```c
|
||||
// 首页点击设置按钮,跳转到设置页面
|
||||
void home_presenter_on_settings_btn_click(home_presenter_t *presenter) {
|
||||
page_t *settings_page = settings_page_get_instance();
|
||||
page_manager_push(&g_page_manager, settings_page);
|
||||
}
|
||||
|
||||
// 设置页面点击返回按钮,返回首页
|
||||
event_result_t settings_view_handle_event(settings_view_interface_t *view, input_event_t *event) {
|
||||
if (event->type == EVENT_TYPE_TOUCH_DOWN) {
|
||||
// 判断返回按钮点击
|
||||
if (is_point_in_rect(event->x, event->y, BACK_BTN_X, BACK_BTN_Y, BACK_BTN_W, BACK_BTN_H)) {
|
||||
// 调用出栈接口,返回上一页
|
||||
page_manager_pop(&g_page_manager);
|
||||
return EVENT_HANDLED;
|
||||
}
|
||||
}
|
||||
return EVENT_UNHANDLED;
|
||||
}
|
||||
```
|
||||
|
||||
#### 4.3.6 当前项目重构落地规划(已执行)
|
||||
为解决 `MenuProc_See_AppInfo`、`MenuProc_See_YC` 这类“页面入口被函数化”的扩展瓶颈,本项目按以下路径重构,并要求每个模块均可独立测试:
|
||||
|
||||
1. **目录与职责重构(已完成)**
|
||||
- 新增 `src/Drv/pages` 作为多页面管理根目录;
|
||||
- 新增 `src/Drv/pages/global`,统一承载跨页面能力(`global_state`、`renderer_lcd`);
|
||||
- 新增 `src/Drv/pages/menu/page.c`,将现有菜单 MVP 封装成标准页面;
|
||||
- 新增示例业务页面 `src/Drv/pages/app_info`、`src/Drv/pages/yc`,用于验证页面跳转链路与生命周期。
|
||||
|
||||
2. **核心调度中枢(已完成)**
|
||||
- 新增 `page.h` 定义标准页面抽象(ID、缓存策略、生命周期钩子、事件入口、循环入口、私有 MVP 指针);
|
||||
- 新增 `page_manager.c`,实现:
|
||||
- 静态页面注册(防重复、上限保护);
|
||||
- 栈式 `push/pop` 生命周期调度;
|
||||
- 栈顶独占事件分发;
|
||||
- 全局返回键兜底(页面未消费时触发 `pop`);
|
||||
- 栈深度查询接口(用于诊断与测试)。
|
||||
|
||||
3. **菜单页面化改造(已完成)**
|
||||
- `MenuApp_Init` 改为:
|
||||
- 初始化全局状态;
|
||||
- 初始化并激活 `PageManager`;
|
||||
- 注册 `Menu/AppInfo/YC` 页面;
|
||||
- 默认入栈 `PAGE_ID_MENU`;
|
||||
- `MenuApp_HandleInput` 改为统一输入事件分发;
|
||||
- `MenuApp_Render` 改为调度当前栈顶页面 `on_loop`;
|
||||
- `MenuProc_See_AppInfo`、`MenuProc_See_YC` 改为页面跳转入口(非业务实现函数);
|
||||
- `MenuProc_See_YC` 跳转前通过 `GlobalModel` 同步当前菜单参数,实现跨页面上下文传递。
|
||||
|
||||
4. **可靠性约束(已完成)**
|
||||
- 全部页面实例采用静态内存,避免运行期动态分配;
|
||||
- 页面栈深度与页面注册数均有编译期上限;
|
||||
- 生命周期调用顺序固定:`exit -> (destroy) -> enter`;
|
||||
- 禁止后台页面收事件,仅栈顶可处理输入。
|
||||
|
||||
5. **单元可测性设计(目标 > 80%,已落地测试入口)**
|
||||
- 新增 `test_p1_page_manager.c`:覆盖页面注册/入栈/出栈/全局返回事件/循环调度;
|
||||
- 新增 `test_p1_menu_page_actions.c`:覆盖菜单页跳转 `AppInfo/YC` 与返回链路;
|
||||
- 保留并复用原 `menu tree/layout/navigator/runtime startup` 测试,保障回归稳定。
|
||||
|
||||
6. **后续扩展规范(作为后续页面开发模板)**
|
||||
- 新页面必须以 `src/Drv/pages/<page_name>/page.c` 实现标准生命周期;
|
||||
- 页面间禁止直接互调,只允许通过 `GlobalModel` 同步共享状态;
|
||||
- 菜单项 `pfnWinProc` 仅作为“页面路由入口”,不再承载页面业务实现。
|
||||
|
||||
### 4.4 输入层
|
||||
|
||||
@@ -111,9 +633,9 @@ columns 1
|
||||
|
||||
### 5.1 菜单模型与菜单树
|
||||
|
||||
- 静态菜单模型:`tagMenuModel`(定义于 `src/Drv/menu/model/display.h`,数据在 `src/Drv/menu/model/display.c`)
|
||||
- 运行时菜单项:`tagMenuItem`(定义于 `src/Drv/menu/common/menu_item_types.h`,实例由 `src/Drv/menu/app/menu.c` 管理)
|
||||
- 运行时控制:`tagMenuCtrl`、`tagDspCtrl`(定义于 `src/Drv/menu/common/menu_state_types.h`,实例为 `app/menu.c` 内部私有)
|
||||
- 静态菜单模型:`tagMenuModel`(定义于 `src/Drv/pages/menu/model/display.h`,数据在 `src/Drv/pages/menu/model/menu_model.c`)
|
||||
- 运行时菜单项:`tagMenuItem`(定义于 `src/Drv/pages/menu/view/menu_view.h`,实例由 `src/Drv/pages/menu/page.c` 管理)
|
||||
- 运行时控制:`tagMenuCtrl`、`tagDspCtrl`(定义于 `src/Drv/pages/menu/view/menu_view.h`,实例为 `src/Drv/pages/menu/page.c` 内部私有)
|
||||
|
||||
关键关系:
|
||||
|
||||
@@ -173,18 +695,18 @@ columns 1
|
||||
|
||||
- `src/Drv/menu/app/menu.c`
|
||||
- 菜单应用 Facade(`MenuApp_*` 对外接口)
|
||||
- 持有运行时私有状态(`s_menuCtrl`、`s_dspCtrl`、`s_menuItems`)
|
||||
- `src/Drv/menu/presenter/menu_presenter.c`
|
||||
- 负责 PageManager 初始化与 `MenuPage` 生命周期托管
|
||||
- `src/Drv/pages/menu/presenter/menu_presenter.c`
|
||||
- 控制调度中枢:处理输入事件、调用导航器、触发重建路径与刷新
|
||||
- `src/Drv/menu/model/menu_model.c`
|
||||
- `src/Drv/pages/menu/model/menu_model.c`
|
||||
- 模型初始化:树构建、菜单名修饰、初始状态建立
|
||||
- `src/Drv/menu/view/menu_view.c`
|
||||
- `src/Drv/pages/menu/view/menu_view.c`
|
||||
- 纯渲染:顶部栏、多级菜单框、高亮反显、全量/增量刷新策略
|
||||
- `src/Drv/menu/presenter/menu_navigator.c`
|
||||
- `src/Drv/pages/menu/presenter/menu_navigator.c`
|
||||
- 纯导航状态机:按键到 `MenuNavResult`(是否刷新、是否跳过渲染)
|
||||
- `src/Drv/menu/view/menu_layout.c`
|
||||
- `src/Drv/pages/menu/view/menu_layout.c`
|
||||
- 菜单布局计算:宽度统计、层级矩形定位、越界回退策略
|
||||
- `src/Drv/menu/model/display.c`
|
||||
- `src/Drv/pages/menu/model/menu_model.c`(包含静态菜单表数据)
|
||||
- 静态菜单模型表 `g_tMenuModelTab`(业务菜单定义)
|
||||
|
||||
#### 6.4.2 运行时调用时序
|
||||
@@ -313,12 +835,12 @@ ctest --test-dir build -C Debug --output-on-failure
|
||||
## 10. 模块依赖关系(代码级)
|
||||
|
||||
- `main.c` 依赖:`menu`、`key`、`remoteDisplay`、`tcp`、`thread_utils`
|
||||
- `src/Drv/menu/app/menu.c` 依赖:`presenter/menu_presenter`、`model/display`、`key`、`lcd`
|
||||
- `src/Drv/menu/presenter/menu_presenter.c` 依赖:`model/menu_model`、`view/menu_view`、`presenter/menu_navigator`
|
||||
- `src/Drv/menu/view/menu_view.c` 依赖:`model/menu_layout`、`view/menu_render_port`、`lcd`
|
||||
- `src/Drv/menu/app/menu.c` 依赖:`pages/page_manager`、`pages/menu/page`、`key`
|
||||
- `src/Drv/pages/menu/presenter/menu_presenter.c` 依赖:`model/menu_model`、`view/menu_view`、`presenter/menu_navigator`
|
||||
- `src/Drv/pages/menu/view/menu_view.c` 依赖:`view/menu_layout`、`view/menu_render_port`、`lcd`
|
||||
- `remoteDisplay.c` 依赖:`lcd`、`key`、`tcp`、`thread_utils`
|
||||
- `lcd.c` 依赖:`ascii`
|
||||
- `src/Drv/menu/model/display.c` 提供:静态菜单表(被 menu 模块使用)
|
||||
- `src/Drv/pages/menu/model/menu_model.c` 提供:静态菜单表(被 `MenuPage` 使用)
|
||||
|
||||
## 11. 已知风险与改进建议
|
||||
|
||||
|
||||
161
docs/绘图/.$HMI 图像绘制.drawio.bkp
Normal file
161
docs/绘图/.$HMI 图像绘制.drawio.bkp
Normal file
@@ -0,0 +1,161 @@
|
||||
<mxfile host="Electron" agent="Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) draw.io/29.6.6 Chrome/144.0.7559.236 Electron/40.8.4 Safari/537.36" version="29.6.6" pages="6">
|
||||
<diagram name="显示指定层级菜单项流程" id="etDREPkzm4HehLh3Wzss">
|
||||
<mxGraphModel dx="752" dy="817" grid="1" gridSize="10" guides="1" tooltips="1" connect="1" arrows="1" fold="1" page="1" pageScale="1" pageWidth="827" pageHeight="1169" math="0" shadow="0">
|
||||
<root>
|
||||
<mxCell id="0" />
|
||||
<mxCell id="1" parent="0" />
|
||||
<mxCell id="Dq_V7AeBsTtqcCJDGkD4-1" parent="1" style="swimlane;childLayout=stackLayout;horizontal=1;fillColor=none;horizontalStack=1;resizeParent=1;resizeParentMax=0;resizeLast=0;collapsible=0;strokeColor=none;stackBorder=10;stackSpacing=-12;resizable=1;align=center;points=[];whiteSpace=wrap;html=1;" value="显示指定层级菜单项" vertex="1">
|
||||
<mxGeometry height="100" width="610" x="100" y="560" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="Dq_V7AeBsTtqcCJDGkD4-2" parent="Dq_V7AeBsTtqcCJDGkD4-1" style="shape=step;perimeter=stepPerimeter;fixedSize=1;points=[];whiteSpace=wrap;html=1;" value="绘制该层级边框" vertex="1">
|
||||
<mxGeometry height="57" width="160" x="10" y="33" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="Dq_V7AeBsTtqcCJDGkD4-3" parent="Dq_V7AeBsTtqcCJDGkD4-1" style="shape=step;perimeter=stepPerimeter;fixedSize=1;points=[];whiteSpace=wrap;html=1;" value="获取该层级选中菜单项" vertex="1">
|
||||
<mxGeometry height="57" width="182" x="158" y="33" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="Dq_V7AeBsTtqcCJDGkD4-4" parent="Dq_V7AeBsTtqcCJDGkD4-1" style="shape=step;perimeter=stepPerimeter;fixedSize=1;points=[];whiteSpace=wrap;html=1;" value="绘制该层级下菜单项" vertex="1">
|
||||
<mxGeometry height="57" width="162" x="328" y="33" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="Dq_V7AeBsTtqcCJDGkD4-5" parent="Dq_V7AeBsTtqcCJDGkD4-1" style="shape=step;perimeter=stepPerimeter;fixedSize=1;points=[];whiteSpace=wrap;html=1;" value="反显选中项" vertex="1">
|
||||
<mxGeometry height="57" width="122" x="478" y="33" as="geometry" />
|
||||
</mxCell>
|
||||
</root>
|
||||
</mxGraphModel>
|
||||
</diagram>
|
||||
<diagram id="39Z0d1SsfJPrEY7NQ2dw" name="MVP 事件处理流程">
|
||||
<mxGraphModel dx="1090" dy="1185" grid="1" gridSize="10" guides="1" tooltips="1" connect="1" arrows="1" fold="1" page="1" pageScale="1" pageWidth="827" pageHeight="1169" math="0" shadow="0">
|
||||
<root>
|
||||
<mxCell id="0" />
|
||||
<mxCell id="1" parent="0" />
|
||||
<mxCell id="CHJpIxpMOG3BUcIp9TBN-1" edge="1" parent="1" source="CHJpIxpMOG3BUcIp9TBN-2" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;" target="CHJpIxpMOG3BUcIp9TBN-3" value="">
|
||||
<mxGeometry relative="1" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="CHJpIxpMOG3BUcIp9TBN-2" parent="1" style="rounded=0;whiteSpace=wrap;html=1;" value="<span style="text-wrap-mode: nowrap;">外部事件</span>" vertex="1">
|
||||
<mxGeometry height="60" width="120" x="354" y="490" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="CHJpIxpMOG3BUcIp9TBN-3" parent="1" style="whiteSpace=wrap;html=1;rounded=0;" value="Presenter 协同层" vertex="1">
|
||||
<mxGeometry height="60" width="120" x="354" y="570" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="CHJpIxpMOG3BUcIp9TBN-4" parent="1" style="whiteSpace=wrap;html=1;rounded=0;" value="View 视图层" vertex="1">
|
||||
<mxGeometry height="60" width="120" x="284" y="680" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="CHJpIxpMOG3BUcIp9TBN-5" parent="1" style="whiteSpace=wrap;html=1;rounded=0;" value="Model 模型层&nbsp;" vertex="1">
|
||||
<mxGeometry height="60" width="120" x="424" y="680" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="CHJpIxpMOG3BUcIp9TBN-6" edge="1" parent="1" source="CHJpIxpMOG3BUcIp9TBN-5" style="endArrow=classic;html=1;rounded=1;exitX=0.5;exitY=0;exitDx=0;exitDy=0;entryX=0.5;entryY=1;entryDx=0;entryDy=0;edgeStyle=orthogonalEdgeStyle;jumpStyle=arc;startArrow=classic;startFill=1;" target="CHJpIxpMOG3BUcIp9TBN-3" value="">
|
||||
<mxGeometry height="50" relative="1" width="50" as="geometry">
|
||||
<mxPoint x="594" y="630" as="sourcePoint" />
|
||||
<mxPoint x="644" y="580" as="targetPoint" />
|
||||
</mxGeometry>
|
||||
</mxCell>
|
||||
<mxCell id="CHJpIxpMOG3BUcIp9TBN-7" edge="1" parent="1" source="CHJpIxpMOG3BUcIp9TBN-3" style="endArrow=classic;html=1;rounded=1;entryX=0.5;entryY=0;entryDx=0;entryDy=0;exitX=0.5;exitY=1;exitDx=0;exitDy=0;edgeStyle=orthogonalEdgeStyle;" target="CHJpIxpMOG3BUcIp9TBN-4" value="">
|
||||
<mxGeometry height="50" relative="1" width="50" as="geometry">
|
||||
<mxPoint x="374" y="670" as="sourcePoint" />
|
||||
<mxPoint x="484" y="660" as="targetPoint" />
|
||||
</mxGeometry>
|
||||
</mxCell>
|
||||
</root>
|
||||
</mxGraphModel>
|
||||
</diagram>
|
||||
<diagram id="X0IBgYVqs8-xy_G4TQXO" name="MVP 架构示意图">
|
||||
<mxGraphModel dx="1090" dy="1185" grid="1" gridSize="10" guides="1" tooltips="1" connect="1" arrows="1" fold="1" page="1" pageScale="1" pageWidth="827" pageHeight="1169" math="0" shadow="0">
|
||||
<root>
|
||||
<mxCell id="0" />
|
||||
<mxCell id="1" parent="0" />
|
||||
<mxCell id="wkRVSMCZ4WoaQlwrK3vp-14" connectable="0" parent="1" style="group" value="" vertex="1">
|
||||
<mxGeometry height="300" width="600" x="100" y="300" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="wkRVSMCZ4WoaQlwrK3vp-1" parent="wkRVSMCZ4WoaQlwrK3vp-14" style="rounded=1;whiteSpace=wrap;html=1;" value="<font style="font-size: 21px;"><b>MVP 架构示意图</b></font>" vertex="1">
|
||||
<mxGeometry height="50" width="600" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="wkRVSMCZ4WoaQlwrK3vp-2" parent="wkRVSMCZ4WoaQlwrK3vp-14" style="rounded=1;whiteSpace=wrap;html=1;" value="" vertex="1">
|
||||
<mxGeometry height="240" width="180" y="60" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="wkRVSMCZ4WoaQlwrK3vp-3" parent="wkRVSMCZ4WoaQlwrK3vp-14" style="rounded=1;whiteSpace=wrap;html=1;" value="" vertex="1">
|
||||
<mxGeometry height="240" width="185" x="207.5" y="60" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="wkRVSMCZ4WoaQlwrK3vp-4" parent="wkRVSMCZ4WoaQlwrK3vp-14" style="rounded=1;whiteSpace=wrap;html=1;" value="" vertex="1">
|
||||
<mxGeometry height="240" width="185" x="415" y="60" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="wkRVSMCZ4WoaQlwrK3vp-5" parent="wkRVSMCZ4WoaQlwrK3vp-14" style="rounded=1;whiteSpace=wrap;html=1;" value="<font style="font-size: 16px;"><b>View (视图层)</b></font>" vertex="1">
|
||||
<mxGeometry height="40" width="160" x="10" y="70" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="wkRVSMCZ4WoaQlwrK3vp-6" parent="wkRVSMCZ4WoaQlwrK3vp-14" style="rounded=1;whiteSpace=wrap;html=1;" value="<b style="font-size: 16px;">Presenter (协调层)</b>" vertex="1">
|
||||
<mxGeometry height="40" width="160" x="220" y="70" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="wkRVSMCZ4WoaQlwrK3vp-7" parent="wkRVSMCZ4WoaQlwrK3vp-14" style="rounded=1;whiteSpace=wrap;html=1;" value="<b style="font-size: 16px;">Model (数据层)</b>" vertex="1">
|
||||
<mxGeometry height="40" width="160" x="430" y="70" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="wkRVSMCZ4WoaQlwrK3vp-8" parent="wkRVSMCZ4WoaQlwrK3vp-14" style="rounded=1;whiteSpace=wrap;html=1;" value="<span style="font-size: 16px;"><b>负责显示内容</b></span>" vertex="1">
|
||||
<mxGeometry height="40" width="160" x="10" y="250" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="wkRVSMCZ4WoaQlwrK3vp-9" parent="wkRVSMCZ4WoaQlwrK3vp-14" style="rounded=1;whiteSpace=wrap;html=1;" value="<font style="font-size: 16px;"><b>负责流程协调</b></font>" vertex="1">
|
||||
<mxGeometry height="40" width="160" x="220" y="250" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="wkRVSMCZ4WoaQlwrK3vp-10" parent="wkRVSMCZ4WoaQlwrK3vp-14" style="rounded=1;whiteSpace=wrap;html=1;" value="<font style="font-size: 16px;"><b>负责数据和规则</b></font>" vertex="1">
|
||||
<mxGeometry height="40" width="160" x="430" y="250" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="wkRVSMCZ4WoaQlwrK3vp-11" parent="wkRVSMCZ4WoaQlwrK3vp-14" style="text;html=1;whiteSpace=wrap;strokeColor=none;fillColor=none;align=left;verticalAlign=middle;rounded=0;" value="<b><font style="font-size: 14px;">LCD 显示</font></b><div><b><font style="font-size: 14px;">LED 指示</font></b></div><div><b><font style="font-size: 14px;">按键输入</font></b></div>" vertex="1">
|
||||
<mxGeometry height="60" width="80" x="20" y="150" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="wkRVSMCZ4WoaQlwrK3vp-12" parent="wkRVSMCZ4WoaQlwrK3vp-14" style="text;html=1;whiteSpace=wrap;strokeColor=none;fillColor=none;align=left;verticalAlign=middle;rounded=0;" value="<b><font style="font-size: 14px;">接收外部事件</font></b><div><b><font style="font-size: 14px;">调用 Model 处理</font></b></div><div><span style="font-size: 14px;"><b>更新 View 状态</b></span></div>" vertex="1">
|
||||
<mxGeometry height="60" width="120" x="230" y="150" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="wkRVSMCZ4WoaQlwrK3vp-13" parent="wkRVSMCZ4WoaQlwrK3vp-14" style="text;html=1;whiteSpace=wrap;strokeColor=none;fillColor=none;align=left;verticalAlign=middle;rounded=0;" value="<span style="font-size: 14px;"><b>菜单逻辑计算</b></span><br><div><b><font style="font-size: 14px;">参数边界检查</font></b></div><div><span style="font-size: 14px;"><b>传感器数据获取</b></span></div><div><span style="font-size: 14px;"><b>数据存储读写</b></span></div>" vertex="1">
|
||||
<mxGeometry height="60" width="120" x="440" y="150" as="geometry" />
|
||||
</mxCell>
|
||||
</root>
|
||||
</mxGraphModel>
|
||||
</diagram>
|
||||
<diagram id="HxDEZlLnL0qG7Ur4KydZ" name="页面管理器架构设计">
|
||||
<mxGraphModel dx="1363" dy="1481" grid="1" gridSize="10" guides="1" tooltips="1" connect="1" arrows="1" fold="1" page="1" pageScale="1" pageWidth="827" pageHeight="1169" math="0" shadow="0">
|
||||
<root>
|
||||
<mxCell id="0" />
|
||||
<mxCell id="1" parent="0" />
|
||||
<mxCell id="LuWILqjna9OgU5SQR-9t-1" parent="1" style="rounded=0;whiteSpace=wrap;html=1;" value="<font style="font-size: 29px;">页面管理器(<span style="background-color: transparent; color: light-dark(rgb(0, 0, 0), rgb(255, 255, 255));">Page Manager</span>)</font><div><span style="font-size: 29px;"><br></span></div><div><span style="font-size: 29px;"><br></span><div><font style="font-size: 29px;"><br></font></div><div><font style="font-size: 29px;"><br></font></div></div>" vertex="1">
|
||||
<mxGeometry height="240" width="500" x="130" y="290" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="LuWILqjna9OgU5SQR-9t-2" parent="1" style="rounded=0;whiteSpace=wrap;html=1;" value="<div><div><font style="font-size: 29px;">页面栈 (Page Stack)</font></div></div><div><font style="font-size: 29px;"><br></font></div><div><font style="font-size: 29px;"><br></font></div>" vertex="1">
|
||||
<mxGeometry height="130" width="500" x="130" y="400" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="LuWILqjna9OgU5SQR-9t-3" parent="1" style="rounded=0;whiteSpace=wrap;html=1;" value="<span style="font-size: 23px;">栈顶页面</span>" vertex="1">
|
||||
<mxGeometry height="60" width="240" x="140" y="460" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="LuWILqjna9OgU5SQR-9t-4" parent="1" style="rounded=0;whiteSpace=wrap;html=1;" value="<span style="font-size: 23px;">后台页面1</span>" vertex="1">
|
||||
<mxGeometry height="60" width="240" x="380" y="460" as="geometry" />
|
||||
</mxCell>
|
||||
</root>
|
||||
</mxGraphModel>
|
||||
</diagram>
|
||||
<diagram id="R1sptnBL6Yvgqzv1JQT0" name="菜单页面架构设计">
|
||||
<mxGraphModel dx="1090" dy="1185" grid="1" gridSize="10" guides="1" tooltips="1" connect="1" arrows="1" fold="1" page="1" pageScale="1" pageWidth="827" pageHeight="1169" math="0" shadow="0">
|
||||
<root>
|
||||
<mxCell id="0" />
|
||||
<mxCell id="1" parent="0" />
|
||||
<mxCell id="_v9aWsQiSdQDmT4Fcf7S-1" parent="1" style="rounded=0;whiteSpace=wrap;html=1;" value="<font style="font-size: 29px;">菜单页面</font><div><font style="font-size: 29px;"><br></font></div><div><font style="font-size: 29px;"><br></font></div>" vertex="1">
|
||||
<mxGeometry height="130" width="500" x="164" y="480" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="_v9aWsQiSdQDmT4Fcf7S-2" parent="1" style="rounded=0;whiteSpace=wrap;html=1;" value="<font style="font-size: 23px;">page</font>" vertex="1">
|
||||
<mxGeometry height="60" width="120" x="174" y="540" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="_v9aWsQiSdQDmT4Fcf7S-3" parent="1" style="rounded=0;whiteSpace=wrap;html=1;" value="<span style="font-size: 23px;">model</span>" vertex="1">
|
||||
<mxGeometry height="60" width="120" x="294" y="540" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="_v9aWsQiSdQDmT4Fcf7S-4" parent="1" style="rounded=0;whiteSpace=wrap;html=1;" value="<span style="font-size: 23px;">view</span>" vertex="1">
|
||||
<mxGeometry height="60" width="120" x="414" y="540" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="_v9aWsQiSdQDmT4Fcf7S-5" parent="1" style="rounded=0;whiteSpace=wrap;html=1;" value="<span style="font-size: 23px;">presenter</span>" vertex="1">
|
||||
<mxGeometry height="60" width="120" x="534" y="540" as="geometry" />
|
||||
</mxCell>
|
||||
</root>
|
||||
</mxGraphModel>
|
||||
</diagram>
|
||||
<diagram id="ae4TW034q6_u2rk01mG-" name="第 6 页">
|
||||
<mxGraphModel dx="1090" dy="1185" grid="1" gridSize="10" guides="1" tooltips="1" connect="1" arrows="1" fold="1" page="1" pageScale="1" pageWidth="827" pageHeight="1169" math="0" shadow="0">
|
||||
<root>
|
||||
<mxCell id="0" />
|
||||
<mxCell id="1" parent="0" />
|
||||
</root>
|
||||
</mxGraphModel>
|
||||
</diagram>
|
||||
</mxfile>
|
||||
506
docs/绘图/.$HMI 图像绘制.drawio.dtmp
Normal file
506
docs/绘图/.$HMI 图像绘制.drawio.dtmp
Normal file
@@ -0,0 +1,506 @@
|
||||
<mxfile host="Electron" agent="Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) draw.io/29.6.6 Chrome/144.0.7559.236 Electron/40.8.4 Safari/537.36" version="29.6.6" pages="8">
|
||||
<diagram name="显示指定层级菜单项流程" id="etDREPkzm4HehLh3Wzss">
|
||||
<mxGraphModel dx="752" dy="817" grid="1" gridSize="10" guides="1" tooltips="1" connect="1" arrows="1" fold="1" page="1" pageScale="1" pageWidth="827" pageHeight="1169" math="0" shadow="0">
|
||||
<root>
|
||||
<mxCell id="0" />
|
||||
<mxCell id="1" parent="0" />
|
||||
<mxCell id="Dq_V7AeBsTtqcCJDGkD4-1" parent="1" style="swimlane;childLayout=stackLayout;horizontal=1;fillColor=none;horizontalStack=1;resizeParent=1;resizeParentMax=0;resizeLast=0;collapsible=0;strokeColor=none;stackBorder=10;stackSpacing=-12;resizable=1;align=center;points=[];whiteSpace=wrap;html=1;" value="显示指定层级菜单项" vertex="1">
|
||||
<mxGeometry height="100" width="610" x="100" y="560" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="Dq_V7AeBsTtqcCJDGkD4-2" parent="Dq_V7AeBsTtqcCJDGkD4-1" style="shape=step;perimeter=stepPerimeter;fixedSize=1;points=[];whiteSpace=wrap;html=1;" value="绘制该层级边框" vertex="1">
|
||||
<mxGeometry height="57" width="160" x="10" y="33" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="Dq_V7AeBsTtqcCJDGkD4-3" parent="Dq_V7AeBsTtqcCJDGkD4-1" style="shape=step;perimeter=stepPerimeter;fixedSize=1;points=[];whiteSpace=wrap;html=1;" value="获取该层级选中菜单项" vertex="1">
|
||||
<mxGeometry height="57" width="182" x="158" y="33" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="Dq_V7AeBsTtqcCJDGkD4-4" parent="Dq_V7AeBsTtqcCJDGkD4-1" style="shape=step;perimeter=stepPerimeter;fixedSize=1;points=[];whiteSpace=wrap;html=1;" value="绘制该层级下菜单项" vertex="1">
|
||||
<mxGeometry height="57" width="162" x="328" y="33" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="Dq_V7AeBsTtqcCJDGkD4-5" parent="Dq_V7AeBsTtqcCJDGkD4-1" style="shape=step;perimeter=stepPerimeter;fixedSize=1;points=[];whiteSpace=wrap;html=1;" value="反显选中项" vertex="1">
|
||||
<mxGeometry height="57" width="122" x="478" y="33" as="geometry" />
|
||||
</mxCell>
|
||||
</root>
|
||||
</mxGraphModel>
|
||||
</diagram>
|
||||
<diagram id="39Z0d1SsfJPrEY7NQ2dw" name="MVP 事件处理流程">
|
||||
<mxGraphModel dx="1090" dy="1185" grid="1" gridSize="10" guides="1" tooltips="1" connect="1" arrows="1" fold="1" page="1" pageScale="1" pageWidth="827" pageHeight="1169" math="0" shadow="0">
|
||||
<root>
|
||||
<mxCell id="0" />
|
||||
<mxCell id="1" parent="0" />
|
||||
<mxCell id="CHJpIxpMOG3BUcIp9TBN-1" edge="1" parent="1" source="CHJpIxpMOG3BUcIp9TBN-2" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;" target="CHJpIxpMOG3BUcIp9TBN-3" value="">
|
||||
<mxGeometry relative="1" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="CHJpIxpMOG3BUcIp9TBN-2" parent="1" style="rounded=0;whiteSpace=wrap;html=1;" value="<span style="text-wrap-mode: nowrap;">外部事件</span>" vertex="1">
|
||||
<mxGeometry height="60" width="120" x="354" y="490" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="CHJpIxpMOG3BUcIp9TBN-3" parent="1" style="whiteSpace=wrap;html=1;rounded=0;" value="Presenter 协同层" vertex="1">
|
||||
<mxGeometry height="60" width="120" x="354" y="570" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="CHJpIxpMOG3BUcIp9TBN-4" parent="1" style="whiteSpace=wrap;html=1;rounded=0;" value="View 视图层" vertex="1">
|
||||
<mxGeometry height="60" width="120" x="284" y="680" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="CHJpIxpMOG3BUcIp9TBN-5" parent="1" style="whiteSpace=wrap;html=1;rounded=0;" value="Model 模型层&nbsp;" vertex="1">
|
||||
<mxGeometry height="60" width="120" x="424" y="680" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="CHJpIxpMOG3BUcIp9TBN-6" edge="1" parent="1" source="CHJpIxpMOG3BUcIp9TBN-5" style="endArrow=classic;html=1;rounded=1;exitX=0.5;exitY=0;exitDx=0;exitDy=0;entryX=0.5;entryY=1;entryDx=0;entryDy=0;edgeStyle=orthogonalEdgeStyle;jumpStyle=arc;startArrow=classic;startFill=1;" target="CHJpIxpMOG3BUcIp9TBN-3" value="">
|
||||
<mxGeometry height="50" relative="1" width="50" as="geometry">
|
||||
<mxPoint x="594" y="630" as="sourcePoint" />
|
||||
<mxPoint x="644" y="580" as="targetPoint" />
|
||||
</mxGeometry>
|
||||
</mxCell>
|
||||
<mxCell id="CHJpIxpMOG3BUcIp9TBN-7" edge="1" parent="1" source="CHJpIxpMOG3BUcIp9TBN-3" style="endArrow=classic;html=1;rounded=1;entryX=0.5;entryY=0;entryDx=0;entryDy=0;exitX=0.5;exitY=1;exitDx=0;exitDy=0;edgeStyle=orthogonalEdgeStyle;" target="CHJpIxpMOG3BUcIp9TBN-4" value="">
|
||||
<mxGeometry height="50" relative="1" width="50" as="geometry">
|
||||
<mxPoint x="374" y="670" as="sourcePoint" />
|
||||
<mxPoint x="484" y="660" as="targetPoint" />
|
||||
</mxGeometry>
|
||||
</mxCell>
|
||||
</root>
|
||||
</mxGraphModel>
|
||||
</diagram>
|
||||
<diagram id="X0IBgYVqs8-xy_G4TQXO" name="MVP 架构示意图">
|
||||
<mxGraphModel dx="1090" dy="1185" grid="1" gridSize="10" guides="1" tooltips="1" connect="1" arrows="1" fold="1" page="1" pageScale="1" pageWidth="827" pageHeight="1169" math="0" shadow="0">
|
||||
<root>
|
||||
<mxCell id="0" />
|
||||
<mxCell id="1" parent="0" />
|
||||
<mxCell id="wkRVSMCZ4WoaQlwrK3vp-14" connectable="0" parent="1" style="group" value="" vertex="1">
|
||||
<mxGeometry height="300" width="600" x="100" y="300" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="wkRVSMCZ4WoaQlwrK3vp-1" parent="wkRVSMCZ4WoaQlwrK3vp-14" style="rounded=1;whiteSpace=wrap;html=1;" value="<font style="font-size: 21px;"><b>MVP 架构示意图</b></font>" vertex="1">
|
||||
<mxGeometry height="50" width="600" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="wkRVSMCZ4WoaQlwrK3vp-2" parent="wkRVSMCZ4WoaQlwrK3vp-14" style="rounded=1;whiteSpace=wrap;html=1;" value="" vertex="1">
|
||||
<mxGeometry height="240" width="180" y="60" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="wkRVSMCZ4WoaQlwrK3vp-3" parent="wkRVSMCZ4WoaQlwrK3vp-14" style="rounded=1;whiteSpace=wrap;html=1;" value="" vertex="1">
|
||||
<mxGeometry height="240" width="185" x="207.5" y="60" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="wkRVSMCZ4WoaQlwrK3vp-4" parent="wkRVSMCZ4WoaQlwrK3vp-14" style="rounded=1;whiteSpace=wrap;html=1;" value="" vertex="1">
|
||||
<mxGeometry height="240" width="185" x="415" y="60" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="wkRVSMCZ4WoaQlwrK3vp-5" parent="wkRVSMCZ4WoaQlwrK3vp-14" style="rounded=1;whiteSpace=wrap;html=1;" value="<font style="font-size: 16px;"><b>View (视图层)</b></font>" vertex="1">
|
||||
<mxGeometry height="40" width="160" x="10" y="70" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="wkRVSMCZ4WoaQlwrK3vp-6" parent="wkRVSMCZ4WoaQlwrK3vp-14" style="rounded=1;whiteSpace=wrap;html=1;" value="<b style="font-size: 16px;">Presenter (协调层)</b>" vertex="1">
|
||||
<mxGeometry height="40" width="160" x="220" y="70" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="wkRVSMCZ4WoaQlwrK3vp-7" parent="wkRVSMCZ4WoaQlwrK3vp-14" style="rounded=1;whiteSpace=wrap;html=1;" value="<b style="font-size: 16px;">Model (数据层)</b>" vertex="1">
|
||||
<mxGeometry height="40" width="160" x="430" y="70" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="wkRVSMCZ4WoaQlwrK3vp-8" parent="wkRVSMCZ4WoaQlwrK3vp-14" style="rounded=1;whiteSpace=wrap;html=1;" value="<span style="font-size: 16px;"><b>负责显示内容</b></span>" vertex="1">
|
||||
<mxGeometry height="40" width="160" x="10" y="250" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="wkRVSMCZ4WoaQlwrK3vp-9" parent="wkRVSMCZ4WoaQlwrK3vp-14" style="rounded=1;whiteSpace=wrap;html=1;" value="<font style="font-size: 16px;"><b>负责流程协调</b></font>" vertex="1">
|
||||
<mxGeometry height="40" width="160" x="220" y="250" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="wkRVSMCZ4WoaQlwrK3vp-10" parent="wkRVSMCZ4WoaQlwrK3vp-14" style="rounded=1;whiteSpace=wrap;html=1;" value="<font style="font-size: 16px;"><b>负责数据和规则</b></font>" vertex="1">
|
||||
<mxGeometry height="40" width="160" x="430" y="250" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="wkRVSMCZ4WoaQlwrK3vp-11" parent="wkRVSMCZ4WoaQlwrK3vp-14" style="text;html=1;whiteSpace=wrap;strokeColor=none;fillColor=none;align=left;verticalAlign=middle;rounded=0;" value="<b><font style="font-size: 14px;">LCD 显示</font></b><div><b><font style="font-size: 14px;">LED 指示</font></b></div><div><b><font style="font-size: 14px;">按键输入</font></b></div>" vertex="1">
|
||||
<mxGeometry height="60" width="80" x="20" y="150" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="wkRVSMCZ4WoaQlwrK3vp-12" parent="wkRVSMCZ4WoaQlwrK3vp-14" style="text;html=1;whiteSpace=wrap;strokeColor=none;fillColor=none;align=left;verticalAlign=middle;rounded=0;" value="<b><font style="font-size: 14px;">接收外部事件</font></b><div><b><font style="font-size: 14px;">调用 Model 处理</font></b></div><div><span style="font-size: 14px;"><b>更新 View 状态</b></span></div>" vertex="1">
|
||||
<mxGeometry height="60" width="120" x="230" y="150" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="wkRVSMCZ4WoaQlwrK3vp-13" parent="wkRVSMCZ4WoaQlwrK3vp-14" style="text;html=1;whiteSpace=wrap;strokeColor=none;fillColor=none;align=left;verticalAlign=middle;rounded=0;" value="<span style="font-size: 14px;"><b>菜单逻辑计算</b></span><br><div><b><font style="font-size: 14px;">参数边界检查</font></b></div><div><span style="font-size: 14px;"><b>传感器数据获取</b></span></div><div><span style="font-size: 14px;"><b>数据存储读写</b></span></div>" vertex="1">
|
||||
<mxGeometry height="60" width="120" x="440" y="150" as="geometry" />
|
||||
</mxCell>
|
||||
</root>
|
||||
</mxGraphModel>
|
||||
</diagram>
|
||||
<diagram id="HxDEZlLnL0qG7Ur4KydZ" name="页面管理器架构设计">
|
||||
<mxGraphModel dx="1363" dy="1481" grid="1" gridSize="10" guides="1" tooltips="1" connect="1" arrows="1" fold="1" page="1" pageScale="1" pageWidth="827" pageHeight="1169" math="0" shadow="0">
|
||||
<root>
|
||||
<mxCell id="0" />
|
||||
<mxCell id="1" parent="0" />
|
||||
<mxCell id="LuWILqjna9OgU5SQR-9t-1" parent="1" style="rounded=0;whiteSpace=wrap;html=1;" value="<font style="font-size: 29px;">页面管理器(<span style="background-color: transparent; color: light-dark(rgb(0, 0, 0), rgb(255, 255, 255));">Page Manager</span>)</font><div><span style="font-size: 29px;"><br></span></div><div><span style="font-size: 29px;"><br></span><div><font style="font-size: 29px;"><br></font></div><div><font style="font-size: 29px;"><br></font></div></div>" vertex="1">
|
||||
<mxGeometry height="240" width="500" x="130" y="290" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="LuWILqjna9OgU5SQR-9t-2" parent="1" style="rounded=0;whiteSpace=wrap;html=1;" value="<div><div><font style="font-size: 29px;">页面栈 (Page Stack)</font></div></div><div><font style="font-size: 29px;"><br></font></div><div><font style="font-size: 29px;"><br></font></div>" vertex="1">
|
||||
<mxGeometry height="130" width="500" x="130" y="400" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="LuWILqjna9OgU5SQR-9t-3" parent="1" style="rounded=0;whiteSpace=wrap;html=1;" value="<span style="font-size: 23px;">栈顶页面</span>" vertex="1">
|
||||
<mxGeometry height="60" width="240" x="140" y="460" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="LuWILqjna9OgU5SQR-9t-4" parent="1" style="rounded=0;whiteSpace=wrap;html=1;" value="<span style="font-size: 23px;">后台页面1</span>" vertex="1">
|
||||
<mxGeometry height="60" width="240" x="380" y="460" as="geometry" />
|
||||
</mxCell>
|
||||
</root>
|
||||
</mxGraphModel>
|
||||
</diagram>
|
||||
<diagram id="R1sptnBL6Yvgqzv1JQT0" name="菜单页面架构设计">
|
||||
<mxGraphModel dx="1090" dy="1185" grid="1" gridSize="10" guides="1" tooltips="1" connect="1" arrows="1" fold="1" page="1" pageScale="1" pageWidth="827" pageHeight="1169" math="0" shadow="0">
|
||||
<root>
|
||||
<mxCell id="0" />
|
||||
<mxCell id="1" parent="0" />
|
||||
<mxCell id="_v9aWsQiSdQDmT4Fcf7S-1" parent="1" style="rounded=0;whiteSpace=wrap;html=1;" value="<font style="font-size: 29px;">菜单页面</font><div><font style="font-size: 29px;"><br></font></div><div><font style="font-size: 29px;"><br></font></div>" vertex="1">
|
||||
<mxGeometry height="130" width="500" x="164" y="480" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="_v9aWsQiSdQDmT4Fcf7S-2" parent="1" style="rounded=0;whiteSpace=wrap;html=1;" value="<font style="font-size: 23px;">page</font>" vertex="1">
|
||||
<mxGeometry height="60" width="120" x="174" y="540" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="_v9aWsQiSdQDmT4Fcf7S-3" parent="1" style="rounded=0;whiteSpace=wrap;html=1;" value="<span style="font-size: 23px;">model</span>" vertex="1">
|
||||
<mxGeometry height="60" width="120" x="294" y="540" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="_v9aWsQiSdQDmT4Fcf7S-4" parent="1" style="rounded=0;whiteSpace=wrap;html=1;" value="<span style="font-size: 23px;">view</span>" vertex="1">
|
||||
<mxGeometry height="60" width="120" x="414" y="540" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="_v9aWsQiSdQDmT4Fcf7S-5" parent="1" style="rounded=0;whiteSpace=wrap;html=1;" value="<span style="font-size: 23px;">presenter</span>" vertex="1">
|
||||
<mxGeometry height="60" width="120" x="534" y="540" as="geometry" />
|
||||
</mxCell>
|
||||
</root>
|
||||
</mxGraphModel>
|
||||
</diagram>
|
||||
<diagram id="ae4TW034q6_u2rk01mG-" name="菜单树构建逻辑">
|
||||
<mxGraphModel dx="1224" dy="1192" grid="1" gridSize="10" guides="1" tooltips="1" connect="1" arrows="1" fold="1" page="1" pageScale="1" pageWidth="827" pageHeight="1169" math="0" shadow="0">
|
||||
<root>
|
||||
<mxCell id="0" />
|
||||
<mxCell id="1" parent="0" />
|
||||
<mxCell id="uTcgnOhw-1hiChn4308x-321" parent="1" style="whiteSpace=wrap;strokeWidth=2;fillColor=light-dark(#eeeeee,#1f2020);strokeColor=light-dark(#999999,#cccccc);fontColor=light-dark(#333333,#cccccc);" value="主菜单(class=0)" vertex="1">
|
||||
<mxGeometry height="54" width="171" x="36" y="20" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="uTcgnOhw-1hiChn4308x-322" parent="1" style="whiteSpace=wrap;strokeWidth=2;fillColor=light-dark(#eeeeee,#1f2020);strokeColor=light-dark(#999999,#cccccc);fontColor=light-dark(#333333,#cccccc);" value="参数设置(class=1)" vertex="1">
|
||||
<mxGeometry height="54" width="187" x="260" y="140" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="uTcgnOhw-1hiChn4308x-323" parent="1" style="whiteSpace=wrap;strokeWidth=2;fillColor=light-dark(#eeeeee,#1f2020);strokeColor=light-dark(#999999,#cccccc);fontColor=light-dark(#333333,#cccccc);" value="信息查看(class=1)" vertex="1">
|
||||
<mxGeometry height="54" width="187" x="250" y="276" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="uTcgnOhw-1hiChn4308x-324" parent="1" style="whiteSpace=wrap;strokeWidth=2;fillColor=light-dark(#eeeeee,#1f2020);strokeColor=light-dark(#999999,#cccccc);fontColor=light-dark(#333333,#cccccc);" value="关于系统(class=1)" vertex="1">
|
||||
<mxGeometry height="54" width="187" x="250" y="430" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="uTcgnOhw-1hiChn4308x-325" parent="1" style="whiteSpace=wrap;strokeWidth=2;fillColor=light-dark(#eeeeee,#1f2020);strokeColor=light-dark(#999999,#cccccc);fontColor=light-dark(#333333,#cccccc);" value="定值设置(class=2)" vertex="1">
|
||||
<mxGeometry height="54" width="187" x="520" y="107" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="uTcgnOhw-1hiChn4308x-326" parent="1" style="whiteSpace=wrap;strokeWidth=2;fillColor=light-dark(#eeeeee,#1f2020);strokeColor=light-dark(#999999,#cccccc);fontColor=light-dark(#333333,#cccccc);" value="开关设置(class=2)" vertex="1">
|
||||
<mxGeometry height="54" width="187" x="520" y="207" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="uTcgnOhw-1hiChn4308x-327" parent="1" style="whiteSpace=wrap;strokeWidth=2;fillColor=light-dark(#eeeeee,#1f2020);strokeColor=light-dark(#999999,#cccccc);fontColor=light-dark(#333333,#cccccc);" value="运行信息(class=2)" vertex="1">
|
||||
<mxGeometry height="54" width="187" x="520" y="294" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="uTcgnOhw-1hiChn4308x-328" edge="1" parent="1" source="uTcgnOhw-1hiChn4308x-321" style="startArrow=none;endArrow=block;entryX=0.64;entryY=0;rounded=0;edgeStyle=orthogonalEdgeStyle;exitX=1;exitY=0.5;exitDx=0;exitDy=0;" target="uTcgnOhw-1hiChn4308x-322" value="lower">
|
||||
<mxGeometry relative="1" as="geometry">
|
||||
<Array as="points">
|
||||
<mxPoint x="380" y="47" />
|
||||
</Array>
|
||||
<mxPoint x="240" y="40" as="sourcePoint" />
|
||||
</mxGeometry>
|
||||
</mxCell>
|
||||
<mxCell id="uTcgnOhw-1hiChn4308x-329" edge="1" parent="1" source="uTcgnOhw-1hiChn4308x-322" style="startArrow=none;endArrow=block;exitX=0.667;exitY=1.037;entryX=0.72;entryY=0;rounded=0;exitDx=0;exitDy=0;exitPerimeter=0;" target="uTcgnOhw-1hiChn4308x-323" value="behind">
|
||||
<mxGeometry relative="1" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="uTcgnOhw-1hiChn4308x-330" edge="1" parent="1" source="uTcgnOhw-1hiChn4308x-323" style="startArrow=none;endArrow=block;exitX=0.75;exitY=1;entryX=0.75;entryY=0;rounded=0;exitDx=0;exitDy=0;entryDx=0;entryDy=0;" target="uTcgnOhw-1hiChn4308x-324" value="behind">
|
||||
<mxGeometry relative="1" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="uTcgnOhw-1hiChn4308x-331" edge="1" parent="1" source="uTcgnOhw-1hiChn4308x-324" style="startArrow=none;endArrow=block;exitX=0.75;exitY=1;entryX=-0.001;entryY=0.171;rounded=0;edgeStyle=orthogonalEdgeStyle;exitDx=0;exitDy=0;entryDx=0;entryDy=0;entryPerimeter=0;" target="uTcgnOhw-1hiChn4308x-322" value="behind 回环">
|
||||
<mxGeometry relative="1" as="geometry">
|
||||
<Array as="points">
|
||||
<mxPoint x="390" y="520" />
|
||||
<mxPoint x="110" y="520" />
|
||||
<mxPoint x="110" y="149" />
|
||||
</Array>
|
||||
</mxGeometry>
|
||||
</mxCell>
|
||||
<mxCell id="uTcgnOhw-1hiChn4308x-332" edge="1" parent="1" source="uTcgnOhw-1hiChn4308x-322" style="startArrow=none;endArrow=block;exitX=0;exitY=0.75;entryX=0.25;entryY=1;rounded=0;edgeStyle=orthogonalEdgeStyle;entryDx=0;entryDy=0;exitDx=0;exitDy=0;" target="uTcgnOhw-1hiChn4308x-324" value="before 回环">
|
||||
<mxGeometry relative="1" as="geometry">
|
||||
<Array as="points">
|
||||
<mxPoint x="180" y="180" />
|
||||
<mxPoint x="180" y="500" />
|
||||
<mxPoint x="297" y="500" />
|
||||
</Array>
|
||||
</mxGeometry>
|
||||
</mxCell>
|
||||
<mxCell id="uTcgnOhw-1hiChn4308x-333" edge="1" parent="1" source="uTcgnOhw-1hiChn4308x-323" style="startArrow=none;endArrow=block;exitX=0.269;exitY=0.012;entryX=0.33;entryY=1;rounded=0;exitDx=0;exitDy=0;exitPerimeter=0;" value="before">
|
||||
<mxGeometry relative="1" as="geometry">
|
||||
<mxPoint x="321.79" y="276" as="sourcePoint" />
|
||||
<mxPoint x="300.00000000000006" y="194" as="targetPoint" />
|
||||
</mxGeometry>
|
||||
</mxCell>
|
||||
<mxCell id="uTcgnOhw-1hiChn4308x-334" edge="1" parent="1" source="uTcgnOhw-1hiChn4308x-324" style="startArrow=none;endArrow=block;exitX=0.4;exitY=0;entryX=0.399;entryY=0.986;rounded=0;entryDx=0;entryDy=0;entryPerimeter=0;" target="uTcgnOhw-1hiChn4308x-323" value="before">
|
||||
<mxGeometry relative="1" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="uTcgnOhw-1hiChn4308x-335" edge="1" parent="1" source="uTcgnOhw-1hiChn4308x-322" style="startArrow=none;endArrow=block;exitX=1;exitY=0.5;entryX=0;entryY=0.5;rounded=0;entryDx=0;entryDy=0;exitDx=0;exitDy=0;edgeStyle=orthogonalEdgeStyle;" target="uTcgnOhw-1hiChn4308x-325" value="lower">
|
||||
<mxGeometry relative="1" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="uTcgnOhw-1hiChn4308x-336" edge="1" parent="1" source="uTcgnOhw-1hiChn4308x-325" style="startArrow=none;endArrow=block;exitX=0.912;exitY=0.975;entryX=0.91;entryY=0;rounded=0;edgeStyle=orthogonalEdgeStyle;exitDx=0;exitDy=0;exitPerimeter=0;" target="uTcgnOhw-1hiChn4308x-326" value="behind">
|
||||
<mxGeometry relative="1" x="0.1907" as="geometry">
|
||||
<mxPoint as="offset" />
|
||||
</mxGeometry>
|
||||
</mxCell>
|
||||
<mxCell id="uTcgnOhw-1hiChn4308x-337" edge="1" parent="1" source="uTcgnOhw-1hiChn4308x-326" style="startArrow=none;endArrow=block;exitX=0.332;exitY=-0.003;entryX=0.33;entryY=1;rounded=0;exitDx=0;exitDy=0;exitPerimeter=0;" target="uTcgnOhw-1hiChn4308x-325" value="behind 回环">
|
||||
<mxGeometry relative="1" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="uTcgnOhw-1hiChn4308x-338" edge="1" parent="1" source="uTcgnOhw-1hiChn4308x-325" style="startArrow=none;endArrow=block;exitX=0.669;exitY=0.986;rounded=0;exitDx=0;exitDy=0;exitPerimeter=0;entryX=0.673;entryY=-0.017;entryDx=0;entryDy=0;entryPerimeter=0;" target="uTcgnOhw-1hiChn4308x-326" value="before 回环">
|
||||
<mxGeometry relative="1" x="0.1635" y="-6" as="geometry">
|
||||
<mxPoint as="offset" />
|
||||
<mxPoint x="647" y="167" as="sourcePoint" />
|
||||
<mxPoint x="645" y="187" as="targetPoint" />
|
||||
</mxGeometry>
|
||||
</mxCell>
|
||||
<mxCell id="uTcgnOhw-1hiChn4308x-339" edge="1" parent="1" source="uTcgnOhw-1hiChn4308x-326" style="startArrow=none;endArrow=block;exitX=0.133;exitY=-0.013;entryX=0.13;entryY=1;rounded=0;exitDx=0;exitDy=0;exitPerimeter=0;" target="uTcgnOhw-1hiChn4308x-325" value="before">
|
||||
<mxGeometry relative="1" x="0.0153" y="9" as="geometry">
|
||||
<mxPoint as="offset" />
|
||||
</mxGeometry>
|
||||
</mxCell>
|
||||
<mxCell id="uTcgnOhw-1hiChn4308x-340" edge="1" parent="1" source="uTcgnOhw-1hiChn4308x-323" style="startArrow=none;endArrow=block;exitX=0.999;exitY=0.807;entryX=0;entryY=0.5;rounded=0;edgeStyle=orthogonalEdgeStyle;entryDx=0;entryDy=0;exitDx=0;exitDy=0;exitPerimeter=0;" target="uTcgnOhw-1hiChn4308x-327" value="lower">
|
||||
<mxGeometry relative="1" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="uTcgnOhw-1hiChn4308x-341" edge="1" parent="1" source="uTcgnOhw-1hiChn4308x-322" style="startArrow=none;endArrow=block;exitX=0.21;exitY=0;entryX=0.5;entryY=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" target="uTcgnOhw-1hiChn4308x-321" value="higher">
|
||||
<mxGeometry relative="1" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="uTcgnOhw-1hiChn4308x-342" edge="1" parent="1" source="uTcgnOhw-1hiChn4308x-323" style="startArrow=none;endArrow=block;exitX=0;exitY=0.5;entryX=0.26;entryY=1;rounded=0;edgeStyle=orthogonalEdgeStyle;exitDx=0;exitDy=0;" target="uTcgnOhw-1hiChn4308x-321" value="higher">
|
||||
<mxGeometry relative="1" as="geometry">
|
||||
<Array as="points">
|
||||
<mxPoint x="80" y="303" />
|
||||
<mxPoint x="80" y="276" />
|
||||
</Array>
|
||||
</mxGeometry>
|
||||
</mxCell>
|
||||
<mxCell id="uTcgnOhw-1hiChn4308x-343" edge="1" parent="1" style="startArrow=none;endArrow=block;entryX=0;entryY=0.83;rounded=0;edgeStyle=orthogonalEdgeStyle;" value="higher">
|
||||
<mxGeometry relative="1" x="0.4125" as="geometry">
|
||||
<mxPoint as="offset" />
|
||||
<Array as="points">
|
||||
<mxPoint x="250" y="460" />
|
||||
<mxPoint x="20" y="460" />
|
||||
<mxPoint x="20" y="40" />
|
||||
</Array>
|
||||
<mxPoint x="250" y="460" as="sourcePoint" />
|
||||
<mxPoint x="36" y="40.019999999999996" as="targetPoint" />
|
||||
</mxGeometry>
|
||||
</mxCell>
|
||||
<mxCell id="uTcgnOhw-1hiChn4308x-344" edge="1" parent="1" source="uTcgnOhw-1hiChn4308x-325" style="startArrow=none;endArrow=block;exitX=0.5;exitY=0;entryX=0.905;entryY=0.019;rounded=0;edgeStyle=orthogonalEdgeStyle;entryDx=0;entryDy=0;entryPerimeter=0;exitDx=0;exitDy=0;" target="uTcgnOhw-1hiChn4308x-322" value="higher">
|
||||
<mxGeometry relative="1" as="geometry">
|
||||
<Array as="points">
|
||||
<mxPoint x="614" y="80" />
|
||||
<mxPoint x="429" y="80" />
|
||||
</Array>
|
||||
</mxGeometry>
|
||||
</mxCell>
|
||||
<mxCell id="uTcgnOhw-1hiChn4308x-345" edge="1" parent="1" source="uTcgnOhw-1hiChn4308x-326" style="startArrow=none;endArrow=block;exitX=0;exitY=0.5;rounded=0;edgeStyle=orthogonalEdgeStyle;exitDx=0;exitDy=0;" value="higher">
|
||||
<mxGeometry relative="1" x="-0.3947" as="geometry">
|
||||
<mxPoint as="offset" />
|
||||
<Array as="points">
|
||||
<mxPoint x="420" y="234" />
|
||||
</Array>
|
||||
<mxPoint x="420" y="194" as="targetPoint" />
|
||||
</mxGeometry>
|
||||
</mxCell>
|
||||
<mxCell id="uTcgnOhw-1hiChn4308x-346" edge="1" parent="1" source="uTcgnOhw-1hiChn4308x-327" style="startArrow=none;endArrow=block;exitX=0.43;exitY=0;entryX=1.005;entryY=0.163;rounded=0;edgeStyle=orthogonalEdgeStyle;entryDx=0;entryDy=0;entryPerimeter=0;" target="uTcgnOhw-1hiChn4308x-323" value="higher">
|
||||
<mxGeometry relative="1" x="0.4124" y="-3" as="geometry">
|
||||
<mxPoint as="offset" />
|
||||
<Array as="points">
|
||||
<mxPoint x="600" y="285" />
|
||||
</Array>
|
||||
</mxGeometry>
|
||||
</mxCell>
|
||||
<mxCell id="uTcgnOhw-1hiChn4308x-347" edge="1" parent="1" source="uTcgnOhw-1hiChn4308x-327" style="endArrow=classic;html=1;rounded=0;exitX=0.677;exitY=0.972;exitDx=0;exitDy=0;exitPerimeter=0;entryX=0.709;entryY=0.015;entryDx=0;entryDy=0;entryPerimeter=0;curved=1;" target="uTcgnOhw-1hiChn4308x-327" value="">
|
||||
<mxGeometry height="50" relative="1" width="50" as="geometry">
|
||||
<Array as="points">
|
||||
<mxPoint x="710" y="380" />
|
||||
<mxPoint x="730" y="330" />
|
||||
<mxPoint x="730" y="300" />
|
||||
<mxPoint x="720" y="260" />
|
||||
</Array>
|
||||
<mxPoint x="657" y="460" as="sourcePoint" />
|
||||
<mxPoint x="707" y="410" as="targetPoint" />
|
||||
</mxGeometry>
|
||||
</mxCell>
|
||||
<mxCell id="uTcgnOhw-1hiChn4308x-348" connectable="0" parent="uTcgnOhw-1hiChn4308x-347" style="edgeLabel;html=1;align=center;verticalAlign=middle;resizable=0;points=[];" value="before" vertex="1">
|
||||
<mxGeometry relative="1" x="-0.0353" as="geometry">
|
||||
<mxPoint as="offset" />
|
||||
</mxGeometry>
|
||||
</mxCell>
|
||||
<mxCell id="uTcgnOhw-1hiChn4308x-351" edge="1" parent="1" source="uTcgnOhw-1hiChn4308x-327" style="curved=1;endArrow=classic;html=1;rounded=0;entryX=0.5;entryY=1;entryDx=0;entryDy=0;exitX=0.5;exitY=0;exitDx=0;exitDy=0;" target="uTcgnOhw-1hiChn4308x-327" value="">
|
||||
<mxGeometry height="50" relative="1" width="50" as="geometry">
|
||||
<Array as="points">
|
||||
<mxPoint x="750" y="230" />
|
||||
<mxPoint x="750" y="400" />
|
||||
<mxPoint x="590" y="390" />
|
||||
</Array>
|
||||
<mxPoint x="570" y="484" as="sourcePoint" />
|
||||
<mxPoint x="620" y="434" as="targetPoint" />
|
||||
</mxGeometry>
|
||||
</mxCell>
|
||||
<mxCell id="uTcgnOhw-1hiChn4308x-352" connectable="0" parent="uTcgnOhw-1hiChn4308x-351" style="edgeLabel;html=1;align=center;verticalAlign=middle;resizable=0;points=[];" value="behind" vertex="1">
|
||||
<mxGeometry relative="1" x="0.0226" y="-5" as="geometry">
|
||||
<mxPoint as="offset" />
|
||||
</mxGeometry>
|
||||
</mxCell>
|
||||
</root>
|
||||
</mxGraphModel>
|
||||
</diagram>
|
||||
<diagram id="5RVJ2jwpCgCB7Hji9JxO" name="菜单树判断逻辑">
|
||||
<mxGraphModel dx="2720" dy="2649" grid="1" gridSize="10" guides="1" tooltips="1" connect="1" arrows="1" fold="1" page="1" pageScale="1" pageWidth="827" pageHeight="1169" math="0" shadow="0">
|
||||
<root>
|
||||
<mxCell id="0" />
|
||||
<mxCell id="1" parent="0" />
|
||||
<mxCell id="HZ-q9eH6YA3dt1o5nvaf-1" parent="1" style="whiteSpace=wrap;strokeWidth=2;fillColor=light-dark(#eeeeee,#1f2020);strokeColor=light-dark(#999999,#cccccc);fontColor=light-dark(#333333,#cccccc);" value="byCurClass < byNextClass" vertex="1">
|
||||
<mxGeometry height="54" width="241" x="250" y="380" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="HZ-q9eH6YA3dt1o5nvaf-2" parent="1" style="whiteSpace=wrap;strokeWidth=2;fillColor=light-dark(#eeeeee,#1f2020);strokeColor=light-dark(#999999,#cccccc);fontColor=light-dark(#333333,#cccccc);" value="进入子菜单" vertex="1">
|
||||
<mxGeometry height="54" width="140" x="510" y="380" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="HZ-q9eH6YA3dt1o5nvaf-3" parent="1" style="whiteSpace=wrap;strokeWidth=2;fillColor=light-dark(#eeeeee,#1f2020);strokeColor=light-dark(#999999,#cccccc);fontColor=light-dark(#333333,#cccccc);" value="current.lower = next" vertex="1">
|
||||
<mxGeometry height="54" width="190" x="670" y="380" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="HZ-q9eH6YA3dt1o5nvaf-4" parent="1" style="whiteSpace=wrap;strokeWidth=2;fillColor=light-dark(#eeeeee,#1f2020);strokeColor=light-dark(#999999,#cccccc);fontColor=light-dark(#333333,#cccccc);" value="next.higher = current" vertex="1">
|
||||
<mxGeometry height="54" width="209" x="891" y="380" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="HZ-q9eH6YA3dt1o5nvaf-5" parent="1" style="whiteSpace=wrap;strokeWidth=2;fillColor=light-dark(#eeeeee,#1f2020);strokeColor=light-dark(#999999,#cccccc);fontColor=light-dark(#333333,#cccccc);" value="byCurClass == byNextClass" vertex="1">
|
||||
<mxGeometry height="54" width="240" x="250" y="484" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="HZ-q9eH6YA3dt1o5nvaf-6" parent="1" style="whiteSpace=wrap;strokeWidth=2;fillColor=light-dark(#eeeeee,#1f2020);strokeColor=light-dark(#999999,#cccccc);fontColor=light-dark(#333333,#cccccc);" value="同层兄弟" vertex="1">
|
||||
<mxGeometry height="54" width="140" x="511" y="484" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="HZ-q9eH6YA3dt1o5nvaf-7" parent="1" style="whiteSpace=wrap;strokeWidth=2;fillColor=light-dark(#eeeeee,#1f2020);strokeColor=light-dark(#999999,#cccccc);fontColor=light-dark(#333333,#cccccc);" value="current.behind = next" vertex="1">
|
||||
<mxGeometry height="54" width="190" x="670" y="484" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="HZ-q9eH6YA3dt1o5nvaf-8" parent="1" style="whiteSpace=wrap;strokeWidth=2;fillColor=light-dark(#eeeeee,#1f2020);strokeColor=light-dark(#999999,#cccccc);fontColor=light-dark(#333333,#cccccc);" value="next.before = current" vertex="1">
|
||||
<mxGeometry height="54" width="210" x="890" y="484" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="HZ-q9eH6YA3dt1o5nvaf-9" parent="1" style="whiteSpace=wrap;strokeWidth=2;fillColor=light-dark(#eeeeee,#1f2020);strokeColor=light-dark(#999999,#cccccc);fontColor=light-dark(#333333,#cccccc);" value="next.higher = current.higher" vertex="1">
|
||||
<mxGeometry height="78" width="260" x="1140" y="472" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="HZ-q9eH6YA3dt1o5nvaf-10" parent="1" style="whiteSpace=wrap;strokeWidth=2;fillColor=light-dark(#eeeeee,#1f2020);strokeColor=light-dark(#999999,#cccccc);fontColor=light-dark(#333333,#cccccc);" value="byCurClass > byNextClass" vertex="1">
|
||||
<mxGeometry height="54" width="241" x="250" y="591" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="HZ-q9eH6YA3dt1o5nvaf-11" parent="1" style="whiteSpace=wrap;strokeWidth=2;fillColor=light-dark(#eeeeee,#1f2020);strokeColor=light-dark(#999999,#cccccc);fontColor=light-dark(#333333,#cccccc);" value="从深层回退" vertex="1">
|
||||
<mxGeometry height="54" width="140" x="510" y="591" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="HZ-q9eH6YA3dt1o5nvaf-12" parent="1" style="whiteSpace=wrap;strokeWidth=2;fillColor=light-dark(#eeeeee,#1f2020);strokeColor=light-dark(#999999,#cccccc);fontColor=light-dark(#333333,#cccccc);" value="next.before = ptLast[nextClass]" vertex="1">
|
||||
<mxGeometry height="54" width="190" x="670" y="591" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="HZ-q9eH6YA3dt1o5nvaf-27" edge="1" parent="1" source="HZ-q9eH6YA3dt1o5nvaf-13" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;" target="HZ-q9eH6YA3dt1o5nvaf-26" value="">
|
||||
<mxGeometry relative="1" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="HZ-q9eH6YA3dt1o5nvaf-13" parent="1" style="whiteSpace=wrap;strokeWidth=2;fillColor=light-dark(#eeeeee,#1f2020);strokeColor=light-dark(#999999,#cccccc);fontColor=light-dark(#333333,#cccccc);" value="ptLast[nextClass].behind=next" vertex="1">
|
||||
<mxGeometry height="54" width="190" x="1140" y="591" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="HZ-q9eH6YA3dt1o5nvaf-14" edge="1" parent="1" source="HZ-q9eH6YA3dt1o5nvaf-1" style="curved=1;startArrow=none;endArrow=block;exitX=1;exitY=0.5;entryX=0;entryY=0.5;rounded=0;" target="HZ-q9eH6YA3dt1o5nvaf-2" value="">
|
||||
<mxGeometry relative="1" as="geometry">
|
||||
<Array as="points" />
|
||||
</mxGeometry>
|
||||
</mxCell>
|
||||
<mxCell id="HZ-q9eH6YA3dt1o5nvaf-15" edge="1" parent="1" source="HZ-q9eH6YA3dt1o5nvaf-2" style="curved=1;startArrow=none;endArrow=block;exitX=1;exitY=0.5;entryX=0;entryY=0.5;rounded=0;" target="HZ-q9eH6YA3dt1o5nvaf-3" value="">
|
||||
<mxGeometry relative="1" as="geometry">
|
||||
<Array as="points" />
|
||||
</mxGeometry>
|
||||
</mxCell>
|
||||
<mxCell id="HZ-q9eH6YA3dt1o5nvaf-16" edge="1" parent="1" source="HZ-q9eH6YA3dt1o5nvaf-3" style="curved=1;startArrow=none;endArrow=block;exitX=1;exitY=0.5;entryX=0;entryY=0.5;rounded=0;" target="HZ-q9eH6YA3dt1o5nvaf-4" value="">
|
||||
<mxGeometry relative="1" as="geometry">
|
||||
<Array as="points" />
|
||||
</mxGeometry>
|
||||
</mxCell>
|
||||
<mxCell id="HZ-q9eH6YA3dt1o5nvaf-17" edge="1" parent="1" source="HZ-q9eH6YA3dt1o5nvaf-5" style="curved=1;startArrow=none;endArrow=block;exitX=1;exitY=0.5;entryX=0;entryY=0.5;rounded=0;" target="HZ-q9eH6YA3dt1o5nvaf-6" value="">
|
||||
<mxGeometry relative="1" as="geometry">
|
||||
<Array as="points" />
|
||||
</mxGeometry>
|
||||
</mxCell>
|
||||
<mxCell id="HZ-q9eH6YA3dt1o5nvaf-18" edge="1" parent="1" source="HZ-q9eH6YA3dt1o5nvaf-6" style="curved=1;startArrow=none;endArrow=block;exitX=1;exitY=0.5;entryX=0;entryY=0.5;rounded=0;" target="HZ-q9eH6YA3dt1o5nvaf-7" value="">
|
||||
<mxGeometry relative="1" as="geometry">
|
||||
<Array as="points" />
|
||||
</mxGeometry>
|
||||
</mxCell>
|
||||
<mxCell id="HZ-q9eH6YA3dt1o5nvaf-19" edge="1" parent="1" source="HZ-q9eH6YA3dt1o5nvaf-7" style="curved=1;startArrow=none;endArrow=block;exitX=1;exitY=0.5;entryX=0;entryY=0.5;rounded=0;" target="HZ-q9eH6YA3dt1o5nvaf-8" value="">
|
||||
<mxGeometry relative="1" as="geometry">
|
||||
<Array as="points" />
|
||||
</mxGeometry>
|
||||
</mxCell>
|
||||
<mxCell id="HZ-q9eH6YA3dt1o5nvaf-20" edge="1" parent="1" source="HZ-q9eH6YA3dt1o5nvaf-8" style="curved=1;startArrow=none;endArrow=block;exitX=1;exitY=0.5;entryX=0;entryY=0.5;rounded=0;" target="HZ-q9eH6YA3dt1o5nvaf-9" value="">
|
||||
<mxGeometry relative="1" as="geometry">
|
||||
<Array as="points" />
|
||||
</mxGeometry>
|
||||
</mxCell>
|
||||
<mxCell id="HZ-q9eH6YA3dt1o5nvaf-21" edge="1" parent="1" source="HZ-q9eH6YA3dt1o5nvaf-10" style="curved=1;startArrow=none;endArrow=block;exitX=1;exitY=0.5;entryX=0;entryY=0.5;rounded=0;" target="HZ-q9eH6YA3dt1o5nvaf-11" value="">
|
||||
<mxGeometry relative="1" as="geometry">
|
||||
<Array as="points" />
|
||||
</mxGeometry>
|
||||
</mxCell>
|
||||
<mxCell id="HZ-q9eH6YA3dt1o5nvaf-22" edge="1" parent="1" source="HZ-q9eH6YA3dt1o5nvaf-11" style="curved=1;startArrow=none;endArrow=block;exitX=1;exitY=0.5;entryX=0;entryY=0.5;rounded=0;" target="HZ-q9eH6YA3dt1o5nvaf-12" value="">
|
||||
<mxGeometry relative="1" as="geometry">
|
||||
<Array as="points" />
|
||||
</mxGeometry>
|
||||
</mxCell>
|
||||
<mxCell id="HZ-q9eH6YA3dt1o5nvaf-23" edge="1" parent="1" source="HZ-q9eH6YA3dt1o5nvaf-12" style="curved=1;startArrow=none;endArrow=block;exitX=1;exitY=0.5;rounded=0;exitDx=0;exitDy=0;" value="">
|
||||
<mxGeometry relative="1" as="geometry">
|
||||
<Array as="points" />
|
||||
<mxPoint x="890" y="618" as="targetPoint" />
|
||||
</mxGeometry>
|
||||
</mxCell>
|
||||
<mxCell id="HZ-q9eH6YA3dt1o5nvaf-25" edge="1" parent="1" source="HZ-q9eH6YA3dt1o5nvaf-24" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;" target="HZ-q9eH6YA3dt1o5nvaf-13" value="">
|
||||
<mxGeometry relative="1" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="HZ-q9eH6YA3dt1o5nvaf-24" parent="1" style="whiteSpace=wrap;strokeWidth=2;fillColor=light-dark(#eeeeee,#1f2020);strokeColor=light-dark(#999999,#cccccc);fontColor=light-dark(#333333,#cccccc);" value="next.behind=ptLast[nextClass].higher" vertex="1">
|
||||
<mxGeometry height="54" width="209" x="891" y="591" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="HZ-q9eH6YA3dt1o5nvaf-26" parent="1" style="whiteSpace=wrap;html=1;fillColor=light-dark(#eeeeee,#1f2020);strokeColor=light-dark(#999999,#cccccc);fontColor=light-dark(#333333,#cccccc);strokeWidth=2;" value="形成回环" vertex="1">
|
||||
<mxGeometry height="55" width="69" x="1351" y="591" as="geometry" />
|
||||
</mxCell>
|
||||
</root>
|
||||
</mxGraphModel>
|
||||
</diagram>
|
||||
<diagram id="zG06bic9s9vmrHAmIL5z" name="模型层初始化顺序">
|
||||
<mxGraphModel dx="628" dy="611" grid="1" gridSize="10" guides="1" tooltips="1" connect="1" arrows="1" fold="1" page="1" pageScale="1" pageWidth="827" pageHeight="1169" math="0" shadow="0">
|
||||
<root>
|
||||
<mxCell id="0" />
|
||||
<mxCell id="1" parent="0" />
|
||||
<mxCell id="4pC_cjyCFYns3d6entyY-21" edge="1" parent="1" source="4pC_cjyCFYns3d6entyY-1" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;entryX=0.5;entryY=0;entryDx=0;entryDy=0;" target="4pC_cjyCFYns3d6entyY-3">
|
||||
<mxGeometry relative="1" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="4pC_cjyCFYns3d6entyY-1" parent="1" style="whiteSpace=wrap;strokeWidth=2;fillColor=light-dark(#eeeeee,#1f2020);strokeColor=light-dark(#999999,#cccccc);fontColor=light-dark(#333333,#cccccc);rounded=1;" value="开始" vertex="1">
|
||||
<mxGeometry height="24" width="210" x="320" y="120" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="4pC_cjyCFYns3d6entyY-3" parent="1" style="whiteSpace=wrap;strokeWidth=2;fillColor=light-dark(#eeeeee,#1f2020);strokeColor=light-dark(#999999,#cccccc);fontColor=light-dark(#333333,#cccccc);" value="计算菜单个数 maxItem " vertex="1">
|
||||
<mxGeometry height="30" width="210" x="320" y="171" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="4pC_cjyCFYns3d6entyY-5" parent="1" style="whiteSpace=wrap;strokeWidth=2;fillColor=light-dark(#eeeeee,#1f2020);strokeColor=light-dark(#999999,#cccccc);fontColor=light-dark(#333333,#cccccc);" value="统计 0 级菜单数量 by0LevelNum " vertex="1">
|
||||
<mxGeometry height="30" width="210" x="320" y="225" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="4pC_cjyCFYns3d6entyY-6" parent="1" style="whiteSpace=wrap;strokeWidth=2;fillColor=light-dark(#eeeeee,#1f2020);strokeColor=light-dark(#999999,#cccccc);fontColor=light-dark(#333333,#cccccc);" value="构建菜单树链路" vertex="1">
|
||||
<mxGeometry height="30" width="210" x="320" y="279" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="4pC_cjyCFYns3d6entyY-7" parent="1" style="whiteSpace=wrap;strokeWidth=2;fillColor=light-dark(#eeeeee,#1f2020);strokeColor=light-dark(#999999,#cccccc);fontColor=light-dark(#333333,#cccccc);" value="补充带子菜单项的右侧箭头》" vertex="1">
|
||||
<mxGeometry height="40" width="210" x="320" y="328" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="4pC_cjyCFYns3d6entyY-8" parent="1" style="whiteSpace=wrap;strokeWidth=2;fillColor=light-dark(#eeeeee,#1f2020);strokeColor=light-dark(#999999,#cccccc);fontColor=light-dark(#333333,#cccccc);" value="计算各节点 wPos 与 wNum" vertex="1">
|
||||
<mxGeometry height="30" width="210" x="320" y="387" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="4pC_cjyCFYns3d6entyY-9" parent="1" style="whiteSpace=wrap;strokeWidth=2;fillColor=light-dark(#eeeeee,#1f2020);strokeColor=light-dark(#999999,#cccccc);fontColor=light-dark(#333333,#cccccc);" value="计算整棵菜单树的显示位置" vertex="1">
|
||||
<mxGeometry height="30" width="210" x="320" y="441" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="4pC_cjyCFYns3d6entyY-10" parent="1" style="whiteSpace=wrap;strokeWidth=2;fillColor=light-dark(#eeeeee,#1f2020);strokeColor=light-dark(#999999,#cccccc);fontColor=light-dark(#333333,#cccccc);rounded=1;" value="函数结束" vertex="1">
|
||||
<mxGeometry height="20" width="210" x="320" y="500" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="4pC_cjyCFYns3d6entyY-13" edge="1" parent="1" source="4pC_cjyCFYns3d6entyY-3" style="curved=1;startArrow=none;endArrow=block;exitX=0.5;exitY=1;entryX=0.5;entryY=0;rounded=0;entryDx=0;entryDy=0;" target="4pC_cjyCFYns3d6entyY-5" value="">
|
||||
<mxGeometry relative="1" as="geometry">
|
||||
<Array as="points" />
|
||||
<mxPoint x="426" y="470" as="targetPoint" />
|
||||
</mxGeometry>
|
||||
</mxCell>
|
||||
<mxCell id="4pC_cjyCFYns3d6entyY-15" edge="1" parent="1" source="4pC_cjyCFYns3d6entyY-5" style="curved=1;startArrow=none;endArrow=block;exitX=0.5;exitY=1;entryX=0.5;entryY=0;rounded=0;" target="4pC_cjyCFYns3d6entyY-6" value="">
|
||||
<mxGeometry relative="1" as="geometry">
|
||||
<Array as="points" />
|
||||
</mxGeometry>
|
||||
</mxCell>
|
||||
<mxCell id="4pC_cjyCFYns3d6entyY-16" edge="1" parent="1" source="4pC_cjyCFYns3d6entyY-6" style="curved=1;startArrow=none;endArrow=block;exitX=0.5;exitY=1;entryX=0.5;entryY=0;rounded=0;" target="4pC_cjyCFYns3d6entyY-7" value="">
|
||||
<mxGeometry relative="1" as="geometry">
|
||||
<Array as="points" />
|
||||
</mxGeometry>
|
||||
</mxCell>
|
||||
<mxCell id="4pC_cjyCFYns3d6entyY-17" edge="1" parent="1" source="4pC_cjyCFYns3d6entyY-7" style="curved=1;startArrow=none;endArrow=block;exitX=0.5;exitY=1;entryX=0.5;entryY=0;rounded=0;" target="4pC_cjyCFYns3d6entyY-8" value="">
|
||||
<mxGeometry relative="1" as="geometry">
|
||||
<Array as="points" />
|
||||
</mxGeometry>
|
||||
</mxCell>
|
||||
<mxCell id="4pC_cjyCFYns3d6entyY-18" edge="1" parent="1" source="4pC_cjyCFYns3d6entyY-8" style="curved=1;startArrow=none;endArrow=block;exitX=0.5;exitY=1;entryX=0.5;entryY=0;rounded=0;" target="4pC_cjyCFYns3d6entyY-9" value="">
|
||||
<mxGeometry relative="1" as="geometry">
|
||||
<Array as="points" />
|
||||
</mxGeometry>
|
||||
</mxCell>
|
||||
<mxCell id="4pC_cjyCFYns3d6entyY-19" edge="1" parent="1" source="4pC_cjyCFYns3d6entyY-9" style="curved=1;startArrow=none;endArrow=block;exitX=0.5;exitY=1;entryX=0.5;entryY=0;rounded=0;" target="4pC_cjyCFYns3d6entyY-10" value="">
|
||||
<mxGeometry relative="1" as="geometry">
|
||||
<Array as="points" />
|
||||
</mxGeometry>
|
||||
</mxCell>
|
||||
</root>
|
||||
</mxGraphModel>
|
||||
</diagram>
|
||||
</mxfile>
|
||||
506
docs/绘图/HMI 图像绘制.drawio
Normal file
506
docs/绘图/HMI 图像绘制.drawio
Normal file
@@ -0,0 +1,506 @@
|
||||
<mxfile host="Electron" agent="Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) draw.io/29.6.6 Chrome/144.0.7559.236 Electron/40.8.4 Safari/537.36" version="29.6.6" pages="8">
|
||||
<diagram name="显示指定层级菜单项流程" id="etDREPkzm4HehLh3Wzss">
|
||||
<mxGraphModel dx="752" dy="817" grid="1" gridSize="10" guides="1" tooltips="1" connect="1" arrows="1" fold="1" page="1" pageScale="1" pageWidth="827" pageHeight="1169" math="0" shadow="0">
|
||||
<root>
|
||||
<mxCell id="0" />
|
||||
<mxCell id="1" parent="0" />
|
||||
<mxCell id="Dq_V7AeBsTtqcCJDGkD4-1" parent="1" style="swimlane;childLayout=stackLayout;horizontal=1;fillColor=none;horizontalStack=1;resizeParent=1;resizeParentMax=0;resizeLast=0;collapsible=0;strokeColor=none;stackBorder=10;stackSpacing=-12;resizable=1;align=center;points=[];whiteSpace=wrap;html=1;" value="显示指定层级菜单项" vertex="1">
|
||||
<mxGeometry height="100" width="610" x="100" y="560" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="Dq_V7AeBsTtqcCJDGkD4-2" parent="Dq_V7AeBsTtqcCJDGkD4-1" style="shape=step;perimeter=stepPerimeter;fixedSize=1;points=[];whiteSpace=wrap;html=1;" value="绘制该层级边框" vertex="1">
|
||||
<mxGeometry height="57" width="160" x="10" y="33" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="Dq_V7AeBsTtqcCJDGkD4-3" parent="Dq_V7AeBsTtqcCJDGkD4-1" style="shape=step;perimeter=stepPerimeter;fixedSize=1;points=[];whiteSpace=wrap;html=1;" value="获取该层级选中菜单项" vertex="1">
|
||||
<mxGeometry height="57" width="182" x="158" y="33" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="Dq_V7AeBsTtqcCJDGkD4-4" parent="Dq_V7AeBsTtqcCJDGkD4-1" style="shape=step;perimeter=stepPerimeter;fixedSize=1;points=[];whiteSpace=wrap;html=1;" value="绘制该层级下菜单项" vertex="1">
|
||||
<mxGeometry height="57" width="162" x="328" y="33" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="Dq_V7AeBsTtqcCJDGkD4-5" parent="Dq_V7AeBsTtqcCJDGkD4-1" style="shape=step;perimeter=stepPerimeter;fixedSize=1;points=[];whiteSpace=wrap;html=1;" value="反显选中项" vertex="1">
|
||||
<mxGeometry height="57" width="122" x="478" y="33" as="geometry" />
|
||||
</mxCell>
|
||||
</root>
|
||||
</mxGraphModel>
|
||||
</diagram>
|
||||
<diagram id="39Z0d1SsfJPrEY7NQ2dw" name="MVP 事件处理流程">
|
||||
<mxGraphModel dx="1090" dy="1185" grid="1" gridSize="10" guides="1" tooltips="1" connect="1" arrows="1" fold="1" page="1" pageScale="1" pageWidth="827" pageHeight="1169" math="0" shadow="0">
|
||||
<root>
|
||||
<mxCell id="0" />
|
||||
<mxCell id="1" parent="0" />
|
||||
<mxCell id="CHJpIxpMOG3BUcIp9TBN-1" edge="1" parent="1" source="CHJpIxpMOG3BUcIp9TBN-2" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;" target="CHJpIxpMOG3BUcIp9TBN-3" value="">
|
||||
<mxGeometry relative="1" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="CHJpIxpMOG3BUcIp9TBN-2" parent="1" style="rounded=0;whiteSpace=wrap;html=1;" value="<span style="text-wrap-mode: nowrap;">外部事件</span>" vertex="1">
|
||||
<mxGeometry height="60" width="120" x="354" y="490" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="CHJpIxpMOG3BUcIp9TBN-3" parent="1" style="whiteSpace=wrap;html=1;rounded=0;" value="Presenter 协同层" vertex="1">
|
||||
<mxGeometry height="60" width="120" x="354" y="570" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="CHJpIxpMOG3BUcIp9TBN-4" parent="1" style="whiteSpace=wrap;html=1;rounded=0;" value="View 视图层" vertex="1">
|
||||
<mxGeometry height="60" width="120" x="284" y="680" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="CHJpIxpMOG3BUcIp9TBN-5" parent="1" style="whiteSpace=wrap;html=1;rounded=0;" value="Model 模型层&nbsp;" vertex="1">
|
||||
<mxGeometry height="60" width="120" x="424" y="680" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="CHJpIxpMOG3BUcIp9TBN-6" edge="1" parent="1" source="CHJpIxpMOG3BUcIp9TBN-5" style="endArrow=classic;html=1;rounded=1;exitX=0.5;exitY=0;exitDx=0;exitDy=0;entryX=0.5;entryY=1;entryDx=0;entryDy=0;edgeStyle=orthogonalEdgeStyle;jumpStyle=arc;startArrow=classic;startFill=1;" target="CHJpIxpMOG3BUcIp9TBN-3" value="">
|
||||
<mxGeometry height="50" relative="1" width="50" as="geometry">
|
||||
<mxPoint x="594" y="630" as="sourcePoint" />
|
||||
<mxPoint x="644" y="580" as="targetPoint" />
|
||||
</mxGeometry>
|
||||
</mxCell>
|
||||
<mxCell id="CHJpIxpMOG3BUcIp9TBN-7" edge="1" parent="1" source="CHJpIxpMOG3BUcIp9TBN-3" style="endArrow=classic;html=1;rounded=1;entryX=0.5;entryY=0;entryDx=0;entryDy=0;exitX=0.5;exitY=1;exitDx=0;exitDy=0;edgeStyle=orthogonalEdgeStyle;" target="CHJpIxpMOG3BUcIp9TBN-4" value="">
|
||||
<mxGeometry height="50" relative="1" width="50" as="geometry">
|
||||
<mxPoint x="374" y="670" as="sourcePoint" />
|
||||
<mxPoint x="484" y="660" as="targetPoint" />
|
||||
</mxGeometry>
|
||||
</mxCell>
|
||||
</root>
|
||||
</mxGraphModel>
|
||||
</diagram>
|
||||
<diagram id="X0IBgYVqs8-xy_G4TQXO" name="MVP 架构示意图">
|
||||
<mxGraphModel dx="1090" dy="1185" grid="1" gridSize="10" guides="1" tooltips="1" connect="1" arrows="1" fold="1" page="1" pageScale="1" pageWidth="827" pageHeight="1169" math="0" shadow="0">
|
||||
<root>
|
||||
<mxCell id="0" />
|
||||
<mxCell id="1" parent="0" />
|
||||
<mxCell id="wkRVSMCZ4WoaQlwrK3vp-14" connectable="0" parent="1" style="group" value="" vertex="1">
|
||||
<mxGeometry height="300" width="600" x="100" y="300" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="wkRVSMCZ4WoaQlwrK3vp-1" parent="wkRVSMCZ4WoaQlwrK3vp-14" style="rounded=1;whiteSpace=wrap;html=1;" value="<font style="font-size: 21px;"><b>MVP 架构示意图</b></font>" vertex="1">
|
||||
<mxGeometry height="50" width="600" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="wkRVSMCZ4WoaQlwrK3vp-2" parent="wkRVSMCZ4WoaQlwrK3vp-14" style="rounded=1;whiteSpace=wrap;html=1;" value="" vertex="1">
|
||||
<mxGeometry height="240" width="180" y="60" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="wkRVSMCZ4WoaQlwrK3vp-3" parent="wkRVSMCZ4WoaQlwrK3vp-14" style="rounded=1;whiteSpace=wrap;html=1;" value="" vertex="1">
|
||||
<mxGeometry height="240" width="185" x="207.5" y="60" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="wkRVSMCZ4WoaQlwrK3vp-4" parent="wkRVSMCZ4WoaQlwrK3vp-14" style="rounded=1;whiteSpace=wrap;html=1;" value="" vertex="1">
|
||||
<mxGeometry height="240" width="185" x="415" y="60" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="wkRVSMCZ4WoaQlwrK3vp-5" parent="wkRVSMCZ4WoaQlwrK3vp-14" style="rounded=1;whiteSpace=wrap;html=1;" value="<font style="font-size: 16px;"><b>View (视图层)</b></font>" vertex="1">
|
||||
<mxGeometry height="40" width="160" x="10" y="70" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="wkRVSMCZ4WoaQlwrK3vp-6" parent="wkRVSMCZ4WoaQlwrK3vp-14" style="rounded=1;whiteSpace=wrap;html=1;" value="<b style="font-size: 16px;">Presenter (协调层)</b>" vertex="1">
|
||||
<mxGeometry height="40" width="160" x="220" y="70" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="wkRVSMCZ4WoaQlwrK3vp-7" parent="wkRVSMCZ4WoaQlwrK3vp-14" style="rounded=1;whiteSpace=wrap;html=1;" value="<b style="font-size: 16px;">Model (数据层)</b>" vertex="1">
|
||||
<mxGeometry height="40" width="160" x="430" y="70" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="wkRVSMCZ4WoaQlwrK3vp-8" parent="wkRVSMCZ4WoaQlwrK3vp-14" style="rounded=1;whiteSpace=wrap;html=1;" value="<span style="font-size: 16px;"><b>负责显示内容</b></span>" vertex="1">
|
||||
<mxGeometry height="40" width="160" x="10" y="250" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="wkRVSMCZ4WoaQlwrK3vp-9" parent="wkRVSMCZ4WoaQlwrK3vp-14" style="rounded=1;whiteSpace=wrap;html=1;" value="<font style="font-size: 16px;"><b>负责流程协调</b></font>" vertex="1">
|
||||
<mxGeometry height="40" width="160" x="220" y="250" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="wkRVSMCZ4WoaQlwrK3vp-10" parent="wkRVSMCZ4WoaQlwrK3vp-14" style="rounded=1;whiteSpace=wrap;html=1;" value="<font style="font-size: 16px;"><b>负责数据和规则</b></font>" vertex="1">
|
||||
<mxGeometry height="40" width="160" x="430" y="250" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="wkRVSMCZ4WoaQlwrK3vp-11" parent="wkRVSMCZ4WoaQlwrK3vp-14" style="text;html=1;whiteSpace=wrap;strokeColor=none;fillColor=none;align=left;verticalAlign=middle;rounded=0;" value="<b><font style="font-size: 14px;">LCD 显示</font></b><div><b><font style="font-size: 14px;">LED 指示</font></b></div><div><b><font style="font-size: 14px;">按键输入</font></b></div>" vertex="1">
|
||||
<mxGeometry height="60" width="80" x="20" y="150" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="wkRVSMCZ4WoaQlwrK3vp-12" parent="wkRVSMCZ4WoaQlwrK3vp-14" style="text;html=1;whiteSpace=wrap;strokeColor=none;fillColor=none;align=left;verticalAlign=middle;rounded=0;" value="<b><font style="font-size: 14px;">接收外部事件</font></b><div><b><font style="font-size: 14px;">调用 Model 处理</font></b></div><div><span style="font-size: 14px;"><b>更新 View 状态</b></span></div>" vertex="1">
|
||||
<mxGeometry height="60" width="120" x="230" y="150" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="wkRVSMCZ4WoaQlwrK3vp-13" parent="wkRVSMCZ4WoaQlwrK3vp-14" style="text;html=1;whiteSpace=wrap;strokeColor=none;fillColor=none;align=left;verticalAlign=middle;rounded=0;" value="<span style="font-size: 14px;"><b>菜单逻辑计算</b></span><br><div><b><font style="font-size: 14px;">参数边界检查</font></b></div><div><span style="font-size: 14px;"><b>传感器数据获取</b></span></div><div><span style="font-size: 14px;"><b>数据存储读写</b></span></div>" vertex="1">
|
||||
<mxGeometry height="60" width="120" x="440" y="150" as="geometry" />
|
||||
</mxCell>
|
||||
</root>
|
||||
</mxGraphModel>
|
||||
</diagram>
|
||||
<diagram id="HxDEZlLnL0qG7Ur4KydZ" name="页面管理器架构设计">
|
||||
<mxGraphModel dx="1363" dy="1481" grid="1" gridSize="10" guides="1" tooltips="1" connect="1" arrows="1" fold="1" page="1" pageScale="1" pageWidth="827" pageHeight="1169" math="0" shadow="0">
|
||||
<root>
|
||||
<mxCell id="0" />
|
||||
<mxCell id="1" parent="0" />
|
||||
<mxCell id="LuWILqjna9OgU5SQR-9t-1" parent="1" style="rounded=0;whiteSpace=wrap;html=1;" value="<font style="font-size: 29px;">页面管理器(<span style="background-color: transparent; color: light-dark(rgb(0, 0, 0), rgb(255, 255, 255));">Page Manager</span>)</font><div><span style="font-size: 29px;"><br></span></div><div><span style="font-size: 29px;"><br></span><div><font style="font-size: 29px;"><br></font></div><div><font style="font-size: 29px;"><br></font></div></div>" vertex="1">
|
||||
<mxGeometry height="240" width="500" x="130" y="290" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="LuWILqjna9OgU5SQR-9t-2" parent="1" style="rounded=0;whiteSpace=wrap;html=1;" value="<div><div><font style="font-size: 29px;">页面栈 (Page Stack)</font></div></div><div><font style="font-size: 29px;"><br></font></div><div><font style="font-size: 29px;"><br></font></div>" vertex="1">
|
||||
<mxGeometry height="130" width="500" x="130" y="400" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="LuWILqjna9OgU5SQR-9t-3" parent="1" style="rounded=0;whiteSpace=wrap;html=1;" value="<span style="font-size: 23px;">栈顶页面</span>" vertex="1">
|
||||
<mxGeometry height="60" width="240" x="140" y="460" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="LuWILqjna9OgU5SQR-9t-4" parent="1" style="rounded=0;whiteSpace=wrap;html=1;" value="<span style="font-size: 23px;">后台页面1</span>" vertex="1">
|
||||
<mxGeometry height="60" width="240" x="380" y="460" as="geometry" />
|
||||
</mxCell>
|
||||
</root>
|
||||
</mxGraphModel>
|
||||
</diagram>
|
||||
<diagram id="R1sptnBL6Yvgqzv1JQT0" name="菜单页面架构设计">
|
||||
<mxGraphModel dx="1090" dy="1185" grid="1" gridSize="10" guides="1" tooltips="1" connect="1" arrows="1" fold="1" page="1" pageScale="1" pageWidth="827" pageHeight="1169" math="0" shadow="0">
|
||||
<root>
|
||||
<mxCell id="0" />
|
||||
<mxCell id="1" parent="0" />
|
||||
<mxCell id="_v9aWsQiSdQDmT4Fcf7S-1" parent="1" style="rounded=0;whiteSpace=wrap;html=1;" value="<font style="font-size: 29px;">菜单页面</font><div><font style="font-size: 29px;"><br></font></div><div><font style="font-size: 29px;"><br></font></div>" vertex="1">
|
||||
<mxGeometry height="130" width="500" x="164" y="480" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="_v9aWsQiSdQDmT4Fcf7S-2" parent="1" style="rounded=0;whiteSpace=wrap;html=1;" value="<font style="font-size: 23px;">page</font>" vertex="1">
|
||||
<mxGeometry height="60" width="120" x="174" y="540" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="_v9aWsQiSdQDmT4Fcf7S-3" parent="1" style="rounded=0;whiteSpace=wrap;html=1;" value="<span style="font-size: 23px;">model</span>" vertex="1">
|
||||
<mxGeometry height="60" width="120" x="294" y="540" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="_v9aWsQiSdQDmT4Fcf7S-4" parent="1" style="rounded=0;whiteSpace=wrap;html=1;" value="<span style="font-size: 23px;">view</span>" vertex="1">
|
||||
<mxGeometry height="60" width="120" x="414" y="540" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="_v9aWsQiSdQDmT4Fcf7S-5" parent="1" style="rounded=0;whiteSpace=wrap;html=1;" value="<span style="font-size: 23px;">presenter</span>" vertex="1">
|
||||
<mxGeometry height="60" width="120" x="534" y="540" as="geometry" />
|
||||
</mxCell>
|
||||
</root>
|
||||
</mxGraphModel>
|
||||
</diagram>
|
||||
<diagram id="ae4TW034q6_u2rk01mG-" name="菜单树构建逻辑">
|
||||
<mxGraphModel dx="1224" dy="1192" grid="1" gridSize="10" guides="1" tooltips="1" connect="1" arrows="1" fold="1" page="1" pageScale="1" pageWidth="827" pageHeight="1169" math="0" shadow="0">
|
||||
<root>
|
||||
<mxCell id="0" />
|
||||
<mxCell id="1" parent="0" />
|
||||
<mxCell id="uTcgnOhw-1hiChn4308x-321" parent="1" style="whiteSpace=wrap;strokeWidth=2;fillColor=light-dark(#eeeeee,#1f2020);strokeColor=light-dark(#999999,#cccccc);fontColor=light-dark(#333333,#cccccc);" value="主菜单(class=0)" vertex="1">
|
||||
<mxGeometry height="54" width="171" x="36" y="20" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="uTcgnOhw-1hiChn4308x-322" parent="1" style="whiteSpace=wrap;strokeWidth=2;fillColor=light-dark(#eeeeee,#1f2020);strokeColor=light-dark(#999999,#cccccc);fontColor=light-dark(#333333,#cccccc);" value="参数设置(class=1)" vertex="1">
|
||||
<mxGeometry height="54" width="187" x="260" y="140" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="uTcgnOhw-1hiChn4308x-323" parent="1" style="whiteSpace=wrap;strokeWidth=2;fillColor=light-dark(#eeeeee,#1f2020);strokeColor=light-dark(#999999,#cccccc);fontColor=light-dark(#333333,#cccccc);" value="信息查看(class=1)" vertex="1">
|
||||
<mxGeometry height="54" width="187" x="250" y="276" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="uTcgnOhw-1hiChn4308x-324" parent="1" style="whiteSpace=wrap;strokeWidth=2;fillColor=light-dark(#eeeeee,#1f2020);strokeColor=light-dark(#999999,#cccccc);fontColor=light-dark(#333333,#cccccc);" value="关于系统(class=1)" vertex="1">
|
||||
<mxGeometry height="54" width="187" x="250" y="430" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="uTcgnOhw-1hiChn4308x-325" parent="1" style="whiteSpace=wrap;strokeWidth=2;fillColor=light-dark(#eeeeee,#1f2020);strokeColor=light-dark(#999999,#cccccc);fontColor=light-dark(#333333,#cccccc);" value="定值设置(class=2)" vertex="1">
|
||||
<mxGeometry height="54" width="187" x="520" y="107" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="uTcgnOhw-1hiChn4308x-326" parent="1" style="whiteSpace=wrap;strokeWidth=2;fillColor=light-dark(#eeeeee,#1f2020);strokeColor=light-dark(#999999,#cccccc);fontColor=light-dark(#333333,#cccccc);" value="开关设置(class=2)" vertex="1">
|
||||
<mxGeometry height="54" width="187" x="520" y="207" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="uTcgnOhw-1hiChn4308x-327" parent="1" style="whiteSpace=wrap;strokeWidth=2;fillColor=light-dark(#eeeeee,#1f2020);strokeColor=light-dark(#999999,#cccccc);fontColor=light-dark(#333333,#cccccc);" value="运行信息(class=2)" vertex="1">
|
||||
<mxGeometry height="54" width="187" x="520" y="294" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="uTcgnOhw-1hiChn4308x-328" edge="1" parent="1" source="uTcgnOhw-1hiChn4308x-321" style="startArrow=none;endArrow=block;entryX=0.64;entryY=0;rounded=0;edgeStyle=orthogonalEdgeStyle;exitX=1;exitY=0.5;exitDx=0;exitDy=0;" target="uTcgnOhw-1hiChn4308x-322" value="lower">
|
||||
<mxGeometry relative="1" as="geometry">
|
||||
<Array as="points">
|
||||
<mxPoint x="380" y="47" />
|
||||
</Array>
|
||||
<mxPoint x="240" y="40" as="sourcePoint" />
|
||||
</mxGeometry>
|
||||
</mxCell>
|
||||
<mxCell id="uTcgnOhw-1hiChn4308x-329" edge="1" parent="1" source="uTcgnOhw-1hiChn4308x-322" style="startArrow=none;endArrow=block;exitX=0.667;exitY=1.037;entryX=0.72;entryY=0;rounded=0;exitDx=0;exitDy=0;exitPerimeter=0;" target="uTcgnOhw-1hiChn4308x-323" value="behind">
|
||||
<mxGeometry relative="1" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="uTcgnOhw-1hiChn4308x-330" edge="1" parent="1" source="uTcgnOhw-1hiChn4308x-323" style="startArrow=none;endArrow=block;exitX=0.75;exitY=1;entryX=0.75;entryY=0;rounded=0;exitDx=0;exitDy=0;entryDx=0;entryDy=0;" target="uTcgnOhw-1hiChn4308x-324" value="behind">
|
||||
<mxGeometry relative="1" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="uTcgnOhw-1hiChn4308x-331" edge="1" parent="1" source="uTcgnOhw-1hiChn4308x-324" style="startArrow=none;endArrow=block;exitX=0.75;exitY=1;entryX=-0.001;entryY=0.171;rounded=0;edgeStyle=orthogonalEdgeStyle;exitDx=0;exitDy=0;entryDx=0;entryDy=0;entryPerimeter=0;" target="uTcgnOhw-1hiChn4308x-322" value="behind 回环">
|
||||
<mxGeometry relative="1" as="geometry">
|
||||
<Array as="points">
|
||||
<mxPoint x="390" y="520" />
|
||||
<mxPoint x="110" y="520" />
|
||||
<mxPoint x="110" y="149" />
|
||||
</Array>
|
||||
</mxGeometry>
|
||||
</mxCell>
|
||||
<mxCell id="uTcgnOhw-1hiChn4308x-332" edge="1" parent="1" source="uTcgnOhw-1hiChn4308x-322" style="startArrow=none;endArrow=block;exitX=0;exitY=0.75;entryX=0.25;entryY=1;rounded=0;edgeStyle=orthogonalEdgeStyle;entryDx=0;entryDy=0;exitDx=0;exitDy=0;" target="uTcgnOhw-1hiChn4308x-324" value="before 回环">
|
||||
<mxGeometry relative="1" as="geometry">
|
||||
<Array as="points">
|
||||
<mxPoint x="180" y="180" />
|
||||
<mxPoint x="180" y="500" />
|
||||
<mxPoint x="297" y="500" />
|
||||
</Array>
|
||||
</mxGeometry>
|
||||
</mxCell>
|
||||
<mxCell id="uTcgnOhw-1hiChn4308x-333" edge="1" parent="1" source="uTcgnOhw-1hiChn4308x-323" style="startArrow=none;endArrow=block;exitX=0.269;exitY=0.012;entryX=0.33;entryY=1;rounded=0;exitDx=0;exitDy=0;exitPerimeter=0;" value="before">
|
||||
<mxGeometry relative="1" as="geometry">
|
||||
<mxPoint x="321.79" y="276" as="sourcePoint" />
|
||||
<mxPoint x="300.00000000000006" y="194" as="targetPoint" />
|
||||
</mxGeometry>
|
||||
</mxCell>
|
||||
<mxCell id="uTcgnOhw-1hiChn4308x-334" edge="1" parent="1" source="uTcgnOhw-1hiChn4308x-324" style="startArrow=none;endArrow=block;exitX=0.4;exitY=0;entryX=0.399;entryY=0.986;rounded=0;entryDx=0;entryDy=0;entryPerimeter=0;" target="uTcgnOhw-1hiChn4308x-323" value="before">
|
||||
<mxGeometry relative="1" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="uTcgnOhw-1hiChn4308x-335" edge="1" parent="1" source="uTcgnOhw-1hiChn4308x-322" style="startArrow=none;endArrow=block;exitX=1;exitY=0.5;entryX=0;entryY=0.5;rounded=0;entryDx=0;entryDy=0;exitDx=0;exitDy=0;edgeStyle=orthogonalEdgeStyle;" target="uTcgnOhw-1hiChn4308x-325" value="lower">
|
||||
<mxGeometry relative="1" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="uTcgnOhw-1hiChn4308x-336" edge="1" parent="1" source="uTcgnOhw-1hiChn4308x-325" style="startArrow=none;endArrow=block;exitX=0.912;exitY=0.975;entryX=0.91;entryY=0;rounded=0;edgeStyle=orthogonalEdgeStyle;exitDx=0;exitDy=0;exitPerimeter=0;" target="uTcgnOhw-1hiChn4308x-326" value="behind">
|
||||
<mxGeometry relative="1" x="0.1907" as="geometry">
|
||||
<mxPoint as="offset" />
|
||||
</mxGeometry>
|
||||
</mxCell>
|
||||
<mxCell id="uTcgnOhw-1hiChn4308x-337" edge="1" parent="1" source="uTcgnOhw-1hiChn4308x-326" style="startArrow=none;endArrow=block;exitX=0.332;exitY=-0.003;entryX=0.33;entryY=1;rounded=0;exitDx=0;exitDy=0;exitPerimeter=0;" target="uTcgnOhw-1hiChn4308x-325" value="behind 回环">
|
||||
<mxGeometry relative="1" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="uTcgnOhw-1hiChn4308x-338" edge="1" parent="1" source="uTcgnOhw-1hiChn4308x-325" style="startArrow=none;endArrow=block;exitX=0.669;exitY=0.986;rounded=0;exitDx=0;exitDy=0;exitPerimeter=0;entryX=0.673;entryY=-0.017;entryDx=0;entryDy=0;entryPerimeter=0;" target="uTcgnOhw-1hiChn4308x-326" value="before 回环">
|
||||
<mxGeometry relative="1" x="0.1635" y="-6" as="geometry">
|
||||
<mxPoint as="offset" />
|
||||
<mxPoint x="647" y="167" as="sourcePoint" />
|
||||
<mxPoint x="645" y="187" as="targetPoint" />
|
||||
</mxGeometry>
|
||||
</mxCell>
|
||||
<mxCell id="uTcgnOhw-1hiChn4308x-339" edge="1" parent="1" source="uTcgnOhw-1hiChn4308x-326" style="startArrow=none;endArrow=block;exitX=0.133;exitY=-0.013;entryX=0.13;entryY=1;rounded=0;exitDx=0;exitDy=0;exitPerimeter=0;" target="uTcgnOhw-1hiChn4308x-325" value="before">
|
||||
<mxGeometry relative="1" x="0.0153" y="9" as="geometry">
|
||||
<mxPoint as="offset" />
|
||||
</mxGeometry>
|
||||
</mxCell>
|
||||
<mxCell id="uTcgnOhw-1hiChn4308x-340" edge="1" parent="1" source="uTcgnOhw-1hiChn4308x-323" style="startArrow=none;endArrow=block;exitX=0.999;exitY=0.807;entryX=0;entryY=0.5;rounded=0;edgeStyle=orthogonalEdgeStyle;entryDx=0;entryDy=0;exitDx=0;exitDy=0;exitPerimeter=0;" target="uTcgnOhw-1hiChn4308x-327" value="lower">
|
||||
<mxGeometry relative="1" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="uTcgnOhw-1hiChn4308x-341" edge="1" parent="1" source="uTcgnOhw-1hiChn4308x-322" style="startArrow=none;endArrow=block;exitX=0.21;exitY=0;entryX=0.5;entryY=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" target="uTcgnOhw-1hiChn4308x-321" value="higher">
|
||||
<mxGeometry relative="1" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="uTcgnOhw-1hiChn4308x-342" edge="1" parent="1" source="uTcgnOhw-1hiChn4308x-323" style="startArrow=none;endArrow=block;exitX=0;exitY=0.5;entryX=0.26;entryY=1;rounded=0;edgeStyle=orthogonalEdgeStyle;exitDx=0;exitDy=0;" target="uTcgnOhw-1hiChn4308x-321" value="higher">
|
||||
<mxGeometry relative="1" as="geometry">
|
||||
<Array as="points">
|
||||
<mxPoint x="80" y="303" />
|
||||
<mxPoint x="80" y="276" />
|
||||
</Array>
|
||||
</mxGeometry>
|
||||
</mxCell>
|
||||
<mxCell id="uTcgnOhw-1hiChn4308x-343" edge="1" parent="1" style="startArrow=none;endArrow=block;entryX=0;entryY=0.83;rounded=0;edgeStyle=orthogonalEdgeStyle;" value="higher">
|
||||
<mxGeometry relative="1" x="0.4125" as="geometry">
|
||||
<mxPoint as="offset" />
|
||||
<Array as="points">
|
||||
<mxPoint x="250" y="460" />
|
||||
<mxPoint x="20" y="460" />
|
||||
<mxPoint x="20" y="40" />
|
||||
</Array>
|
||||
<mxPoint x="250" y="460" as="sourcePoint" />
|
||||
<mxPoint x="36" y="40.019999999999996" as="targetPoint" />
|
||||
</mxGeometry>
|
||||
</mxCell>
|
||||
<mxCell id="uTcgnOhw-1hiChn4308x-344" edge="1" parent="1" source="uTcgnOhw-1hiChn4308x-325" style="startArrow=none;endArrow=block;exitX=0.5;exitY=0;entryX=0.905;entryY=0.019;rounded=0;edgeStyle=orthogonalEdgeStyle;entryDx=0;entryDy=0;entryPerimeter=0;exitDx=0;exitDy=0;" target="uTcgnOhw-1hiChn4308x-322" value="higher">
|
||||
<mxGeometry relative="1" as="geometry">
|
||||
<Array as="points">
|
||||
<mxPoint x="614" y="80" />
|
||||
<mxPoint x="429" y="80" />
|
||||
</Array>
|
||||
</mxGeometry>
|
||||
</mxCell>
|
||||
<mxCell id="uTcgnOhw-1hiChn4308x-345" edge="1" parent="1" source="uTcgnOhw-1hiChn4308x-326" style="startArrow=none;endArrow=block;exitX=0;exitY=0.5;rounded=0;edgeStyle=orthogonalEdgeStyle;exitDx=0;exitDy=0;" value="higher">
|
||||
<mxGeometry relative="1" x="-0.3947" as="geometry">
|
||||
<mxPoint as="offset" />
|
||||
<Array as="points">
|
||||
<mxPoint x="420" y="234" />
|
||||
</Array>
|
||||
<mxPoint x="420" y="194" as="targetPoint" />
|
||||
</mxGeometry>
|
||||
</mxCell>
|
||||
<mxCell id="uTcgnOhw-1hiChn4308x-346" edge="1" parent="1" source="uTcgnOhw-1hiChn4308x-327" style="startArrow=none;endArrow=block;exitX=0.43;exitY=0;entryX=1.005;entryY=0.163;rounded=0;edgeStyle=orthogonalEdgeStyle;entryDx=0;entryDy=0;entryPerimeter=0;" target="uTcgnOhw-1hiChn4308x-323" value="higher">
|
||||
<mxGeometry relative="1" x="0.4124" y="-3" as="geometry">
|
||||
<mxPoint as="offset" />
|
||||
<Array as="points">
|
||||
<mxPoint x="600" y="285" />
|
||||
</Array>
|
||||
</mxGeometry>
|
||||
</mxCell>
|
||||
<mxCell id="uTcgnOhw-1hiChn4308x-347" edge="1" parent="1" source="uTcgnOhw-1hiChn4308x-327" style="endArrow=classic;html=1;rounded=0;exitX=0.677;exitY=0.972;exitDx=0;exitDy=0;exitPerimeter=0;entryX=0.709;entryY=0.015;entryDx=0;entryDy=0;entryPerimeter=0;curved=1;" target="uTcgnOhw-1hiChn4308x-327" value="">
|
||||
<mxGeometry height="50" relative="1" width="50" as="geometry">
|
||||
<Array as="points">
|
||||
<mxPoint x="710" y="380" />
|
||||
<mxPoint x="730" y="330" />
|
||||
<mxPoint x="730" y="300" />
|
||||
<mxPoint x="720" y="260" />
|
||||
</Array>
|
||||
<mxPoint x="657" y="460" as="sourcePoint" />
|
||||
<mxPoint x="707" y="410" as="targetPoint" />
|
||||
</mxGeometry>
|
||||
</mxCell>
|
||||
<mxCell id="uTcgnOhw-1hiChn4308x-348" connectable="0" parent="uTcgnOhw-1hiChn4308x-347" style="edgeLabel;html=1;align=center;verticalAlign=middle;resizable=0;points=[];" value="before" vertex="1">
|
||||
<mxGeometry relative="1" x="-0.0353" as="geometry">
|
||||
<mxPoint as="offset" />
|
||||
</mxGeometry>
|
||||
</mxCell>
|
||||
<mxCell id="uTcgnOhw-1hiChn4308x-351" edge="1" parent="1" source="uTcgnOhw-1hiChn4308x-327" style="curved=1;endArrow=classic;html=1;rounded=0;entryX=0.5;entryY=1;entryDx=0;entryDy=0;exitX=0.5;exitY=0;exitDx=0;exitDy=0;" target="uTcgnOhw-1hiChn4308x-327" value="">
|
||||
<mxGeometry height="50" relative="1" width="50" as="geometry">
|
||||
<Array as="points">
|
||||
<mxPoint x="750" y="230" />
|
||||
<mxPoint x="750" y="400" />
|
||||
<mxPoint x="590" y="390" />
|
||||
</Array>
|
||||
<mxPoint x="570" y="484" as="sourcePoint" />
|
||||
<mxPoint x="620" y="434" as="targetPoint" />
|
||||
</mxGeometry>
|
||||
</mxCell>
|
||||
<mxCell id="uTcgnOhw-1hiChn4308x-352" connectable="0" parent="uTcgnOhw-1hiChn4308x-351" style="edgeLabel;html=1;align=center;verticalAlign=middle;resizable=0;points=[];" value="behind" vertex="1">
|
||||
<mxGeometry relative="1" x="0.0226" y="-5" as="geometry">
|
||||
<mxPoint as="offset" />
|
||||
</mxGeometry>
|
||||
</mxCell>
|
||||
</root>
|
||||
</mxGraphModel>
|
||||
</diagram>
|
||||
<diagram id="5RVJ2jwpCgCB7Hji9JxO" name="菜单树判断逻辑">
|
||||
<mxGraphModel dx="2720" dy="2649" grid="1" gridSize="10" guides="1" tooltips="1" connect="1" arrows="1" fold="1" page="1" pageScale="1" pageWidth="827" pageHeight="1169" math="0" shadow="0">
|
||||
<root>
|
||||
<mxCell id="0" />
|
||||
<mxCell id="1" parent="0" />
|
||||
<mxCell id="HZ-q9eH6YA3dt1o5nvaf-1" parent="1" style="whiteSpace=wrap;strokeWidth=2;fillColor=light-dark(#eeeeee,#1f2020);strokeColor=light-dark(#999999,#cccccc);fontColor=light-dark(#333333,#cccccc);" value="byCurClass < byNextClass" vertex="1">
|
||||
<mxGeometry height="54" width="241" x="250" y="380" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="HZ-q9eH6YA3dt1o5nvaf-2" parent="1" style="whiteSpace=wrap;strokeWidth=2;fillColor=light-dark(#eeeeee,#1f2020);strokeColor=light-dark(#999999,#cccccc);fontColor=light-dark(#333333,#cccccc);" value="进入子菜单" vertex="1">
|
||||
<mxGeometry height="54" width="140" x="510" y="380" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="HZ-q9eH6YA3dt1o5nvaf-3" parent="1" style="whiteSpace=wrap;strokeWidth=2;fillColor=light-dark(#eeeeee,#1f2020);strokeColor=light-dark(#999999,#cccccc);fontColor=light-dark(#333333,#cccccc);" value="current.lower = next" vertex="1">
|
||||
<mxGeometry height="54" width="190" x="670" y="380" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="HZ-q9eH6YA3dt1o5nvaf-4" parent="1" style="whiteSpace=wrap;strokeWidth=2;fillColor=light-dark(#eeeeee,#1f2020);strokeColor=light-dark(#999999,#cccccc);fontColor=light-dark(#333333,#cccccc);" value="next.higher = current" vertex="1">
|
||||
<mxGeometry height="54" width="209" x="891" y="380" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="HZ-q9eH6YA3dt1o5nvaf-5" parent="1" style="whiteSpace=wrap;strokeWidth=2;fillColor=light-dark(#eeeeee,#1f2020);strokeColor=light-dark(#999999,#cccccc);fontColor=light-dark(#333333,#cccccc);" value="byCurClass == byNextClass" vertex="1">
|
||||
<mxGeometry height="54" width="240" x="250" y="484" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="HZ-q9eH6YA3dt1o5nvaf-6" parent="1" style="whiteSpace=wrap;strokeWidth=2;fillColor=light-dark(#eeeeee,#1f2020);strokeColor=light-dark(#999999,#cccccc);fontColor=light-dark(#333333,#cccccc);" value="同层兄弟" vertex="1">
|
||||
<mxGeometry height="54" width="140" x="511" y="484" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="HZ-q9eH6YA3dt1o5nvaf-7" parent="1" style="whiteSpace=wrap;strokeWidth=2;fillColor=light-dark(#eeeeee,#1f2020);strokeColor=light-dark(#999999,#cccccc);fontColor=light-dark(#333333,#cccccc);" value="current.behind = next" vertex="1">
|
||||
<mxGeometry height="54" width="190" x="670" y="484" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="HZ-q9eH6YA3dt1o5nvaf-8" parent="1" style="whiteSpace=wrap;strokeWidth=2;fillColor=light-dark(#eeeeee,#1f2020);strokeColor=light-dark(#999999,#cccccc);fontColor=light-dark(#333333,#cccccc);" value="next.before = current" vertex="1">
|
||||
<mxGeometry height="54" width="210" x="890" y="484" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="HZ-q9eH6YA3dt1o5nvaf-9" parent="1" style="whiteSpace=wrap;strokeWidth=2;fillColor=light-dark(#eeeeee,#1f2020);strokeColor=light-dark(#999999,#cccccc);fontColor=light-dark(#333333,#cccccc);" value="next.higher = current.higher" vertex="1">
|
||||
<mxGeometry height="78" width="260" x="1140" y="472" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="HZ-q9eH6YA3dt1o5nvaf-10" parent="1" style="whiteSpace=wrap;strokeWidth=2;fillColor=light-dark(#eeeeee,#1f2020);strokeColor=light-dark(#999999,#cccccc);fontColor=light-dark(#333333,#cccccc);" value="byCurClass > byNextClass" vertex="1">
|
||||
<mxGeometry height="54" width="241" x="250" y="591" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="HZ-q9eH6YA3dt1o5nvaf-11" parent="1" style="whiteSpace=wrap;strokeWidth=2;fillColor=light-dark(#eeeeee,#1f2020);strokeColor=light-dark(#999999,#cccccc);fontColor=light-dark(#333333,#cccccc);" value="从深层回退" vertex="1">
|
||||
<mxGeometry height="54" width="140" x="510" y="591" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="HZ-q9eH6YA3dt1o5nvaf-12" parent="1" style="whiteSpace=wrap;strokeWidth=2;fillColor=light-dark(#eeeeee,#1f2020);strokeColor=light-dark(#999999,#cccccc);fontColor=light-dark(#333333,#cccccc);" value="next.before = ptLast[nextClass]" vertex="1">
|
||||
<mxGeometry height="54" width="190" x="670" y="591" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="HZ-q9eH6YA3dt1o5nvaf-27" edge="1" parent="1" source="HZ-q9eH6YA3dt1o5nvaf-13" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;" target="HZ-q9eH6YA3dt1o5nvaf-26" value="">
|
||||
<mxGeometry relative="1" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="HZ-q9eH6YA3dt1o5nvaf-13" parent="1" style="whiteSpace=wrap;strokeWidth=2;fillColor=light-dark(#eeeeee,#1f2020);strokeColor=light-dark(#999999,#cccccc);fontColor=light-dark(#333333,#cccccc);" value="ptLast[nextClass].behind=next" vertex="1">
|
||||
<mxGeometry height="54" width="190" x="1140" y="591" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="HZ-q9eH6YA3dt1o5nvaf-14" edge="1" parent="1" source="HZ-q9eH6YA3dt1o5nvaf-1" style="curved=1;startArrow=none;endArrow=block;exitX=1;exitY=0.5;entryX=0;entryY=0.5;rounded=0;" target="HZ-q9eH6YA3dt1o5nvaf-2" value="">
|
||||
<mxGeometry relative="1" as="geometry">
|
||||
<Array as="points" />
|
||||
</mxGeometry>
|
||||
</mxCell>
|
||||
<mxCell id="HZ-q9eH6YA3dt1o5nvaf-15" edge="1" parent="1" source="HZ-q9eH6YA3dt1o5nvaf-2" style="curved=1;startArrow=none;endArrow=block;exitX=1;exitY=0.5;entryX=0;entryY=0.5;rounded=0;" target="HZ-q9eH6YA3dt1o5nvaf-3" value="">
|
||||
<mxGeometry relative="1" as="geometry">
|
||||
<Array as="points" />
|
||||
</mxGeometry>
|
||||
</mxCell>
|
||||
<mxCell id="HZ-q9eH6YA3dt1o5nvaf-16" edge="1" parent="1" source="HZ-q9eH6YA3dt1o5nvaf-3" style="curved=1;startArrow=none;endArrow=block;exitX=1;exitY=0.5;entryX=0;entryY=0.5;rounded=0;" target="HZ-q9eH6YA3dt1o5nvaf-4" value="">
|
||||
<mxGeometry relative="1" as="geometry">
|
||||
<Array as="points" />
|
||||
</mxGeometry>
|
||||
</mxCell>
|
||||
<mxCell id="HZ-q9eH6YA3dt1o5nvaf-17" edge="1" parent="1" source="HZ-q9eH6YA3dt1o5nvaf-5" style="curved=1;startArrow=none;endArrow=block;exitX=1;exitY=0.5;entryX=0;entryY=0.5;rounded=0;" target="HZ-q9eH6YA3dt1o5nvaf-6" value="">
|
||||
<mxGeometry relative="1" as="geometry">
|
||||
<Array as="points" />
|
||||
</mxGeometry>
|
||||
</mxCell>
|
||||
<mxCell id="HZ-q9eH6YA3dt1o5nvaf-18" edge="1" parent="1" source="HZ-q9eH6YA3dt1o5nvaf-6" style="curved=1;startArrow=none;endArrow=block;exitX=1;exitY=0.5;entryX=0;entryY=0.5;rounded=0;" target="HZ-q9eH6YA3dt1o5nvaf-7" value="">
|
||||
<mxGeometry relative="1" as="geometry">
|
||||
<Array as="points" />
|
||||
</mxGeometry>
|
||||
</mxCell>
|
||||
<mxCell id="HZ-q9eH6YA3dt1o5nvaf-19" edge="1" parent="1" source="HZ-q9eH6YA3dt1o5nvaf-7" style="curved=1;startArrow=none;endArrow=block;exitX=1;exitY=0.5;entryX=0;entryY=0.5;rounded=0;" target="HZ-q9eH6YA3dt1o5nvaf-8" value="">
|
||||
<mxGeometry relative="1" as="geometry">
|
||||
<Array as="points" />
|
||||
</mxGeometry>
|
||||
</mxCell>
|
||||
<mxCell id="HZ-q9eH6YA3dt1o5nvaf-20" edge="1" parent="1" source="HZ-q9eH6YA3dt1o5nvaf-8" style="curved=1;startArrow=none;endArrow=block;exitX=1;exitY=0.5;entryX=0;entryY=0.5;rounded=0;" target="HZ-q9eH6YA3dt1o5nvaf-9" value="">
|
||||
<mxGeometry relative="1" as="geometry">
|
||||
<Array as="points" />
|
||||
</mxGeometry>
|
||||
</mxCell>
|
||||
<mxCell id="HZ-q9eH6YA3dt1o5nvaf-21" edge="1" parent="1" source="HZ-q9eH6YA3dt1o5nvaf-10" style="curved=1;startArrow=none;endArrow=block;exitX=1;exitY=0.5;entryX=0;entryY=0.5;rounded=0;" target="HZ-q9eH6YA3dt1o5nvaf-11" value="">
|
||||
<mxGeometry relative="1" as="geometry">
|
||||
<Array as="points" />
|
||||
</mxGeometry>
|
||||
</mxCell>
|
||||
<mxCell id="HZ-q9eH6YA3dt1o5nvaf-22" edge="1" parent="1" source="HZ-q9eH6YA3dt1o5nvaf-11" style="curved=1;startArrow=none;endArrow=block;exitX=1;exitY=0.5;entryX=0;entryY=0.5;rounded=0;" target="HZ-q9eH6YA3dt1o5nvaf-12" value="">
|
||||
<mxGeometry relative="1" as="geometry">
|
||||
<Array as="points" />
|
||||
</mxGeometry>
|
||||
</mxCell>
|
||||
<mxCell id="HZ-q9eH6YA3dt1o5nvaf-23" edge="1" parent="1" source="HZ-q9eH6YA3dt1o5nvaf-12" style="curved=1;startArrow=none;endArrow=block;exitX=1;exitY=0.5;rounded=0;exitDx=0;exitDy=0;" value="">
|
||||
<mxGeometry relative="1" as="geometry">
|
||||
<Array as="points" />
|
||||
<mxPoint x="890" y="618" as="targetPoint" />
|
||||
</mxGeometry>
|
||||
</mxCell>
|
||||
<mxCell id="HZ-q9eH6YA3dt1o5nvaf-25" edge="1" parent="1" source="HZ-q9eH6YA3dt1o5nvaf-24" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;" target="HZ-q9eH6YA3dt1o5nvaf-13" value="">
|
||||
<mxGeometry relative="1" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="HZ-q9eH6YA3dt1o5nvaf-24" parent="1" style="whiteSpace=wrap;strokeWidth=2;fillColor=light-dark(#eeeeee,#1f2020);strokeColor=light-dark(#999999,#cccccc);fontColor=light-dark(#333333,#cccccc);" value="next.behind=ptLast[nextClass].higher" vertex="1">
|
||||
<mxGeometry height="54" width="209" x="891" y="591" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="HZ-q9eH6YA3dt1o5nvaf-26" parent="1" style="whiteSpace=wrap;html=1;fillColor=light-dark(#eeeeee,#1f2020);strokeColor=light-dark(#999999,#cccccc);fontColor=light-dark(#333333,#cccccc);strokeWidth=2;" value="形成回环" vertex="1">
|
||||
<mxGeometry height="55" width="69" x="1351" y="591" as="geometry" />
|
||||
</mxCell>
|
||||
</root>
|
||||
</mxGraphModel>
|
||||
</diagram>
|
||||
<diagram id="zG06bic9s9vmrHAmIL5z" name="第 8 页">
|
||||
<mxGraphModel dx="628" dy="611" grid="1" gridSize="10" guides="1" tooltips="1" connect="1" arrows="1" fold="1" page="1" pageScale="1" pageWidth="827" pageHeight="1169" math="0" shadow="0">
|
||||
<root>
|
||||
<mxCell id="0" />
|
||||
<mxCell id="1" parent="0" />
|
||||
<mxCell id="4pC_cjyCFYns3d6entyY-21" edge="1" parent="1" source="4pC_cjyCFYns3d6entyY-1" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;entryX=0.5;entryY=0;entryDx=0;entryDy=0;" target="4pC_cjyCFYns3d6entyY-3">
|
||||
<mxGeometry relative="1" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="4pC_cjyCFYns3d6entyY-1" parent="1" style="whiteSpace=wrap;strokeWidth=2;fillColor=light-dark(#eeeeee,#1f2020);strokeColor=light-dark(#999999,#cccccc);fontColor=light-dark(#333333,#cccccc);rounded=1;" value="开始" vertex="1">
|
||||
<mxGeometry height="24" width="210" x="320" y="120" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="4pC_cjyCFYns3d6entyY-3" parent="1" style="whiteSpace=wrap;strokeWidth=2;fillColor=light-dark(#eeeeee,#1f2020);strokeColor=light-dark(#999999,#cccccc);fontColor=light-dark(#333333,#cccccc);" value="计算菜单个数 maxItem " vertex="1">
|
||||
<mxGeometry height="30" width="210" x="320" y="171" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="4pC_cjyCFYns3d6entyY-5" parent="1" style="whiteSpace=wrap;strokeWidth=2;fillColor=light-dark(#eeeeee,#1f2020);strokeColor=light-dark(#999999,#cccccc);fontColor=light-dark(#333333,#cccccc);" value="统计 0 级菜单数量 by0LevelNum " vertex="1">
|
||||
<mxGeometry height="30" width="210" x="320" y="225" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="4pC_cjyCFYns3d6entyY-6" parent="1" style="whiteSpace=wrap;strokeWidth=2;fillColor=light-dark(#eeeeee,#1f2020);strokeColor=light-dark(#999999,#cccccc);fontColor=light-dark(#333333,#cccccc);" value="构建菜单树链路" vertex="1">
|
||||
<mxGeometry height="30" width="210" x="320" y="279" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="4pC_cjyCFYns3d6entyY-7" parent="1" style="whiteSpace=wrap;strokeWidth=2;fillColor=light-dark(#eeeeee,#1f2020);strokeColor=light-dark(#999999,#cccccc);fontColor=light-dark(#333333,#cccccc);" value="补充带子菜单项的右侧箭头》" vertex="1">
|
||||
<mxGeometry height="40" width="210" x="320" y="328" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="4pC_cjyCFYns3d6entyY-8" parent="1" style="whiteSpace=wrap;strokeWidth=2;fillColor=light-dark(#eeeeee,#1f2020);strokeColor=light-dark(#999999,#cccccc);fontColor=light-dark(#333333,#cccccc);" value="计算各节点 wPos 与 wNum" vertex="1">
|
||||
<mxGeometry height="30" width="210" x="320" y="387" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="4pC_cjyCFYns3d6entyY-9" parent="1" style="whiteSpace=wrap;strokeWidth=2;fillColor=light-dark(#eeeeee,#1f2020);strokeColor=light-dark(#999999,#cccccc);fontColor=light-dark(#333333,#cccccc);" value="计算整棵菜单树的显示位置" vertex="1">
|
||||
<mxGeometry height="30" width="210" x="320" y="441" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="4pC_cjyCFYns3d6entyY-10" parent="1" style="whiteSpace=wrap;strokeWidth=2;fillColor=light-dark(#eeeeee,#1f2020);strokeColor=light-dark(#999999,#cccccc);fontColor=light-dark(#333333,#cccccc);rounded=1;" value="函数结束" vertex="1">
|
||||
<mxGeometry height="20" width="210" x="320" y="500" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="4pC_cjyCFYns3d6entyY-13" edge="1" parent="1" source="4pC_cjyCFYns3d6entyY-3" style="curved=1;startArrow=none;endArrow=block;exitX=0.5;exitY=1;entryX=0.5;entryY=0;rounded=0;entryDx=0;entryDy=0;" target="4pC_cjyCFYns3d6entyY-5" value="">
|
||||
<mxGeometry relative="1" as="geometry">
|
||||
<Array as="points" />
|
||||
<mxPoint x="426" y="470" as="targetPoint" />
|
||||
</mxGeometry>
|
||||
</mxCell>
|
||||
<mxCell id="4pC_cjyCFYns3d6entyY-15" edge="1" parent="1" source="4pC_cjyCFYns3d6entyY-5" style="curved=1;startArrow=none;endArrow=block;exitX=0.5;exitY=1;entryX=0.5;entryY=0;rounded=0;" target="4pC_cjyCFYns3d6entyY-6" value="">
|
||||
<mxGeometry relative="1" as="geometry">
|
||||
<Array as="points" />
|
||||
</mxGeometry>
|
||||
</mxCell>
|
||||
<mxCell id="4pC_cjyCFYns3d6entyY-16" edge="1" parent="1" source="4pC_cjyCFYns3d6entyY-6" style="curved=1;startArrow=none;endArrow=block;exitX=0.5;exitY=1;entryX=0.5;entryY=0;rounded=0;" target="4pC_cjyCFYns3d6entyY-7" value="">
|
||||
<mxGeometry relative="1" as="geometry">
|
||||
<Array as="points" />
|
||||
</mxGeometry>
|
||||
</mxCell>
|
||||
<mxCell id="4pC_cjyCFYns3d6entyY-17" edge="1" parent="1" source="4pC_cjyCFYns3d6entyY-7" style="curved=1;startArrow=none;endArrow=block;exitX=0.5;exitY=1;entryX=0.5;entryY=0;rounded=0;" target="4pC_cjyCFYns3d6entyY-8" value="">
|
||||
<mxGeometry relative="1" as="geometry">
|
||||
<Array as="points" />
|
||||
</mxGeometry>
|
||||
</mxCell>
|
||||
<mxCell id="4pC_cjyCFYns3d6entyY-18" edge="1" parent="1" source="4pC_cjyCFYns3d6entyY-8" style="curved=1;startArrow=none;endArrow=block;exitX=0.5;exitY=1;entryX=0.5;entryY=0;rounded=0;" target="4pC_cjyCFYns3d6entyY-9" value="">
|
||||
<mxGeometry relative="1" as="geometry">
|
||||
<Array as="points" />
|
||||
</mxGeometry>
|
||||
</mxCell>
|
||||
<mxCell id="4pC_cjyCFYns3d6entyY-19" edge="1" parent="1" source="4pC_cjyCFYns3d6entyY-9" style="curved=1;startArrow=none;endArrow=block;exitX=0.5;exitY=1;entryX=0.5;entryY=0;rounded=0;" target="4pC_cjyCFYns3d6entyY-10" value="">
|
||||
<mxGeometry relative="1" as="geometry">
|
||||
<Array as="points" />
|
||||
</mxGeometry>
|
||||
</mxCell>
|
||||
</root>
|
||||
</mxGraphModel>
|
||||
</diagram>
|
||||
</mxfile>
|
||||
@@ -1,6 +1,8 @@
|
||||
#ifndef __TYPES__H__
|
||||
#define __TYPES__H__
|
||||
|
||||
#include <stdio.h>
|
||||
|
||||
#define uint8_t unsigned char
|
||||
#define uint16_t unsigned short
|
||||
#define uint32_t unsigned int
|
||||
@@ -15,4 +17,18 @@
|
||||
typedef int (*FUNCPTR) ( );
|
||||
|
||||
|
||||
// 调试模式断言
|
||||
#ifdef DEBUG
|
||||
#define ASSERT(expr) \
|
||||
do { \
|
||||
if (expr) { \
|
||||
printf("Assertion failed: %s, file %s, line %d\n", \
|
||||
#expr, __FILE__, __LINE__); \
|
||||
} \
|
||||
} while (0)
|
||||
#else
|
||||
#define ASSERT(expr) ((void)0) // 发布模式禁用断言
|
||||
#endif
|
||||
|
||||
|
||||
#endif
|
||||
|
||||
@@ -1,191 +1,54 @@
|
||||
#include "menu.h"
|
||||
|
||||
#include <stddef.h>
|
||||
#include <stdio.h>
|
||||
#include "../../pages/menu/page.h"
|
||||
|
||||
#include "../model/display.h"
|
||||
#include "../../key.h"
|
||||
#include "../view/menu_layout.h"
|
||||
#include "../presenter/menu_presenter.h"
|
||||
#include "../view/menu_render_port.h"
|
||||
#include "../common/menu_state_types.h"
|
||||
|
||||
/* -------------------------------------------------------------------------
|
||||
* 运行态状态收口(App 层私有)
|
||||
*
|
||||
* 设计意图:
|
||||
* 1) 避免全局变量四处散落,统一由 app/menu.c 托管生命周期;
|
||||
* 2) 外部模块只能经由 MenuApp_* 接口访问,降低耦合;
|
||||
* 3) Presenter/Model/View 依赖都在初始化时一次性装配。
|
||||
*
|
||||
* 变量职责:
|
||||
* - s_dspCtrl:
|
||||
* 显示刷新控制状态(例如首帧标记 bFirst)。
|
||||
* - s_menuCtrl:
|
||||
* 导航与路由控制状态(当前节点、各级 route、0 级入口等)。
|
||||
* - s_menuItems[300]:
|
||||
* 运行时菜单节点池(树构建后常驻内存),由 Model 初始化、View 读取布局/渲染。
|
||||
* - g_menuPresenter:
|
||||
* MVP 调度中枢实例,封装输入处理与刷新协同。
|
||||
* - g_menuLayoutConfig:
|
||||
* 布局参数快照(屏幕尺寸、行高、边距、菜单宽度等),初始化时写入一次。
|
||||
* ------------------------------------------------------------------------- */
|
||||
static tagDspCtrl s_dspCtrl;
|
||||
static tagMenuCtrl s_menuCtrl;
|
||||
static tagMenuItem s_menuItems[300];
|
||||
|
||||
static MenuPresenter g_menuPresenter;
|
||||
static MenuLayoutConfig g_menuLayoutConfig;
|
||||
|
||||
void MenuApp_Render(void)
|
||||
void MenuProc_See_AppInfo(void)
|
||||
{
|
||||
/* 统一渲染入口:由 Presenter 决定全量/增量刷新策略。 */
|
||||
MenuPresenter_Refresh(&g_menuPresenter);
|
||||
MenuPage_TriggerCurrentAction();
|
||||
}
|
||||
|
||||
void MenuApp_HandleInput(uint8_t keyVal)
|
||||
void MenuProc_See_YC(void)
|
||||
{
|
||||
/* 单次输入事件入口:按键解释与状态迁移都在 Presenter 内完成。 */
|
||||
MenuPresenter_HandleInput(&g_menuPresenter, keyVal);
|
||||
MenuPage_TriggerCurrentAction();
|
||||
}
|
||||
|
||||
void MenuApp_PollInput(void)
|
||||
{
|
||||
/* 轮询输入设备(键盘/按键),再复用事件处理入口。 */
|
||||
MenuApp_HandleInput(Key_Read());
|
||||
}
|
||||
void MenuProc_Set_Value(void) { MenuPage_TriggerCurrentAction(); }
|
||||
void MenuProc_Set_Soft(void) { MenuPage_TriggerCurrentAction(); }
|
||||
void MenuProc_Cfg_Time(void) { MenuPage_TriggerCurrentAction(); }
|
||||
void MenuProc_Cfg_RevEvent(void) { MenuPage_TriggerCurrentAction(); }
|
||||
void MenuProc_Cfg_ManualWave(void) { MenuPage_TriggerCurrentAction(); }
|
||||
void MenuProc_See_Input(void) { MenuPage_TriggerCurrentAction(); }
|
||||
void MenuProc_See_Set(void) { MenuPage_TriggerCurrentAction(); }
|
||||
void MenuProc_See_Soft(void) { MenuPage_TriggerCurrentAction(); }
|
||||
void MenuProc_YX_SetCommType(void) { MenuPage_TriggerCurrentAction(); }
|
||||
void MenuProc_YX_SetWidth(void) { MenuPage_TriggerCurrentAction(); }
|
||||
void MenuProc_YX_SetTwin(void) { MenuPage_TriggerCurrentAction(); }
|
||||
|
||||
void MenuApp_Init(void)
|
||||
{
|
||||
const MenuRenderPort *renderPort = MenuRenderPort_Lcd();
|
||||
uint16_t lcdSizeX;
|
||||
uint16_t lcdSizeY;
|
||||
|
||||
/* 1) 初始化底层渲染设备(LCD/模拟器具体实现由 renderPort 决定)。 */
|
||||
renderPort->init();
|
||||
|
||||
/* 2) 通过渲染端口查询设备能力,避免 app 层直接依赖 lcd.h 宏。 */
|
||||
lcdSizeX = renderPort->get_size_x();
|
||||
lcdSizeY = renderPort->get_size_y();
|
||||
|
||||
/* 3) 组装布局配置(后续由 View 布局计算与渲染阶段复用)。 */
|
||||
g_menuLayoutConfig.lcdSizeX = lcdSizeX;
|
||||
g_menuLayoutConfig.lcdSizeY = lcdSizeY;
|
||||
g_menuLayoutConfig.menuYMin = MENU_YMIN;
|
||||
g_menuLayoutConfig.menuYMax = MENU_YMAX_FROM_LCD(lcdSizeY);
|
||||
g_menuLayoutConfig.lineHeight = LINE_HEIGHT;
|
||||
g_menuLayoutConfig.menuWidth = MENU_WITDTH;
|
||||
g_menuLayoutConfig.menuXAdd = MENU_XADD;
|
||||
g_menuLayoutConfig.menuYAdd = MENU_YADD;
|
||||
|
||||
/* 4) 装配 Presenter:注入控制状态、菜单池、渲染端口与布局配置。 */
|
||||
MenuPresenter_Setup(&g_menuPresenter,
|
||||
&s_dspCtrl,
|
||||
&s_menuCtrl,
|
||||
s_menuItems,
|
||||
renderPort,
|
||||
&g_menuLayoutConfig);
|
||||
|
||||
/* 5) 初始化模型数据并建立初始路由/布局。 */
|
||||
MenuPresenter_InitModel(&g_menuPresenter, g_tMenuModelTab, MENU_MAX_ITEM);
|
||||
}
|
||||
|
||||
const tagMenuItem *MenuApp_GetMenuItems(uint16_t *count)
|
||||
{
|
||||
/* 诊断/测试只读访问接口:
|
||||
* - 返回内部菜单池首地址;
|
||||
* - 若 count 非空,同步返回数组容量。 */
|
||||
if (count != NULL)
|
||||
{
|
||||
*count = (uint16_t)(sizeof(s_menuItems) / sizeof(s_menuItems[0]));
|
||||
}
|
||||
return s_menuItems;
|
||||
}
|
||||
|
||||
void MenuProc_See_AppInfo()
|
||||
{
|
||||
printf("MenuProc_See_AppInfo\n");
|
||||
}
|
||||
|
||||
void MenuProc_See_YC()
|
||||
{
|
||||
printf("MenuProc_See_YC\n");
|
||||
}
|
||||
void MenuProc_Set_Value()
|
||||
{
|
||||
printf("MenuProc_Set_Value\n");
|
||||
}
|
||||
void MenuProc_Set_Soft()
|
||||
{
|
||||
printf("MenuProc_Set_Soft\n");
|
||||
}
|
||||
void MenuProc_Cfg_Time()
|
||||
{
|
||||
printf("MenuProc_Cfg_Time\n");
|
||||
}
|
||||
void MenuProc_Cfg_RevEvent()
|
||||
{
|
||||
printf("MenuProc_Cfg_RevEvent\n");
|
||||
}
|
||||
void MenuProc_Cfg_ManualWave()
|
||||
{
|
||||
printf("MenuProc_Cfg_ManualWave\n");
|
||||
}
|
||||
void MenuProc_See_Input()
|
||||
{
|
||||
printf("MenuProc_See_Input\n");
|
||||
}
|
||||
|
||||
void MenuProc_See_Set()
|
||||
{
|
||||
printf("MenuProc_See_Set\n");
|
||||
}
|
||||
|
||||
void MenuProc_See_Soft()
|
||||
{
|
||||
printf("MenuProc_See_Soft\n");
|
||||
}
|
||||
void MenuProc_YX_SetCommType()
|
||||
{
|
||||
printf("MenuProc_YX_SetCommType\n");
|
||||
}
|
||||
|
||||
void MenuProc_YX_SetWidth()
|
||||
{
|
||||
printf("MenuProc_YX_SetWidth\n");
|
||||
}
|
||||
|
||||
void MenuProc_YX_SetTwin()
|
||||
{
|
||||
printf("MenuProc_YX_SetTwin\n");
|
||||
}
|
||||
|
||||
/* ---------- 以下为 display.c 菜单表引用、暂无具体实现的桩函数,避免链接未定义符号 ---------- */
|
||||
void Menu_NonPfunc(void) { printf("Menu_NonPfunc\n"); }
|
||||
void MenuProc_AllInf_Default(void) { printf("MenuProc_AllInf_Default\n"); }
|
||||
void MenuProc_Para_Default(void) { printf("MenuProc_Para_Default\n"); }
|
||||
void MenuProc_Set_Default(void) { printf("MenuProc_Set_Default\n"); }
|
||||
void MenuProc_Resume_Soft(void) { printf("MenuProc_Resume_Soft\n"); }
|
||||
void MenuProc_Cfg_CellDef(void) { printf("MenuProc_Cfg_CellDef\n"); }
|
||||
void MenuProc_Cfg_ShowAnaType(void) { printf("MenuProc_Cfg_ShowAnaType\n"); }
|
||||
void MenuProc_Dbg_XuYX(void) { printf("MenuProc_Dbg_XuYX\n"); }
|
||||
void MenuProc_Dbg_XuYC(void) { printf("MenuProc_Dbg_XuYC\n"); }
|
||||
void MenuProc_Dbg_XuEvent(void) { printf("MenuProc_Dbg_XuEvent\n"); }
|
||||
void MenuProc_Dbg_Relay(void) { printf("MenuProc_Dbg_Relay\n"); }
|
||||
void MenuProc_See_VersionBoard(void) { printf("MenuProc_See_VersionBoard\n"); }
|
||||
void MenuProc_Cfg_ClrRec(void) { printf("MenuProc_Cfg_ClrRec\n"); }
|
||||
void MenuProc_Cfg_ComPara(void) { printf("MenuProc_Cfg_ComPara\n"); }
|
||||
void MenuProc_Cfg_EditIP(void) { printf("MenuProc_Cfg_EditIP\n"); }
|
||||
void MenuProc_Cfg_EditSntp(void) { printf("MenuProc_Cfg_EditSntp\n"); }
|
||||
void MenuProc_Cfg_CellConf(void) { printf("MenuProc_Cfg_CellConf\n"); }
|
||||
void MenuProc_See_RecSOE(void) { printf("MenuProc_See_RecSOE\n"); }
|
||||
void MenuProc_See_RecAct(void) { printf("MenuProc_See_RecAct\n"); }
|
||||
void MenuProc_See_RecOpt(void) { printf("MenuProc_See_RecOpt\n"); }
|
||||
void MenuProc_See_RecAlm(void) { printf("MenuProc_See_RecAlm\n"); }
|
||||
void MenuProc_See_RecStart(void) { printf("MenuProc_See_RecStart\n"); }
|
||||
void MenuProc_See_RecYK(void) { printf("MenuProc_See_RecYK\n"); }
|
||||
void MenuProc_See_RecChk(void) { printf("MenuProc_See_RecChk\n"); }
|
||||
void MenuProc_See_RecRun(void) { printf("MenuProc_See_RecRun\n"); }
|
||||
void MenuProc_See_RecFault(void) { printf("MenuProc_See_RecFault\n"); }
|
||||
void MenuProc_YC_SetSqValue(void) { printf("MenuProc_YC_SetSqValue\n"); }
|
||||
void MenuProc_YC_SetAdjCoe(void) { printf("MenuProc_YC_SetAdjCoe\n"); }
|
||||
void Menu_NonPfunc(void) {}
|
||||
void MenuProc_AllInf_Default(void) { MenuPage_TriggerCurrentAction(); }
|
||||
void MenuProc_Para_Default(void) { MenuPage_TriggerCurrentAction(); }
|
||||
void MenuProc_Set_Default(void) { MenuPage_TriggerCurrentAction(); }
|
||||
void MenuProc_Resume_Soft(void) { MenuPage_TriggerCurrentAction(); }
|
||||
void MenuProc_Cfg_CellDef(void) { MenuPage_TriggerCurrentAction(); }
|
||||
void MenuProc_Cfg_ShowAnaType(void) { MenuPage_TriggerCurrentAction(); }
|
||||
void MenuProc_Dbg_XuYX(void) { MenuPage_TriggerCurrentAction(); }
|
||||
void MenuProc_Dbg_XuYC(void) { MenuPage_TriggerCurrentAction(); }
|
||||
void MenuProc_Dbg_XuEvent(void) { MenuPage_TriggerCurrentAction(); }
|
||||
void MenuProc_Dbg_Relay(void) { MenuPage_TriggerCurrentAction(); }
|
||||
void MenuProc_See_VersionBoard(void) { MenuPage_TriggerCurrentAction(); }
|
||||
void MenuProc_Cfg_ClrRec(void) { MenuPage_TriggerCurrentAction(); }
|
||||
void MenuProc_Cfg_ComPara(void) { MenuPage_TriggerCurrentAction(); }
|
||||
void MenuProc_Cfg_EditIP(void) { MenuPage_TriggerCurrentAction(); }
|
||||
void MenuProc_Cfg_EditSntp(void) { MenuPage_TriggerCurrentAction(); }
|
||||
void MenuProc_Cfg_CellConf(void) { MenuPage_TriggerCurrentAction(); }
|
||||
void MenuProc_See_RecSOE(void) { MenuPage_TriggerCurrentAction(); }
|
||||
void MenuProc_See_RecAct(void) { MenuPage_TriggerCurrentAction(); }
|
||||
void MenuProc_See_RecOpt(void) { MenuPage_TriggerCurrentAction(); }
|
||||
void MenuProc_See_RecAlm(void) { MenuPage_TriggerCurrentAction(); }
|
||||
void MenuProc_See_RecStart(void) { MenuPage_TriggerCurrentAction(); }
|
||||
void MenuProc_See_RecYK(void) { MenuPage_TriggerCurrentAction(); }
|
||||
void MenuProc_See_RecChk(void) { MenuPage_TriggerCurrentAction(); }
|
||||
void MenuProc_See_RecRun(void) { MenuPage_TriggerCurrentAction(); }
|
||||
void MenuProc_See_RecFault(void) { MenuPage_TriggerCurrentAction(); }
|
||||
void MenuProc_YC_SetSqValue(void) { MenuPage_TriggerCurrentAction(); }
|
||||
void MenuProc_YC_SetAdjCoe(void) { MenuPage_TriggerCurrentAction(); }
|
||||
|
||||
@@ -10,14 +10,6 @@
|
||||
* - 结构采用 MVP:Model(状态) / View(渲染) / Presenter(调度)
|
||||
*/
|
||||
|
||||
#include "../common/menu_item_types.h"
|
||||
|
||||
void MenuApp_Init(void);
|
||||
void MenuApp_HandleInput(uint8_t keyVal);
|
||||
void MenuApp_PollInput(void);
|
||||
void MenuApp_Render(void);
|
||||
const tagMenuItem *MenuApp_GetMenuItems(uint16_t *count);
|
||||
|
||||
/* 非功能键处理 */
|
||||
void Menu_NonPfunc();
|
||||
void MenuProc_AllInf_Default();
|
||||
|
||||
@@ -1,52 +0,0 @@
|
||||
#ifndef MENU_ITEM_TYPES_H
|
||||
#define MENU_ITEM_TYPES_H
|
||||
|
||||
#include "types.h"
|
||||
|
||||
typedef struct MenuItem MenuItem;
|
||||
typedef MenuItem *tagPMenuItem;
|
||||
typedef MenuItem tagMenuItem;
|
||||
|
||||
typedef struct
|
||||
{
|
||||
MenuItem *higher;
|
||||
MenuItem *lower;
|
||||
MenuItem *before;
|
||||
MenuItem *behind;
|
||||
} MenuLinks;
|
||||
|
||||
typedef struct
|
||||
{
|
||||
uint16_t wSPosX;
|
||||
uint16_t wSPosY;
|
||||
uint16_t wEPosX;
|
||||
uint16_t wEPosY;
|
||||
} MenuRect;
|
||||
|
||||
struct MenuItem
|
||||
{
|
||||
/* 导航关系 */
|
||||
MenuItem *ptHigher;
|
||||
MenuItem *ptLower;
|
||||
MenuItem *ptBefore;
|
||||
MenuItem *ptBehind;
|
||||
|
||||
/* 菜单元数据 */
|
||||
uint8_t byClass;
|
||||
uint8_t byName[50];
|
||||
uint8_t byTip[50];
|
||||
uint8_t byAttrib;
|
||||
uint16_t wPassword;
|
||||
uint16_t wPara;
|
||||
FUNCPTR pfnWinProc;
|
||||
|
||||
/* 视图与布局 */
|
||||
uint16_t wPos;
|
||||
uint16_t wNum;
|
||||
uint16_t wSPosX;
|
||||
uint16_t wSPosY;
|
||||
uint16_t wEPosX;
|
||||
uint16_t wEPosY;
|
||||
};
|
||||
|
||||
#endif
|
||||
@@ -1,23 +0,0 @@
|
||||
#ifndef MENU_STATE_TYPES_H
|
||||
#define MENU_STATE_TYPES_H
|
||||
|
||||
#include "menu_item_types.h"
|
||||
|
||||
typedef struct
|
||||
{
|
||||
/* 当前菜单显示只依赖首帧刷新标记 */
|
||||
unsigned bFirst : 1;
|
||||
} tagDspCtrl;
|
||||
|
||||
typedef struct
|
||||
{
|
||||
uint8_t by0LevelNum;
|
||||
|
||||
tagPMenuItem ptHead;
|
||||
tagPMenuItem ptCurrent;
|
||||
tagPMenuItem ptRoute[4];
|
||||
tagPMenuItem ptCurBak;
|
||||
tagPMenuItem pt0Level;
|
||||
} tagMenuCtrl;
|
||||
|
||||
#endif
|
||||
@@ -1,147 +0,0 @@
|
||||
#include "../app/menu.h"
|
||||
#include "display.h"
|
||||
|
||||
//========================================== //
|
||||
// 字符串代号常量定义
|
||||
enum _STR_FLAG_ {
|
||||
EN_STR_FLAG_FUNSET = 0, // 投入,退出
|
||||
EN_STR_FLAG_OKCANCEL,
|
||||
EN_VALUE_FLAG_SLOTTYPE, // 板件类型
|
||||
EN_VALUE_FLAG_YESNO, // 是否
|
||||
EN_VALUE_FLAG_CHN_NUM, // 中文数字
|
||||
EN_VALUE_FLAG_YXSET, // 遥信设置
|
||||
EN_VALUE_FLAG_YKSTEP, // 遥控步骤
|
||||
EN_VALUE_FLAG_YKACTION, // 遥控操作类型
|
||||
EN_VALUE_FLAG_CHK, // 自检信息
|
||||
EN_VALUE_FLAG_BEUSED, // 是否使用
|
||||
EN_VALUE_FLAG_PORTTYPE, // 端口类型
|
||||
EN_VALUE_FLAG_MAPNAME, // 转发表名称
|
||||
EN_VALUE_FLAG_RECTITLE, // 记录类型表
|
||||
EN_VALUE_FLAG_BHINFO, // 保护动作/复归
|
||||
EN_VALUE_FLAG_BHSET, // 保护动作/复归
|
||||
EN_VALUE_FLAG_OKCANCEL, // 确定/取消
|
||||
EN_VALUE_FLAG_FILETYPE, // 文件类型
|
||||
EN_VALUE_FLAG_ROLE, // 备份角色
|
||||
EN_VALUE_FLAG_COMMSTATE, // 通讯状态
|
||||
EN_VALUE_FLAG_ALARM, // 告警状态
|
||||
EN_VALUE_FLAG_YXTYPE, // 遥信类型
|
||||
EN_STR_FLAG_MENUALAUTO,
|
||||
EN_STR_FLAG_LINKBREAK,
|
||||
EN_STR_FLAG_DONEUNDONE,
|
||||
EN_STR_FLAG_ACTBACK,
|
||||
EN_STR_FLAG_RESUMEBREAK,
|
||||
EN_STR_FLAG_USESTOP,
|
||||
EN_STR_FLAG_BAUDRATE,
|
||||
EN_STR_FLAG_VERIFY,
|
||||
EN_STR_FLAG_PROTOL,
|
||||
EN_STR_FLAG_RECORD,
|
||||
EN_STR_FLAG_MESSAGE,
|
||||
EN_STR_FLAG_COM,
|
||||
EN_STR_FLAG_MODE,
|
||||
EN_STR_FLAG_COMBAUD,
|
||||
EN_STR_FLAG_DATABIT,
|
||||
EN_STR_FLAG_STOPBIT,
|
||||
EN_STR_FLAG_COMTYPE,
|
||||
EN_STR_FLAG_SAMEV,
|
||||
EN_STR_FLAG_PT,
|
||||
EN_STR_FLAG_CT,
|
||||
EN_STR_FLAG_TRANSTYPE,
|
||||
EN_STR_FLAG_LINEPHS,
|
||||
EN_STR_FLAG_RETOUT,
|
||||
EN_STR_FLAG_YXDI,
|
||||
EN_STR_FLAG_YKPUL,
|
||||
EN_STR_FLAG_WAVE,
|
||||
EN_STR_FLAG_MEMTYPE,
|
||||
EN_STR_FLAG_CHANNEL,
|
||||
EN_STR_FLAG_POWERTYPE,
|
||||
EN_STR_FLAG_ZEROTYPE,
|
||||
EN_STR_FLAG_YCCOMMTYPE,
|
||||
EN_STR_FLAG_YXCOMMTYPE,
|
||||
EN_STR_FLAG_CTRLWORD,
|
||||
EN_STR_FLAG_BUSNAME,
|
||||
EN_STR_FLAG_UNITSHOWMODE,
|
||||
EN_STR_FLAG_BAYMODE,
|
||||
EN_STR_FLAG_BASEANA,
|
||||
EN_STR_FLAG_BASEPHASE,
|
||||
EN_STR_FLAG_BAYCODE,
|
||||
EN_STR_FLAG_SET_NUM,
|
||||
EN_STR_FLAG_DCTYPE,
|
||||
EN_STR_FLAG_DCINTYPE,
|
||||
EN_STR_FLAG_DWINTYPE,
|
||||
EN_STR_FLAG_PTCT,
|
||||
EN_STR_FLAG_HARMOTYPE,
|
||||
EN_STR_FLAG_YXSIN_DI, //设置双点遥信
|
||||
EN_STR_FLAG_SHOWANATYPE,
|
||||
EN_STR_FLAG_ANAPOLARTYPE, // 交流通道极性
|
||||
EN_STR_FLAG_WIRDIAGRAMTYPE // 增加主接线图类型识别.modified by zhanggl 111010
|
||||
|
||||
} enumStrType;
|
||||
|
||||
/* 静态菜单表
|
||||
注意:该表定义时顺序不能乱,需要从 0 级开始,一级一经按顺序写入
|
||||
*/
|
||||
const tagMenuModel g_tMenuModelTab[] =
|
||||
{
|
||||
{ 0, "主界面", "", 0, 0x0000, 0, (FUNCPTR)Menu_NonPfunc },
|
||||
{ 1, "装置信息", "查看装置信息", 0, 0x0000, EN_ANA_0, (FUNCPTR)MenuProc_See_AppInfo },
|
||||
{ 1, "实时数据", "装置实时数据", 0, 0x0000, 0, (FUNCPTR)Menu_NonPfunc },
|
||||
{ 2, "交流量", "查看遥测交流量", EN_MEA_AC, 0x0000, EN_ANA_0, (FUNCPTR)MenuProc_See_YC },
|
||||
{ 2, "直流量", "查看遥测直流量", EN_MEA_DC, 0x0000, EN_ANA_0, (FUNCPTR)MenuProc_See_YC },
|
||||
{ 2, "遥信量", "查看遥信开入量", EN_INPUT_RLY_ALL, 0x0000, EN_INPUT_0, (FUNCPTR)MenuProc_See_Input },
|
||||
{ 1, "参数定值", "保护参数查看与修改", 0, 0x0000, 0, (FUNCPTR)Menu_NonPfunc },
|
||||
{ 2, "整定", "整定装置保护参数", 0, 0x0000, 0, (FUNCPTR)Menu_NonPfunc },
|
||||
{ 3, "参数", "查看设备参数定值", EN_FIGURE_SET, CN_USER_PWD, EN_SIDE_BASIC, (FUNCPTR)MenuProc_Set_Value },
|
||||
{ 3, "定值", "设置装置数值定值", EN_FIGURE_SET, CN_USER_PWD, EN_SIDE_DEF, (FUNCPTR)MenuProc_Set_Value },
|
||||
{ 3, "控制字", "设置装置控制字", EN_SOFT_SET, CN_USER_PWD, EN_SIDE_DEF, (FUNCPTR)MenuProc_Set_Value },
|
||||
{ 3, "软压板", "设置软压板", 0, CN_USER_PWD, EN_SOFT_PRO, (FUNCPTR)MenuProc_Set_Soft },
|
||||
{ 2, "查看", "查看装置保护参数", 0, 0x0000, 0, (FUNCPTR)Menu_NonPfunc },
|
||||
{ 3, "参数", "设置设备参数定值", EN_FIGURE_SET, 0x0000, EN_SIDE_BASIC, (FUNCPTR)MenuProc_See_Set },
|
||||
{ 3, "定值", "查看数值型定值", EN_FIGURE_SET, 0x0000, EN_SIDE_DEF, (FUNCPTR)MenuProc_See_Set },
|
||||
{ 3, "控制字", "查看控制字定值", EN_SOFT_SET, 0x0000, EN_SIDE_DEF, (FUNCPTR)MenuProc_See_Set },
|
||||
{ 3, "软压板", "查看软压板", 0, 0x0000, EN_SOFT_PRO, (FUNCPTR)MenuProc_See_Soft },
|
||||
{ 1, "三遥设置", "", 0, 0x0000, 0, (FUNCPTR)Menu_NonPfunc },
|
||||
{ 2, "遥测死区", "设置遥测量死区门槛", 0, CN_USER_PWD, 0, (FUNCPTR)MenuProc_YC_SetSqValue },
|
||||
{ 2, "遥测系数", "设置遥测量微调系数", EN_MEA_ADJ, CN_USER_PWD, 0, (FUNCPTR)MenuProc_YC_SetAdjCoe },
|
||||
{ 2, "遥信类型", "设置遥信类型", 0, CN_USER_PWD, 0, (FUNCPTR)MenuProc_YX_SetCommType },
|
||||
{ 2, "遥信防抖", "设置遥信防抖时间", 0, CN_USER_PWD, 0, (FUNCPTR)MenuProc_YX_SetWidth },
|
||||
{ 2, "双点遥信", "设置双点遥信虚端子", 0, CN_USER_PWD, 0, (FUNCPTR)MenuProc_YX_SetTwin },
|
||||
{ 1, "装置维护", "", 0, 0x0000, 0, (FUNCPTR)Menu_NonPfunc },
|
||||
{ 2, "时钟设置", "设置系统时钟", 0, CN_USER_PWD, 0, (FUNCPTR)MenuProc_Cfg_Time },
|
||||
{ 2, "强制复归", "可复归未返回事件", EN_REV_FORCE, CN_USER_PWD, 0, (FUNCPTR)MenuProc_Cfg_RevEvent },
|
||||
{ 2, "手动录波", "启动手动录波", 0, CN_USER_PWD, 0, (FUNCPTR)MenuProc_Cfg_ManualWave },
|
||||
{ 2, "清除记录", "", 0, CN_USER_PWD, 0, (FUNCPTR)MenuProc_Cfg_ClrRec },
|
||||
{ 1, "通讯参数", "", 0, 0x0000, 0, (FUNCPTR)Menu_NonPfunc },
|
||||
{ 2, "通讯设置", "外部通讯设置", 0, CN_USER_PWD, 0, (FUNCPTR)MenuProc_Cfg_ComPara },
|
||||
{ 2, "网口设置", "", 0, CN_USER_PWD, 0, (FUNCPTR)MenuProc_Cfg_EditIP },
|
||||
{ 2, "SNTP设置", "", 0, CN_USER_PWD, 0, (FUNCPTR)MenuProc_Cfg_EditSntp },
|
||||
{ 1, "记录查询", "查看各种装置记录", 0, 0x0000, 0, (FUNCPTR)Menu_NonPfunc },
|
||||
{ 2, "SOE记录", "", 0, 0x0000, 0, (FUNCPTR)MenuProc_See_RecSOE },
|
||||
{ 2, "事故记录", "", 0, 0x0000, 0, (FUNCPTR)MenuProc_See_RecAct },
|
||||
{ 2, "操作记录", "", 0, 0x0000, 0, (FUNCPTR)MenuProc_See_RecOpt },
|
||||
{ 2, "保护告警", "", 0, 0x0000, 0, (FUNCPTR)MenuProc_See_RecAlm },
|
||||
{ 2, "保护启动", "", 0, 0x0000, 0, (FUNCPTR)MenuProc_See_RecStart },
|
||||
{ 2, "遥控记录", "", 0, 0x0000, 0, (FUNCPTR)MenuProc_See_RecYK },
|
||||
{ 2, "自检记录", "", 0, 0x0000, 0, (FUNCPTR)MenuProc_See_RecChk },
|
||||
{ 2, "运行记录", "", 0, 0x0000, 0, (FUNCPTR)MenuProc_See_RecRun },
|
||||
{ 2, "运行报告", "", 0, 0x0000, 0, (FUNCPTR)MenuProc_See_RecFault },
|
||||
{ 0, "厂家设置", "设置装置相关参数", 0, CN_COP_PWD, 0, (FUNCPTR)Menu_NonPfunc },
|
||||
{ 1, "元件配置", "配置元件配置", 0, CN_COP_PWD, EN_FACTORY_PASSWORD,(FUNCPTR)MenuProc_Cfg_CellConf },
|
||||
{ 1, "恢复默认", "恢复默认元件定值参数", 0, CN_COP_PWD, 0, (FUNCPTR)Menu_NonPfunc },
|
||||
{ 2, "全部恢复", "全部参数恢复默认", 0, CN_COP_PWD, EN_NO_USER_PWD, (FUNCPTR)MenuProc_AllInf_Default },
|
||||
{ 2, "默认参数", "当前参数恢复默认", 0, CN_COP_PWD, EN_NO_USER_PWD, (FUNCPTR)MenuProc_Para_Default },
|
||||
{ 2, "默认定值", "当前定值区恢复默认", 0, CN_COP_PWD, EN_NO_USER_PWD, (FUNCPTR)MenuProc_Set_Default },
|
||||
{ 2, "软压板", "当前软压板恢复默认", 0, CN_COP_PWD, EN_NO_USER_PWD, (FUNCPTR)MenuProc_Resume_Soft },
|
||||
{ 2, "元件配置", "元件配置恢复默认", 0, CN_COP_PWD, EN_NO_USER_PWD, (FUNCPTR)MenuProc_Cfg_CellDef },
|
||||
{ 1, "交流显示", "交流显示方式设置", 0, CN_COP_PWD, 0, (FUNCPTR)MenuProc_Cfg_ShowAnaType },
|
||||
{ 1, "装置调试", "调试装置", 0, 0x0000, 0, (FUNCPTR)Menu_NonPfunc },
|
||||
{ 2, "虚拟遥信", "设置虚拟遥信值", 0, CN_COP_PWD, 0, (FUNCPTR)MenuProc_Dbg_XuYX },
|
||||
{ 2, "交流虚遥测", "设置虚拟交流遥测值", EN_MEA_AC, CN_COP_PWD, EN_ANA_0, (FUNCPTR)MenuProc_Dbg_XuYC },
|
||||
{ 2, "直流虚遥测", "设置虚拟直流遥测值", EN_MEA_DC, CN_COP_PWD, EN_ANA_0, (FUNCPTR)MenuProc_Dbg_XuYC },
|
||||
{ 2, "电度虚遥测", "设置虚拟电度遥测值", EN_MEA_POWER, CN_COP_PWD, EN_ANA_0, (FUNCPTR)MenuProc_Dbg_XuYC },
|
||||
{ 2, "动作虚事件", "设置虚拟动作事件", EN_ACT_REC, CN_COP_PWD, 0, (FUNCPTR)MenuProc_Dbg_XuEvent },
|
||||
{ 2, "告警虚事件", "设置虚拟告警事件", EN_ALM_REC, CN_COP_PWD, 0, (FUNCPTR)MenuProc_Dbg_XuEvent },
|
||||
{ 2, "动作出口", "进入此菜单保护退出", EN_OUTPUT_TRIP, CN_COP_PWD, EN_INPUT_0, (FUNCPTR)MenuProc_Dbg_Relay },
|
||||
{ 2, "信号出口", "进入此菜单保护退出", EN_OUTPUT_SIGN, CN_COP_PWD, 0, (FUNCPTR)MenuProc_Dbg_Relay },
|
||||
{ 1, "版本信息", "查看板件版本信息", 0, CN_COP_PWD, 0, (FUNCPTR)MenuProc_See_VersionBoard },
|
||||
};
|
||||
const uint32_t MENU_MAX_ITEM = sizeof(g_tMenuModelTab) / sizeof(tagMenuModel);
|
||||
@@ -1,180 +0,0 @@
|
||||
#ifndef __DISPLAY__H__
|
||||
#define __DISPLAY__H__
|
||||
|
||||
#include "types.h"
|
||||
|
||||
|
||||
/* 静态菜定义需要的属性 */
|
||||
typedef struct
|
||||
{
|
||||
uint8_t byClass; // 菜单分级标志;
|
||||
uint8_t byName[50]; // 菜单字符串;
|
||||
uint8_t byTip[50]; // 菜单提示文本;
|
||||
uint8_t byAttrib; // 菜单属性,设置菜单特殊显示效果;
|
||||
uint16_t wPassword; // 访问密码,0x0000表示没有密码;
|
||||
uint16_t wPara; // 菜单执行函数参数;
|
||||
FUNCPTR pfnWinProc; // 界面执行函数指针;
|
||||
}tagMenuModel,*tagPMenuModel;
|
||||
|
||||
extern const tagMenuModel g_tMenuModelTab[];
|
||||
extern const uint32_t MENU_MAX_ITEM;
|
||||
|
||||
|
||||
|
||||
|
||||
//菜单密码设置
|
||||
//==============================================================================
|
||||
|
||||
#define CN_COP_PWD (321) // 厂家密码:可修改保护元件配置、查看内存等
|
||||
#define CN_USER_PWD (700) // 用户超级密码:可用于修改普通密码、定值,预设等
|
||||
#define CN_SUPER_PWD (620)
|
||||
|
||||
|
||||
enum _REV_TYPE_ // 复归类型
|
||||
{
|
||||
EN_REV_FREE, // 非强制复归
|
||||
EN_REV_FORCE // 强制复归
|
||||
};
|
||||
|
||||
enum _SET_SIDE_TYPE_ // 定值类别
|
||||
{
|
||||
EN_SIDE_START = 0, // 备用侧
|
||||
EN_SIDE_BASIC, // 基本信息
|
||||
EN_SIDE_DEVINF, // 装置参数
|
||||
EN_SIDE_COP, // 内部定值
|
||||
EN_SIDE_MATRIX, // 内部定值 出口矩阵
|
||||
EN_SIDE_ALL, // 全侧
|
||||
EN_SIDE_HIGH, // 高压侧
|
||||
EN_SIDE_MED1, // 中压侧
|
||||
EN_SIDE_MED2, // 中压侧
|
||||
EN_SIDE_LOW1, // 低压1侧
|
||||
EN_SIDE_LOW2, // 低压2侧
|
||||
EN_SIDE_LK, // 电抗器
|
||||
EN_SIDE_Z, // Z变
|
||||
EN_SIDE_DEF, // 自定义
|
||||
EN_SIDE_NONE = 0xFF // 不存在
|
||||
};
|
||||
|
||||
// ============================================================================
|
||||
// Const_Soft.h 常量固定部分
|
||||
// ============================================================================
|
||||
// 软压板类型
|
||||
enum _SOFT_TYPE_NUMBER
|
||||
{
|
||||
EN_SOFT_PRO = 0, // 保护功能软压板
|
||||
EN_SOFT_GOOSE, // GOOSE软压板
|
||||
EN_SOFT_MU, // MU软压板
|
||||
EN_SOFT_SWITCH, // 刀闸强制软压板
|
||||
EN_SOFT_BAK, // 备用软压板
|
||||
//----------------------------------------------//
|
||||
EN_SOFT_TYPE_END // 软压板类型总数目,不可改动
|
||||
};
|
||||
|
||||
//============================================================================
|
||||
//实时数据类型标志
|
||||
//============================================================================
|
||||
enum _MEA_TYPE_{
|
||||
//保护交流量
|
||||
EN_MEA_RLY, // 保护交流量1
|
||||
EN_MEA_RLY2, // 保护交流量2
|
||||
EN_MEA_RLY3, // 保护交流量3
|
||||
EN_MEA_ANA, // 保护测量量1
|
||||
EN_MEA_ANA2, // 保护测量量2
|
||||
EN_MEA_ANA3, // 保护测量量3
|
||||
//遥测
|
||||
EN_MEA_AC, // 遥测交流量
|
||||
EN_MEA_DC, // 保护直流量
|
||||
EN_MEA_SYN, // 保护谐波量
|
||||
EN_MEA_POWER, // 保护电能量
|
||||
EN_MEA_DD, // 电度量
|
||||
EN_MEA_JLYC,
|
||||
EN_MEA_GEAR, // 保护档位量
|
||||
EN_MEA_TQ, // 保护同期量
|
||||
EN_MEA_INPUT1, // 保护交流量1
|
||||
EN_MEA_INPUT2, // 保护交流量2
|
||||
EN_MEA_INPUT3, // 保护交流量3
|
||||
EN_INPUT_RLY_ALL, //开入加遥信
|
||||
EN_INPUT_RLY_FAULT,
|
||||
EN_INPUT_RLY_OTHER,
|
||||
EN_INPUT_BS_ALL,
|
||||
EN_INPUT_BS_FAULT,
|
||||
EN_INPUT_BS_OTHER,
|
||||
|
||||
EN_MEA_ADJ, // 遥测微调系数
|
||||
EN_MEA_YX, // 遥信状态
|
||||
EN_OUTPUT_TRIP, // 保护出口调试
|
||||
EN_OUTPUT_SIGN, // 信号出口调试
|
||||
EN_MEA_LS, // 联锁信号.zhanggl
|
||||
EN_MEA_SCRLY // 顺控配置信号.zhanggl
|
||||
};
|
||||
|
||||
|
||||
enum _INDEX_VALUE_TYPE_{
|
||||
|
||||
EN_SOFT_SET,
|
||||
EN_FIGURE_SET
|
||||
};
|
||||
|
||||
//==============================================================================
|
||||
// 菜单常量表相关常量
|
||||
|
||||
//============================================================================
|
||||
//交流量菜单对应wParameter执行菜单参数(应用于菜单常量表)
|
||||
//============================================================================
|
||||
enum _ANA_PARA_ {
|
||||
EN_ANA_0 = 1,
|
||||
EN_ANA_1
|
||||
};
|
||||
|
||||
//============================================================================
|
||||
//开入量菜单对应wParameter执行菜单参数(应用于菜单常量表)
|
||||
//============================================================================
|
||||
enum _INPUT_PARA_ {
|
||||
EN_INPUT_0 = 1,
|
||||
EN_INPUT_1
|
||||
};
|
||||
|
||||
enum _ANA_TYPE_
|
||||
{
|
||||
EN_TYPE_DIF_CURRENT=0 ,
|
||||
EN_TYPE_UNIT_CURRENT ,
|
||||
EN_TYPE_UNIT_VOLTAGE
|
||||
|
||||
};
|
||||
|
||||
enum _NO_USER_PASSWORD_{
|
||||
EN_NO_USER_PWD = 0x55
|
||||
};
|
||||
enum _FACTORY_PASSWORD_{
|
||||
EN_FACTORY_PASSWORD = 0x55
|
||||
};
|
||||
|
||||
// 记录类型
|
||||
enum _REC_TYPE_
|
||||
{
|
||||
EN_ACT_REC = 0, // 事件记录
|
||||
EN_ALM_REC, // 告警记录
|
||||
EN_CHK_REC, // 自检记录
|
||||
EN_SOE_REC, // SOE记录
|
||||
EN_COS_REC, // COS记录
|
||||
EN_LOCK_REC, // 瞬时闭锁记录
|
||||
EN_OVER_REC, // 越限记录
|
||||
EN_START_REC, // 启动记录
|
||||
EN_RUN_REC, // 运行记录
|
||||
EN_INPUT_REC, // 开入变位记录
|
||||
EN_ONOFF_REC, // 保护投退记录
|
||||
EN_OPT_REC, // 保护操作记录
|
||||
EN_YK_REC, // 保护遥控记录
|
||||
EN_SC_REC, // 装置顺控记录
|
||||
EN_SCSTEPINFO_REC, // 装置顺控单步记录
|
||||
EN_FAULT_REC, // 事故报告记录
|
||||
EN_ACTWAVE_REC, // 动作录波信息记录
|
||||
EN_STARTWAVE_REC, // 启动录波信息记录
|
||||
EN_HANDWAVE_REC, // 手动录波信息记录
|
||||
EN_FAULT_NO, // 故障序号
|
||||
EN_ALL_REC = 0xFF, // 所有记录
|
||||
EN_NO_REC = 0xFFFF // 无效记录
|
||||
};
|
||||
|
||||
|
||||
#endif
|
||||
@@ -1,52 +0,0 @@
|
||||
#include "menu_model.h"
|
||||
|
||||
#include <stddef.h>
|
||||
|
||||
#include "menu_tree_builder.h"
|
||||
|
||||
/* 初始化阶段一次性修饰菜单名称:对有子项的节点追加 '\x10' 指示符。 */
|
||||
static void MenuModel_DecorateDisplayNames(tagMenuItem *items, uint32_t maxItem)
|
||||
{
|
||||
for (uint32_t i = 0; i < maxItem; i++)
|
||||
{
|
||||
uint16_t nameCapacity = (uint16_t)sizeof(items[i].byName);
|
||||
uint16_t maxTextLenWithoutNull = (uint16_t)(nameCapacity - 1);
|
||||
uint16_t len = 0;
|
||||
if (items[i].ptLower == NULL)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
while ((len < maxTextLenWithoutNull) && (items[i].byName[len] != '\0'))
|
||||
{
|
||||
len++;
|
||||
}
|
||||
if ((len == 0) || (items[i].byName[len - 1] != '\x10'))
|
||||
{
|
||||
if (len < maxTextLenWithoutNull)
|
||||
{
|
||||
items[i].byName[len] = '\x10';
|
||||
items[i].byName[len + 1] = '\0';
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void MenuModel_Bootstrap(tagMenuCtrl *menuCtrl,
|
||||
tagMenuItem *menuItems,
|
||||
const tagMenuModel *modelTab,
|
||||
uint32_t modelCount)
|
||||
{
|
||||
MenuTree_0LevelNumCal(menuCtrl, modelTab, modelCount);
|
||||
MenuTree_MainCreate(menuItems, modelTab, modelCount);
|
||||
MenuModel_DecorateDisplayNames(menuItems, modelCount);
|
||||
|
||||
menuCtrl->ptHead = &menuItems[0];
|
||||
|
||||
menuCtrl->ptRoute[0] = &menuItems[0];
|
||||
menuCtrl->pt0Level = menuCtrl->ptRoute[0];
|
||||
menuCtrl->ptCurrent = menuCtrl->ptHead->ptLower;
|
||||
menuCtrl->ptCurBak = menuCtrl->ptCurrent;
|
||||
menuCtrl->ptRoute[1] = menuCtrl->ptCurrent;
|
||||
menuCtrl->ptRoute[2] = menuCtrl->ptCurrent;
|
||||
menuCtrl->ptRoute[3] = menuCtrl->ptCurrent;
|
||||
}
|
||||
@@ -1,12 +0,0 @@
|
||||
#ifndef MENU_MODEL_H
|
||||
#define MENU_MODEL_H
|
||||
|
||||
#include "display.h"
|
||||
#include "../common/menu_state_types.h"
|
||||
|
||||
void MenuModel_Bootstrap(tagMenuCtrl *menuCtrl,
|
||||
tagMenuItem *menuItems,
|
||||
const tagMenuModel *modelTab,
|
||||
uint32_t modelCount);
|
||||
|
||||
#endif
|
||||
@@ -1,119 +0,0 @@
|
||||
#include <string.h>
|
||||
|
||||
#include "menu_tree_builder.h"
|
||||
|
||||
void MenuTree_0LevelNumCal(tagMenuCtrl *menuCtrl, const tagMenuModel *modelTab, uint32_t maxItem)
|
||||
{
|
||||
uint8_t by0LevelNum = 0;
|
||||
uint32_t wLoop;
|
||||
|
||||
for (wLoop = 0; wLoop < maxItem; wLoop++)
|
||||
{
|
||||
if (modelTab[wLoop].byClass == 0)
|
||||
{
|
||||
by0LevelNum++;
|
||||
}
|
||||
}
|
||||
menuCtrl->by0LevelNum = by0LevelNum;
|
||||
}
|
||||
|
||||
void MenuTree_MainCreate(tagMenuItem *menuItems, const tagMenuModel *modelTab, uint32_t maxItem)
|
||||
{
|
||||
tagPMenuItem ptFirst[4];
|
||||
tagPMenuItem ptLast[4];
|
||||
tagPMenuItem ptCurrent;
|
||||
tagPMenuItem ptNextNode;
|
||||
uint32_t wLoop1;
|
||||
uint8_t byCurClass;
|
||||
uint8_t byNextClass;
|
||||
uint8_t wLoop2;
|
||||
|
||||
ptFirst[0] = &menuItems[0];
|
||||
ptLast[0] = ptFirst[0];
|
||||
|
||||
ptCurrent = &menuItems[0];
|
||||
ptCurrent->wPos = 0;
|
||||
ptCurrent->wNum = 0;
|
||||
ptCurrent->wSPosX = 0;
|
||||
ptCurrent->wSPosY = 0;
|
||||
ptCurrent->wEPosX = 0;
|
||||
ptCurrent->wEPosY = 0;
|
||||
|
||||
ptCurrent->ptHigher = NULL;
|
||||
ptCurrent->ptLower = NULL;
|
||||
ptCurrent->ptBefore = NULL;
|
||||
ptCurrent->ptBehind = NULL;
|
||||
|
||||
ptCurrent->byClass = modelTab[0].byClass;
|
||||
ptCurrent->byAttrib = modelTab[0].byAttrib;
|
||||
ptCurrent->wPassword = modelTab[0].wPassword;
|
||||
ptCurrent->wPara = modelTab[0].wPara;
|
||||
ptCurrent->pfnWinProc = modelTab[0].pfnWinProc;
|
||||
memcpy(ptCurrent->byName, modelTab[0].byName, 50);
|
||||
memcpy(ptCurrent->byTip, modelTab[0].byTip, 50);
|
||||
|
||||
for (wLoop1 = 1; wLoop1 < maxItem; wLoop1++)
|
||||
{
|
||||
ptNextNode = &menuItems[wLoop1];
|
||||
|
||||
ptNextNode->wPos = 0;
|
||||
ptNextNode->wNum = 0;
|
||||
ptNextNode->wSPosX = 0;
|
||||
ptNextNode->wSPosY = 0;
|
||||
ptNextNode->wEPosX = 0;
|
||||
ptNextNode->wEPosY = 0;
|
||||
|
||||
ptNextNode->ptLower = NULL;
|
||||
ptNextNode->ptBehind = NULL;
|
||||
ptNextNode->ptBefore = NULL;
|
||||
ptNextNode->ptHigher = NULL;
|
||||
|
||||
ptNextNode->byClass = modelTab[wLoop1].byClass;
|
||||
ptNextNode->byAttrib = modelTab[wLoop1].byAttrib;
|
||||
ptNextNode->wPassword = modelTab[wLoop1].wPassword;
|
||||
ptNextNode->wPara = modelTab[wLoop1].wPara;
|
||||
ptNextNode->pfnWinProc = modelTab[wLoop1].pfnWinProc;
|
||||
memcpy(ptNextNode->byName, modelTab[wLoop1].byName, 50);
|
||||
memcpy(ptNextNode->byTip, modelTab[wLoop1].byTip, 50);
|
||||
|
||||
byCurClass = ptCurrent->byClass;
|
||||
byNextClass = ptNextNode->byClass;
|
||||
|
||||
if (byCurClass < byNextClass)
|
||||
{
|
||||
ptCurrent->ptLower = ptNextNode;
|
||||
ptNextNode->ptHigher = ptCurrent;
|
||||
ptFirst[byNextClass] = ptNextNode;
|
||||
ptLast[byNextClass] = ptNextNode;
|
||||
}
|
||||
else if (byCurClass == byNextClass)
|
||||
{
|
||||
ptNextNode->ptBefore = ptCurrent;
|
||||
ptNextNode->ptHigher = ptCurrent->ptHigher;
|
||||
ptCurrent->ptBehind = ptNextNode;
|
||||
ptLast[byNextClass] = ptNextNode;
|
||||
}
|
||||
else if (byCurClass > byNextClass)
|
||||
{
|
||||
ptNextNode->ptBefore = ptLast[byNextClass];
|
||||
ptNextNode->ptHigher = ptFirst[byNextClass]->ptHigher;
|
||||
ptLast[byNextClass]->ptBehind = ptNextNode;
|
||||
ptLast[byNextClass] = ptNextNode;
|
||||
|
||||
for (wLoop2 = byCurClass; wLoop2 > byNextClass; wLoop2--)
|
||||
{
|
||||
ptLast[wLoop2]->ptBehind = ptFirst[wLoop2];
|
||||
ptFirst[wLoop2]->ptBefore = ptLast[wLoop2];
|
||||
}
|
||||
}
|
||||
|
||||
ptCurrent = ptNextNode;
|
||||
}
|
||||
|
||||
byCurClass = ptCurrent->byClass;
|
||||
for (wLoop1 = 0; wLoop1 <= byCurClass; wLoop1++)
|
||||
{
|
||||
ptLast[wLoop1]->ptBehind = ptFirst[wLoop1];
|
||||
ptFirst[wLoop1]->ptBefore = ptLast[wLoop1];
|
||||
}
|
||||
}
|
||||
@@ -1,10 +0,0 @@
|
||||
#ifndef MENU_TREE_BUILDER_H
|
||||
#define MENU_TREE_BUILDER_H
|
||||
|
||||
#include "display.h"
|
||||
#include "../common/menu_state_types.h"
|
||||
|
||||
void MenuTree_0LevelNumCal(tagMenuCtrl *menuCtrl, const tagMenuModel *modelTab, uint32_t maxItem);
|
||||
void MenuTree_MainCreate(tagMenuItem *menuItems, const tagMenuModel *modelTab, uint32_t maxItem);
|
||||
|
||||
#endif
|
||||
@@ -1,15 +0,0 @@
|
||||
#ifndef MENU_NAV_TYPES_H
|
||||
#define MENU_NAV_TYPES_H
|
||||
|
||||
#include "../common/menu_item_types.h"
|
||||
|
||||
typedef struct
|
||||
{
|
||||
tagPMenuItem ptHead;
|
||||
tagPMenuItem ptCurrent;
|
||||
tagPMenuItem ptRoute[4];
|
||||
tagPMenuItem ptCurBak;
|
||||
tagPMenuItem pt0Level;
|
||||
} MenuNavState;
|
||||
|
||||
#endif
|
||||
@@ -1,111 +0,0 @@
|
||||
#include <stddef.h>
|
||||
|
||||
#include "menu_navigator.h"
|
||||
|
||||
#include "../../key.h"
|
||||
|
||||
MenuNavResult MenuNavigator_ProcessKey(MenuNavState *navState, uint8_t keyVal)
|
||||
{
|
||||
MenuNavResult result;
|
||||
tagPMenuItem ptCurrent;
|
||||
tagPMenuItem ptHead;
|
||||
tagPMenuItem *ptRoute;
|
||||
|
||||
result.needRefresh = 0;
|
||||
result.skipRenderThisRound = 0;
|
||||
|
||||
ptCurrent = navState->ptCurrent;
|
||||
ptHead = navState->ptHead;
|
||||
ptRoute = navState->ptRoute;
|
||||
|
||||
switch (keyVal)
|
||||
{
|
||||
case KEY_F1:
|
||||
case KEY_F2:
|
||||
break;
|
||||
case KEY_U:
|
||||
ptCurrent = ptCurrent->ptBefore;
|
||||
result.needRefresh = 1;
|
||||
break;
|
||||
case KEY_D:
|
||||
ptCurrent = ptCurrent->ptBehind;
|
||||
result.needRefresh = 1;
|
||||
break;
|
||||
case KEY_L:
|
||||
ptCurrent = ptRoute[ptCurrent->byClass - 1];
|
||||
if (ptCurrent->byClass == 0)
|
||||
{
|
||||
if (ptCurrent->wPos == 1)
|
||||
{
|
||||
ptCurrent = ptCurrent->ptBefore->ptBefore;
|
||||
}
|
||||
else
|
||||
{
|
||||
ptCurrent = ptCurrent->ptBefore;
|
||||
}
|
||||
ptCurrent = ptCurrent->ptLower;
|
||||
}
|
||||
result.needRefresh = 1;
|
||||
break;
|
||||
case KEY_R:
|
||||
case KEY_ENT:
|
||||
if (ptCurrent->ptLower != NULL)
|
||||
{
|
||||
ptCurrent = ptCurrent->ptLower;
|
||||
result.needRefresh = 1;
|
||||
}
|
||||
else if (ptCurrent->pfnWinProc != NULL)
|
||||
{
|
||||
ptCurrent->pfnWinProc();
|
||||
}
|
||||
break;
|
||||
case KEY_ESC:
|
||||
if (ptCurrent->byClass == 1)
|
||||
{
|
||||
navState->pt0Level = ptHead;
|
||||
navState->ptRoute[0] = ptHead;
|
||||
navState->ptCurrent = ptHead->ptLower;
|
||||
navState->ptCurBak = navState->ptCurrent;
|
||||
navState->ptRoute[1] = navState->ptCurrent;
|
||||
result.skipRenderThisRound = 1;
|
||||
return result;
|
||||
}
|
||||
ptCurrent = ptRoute[ptCurrent->byClass - 1];
|
||||
result.needRefresh = 1;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
navState->ptCurrent = ptCurrent;
|
||||
return result;
|
||||
}
|
||||
|
||||
void MenuNavigator_RebuildRoute(MenuNavState *navState, uint32_t maxItem)
|
||||
{
|
||||
tagPMenuItem ptIndex;
|
||||
|
||||
if (navState->ptCurBak == navState->ptCurrent)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
ptIndex = navState->ptCurrent;
|
||||
for (uint32_t index = 0; index < maxItem; index++)
|
||||
{
|
||||
if (ptIndex->ptHigher == NULL)
|
||||
{
|
||||
ptIndex = ptIndex->ptBefore;
|
||||
}
|
||||
else
|
||||
{
|
||||
ptIndex = ptIndex->ptHigher;
|
||||
navState->ptRoute[ptIndex->byClass] = ptIndex;
|
||||
}
|
||||
if (ptIndex->byClass == 0)
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
navState->ptRoute[navState->ptCurrent->byClass] = navState->ptCurrent;
|
||||
}
|
||||
@@ -1,15 +0,0 @@
|
||||
#ifndef MENU_NAVIGATOR_H
|
||||
#define MENU_NAVIGATOR_H
|
||||
|
||||
#include "menu_nav_types.h"
|
||||
|
||||
typedef struct
|
||||
{
|
||||
uint8_t needRefresh;
|
||||
uint8_t skipRenderThisRound;
|
||||
} MenuNavResult;
|
||||
|
||||
MenuNavResult MenuNavigator_ProcessKey(MenuNavState *navState, uint8_t keyVal);
|
||||
void MenuNavigator_RebuildRoute(MenuNavState *navState, uint32_t maxItem);
|
||||
|
||||
#endif
|
||||
@@ -1,176 +0,0 @@
|
||||
#include "menu_presenter.h"
|
||||
|
||||
#include "../model/menu_model.h"
|
||||
#include "menu_navigator.h"
|
||||
|
||||
/* -------------------------------------------------------------------------
|
||||
* 函数: MenuPresenter_FillNavState
|
||||
* 作用: 将“运行时控制结构 tagMenuCtrl”中的导航相关字段复制到
|
||||
* “导航临时结构 MenuNavState”。
|
||||
*
|
||||
* 设计原因:
|
||||
* - MenuNavigator_* 仅关心导航字段,不应直接操作完整控制结构;
|
||||
* - 通过拷贝到轻量结构,可在 Presenter 内控制读写边界。
|
||||
* ------------------------------------------------------------------------- */
|
||||
static void MenuPresenter_FillNavState(const tagMenuCtrl *ctrl, MenuNavState *nav)
|
||||
{
|
||||
/* 根节点指针:用于 ESC 回到顶层时复位。 */
|
||||
nav->ptHead = ctrl->ptHead;
|
||||
/* 当前焦点节点:导航计算的主要输入。 */
|
||||
nav->ptCurrent = ctrl->ptCurrent;
|
||||
/* 路径第 0 级:顶层菜单路径缓存。 */
|
||||
nav->ptRoute[0] = ctrl->ptRoute[0];
|
||||
/* 路径第 1 级:一级菜单路径缓存。 */
|
||||
nav->ptRoute[1] = ctrl->ptRoute[1];
|
||||
/* 路径第 2 级:二级菜单路径缓存。 */
|
||||
nav->ptRoute[2] = ctrl->ptRoute[2];
|
||||
/* 路径第 3 级:三级菜单路径缓存。 */
|
||||
nav->ptRoute[3] = ctrl->ptRoute[3];
|
||||
/* 上一次焦点备份:用于判断是否发生焦点迁移。 */
|
||||
nav->ptCurBak = ctrl->ptCurBak;
|
||||
/* 当前可见的 0 级入口:用于判断是否需要全屏刷新。 */
|
||||
nav->pt0Level = ctrl->pt0Level;
|
||||
}
|
||||
|
||||
/* -------------------------------------------------------------------------
|
||||
* 函数: MenuPresenter_ApplyNavState
|
||||
* 作用: 将导航临时结构 MenuNavState 的结果回写到 tagMenuCtrl。
|
||||
*
|
||||
* 说明:
|
||||
* - FillNavState + Navigator + ApplyNavState 构成一次输入处理闭环;
|
||||
* - 这样 Presenter 明确“读输入状态 -> 计算 -> 写回状态”三个阶段。
|
||||
* ------------------------------------------------------------------------- */
|
||||
static void MenuPresenter_ApplyNavState(tagMenuCtrl *ctrl, const MenuNavState *nav)
|
||||
{
|
||||
/* 回写根节点(通常不变,但保持对称拷贝便于维护)。 */
|
||||
ctrl->ptHead = nav->ptHead;
|
||||
/* 回写当前焦点。 */
|
||||
ctrl->ptCurrent = nav->ptCurrent;
|
||||
/* 回写 0 级路径缓存。 */
|
||||
ctrl->ptRoute[0] = nav->ptRoute[0];
|
||||
/* 回写 1 级路径缓存。 */
|
||||
ctrl->ptRoute[1] = nav->ptRoute[1];
|
||||
/* 回写 2 级路径缓存。 */
|
||||
ctrl->ptRoute[2] = nav->ptRoute[2];
|
||||
/* 回写 3 级路径缓存。 */
|
||||
ctrl->ptRoute[3] = nav->ptRoute[3];
|
||||
/* 回写焦点备份。 */
|
||||
ctrl->ptCurBak = nav->ptCurBak;
|
||||
/* 回写 0 级入口指针。 */
|
||||
ctrl->pt0Level = nav->pt0Level;
|
||||
}
|
||||
|
||||
/* -------------------------------------------------------------------------
|
||||
* 函数: MenuPresenter_Setup
|
||||
* 作用: 完成 Presenter 的依赖注入和 View 绑定。
|
||||
*
|
||||
* 输入:
|
||||
* - dspCtrl/menuCtrl/menuItems: 运行态状态与数据池
|
||||
* - renderPort: 渲染端口抽象(LCD/模拟器实现)
|
||||
* - layoutConfig: 布局参数
|
||||
* ------------------------------------------------------------------------- */
|
||||
void MenuPresenter_Setup(MenuPresenter *presenter,
|
||||
tagDspCtrl *dspCtrl,
|
||||
tagMenuCtrl *menuCtrl,
|
||||
tagMenuItem *menuItems,
|
||||
const MenuRenderPort *renderPort,
|
||||
const MenuLayoutConfig *layoutConfig)
|
||||
{
|
||||
/* 保存显示控制状态句柄。 */
|
||||
presenter->dspCtrl = dspCtrl;
|
||||
/* 保存菜单控制状态句柄。 */
|
||||
presenter->menuCtrl = menuCtrl;
|
||||
/* 保存菜单节点池句柄。 */
|
||||
presenter->menuItems = menuItems;
|
||||
/* 保存布局配置句柄。 */
|
||||
presenter->layoutConfig = layoutConfig;
|
||||
/* 初始化 View,并注入具体渲染端口。 */
|
||||
MenuView_Init(&presenter->view, renderPort);
|
||||
}
|
||||
|
||||
/* -------------------------------------------------------------------------
|
||||
* 函数: MenuPresenter_InitModel
|
||||
* 作用: 启动时初始化模型与布局。
|
||||
*
|
||||
* 步骤:
|
||||
* 1) 设置首帧标志,确保首次进入能触发完整绘制;
|
||||
* 2) 通过 Model 完成菜单树构建与初始路由;
|
||||
* 3) 通知 View 计算每个节点的布局坐标。
|
||||
* ------------------------------------------------------------------------- */
|
||||
void MenuPresenter_InitModel(MenuPresenter *presenter, const tagMenuModel *modelTab, uint32_t modelCount)
|
||||
{
|
||||
/* 标记“首帧未绘制”,后续输入循环会先触发一次刷新。 */
|
||||
presenter->dspCtrl->bFirst = 1;
|
||||
/* 构建运行时菜单树并初始化控制状态。 */
|
||||
MenuModel_Bootstrap(presenter->menuCtrl,
|
||||
presenter->menuItems,
|
||||
modelTab,
|
||||
modelCount);
|
||||
/* 根据模型结果计算显示矩形位置。 */
|
||||
MenuView_Layout(&presenter->view, presenter->menuCtrl, presenter->layoutConfig);
|
||||
}
|
||||
|
||||
/* -------------------------------------------------------------------------
|
||||
* 函数: MenuPresenter_Refresh
|
||||
* 作用: 统一刷新入口,把“当前状态”交给 View 决定具体渲染策略。
|
||||
* ------------------------------------------------------------------------- */
|
||||
void MenuPresenter_Refresh(MenuPresenter *presenter)
|
||||
{
|
||||
/* View 根据 bFirst / ptCurBak / route 差异决定增量或全量刷新。 */
|
||||
MenuView_RenderByState(&presenter->view, presenter->menuCtrl, presenter->dspCtrl);
|
||||
}
|
||||
|
||||
/* -------------------------------------------------------------------------
|
||||
* 函数: MenuPresenter_HandleInput
|
||||
* 作用: 处理一次输入事件(按键),驱动导航状态迁移与视图刷新。
|
||||
*
|
||||
* 核心流程:
|
||||
* 1) 首帧短路:先渲染再返回;
|
||||
* 2) 拷贝导航状态 -> 调用导航器 -> 回写结果;
|
||||
* 3) 按导航结果决定是否跳过渲染、是否重建路径并刷新。
|
||||
* ------------------------------------------------------------------------- */
|
||||
void MenuPresenter_HandleInput(MenuPresenter *presenter, uint8_t keyVal)
|
||||
{
|
||||
/* 本轮输入使用的导航状态快照。 */
|
||||
MenuNavState navState;
|
||||
/* 导航器输出:是否需要刷新/是否跳过本轮渲染。 */
|
||||
MenuNavResult navResult;
|
||||
|
||||
/* 若仍是首帧阶段:优先做一次渲染,不消费导航逻辑。 */
|
||||
if (presenter->dspCtrl->bFirst)
|
||||
{
|
||||
/* 执行首帧刷新。 */
|
||||
MenuPresenter_Refresh(presenter);
|
||||
/* 首帧路径到此结束。 */
|
||||
return;
|
||||
}
|
||||
|
||||
/* 从控制结构提取导航子集,作为导航器输入。 */
|
||||
MenuPresenter_FillNavState(presenter->menuCtrl, &navState);
|
||||
/* 基于按键执行导航状态机,得到状态变化与渲染建议。 */
|
||||
navResult = MenuNavigator_ProcessKey(&navState, keyVal);
|
||||
/* 将导航器结果回写到运行时控制结构。 */
|
||||
MenuPresenter_ApplyNavState(presenter->menuCtrl, &navState);
|
||||
|
||||
/* 某些按键(如特定 ESC 场景)要求本轮跳过渲染。 */
|
||||
if (navResult.skipRenderThisRound)
|
||||
{
|
||||
/* 按导航器要求直接返回。 */
|
||||
return;
|
||||
}
|
||||
|
||||
/* 只有导航器明确要求刷新时,才进行路径重建与渲染。 */
|
||||
if (navResult.needRefresh)
|
||||
{
|
||||
/* 用于“自底向上”重建 route[] 的临时状态。 */
|
||||
MenuNavState rebuildState;
|
||||
/* 以最新控制状态为基准生成重建输入。 */
|
||||
MenuPresenter_FillNavState(presenter->menuCtrl, &rebuildState);
|
||||
/* 重建各级 route 缓存,保证后续渲染路径一致。 */
|
||||
MenuNavigator_RebuildRoute(&rebuildState, MENU_MAX_ITEM);
|
||||
/* 回写重建后的 route 与相关导航状态。 */
|
||||
MenuPresenter_ApplyNavState(presenter->menuCtrl, &rebuildState);
|
||||
/* 触发刷新,让界面体现本次输入结果。 */
|
||||
MenuPresenter_Refresh(presenter);
|
||||
}
|
||||
}
|
||||
@@ -1,30 +0,0 @@
|
||||
#ifndef MENU_PRESENTER_H
|
||||
#define MENU_PRESENTER_H
|
||||
|
||||
#include "../model/display.h"
|
||||
#include "../view/menu_layout.h"
|
||||
#include "../view/menu_render_port.h"
|
||||
#include "../common/menu_state_types.h"
|
||||
#include "../view/menu_view.h"
|
||||
|
||||
typedef struct
|
||||
{
|
||||
tagDspCtrl *dspCtrl;
|
||||
tagMenuCtrl *menuCtrl;
|
||||
tagMenuItem *menuItems;
|
||||
MenuView view;
|
||||
const MenuLayoutConfig *layoutConfig;
|
||||
} MenuPresenter;
|
||||
|
||||
void MenuPresenter_Setup(MenuPresenter *presenter,
|
||||
tagDspCtrl *dspCtrl,
|
||||
tagMenuCtrl *menuCtrl,
|
||||
tagMenuItem *menuItems,
|
||||
const MenuRenderPort *renderPort,
|
||||
const MenuLayoutConfig *layoutConfig);
|
||||
|
||||
void MenuPresenter_InitModel(MenuPresenter *presenter, const tagMenuModel *modelTab, uint32_t modelCount);
|
||||
void MenuPresenter_HandleInput(MenuPresenter *presenter, uint8_t keyVal);
|
||||
void MenuPresenter_Refresh(MenuPresenter *presenter);
|
||||
|
||||
#endif
|
||||
@@ -1,447 +0,0 @@
|
||||
#include <stddef.h>
|
||||
#include "menu_layout.h"
|
||||
#include "utf8.h"
|
||||
|
||||
/******************************************************************************
|
||||
* 模块: menu_layout.c
|
||||
* 职责: 根据菜单树结构计算各级菜单在 LCD 上的显示矩形区域
|
||||
*
|
||||
* 设计要点:
|
||||
* 1) 采用“先算当前层,再递归/级联算下一层”的方式布点;
|
||||
* 2) 每级菜单都维护:
|
||||
* - ptFirst[level]: 当前层循环链表的起点;
|
||||
* - ptIndex[level]: 当前层遍历游标;
|
||||
* - byMenuNum[level]: 当前层项数量(或当前位置计数);
|
||||
* 3) 对 Y 方向越界提供多级回退策略(向上翻转/贴顶重排),尽量保证菜单可见。
|
||||
*****************************************************************************/
|
||||
|
||||
/* 读取菜单节点导航指针集合(本文件局部辅助,避免 core 泄漏行为函数)。 */
|
||||
static MenuLinks MenuLayout_GetLinks(const MenuItem *item)
|
||||
{
|
||||
MenuLinks links;
|
||||
links.higher = item->ptHigher;
|
||||
links.lower = item->ptLower;
|
||||
links.before = item->ptBefore;
|
||||
links.behind = item->ptBehind;
|
||||
return links;
|
||||
}
|
||||
|
||||
/* 读取菜单节点矩形(本文件局部辅助)。 */
|
||||
static MenuRect MenuLayout_GetRect(const MenuItem *item)
|
||||
{
|
||||
MenuRect rect;
|
||||
rect.wSPosX = item->wSPosX;
|
||||
rect.wSPosY = item->wSPosY;
|
||||
rect.wEPosX = item->wEPosX;
|
||||
rect.wEPosY = item->wEPosY;
|
||||
return rect;
|
||||
}
|
||||
|
||||
/* 给菜单项写入矩形坐标的轻量封装,避免到处重复构造 MenuRect。 */
|
||||
static void MenuLayout_SetRect(MenuItem *item, uint16_t sx, uint16_t sy, uint16_t ex, uint16_t ey)
|
||||
{
|
||||
item->wSPosX = sx;
|
||||
item->wSPosY = sy;
|
||||
item->wEPosX = ex;
|
||||
item->wEPosY = ey;
|
||||
}
|
||||
|
||||
/* 将理论挂接行压缩映射到可视区,避免超出可显示行数。 */
|
||||
static uint16_t MenuLayout_MapMenuPos(uint16_t menuPos, uint8_t itemNum, uint8_t maxNum)
|
||||
{
|
||||
if ((itemNum > maxNum) && (menuPos >= maxNum))
|
||||
{
|
||||
return (uint16_t)(maxNum - (itemNum - menuPos));
|
||||
}
|
||||
return menuPos;
|
||||
}
|
||||
|
||||
/* 子菜单 X 方向统一规则:从父框右边展开。 */
|
||||
static void MenuLayout_CalcSubMenuX(const MenuRect *parentRect,
|
||||
uint8_t maxLen,
|
||||
const MenuLayoutConfig *config,
|
||||
uint16_t *startX,
|
||||
uint16_t *endX)
|
||||
{
|
||||
*startX = parentRect->wEPosX;
|
||||
*endX = (uint16_t)(*startX + maxLen * config->menuWidth + config->menuXAdd);
|
||||
}
|
||||
|
||||
/* 尝试将菜单框整体翻转到上方显示;成功返回 1,失败返回 0。 */
|
||||
static uint8_t MenuLayout_TryFlipUp(uint16_t *startY,
|
||||
uint16_t *endY,
|
||||
uint8_t itemNum,
|
||||
uint16_t flipMinY,
|
||||
const MenuLayoutConfig *config)
|
||||
{
|
||||
uint16_t candidateEndY;
|
||||
uint16_t temp;
|
||||
|
||||
candidateEndY = (uint16_t)(*startY - (itemNum - 1) * config->lineHeight - config->menuYAdd);
|
||||
if ((candidateEndY > flipMinY) && (candidateEndY < config->menuYMax))
|
||||
{
|
||||
temp = *startY;
|
||||
*startY = candidateEndY;
|
||||
*endY = (uint16_t)(temp + config->lineHeight);
|
||||
return 1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Sub2 兜底:回退到顶层起始 Y,并在底部做截断。 */
|
||||
static void MenuLayout_FallbackClampToTop(const tagPMenuItem *ptIndex,
|
||||
uint8_t itemNum,
|
||||
uint16_t *startY,
|
||||
uint16_t *endY,
|
||||
const MenuLayoutConfig *config)
|
||||
{
|
||||
*startY = ptIndex[0]->wSPosY;
|
||||
*endY = (uint16_t)(*startY + itemNum * config->lineHeight + config->menuYAdd);
|
||||
if (*endY > config->menuYMax)
|
||||
{
|
||||
*endY = config->menuYMax;
|
||||
}
|
||||
}
|
||||
|
||||
/* Sub1 兜底:从顶层起始 Y 逐行上探,直到菜单框不越界。 */
|
||||
static void MenuLayout_FallbackProbeUp(const tagPMenuItem *ptIndex,
|
||||
uint8_t itemNum,
|
||||
uint16_t *startY,
|
||||
uint16_t *endY,
|
||||
const MenuLayoutConfig *config)
|
||||
{
|
||||
*startY = ptIndex[0]->wSPosY;
|
||||
*endY = (uint16_t)(*startY + itemNum * config->lineHeight + config->menuYAdd);
|
||||
for (uint16_t i = 1; i < (uint16_t)(ptIndex[0]->wSPosY / config->lineHeight + 1); i++)
|
||||
{
|
||||
if (*endY > config->menuYMax)
|
||||
{
|
||||
*startY = (uint16_t)(ptIndex[0]->wSPosY - config->lineHeight * i);
|
||||
if (*startY < config->lineHeight)
|
||||
{
|
||||
*startY = 0;
|
||||
}
|
||||
*endY = (uint16_t)(*startY + itemNum * config->lineHeight + config->menuYAdd);
|
||||
}
|
||||
else
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* 计算单个菜单项“显示宽度”:
|
||||
* - 中文等多字节字符按 2 列计宽,ASCII 按 1 列;
|
||||
* - 若该项有下级菜单且名称末尾未带特殊标记 '\x10',额外预留 1 列用于层级指示。 */
|
||||
static uint8_t MenuLayout_ItemDisplayLen(const MenuItem *item)
|
||||
{
|
||||
uint8_t displayLen;
|
||||
displayLen = MenuLayout_Utf8LenCal((uint8_t *)item->byName);
|
||||
if (item->ptLower != NULL)
|
||||
{
|
||||
uint8_t byteLen = 0;
|
||||
while ((byteLen < 50) && (item->byName[byteLen] != '\0'))
|
||||
{
|
||||
byteLen++;
|
||||
}
|
||||
if ((byteLen == 0) || (item->byName[byteLen - 1] != '\x10'))
|
||||
{
|
||||
displayLen += 1;
|
||||
}
|
||||
}
|
||||
return displayLen;
|
||||
}
|
||||
|
||||
/* UTF-8 显示长度统计:
|
||||
* utf8_next() 每次返回一个 Unicode 字符占用的字节数 n,
|
||||
* 约定 n>1 视为“宽字符”(占 2 列),否则占 1 列。 */
|
||||
uint8_t MenuLayout_Utf8LenCal(uint8_t *str)
|
||||
{
|
||||
uint8_t strLen = 0;
|
||||
uint32_t unicode;
|
||||
uint8_t index = 0;
|
||||
uint8_t n = 0;
|
||||
|
||||
while (str[index] != '\0')
|
||||
{
|
||||
n = utf8_next(str + index, &unicode);
|
||||
strLen += (n > 1) ? 2 : 1;
|
||||
index += n;
|
||||
}
|
||||
return strLen;
|
||||
}
|
||||
|
||||
/******************************************************************************
|
||||
* 函数: MenuLayout_CharLenCal
|
||||
* 作用:
|
||||
* 1) 统计 bylevel 下一层(bylevel+1)菜单项总数;
|
||||
* 2) 计算该层中最长显示宽度(用于决定菜单框宽度);
|
||||
* 3) 为该层每个节点写 wPos(从 1 开始的位置序号);
|
||||
* 4) 把“下一层项数”写回父节点 wNum。
|
||||
*
|
||||
* 变量字典:
|
||||
* - bylevel: 输入为父层级,函数内部会自增到“子层级”;
|
||||
* - byMenuNum[level]: level 层累计项数(同时作为 wPos 的来源);
|
||||
* - ptFirst[level]: level 层循环链表的起点节点;
|
||||
* - ptIndex[level]: level 层当前遍历节点;
|
||||
* - byMaxLen: 本层所有项显示宽度的最大值(单位: 字符列)。
|
||||
*****************************************************************************/
|
||||
uint8_t MenuLayout_CharLenCal(uint8_t bylevel, uint8_t *byMenuNum, tagPMenuItem *ptFirst, tagPMenuItem *ptIndex)
|
||||
{
|
||||
uint8_t displayLen;
|
||||
uint8_t byMaxLen = 0;
|
||||
MenuLinks links;
|
||||
|
||||
/* 进入下一层,从父节点的 lower 作为该层环形链表起点。 */
|
||||
ptFirst[bylevel + 1] = ptIndex[bylevel]->ptLower;
|
||||
bylevel = bylevel + 1;
|
||||
ptIndex[bylevel] = ptFirst[bylevel];
|
||||
|
||||
byMenuNum[bylevel] = 1;
|
||||
ptIndex[bylevel]->wPos = 1;
|
||||
|
||||
/* 固定上限 300 作为保护,避免链表异常导致死循环。 */
|
||||
for (uint16_t wLoop = 0; wLoop < 300; wLoop++)
|
||||
{
|
||||
links = MenuLayout_GetLinks(ptIndex[bylevel]);
|
||||
displayLen = MenuLayout_ItemDisplayLen(ptIndex[bylevel]);
|
||||
|
||||
if (byMaxLen < displayLen)
|
||||
{
|
||||
byMaxLen = displayLen;
|
||||
}
|
||||
|
||||
ptIndex[bylevel] = links.behind;
|
||||
if (ptIndex[bylevel] == ptFirst[bylevel])
|
||||
{
|
||||
break;
|
||||
}
|
||||
byMenuNum[bylevel]++;
|
||||
ptIndex[bylevel]->wPos = byMenuNum[bylevel];
|
||||
}
|
||||
|
||||
/* 父节点记录子项数量,供分页/显示逻辑复用。 */
|
||||
ptIndex[bylevel - 1]->wNum = byMenuNum[bylevel];
|
||||
return byMaxLen;
|
||||
}
|
||||
|
||||
/******************************************************************************
|
||||
* 函数: MenuLayout_Sub2PosCal
|
||||
* 作用: 计算二级子菜单(及其同层兄弟)的显示矩形
|
||||
* 策略:
|
||||
* - 先按“父项对齐”得到理想 Y;
|
||||
* - 若底部越界,尝试向上翻转;
|
||||
* - 仍不满足则使用贴近顶层菜单的兜底摆放。
|
||||
*
|
||||
* 变量字典:
|
||||
* - byMaxNum: 当前配置下可完整显示的最大行数;
|
||||
* - byMenuPos: 当前菜单相对父菜单的挂接行(理论位置);
|
||||
* - byItemNum: 当前层或下一层项数(代码中按阶段复用);
|
||||
* - wSPosY/wEPosY: 菜单框起止 Y 坐标(含间距边界);
|
||||
* - parentRect: 父菜单矩形,用于对子菜单做相对定位。
|
||||
*
|
||||
* 关键公式:
|
||||
* - X 起点 = parentRect.wEPosX(总是从父框右侧展开);
|
||||
* - X 终点 = X 起点 + byMaxLen * menuWidth + menuXAdd;
|
||||
* - Y 起点 = parentRect.wSPosY + (byMenuPos - 1) * lineHeight;
|
||||
* - Y 终点 = Y 起点 + 子项数 * lineHeight + menuYAdd。
|
||||
*****************************************************************************/
|
||||
void MenuLayout_Sub2PosCal(uint8_t bylevel, tagPMenuItem *ptFirst, tagPMenuItem *ptIndex, const MenuLayoutConfig *config)
|
||||
{
|
||||
uint16_t wSPosY;
|
||||
uint16_t wEPosY;
|
||||
uint8_t byMaxLen;
|
||||
uint8_t byMaxNum;
|
||||
uint16_t byMenuPos;
|
||||
uint8_t byItemNum;
|
||||
uint8_t byMenuNum[4];
|
||||
MenuLinks links;
|
||||
MenuRect parentRect;
|
||||
|
||||
/* 当前可完整容纳的最大行数(预留边框/间距)。 */
|
||||
byMaxNum = (config->menuYMax - config->menuYMin - 6) / config->lineHeight;
|
||||
ptIndex[bylevel] = ptFirst[bylevel];
|
||||
for (uint16_t wLoop = 0; wLoop < 300; wLoop++)
|
||||
{
|
||||
links = MenuLayout_GetLinks(ptIndex[bylevel]);
|
||||
if (links.lower != NULL)
|
||||
{
|
||||
byMaxLen = MenuLayout_CharLenCal(bylevel, byMenuNum, ptFirst, ptIndex);
|
||||
parentRect = MenuLayout_GetRect(ptIndex[bylevel - 1]);
|
||||
MenuLayout_CalcSubMenuX(&parentRect,
|
||||
byMaxLen,
|
||||
config,
|
||||
&ptIndex[bylevel]->wSPosX,
|
||||
&ptIndex[bylevel]->wEPosX);
|
||||
|
||||
/* byMenuPos: 当前项在同层中的理论挂接位置(必要时压缩映射)。 */
|
||||
byMenuPos = ptIndex[bylevel]->wPos;
|
||||
byItemNum = byMenuNum[bylevel];
|
||||
byMenuPos = MenuLayout_MapMenuPos(byMenuPos, byItemNum, byMaxNum);
|
||||
|
||||
wSPosY = parentRect.wSPosY + (byMenuPos - 1) * config->lineHeight;
|
||||
byItemNum = byMenuNum[bylevel + 1];
|
||||
wEPosY = wSPosY + byItemNum * config->lineHeight + config->menuYAdd;
|
||||
|
||||
if (wEPosY < config->menuYMax)
|
||||
{
|
||||
/* 理想位置不越界,直接应用。 */
|
||||
MenuLayout_SetRect(ptIndex[bylevel], ptIndex[bylevel]->wSPosX, wSPosY, ptIndex[bylevel]->wEPosX, wEPosY);
|
||||
}
|
||||
else
|
||||
{
|
||||
/* 底部越界:先尝试“向上翻转”放置整个菜单框。 */
|
||||
if (!MenuLayout_TryFlipUp(&wSPosY, &wEPosY, byItemNum, config->menuYMin, config))
|
||||
{
|
||||
/* 仍不满足:回退到顶层菜单起始 Y,再做截断兜底。 */
|
||||
MenuLayout_FallbackClampToTop(ptIndex, byItemNum, &wSPosY, &wEPosY, config);
|
||||
}
|
||||
MenuLayout_SetRect(ptIndex[bylevel], ptIndex[bylevel]->wSPosX, wSPosY, ptIndex[bylevel]->wEPosX, wEPosY);
|
||||
}
|
||||
}
|
||||
ptIndex[bylevel] = links.behind;
|
||||
if (ptIndex[bylevel] == ptFirst[bylevel])
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/******************************************************************************
|
||||
* 函数: MenuLayout_Sub1PosCal
|
||||
* 作用: 计算一级子菜单(通常是主菜单下拉)的位置,并联动计算二级子菜单
|
||||
* 说明: 与 Sub2 逻辑相近,但在兜底时会逐步向上平移,直到菜单框可容纳。
|
||||
*
|
||||
* 变量字典:
|
||||
* - byMaxNum: 屏幕当前可显示的最大菜单行数;
|
||||
* - byMenuPos: 当前节点理论挂接行(可能被映射压缩);
|
||||
* - byItemNum: 当前下拉框条目数;
|
||||
* - wTemp: 用于“翻转布局”时交换起止 Y;
|
||||
* - byMenuNum[4]: 分层统计数组,索引与菜单层级一一对应。
|
||||
*
|
||||
* 与 Sub2 的核心差异:
|
||||
* - Sub1 兜底策略增加“逐行上探”,尝试找到不越界的最靠下位置,
|
||||
* 视觉上更适合主菜单下拉场景。
|
||||
*****************************************************************************/
|
||||
void MenuLayout_Sub1PosCal(uint8_t bylevel, tagPMenuItem *ptFirst, tagPMenuItem *ptIndex, const MenuLayoutConfig *config)
|
||||
{
|
||||
uint16_t wSPosY;
|
||||
uint16_t wEPosY;
|
||||
uint8_t byMenuNum[4];
|
||||
uint8_t byMaxLen;
|
||||
uint8_t byMaxNum;
|
||||
uint16_t byMenuPos;
|
||||
uint8_t byItemNum;
|
||||
MenuLinks links;
|
||||
MenuRect parentRect;
|
||||
|
||||
byMaxNum = config->menuYMax / config->lineHeight;
|
||||
ptIndex[bylevel] = ptFirst[bylevel];
|
||||
for (uint16_t wLoop = 0; wLoop < 300; wLoop++)
|
||||
{
|
||||
links = MenuLayout_GetLinks(ptIndex[bylevel]);
|
||||
if (links.lower != NULL)
|
||||
{
|
||||
byMaxLen = MenuLayout_CharLenCal(bylevel, byMenuNum, ptFirst, ptIndex);
|
||||
parentRect = MenuLayout_GetRect(ptIndex[bylevel - 1]);
|
||||
MenuLayout_CalcSubMenuX(&parentRect,
|
||||
byMaxLen,
|
||||
config,
|
||||
&ptIndex[bylevel]->wSPosX,
|
||||
&ptIndex[bylevel]->wEPosX);
|
||||
|
||||
byMenuPos = ptIndex[bylevel]->wPos;
|
||||
byItemNum = byMenuNum[bylevel];
|
||||
byMenuPos = MenuLayout_MapMenuPos(byMenuPos, byItemNum, byMaxNum);
|
||||
|
||||
wSPosY = parentRect.wSPosY + (byMenuPos - 1) * config->lineHeight;
|
||||
byItemNum = byMenuNum[bylevel + 1];
|
||||
wEPosY = wSPosY + byItemNum * config->lineHeight + config->menuYAdd;
|
||||
|
||||
if (wEPosY < config->menuYMax)
|
||||
{
|
||||
MenuLayout_SetRect(ptIndex[bylevel], ptIndex[bylevel]->wSPosX, wSPosY, ptIndex[bylevel]->wEPosX, wEPosY);
|
||||
}
|
||||
else
|
||||
{
|
||||
/* 底部越界:优先尝试一次整体翻转到上方。 */
|
||||
if (!MenuLayout_TryFlipUp(&wSPosY, &wEPosY, byItemNum, config->lineHeight, config))
|
||||
{
|
||||
/* 二次兜底:从顶层菜单起始 Y 往上逐行试探,直到不越界。 */
|
||||
MenuLayout_FallbackProbeUp(ptIndex, byItemNum, &wSPosY, &wEPosY, config);
|
||||
}
|
||||
MenuLayout_SetRect(ptIndex[bylevel], ptIndex[bylevel]->wSPosX, wSPosY, ptIndex[bylevel]->wEPosX, wEPosY);
|
||||
}
|
||||
/* 一级菜单定位完成后,继续计算二级菜单。 */
|
||||
MenuLayout_Sub2PosCal(2, ptFirst, ptIndex, config);
|
||||
}
|
||||
ptIndex[bylevel] = links.behind;
|
||||
if (ptIndex[bylevel] == ptFirst[bylevel])
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/******************************************************************************
|
||||
* 函数: MenuLayout_PositionCal
|
||||
* 作用: 入口函数,计算从 0 级到 2 级菜单的显示坐标
|
||||
* 流程:
|
||||
* 1) 先给 0 级菜单按屏宽等分;
|
||||
* 2) 每个 0 级项若有下级,则计算其下拉框;
|
||||
* 3) 再级联计算 1/2 级子菜单。
|
||||
*
|
||||
* 变量字典:
|
||||
* - by0LevelNum: 顶层菜单数量;
|
||||
* - byInterval: 顶层菜单等分后的列宽;
|
||||
* - byMenuNum[0]: 当前顶层节点在同层中的序号(从 1 开始);
|
||||
* - byMenuNum[1]: 顶层节点的直接子项数量(由 CharLenCal 写入);
|
||||
* - ptFirst/ptIndex: 各层遍历上下文,在本函数中作为全流程共享状态。
|
||||
*****************************************************************************/
|
||||
void MenuLayout_PositionCal(tagPMenuItem ptMenuHead, uint8_t by0LevelNum, const MenuLayoutConfig *config)
|
||||
{
|
||||
tagPMenuItem ptFirst[4];
|
||||
tagPMenuItem ptIndex[4];
|
||||
uint8_t byMenuNum[4];
|
||||
uint8_t byMaxLen;
|
||||
uint8_t byInterval;
|
||||
MenuLinks links;
|
||||
|
||||
/* 输入保护:头指针/配置为空,或 0 级菜单数量为 0 时直接返回。 */
|
||||
if ((ptMenuHead == NULL) || (config == NULL) || (by0LevelNum == 0))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
ptFirst[0] = ptMenuHead;
|
||||
ptIndex[0] = ptFirst[0];
|
||||
byMenuNum[0] = 1;
|
||||
ptIndex[0]->wPos = 1;
|
||||
/* 顶层菜单按等分宽度布局。 */
|
||||
byInterval = config->lcdSizeX / by0LevelNum;
|
||||
|
||||
for (uint16_t wLoop = 0; wLoop < by0LevelNum; wLoop++)
|
||||
{
|
||||
links = MenuLayout_GetLinks(ptIndex[0]);
|
||||
if (links.lower != NULL)
|
||||
{
|
||||
/* 先统计该分支子项宽高需求,再写入 0 级菜单框。 */
|
||||
byMaxLen = MenuLayout_CharLenCal(0, byMenuNum, ptFirst, ptIndex);
|
||||
MenuLayout_SetRect(
|
||||
ptIndex[0],
|
||||
(byMenuNum[0] - 1) * byInterval,
|
||||
config->lcdSizeY - config->lineHeight - byMenuNum[1] * config->lineHeight - config->menuYAdd,
|
||||
(byMenuNum[0] - 1) * byInterval + byMaxLen * config->menuWidth + config->menuXAdd,
|
||||
config->lcdSizeY - config->lineHeight);
|
||||
/* 0 级完成后继续展开 1/2 级。 */
|
||||
MenuLayout_Sub1PosCal(1, ptFirst, ptIndex, config);
|
||||
}
|
||||
ptIndex[0] = links.behind;
|
||||
if (ptIndex[0] == ptFirst[0])
|
||||
{
|
||||
break;
|
||||
}
|
||||
byMenuNum[0]++;
|
||||
ptIndex[0]->wPos = byMenuNum[0];
|
||||
}
|
||||
}
|
||||
@@ -1,34 +0,0 @@
|
||||
#ifndef MENU_LAYOUT_H
|
||||
#define MENU_LAYOUT_H
|
||||
|
||||
#include "../common/menu_item_types.h"
|
||||
|
||||
#define CN_HEIGHT 12
|
||||
#define CN_ROWSPACE 2
|
||||
#define LINE_HEIGHT (CN_HEIGHT + CN_ROWSPACE)
|
||||
#define MENU_XADD 4
|
||||
#define MENU_YADD 4
|
||||
#define MENU_WITDTH 7
|
||||
|
||||
typedef struct
|
||||
{
|
||||
uint16_t lcdSizeX;
|
||||
uint16_t lcdSizeY;
|
||||
uint16_t menuYMin;
|
||||
uint16_t menuYMax;
|
||||
uint16_t lineHeight;
|
||||
uint16_t menuWidth;
|
||||
uint16_t menuXAdd;
|
||||
uint16_t menuYAdd;
|
||||
} MenuLayoutConfig;
|
||||
|
||||
#define MENU_YMIN 0
|
||||
#define MENU_YMAX_FROM_LCD(ySize) ((uint16_t)((ySize) - 12U))
|
||||
|
||||
uint8_t MenuLayout_Utf8LenCal(uint8_t *str);
|
||||
uint8_t MenuLayout_CharLenCal(uint8_t bylevel, uint8_t *byMenuNum, tagPMenuItem *ptFirst, tagPMenuItem *ptIndex);
|
||||
void MenuLayout_Sub2PosCal(uint8_t bylevel, tagPMenuItem *ptFirst, tagPMenuItem *ptIndex, const MenuLayoutConfig *config);
|
||||
void MenuLayout_Sub1PosCal(uint8_t bylevel, tagPMenuItem *ptFirst, tagPMenuItem *ptIndex, const MenuLayoutConfig *config);
|
||||
void MenuLayout_PositionCal(tagPMenuItem ptMenuHead, uint8_t by0LevelNum, const MenuLayoutConfig *config);
|
||||
|
||||
#endif
|
||||
@@ -1,45 +0,0 @@
|
||||
#include "menu_render_port.h"
|
||||
|
||||
#include "lcd.h"
|
||||
#include "lcd_draw.h"
|
||||
#include "lcd_text.h"
|
||||
|
||||
static unsigned short MenuRenderPort_LcdSizeX(void)
|
||||
{
|
||||
return LCD_SIZE_X;
|
||||
}
|
||||
|
||||
static unsigned short MenuRenderPort_LcdSizeY(void)
|
||||
{
|
||||
return LCD_SIZE_Y;
|
||||
}
|
||||
|
||||
static unsigned char MenuRenderPort_LcdColorFont(void)
|
||||
{
|
||||
return LCD_FONT;
|
||||
}
|
||||
|
||||
static unsigned char MenuRenderPort_LcdColorBack(void)
|
||||
{
|
||||
return LCD_BACK;
|
||||
}
|
||||
|
||||
static const MenuRenderPort g_lcd_port = {
|
||||
.init = Lcd_Init,
|
||||
.get_size_x = MenuRenderPort_LcdSizeX,
|
||||
.get_size_y = MenuRenderPort_LcdSizeY,
|
||||
.get_color_font = MenuRenderPort_LcdColorFont,
|
||||
.get_color_back = MenuRenderPort_LcdColorBack,
|
||||
.fill_rect = Lcd_FillRect,
|
||||
.line_h = Lcd_LineH,
|
||||
.line_v = Lcd_LineV,
|
||||
.line = Lcd_Line,
|
||||
.set_pixel = Lcd_SetPixel,
|
||||
.invert = Lcd_Invert,
|
||||
.show_str = Lcd_ShowStr,
|
||||
};
|
||||
|
||||
const MenuRenderPort *MenuRenderPort_Lcd(void)
|
||||
{
|
||||
return &g_lcd_port;
|
||||
}
|
||||
@@ -1,114 +0,0 @@
|
||||
#include "menu_view.h"
|
||||
|
||||
static void MenuView_DrawMeitou(MenuView *view, uint16_t yStart, uint16_t width)
|
||||
{
|
||||
uint8_t fontColor = view->port->get_color_font();
|
||||
view->port->line_h(16, 144, yStart, width, fontColor);
|
||||
view->port->line(8, yStart - 8, 16, yStart, width, fontColor);
|
||||
view->port->line(144, yStart, 152, yStart - 8, width, fontColor);
|
||||
}
|
||||
|
||||
static void MenuView_DrawBoundaryBox(MenuView *view, uint16_t leftX, uint16_t topY, uint16_t rightX, uint16_t bottomY)
|
||||
{
|
||||
uint8_t backColor = view->port->get_color_back();
|
||||
uint8_t fontColor = view->port->get_color_font();
|
||||
for (uint16_t y = topY; y < bottomY; y++)
|
||||
{
|
||||
for (uint16_t x = leftX; x < rightX; x++)
|
||||
{
|
||||
view->port->set_pixel(x, y, backColor);
|
||||
}
|
||||
}
|
||||
|
||||
view->port->line_h(leftX, rightX, topY, 1, fontColor);
|
||||
view->port->line_v(topY, bottomY, leftX, 1, fontColor);
|
||||
view->port->line_h(leftX, rightX + 1, bottomY, 1, fontColor);
|
||||
view->port->line_v(topY, bottomY, rightX, 1, fontColor);
|
||||
}
|
||||
|
||||
static void MenuView_ShowOtherLevel(MenuView *view, const tagMenuCtrl *menuCtrl, uint8_t level)
|
||||
{
|
||||
tagPMenuItem ptIndex = menuCtrl->ptRoute[level];
|
||||
tagPMenuItem ptRoute;
|
||||
uint16_t wPosX;
|
||||
uint16_t wPosY;
|
||||
|
||||
MenuView_DrawBoundaryBox(view, ptIndex->wSPosX, ptIndex->wSPosY, ptIndex->wEPosX, ptIndex->wEPosY);
|
||||
|
||||
ptRoute = menuCtrl->ptRoute[level + 1];
|
||||
ptIndex = ptRoute;
|
||||
wPosX = menuCtrl->ptRoute[level]->wSPosX + 4;
|
||||
for (uint16_t index = 0; index < menuCtrl->ptRoute[level]->wNum; index++)
|
||||
{
|
||||
wPosY = menuCtrl->ptRoute[level]->wSPosY + (ptIndex->wPos - 1) * LINE_HEIGHT + 3;
|
||||
view->port->show_str(wPosX, wPosY, ptIndex->byName);
|
||||
if (ptRoute == ptIndex)
|
||||
{
|
||||
view->port->invert(menuCtrl->ptRoute[level]->wSPosX + 2, wPosY - 1, menuCtrl->ptRoute[level]->wEPosX - 2, wPosY + 14);
|
||||
}
|
||||
ptIndex = ptIndex->ptBehind;
|
||||
}
|
||||
}
|
||||
|
||||
static void MenuView_ShowTopLevel(MenuView *view)
|
||||
{
|
||||
uint16_t lcdSizeX = view->port->get_size_x();
|
||||
view->port->fill_rect(0, 0, lcdSizeX - 1, 32, view->port->get_color_back());
|
||||
MenuView_DrawMeitou(view, 16, 2);
|
||||
view->port->show_str(16, 20, (uint8_t *)"当前模式: 无模式");
|
||||
}
|
||||
|
||||
void MenuView_Init(MenuView *view, const MenuRenderPort *renderPort)
|
||||
{
|
||||
view->port = renderPort;
|
||||
}
|
||||
|
||||
void MenuView_Layout(MenuView *view, tagMenuCtrl *menuCtrl, const MenuLayoutConfig *config)
|
||||
{
|
||||
(void)view;
|
||||
MenuLayout_PositionCal(menuCtrl->ptHead, menuCtrl->by0LevelNum, config);
|
||||
}
|
||||
|
||||
void MenuView_RenderByState(MenuView *view, tagMenuCtrl *menuCtrl, tagDspCtrl *dspCtrl)
|
||||
{
|
||||
uint8_t needFullRefresh = 0;
|
||||
uint8_t backColor = view->port->get_color_back();
|
||||
uint16_t lcdSizeX = view->port->get_size_x();
|
||||
uint16_t menuYMax = MENU_YMAX_FROM_LCD(view->port->get_size_y());
|
||||
|
||||
if (dspCtrl->bFirst)
|
||||
{
|
||||
dspCtrl->bFirst = 0;
|
||||
needFullRefresh = 1;
|
||||
}
|
||||
|
||||
if (menuCtrl->pt0Level != menuCtrl->ptRoute[0])
|
||||
{
|
||||
needFullRefresh = 1;
|
||||
view->port->fill_rect(0, MENU_YMIN, lcdSizeX - 1, menuYMax, backColor);
|
||||
menuCtrl->pt0Level = menuCtrl->ptRoute[0];
|
||||
menuCtrl->ptCurBak = menuCtrl->ptCurrent;
|
||||
}
|
||||
else if (menuCtrl->ptCurBak != menuCtrl->ptCurrent)
|
||||
{
|
||||
if (menuCtrl->ptCurrent->byClass >= menuCtrl->ptCurBak->byClass)
|
||||
{
|
||||
MenuView_ShowOtherLevel(view, menuCtrl, menuCtrl->ptCurrent->byClass - 1);
|
||||
}
|
||||
else
|
||||
{
|
||||
view->port->fill_rect(0, MENU_YMIN, lcdSizeX - 1, menuYMax, backColor);
|
||||
needFullRefresh = 1;
|
||||
}
|
||||
menuCtrl->ptCurBak = menuCtrl->ptCurrent;
|
||||
}
|
||||
|
||||
if (needFullRefresh > 0)
|
||||
{
|
||||
MenuView_ShowTopLevel(view);
|
||||
for (uint8_t index = 0; index < menuCtrl->ptCurrent->byClass; index++)
|
||||
{
|
||||
MenuView_ShowOtherLevel(view, menuCtrl, index);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,17 +0,0 @@
|
||||
#ifndef MENU_VIEW_H
|
||||
#define MENU_VIEW_H
|
||||
|
||||
#include "menu_layout.h"
|
||||
#include "menu_render_port.h"
|
||||
#include "../common/menu_state_types.h"
|
||||
|
||||
typedef struct
|
||||
{
|
||||
const MenuRenderPort *port;
|
||||
} MenuView;
|
||||
|
||||
void MenuView_Init(MenuView *view, const MenuRenderPort *renderPort);
|
||||
void MenuView_Layout(MenuView *view, tagMenuCtrl *menuCtrl, const MenuLayoutConfig *config);
|
||||
void MenuView_RenderByState(MenuView *view, tagMenuCtrl *menuCtrl, tagDspCtrl *dspCtrl);
|
||||
|
||||
#endif
|
||||
85
src/Drv/pages/global/global_state.c
Normal file
85
src/Drv/pages/global/global_state.c
Normal file
@@ -0,0 +1,85 @@
|
||||
#include <string.h>
|
||||
|
||||
#include "global_state.h"
|
||||
|
||||
static GlobalModel s_globalModel;
|
||||
|
||||
static void GlobalModel_CopyText(uint8_t *dst, uint16_t dstSize, const uint8_t *src)
|
||||
{
|
||||
uint16_t i;
|
||||
if ((dst == NULL) || (dstSize == 0))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (src == NULL)
|
||||
{
|
||||
dst[0] = '\0';
|
||||
return;
|
||||
}
|
||||
|
||||
for (i = 0; i < (uint16_t)(dstSize - 1); i++)
|
||||
{
|
||||
dst[i] = src[i];
|
||||
if (src[i] == '\0')
|
||||
{
|
||||
return;
|
||||
}
|
||||
}
|
||||
dst[dstSize - 1] = '\0';
|
||||
}
|
||||
|
||||
void GlobalModel_Init(void)
|
||||
{
|
||||
memset(&s_globalModel, 0, sizeof(s_globalModel));
|
||||
}
|
||||
|
||||
GlobalModel *GlobalModel_Instance(void)
|
||||
{
|
||||
return &s_globalModel;
|
||||
}
|
||||
|
||||
void GlobalModel_SetActiveYcType(uint16_t ycType)
|
||||
{
|
||||
s_globalModel.activeYcType = ycType;
|
||||
}
|
||||
|
||||
uint16_t GlobalModel_GetActiveYcType(void)
|
||||
{
|
||||
return s_globalModel.activeYcType;
|
||||
}
|
||||
|
||||
void GlobalModel_SetMenuAction(uint16_t actionId,
|
||||
const uint8_t *menuName,
|
||||
const uint8_t *menuTip,
|
||||
uint16_t menuPara)
|
||||
{
|
||||
s_globalModel.activeActionId = actionId;
|
||||
s_globalModel.activeMenuPara = menuPara;
|
||||
GlobalModel_CopyText(s_globalModel.activeMenuName,
|
||||
(uint16_t)sizeof(s_globalModel.activeMenuName),
|
||||
menuName);
|
||||
GlobalModel_CopyText(s_globalModel.activeMenuTip,
|
||||
(uint16_t)sizeof(s_globalModel.activeMenuTip),
|
||||
menuTip);
|
||||
}
|
||||
|
||||
uint16_t GlobalModel_GetMenuActionId(void)
|
||||
{
|
||||
return s_globalModel.activeActionId;
|
||||
}
|
||||
|
||||
uint16_t GlobalModel_GetMenuActionPara(void)
|
||||
{
|
||||
return s_globalModel.activeMenuPara;
|
||||
}
|
||||
|
||||
const uint8_t *GlobalModel_GetMenuActionName(void)
|
||||
{
|
||||
return s_globalModel.activeMenuName;
|
||||
}
|
||||
|
||||
const uint8_t *GlobalModel_GetMenuActionTip(void)
|
||||
{
|
||||
return s_globalModel.activeMenuTip;
|
||||
}
|
||||
71
src/Drv/pages/global/global_state.h
Normal file
71
src/Drv/pages/global/global_state.h
Normal file
@@ -0,0 +1,71 @@
|
||||
#ifndef PAGE_GLOBAL_STATE_H
|
||||
#define PAGE_GLOBAL_STATE_H
|
||||
|
||||
#include "types.h"
|
||||
|
||||
typedef enum
|
||||
{
|
||||
MENU_ACTION_NONE = 0,
|
||||
MENU_ACTION_SET_VALUE,
|
||||
MENU_ACTION_SET_SOFT,
|
||||
MENU_ACTION_CFG_TIME,
|
||||
MENU_ACTION_CFG_REV_EVENT,
|
||||
MENU_ACTION_CFG_MANUAL_WAVE,
|
||||
MENU_ACTION_SEE_INPUT,
|
||||
MENU_ACTION_SEE_SET,
|
||||
MENU_ACTION_SEE_SOFT,
|
||||
MENU_ACTION_YX_SET_COMM_TYPE,
|
||||
MENU_ACTION_YX_SET_WIDTH,
|
||||
MENU_ACTION_YX_SET_TWIN,
|
||||
MENU_ACTION_ALLINF_DEFAULT,
|
||||
MENU_ACTION_PARA_DEFAULT,
|
||||
MENU_ACTION_SET_DEFAULT,
|
||||
MENU_ACTION_RESUME_SOFT,
|
||||
MENU_ACTION_CFG_CELL_DEF,
|
||||
MENU_ACTION_CFG_SHOW_ANA_TYPE,
|
||||
MENU_ACTION_DBG_XU_YX,
|
||||
MENU_ACTION_DBG_XU_YC,
|
||||
MENU_ACTION_DBG_XU_EVENT,
|
||||
MENU_ACTION_DBG_RELAY,
|
||||
MENU_ACTION_SEE_VERSION_BOARD,
|
||||
MENU_ACTION_CFG_CLR_REC,
|
||||
MENU_ACTION_CFG_COM_PARA,
|
||||
MENU_ACTION_CFG_EDIT_IP,
|
||||
MENU_ACTION_CFG_EDIT_SNTP,
|
||||
MENU_ACTION_CFG_CELL_CONF,
|
||||
MENU_ACTION_SEE_REC_SOE,
|
||||
MENU_ACTION_SEE_REC_ACT,
|
||||
MENU_ACTION_SEE_REC_OPT,
|
||||
MENU_ACTION_SEE_REC_ALM,
|
||||
MENU_ACTION_SEE_REC_START,
|
||||
MENU_ACTION_SEE_REC_YK,
|
||||
MENU_ACTION_SEE_REC_CHK,
|
||||
MENU_ACTION_SEE_REC_RUN,
|
||||
MENU_ACTION_SEE_REC_FAULT,
|
||||
MENU_ACTION_YC_SET_SQ_VALUE,
|
||||
MENU_ACTION_YC_SET_ADJ_COE
|
||||
} menu_action_id_t;
|
||||
|
||||
typedef struct
|
||||
{
|
||||
uint16_t activeYcType;
|
||||
uint16_t activeMenuPara;
|
||||
uint16_t activeActionId;
|
||||
uint8_t activeMenuName[50];
|
||||
uint8_t activeMenuTip[50];
|
||||
} GlobalModel;
|
||||
|
||||
void GlobalModel_Init(void);
|
||||
GlobalModel *GlobalModel_Instance(void);
|
||||
void GlobalModel_SetActiveYcType(uint16_t ycType);
|
||||
uint16_t GlobalModel_GetActiveYcType(void);
|
||||
void GlobalModel_SetMenuAction(uint16_t actionId,
|
||||
const uint8_t *menuName,
|
||||
const uint8_t *menuTip,
|
||||
uint16_t menuPara);
|
||||
uint16_t GlobalModel_GetMenuActionId(void);
|
||||
uint16_t GlobalModel_GetMenuActionPara(void);
|
||||
const uint8_t *GlobalModel_GetMenuActionName(void);
|
||||
const uint8_t *GlobalModel_GetMenuActionTip(void);
|
||||
|
||||
#endif
|
||||
44
src/Drv/pages/global/renderer_lcd.c
Normal file
44
src/Drv/pages/global/renderer_lcd.c
Normal file
@@ -0,0 +1,44 @@
|
||||
#include "renderer_lcd.h"
|
||||
|
||||
#include "lcd.h"
|
||||
#include "lcd_draw.h"
|
||||
#include "lcd_text.h"
|
||||
|
||||
static unsigned short PageRenderer_LcdSizeX(void)
|
||||
{
|
||||
return LCD_SIZE_X;
|
||||
}
|
||||
|
||||
static unsigned short PageRenderer_LcdSizeY(void)
|
||||
{
|
||||
return LCD_SIZE_Y;
|
||||
}
|
||||
|
||||
static unsigned char PageRenderer_LcdColorFont(void)
|
||||
{
|
||||
return LCD_FONT;
|
||||
}
|
||||
|
||||
static unsigned char PageRenderer_LcdColorBack(void)
|
||||
{
|
||||
return LCD_BACK;
|
||||
}
|
||||
|
||||
static const PageRenderPort g_lcd_port = {
|
||||
.get_size_x = PageRenderer_LcdSizeX,
|
||||
.get_size_y = PageRenderer_LcdSizeY,
|
||||
.get_color_font = PageRenderer_LcdColorFont,
|
||||
.get_color_back = PageRenderer_LcdColorBack,
|
||||
.fill_rect = Lcd_FillRect,
|
||||
.line_h = Lcd_LineH,
|
||||
.line_v = Lcd_LineV,
|
||||
.line = Lcd_Line,
|
||||
.set_pixel = Lcd_SetPixel,
|
||||
.invert = Lcd_Invert,
|
||||
.show_str = Lcd_ShowStr,
|
||||
};
|
||||
|
||||
const PageRenderPort *PageRenderer_Lcd(void)
|
||||
{
|
||||
return &g_lcd_port;
|
||||
}
|
||||
@@ -1,9 +1,8 @@
|
||||
#ifndef MENU_RENDER_PORT_H
|
||||
#define MENU_RENDER_PORT_H
|
||||
#ifndef PAGE_RENDERER_LCD_H
|
||||
#define PAGE_RENDERER_LCD_H
|
||||
|
||||
typedef struct
|
||||
{
|
||||
void (*init)(void);
|
||||
unsigned short (*get_size_x)(void);
|
||||
unsigned short (*get_size_y)(void);
|
||||
unsigned char (*get_color_font)(void);
|
||||
@@ -15,8 +14,8 @@ typedef struct
|
||||
signed char (*set_pixel)(unsigned short x, unsigned short y, unsigned char color);
|
||||
signed char (*invert)(unsigned short left_x, unsigned short top_y, unsigned short right_x, unsigned short bottom_y);
|
||||
signed char (*show_str)(unsigned short x, unsigned short y, unsigned char *text);
|
||||
} MenuRenderPort;
|
||||
} PageRenderPort;
|
||||
|
||||
const MenuRenderPort *MenuRenderPort_Lcd(void);
|
||||
const PageRenderPort *PageRenderer_Lcd(void);
|
||||
|
||||
#endif
|
||||
145
src/Drv/pages/menu/def.h
Normal file
145
src/Drv/pages/menu/def.h
Normal file
@@ -0,0 +1,145 @@
|
||||
#ifndef MENU_def_H
|
||||
#define MENU_def_H
|
||||
|
||||
/* layout constants are unified into menu_view */
|
||||
#define MENU_SIZE_Y (160)
|
||||
#define MENU_SIZE_X (160)
|
||||
#define CN_HEIGHT 12
|
||||
#define CN_ROWSPACE 2
|
||||
#define LINE_HEIGHT (CN_HEIGHT + CN_ROWSPACE)
|
||||
#define MENU_XADD 4
|
||||
#define MENU_YADD 4
|
||||
#define MENU_WITDTH 7
|
||||
#define MENU_YMIN 0
|
||||
#define MENU_YMAX (MENU_SIZE_Y - CN_HEIGHT)
|
||||
|
||||
|
||||
/* -------------------------------------------------------------------------
|
||||
* 枚举名: MenuMode
|
||||
* 功能:
|
||||
* 定义菜单页顶部状态栏显示所使用的运行模式。
|
||||
*
|
||||
* 枚举值说明:
|
||||
* MODE_NONE - 无模式/默认模式
|
||||
* MODE_OVERFLOW_PROTECTION - 过流保护模式
|
||||
* MODE_LOCAL_FEEDER_SEGMENT - 就地馈线分段模式
|
||||
* MODE_LOCAL_FEEDER_CONTACT - 就地馈线联络模式
|
||||
* ------------------------------------------------------------------------- */
|
||||
typedef enum
|
||||
{
|
||||
MODE_NONE = 0,
|
||||
MODE_OVERFLOW_PROTECTION = 1,
|
||||
MODE_LOCAL_FEEDER_SEGMENT = 2,
|
||||
MODE_LOCAL_FEEDER_CONTACT = 3
|
||||
} MenuMode;
|
||||
|
||||
typedef struct MenuItem MenuItem;
|
||||
typedef MenuItem *tagPMenuItem;
|
||||
typedef MenuItem tagMenuItem;
|
||||
|
||||
/* -------------------------------------------------------------------------
|
||||
* 结构体名: tagMenuModel
|
||||
* 功能:
|
||||
* 菜单项的静态业务定义数据(名称、属性、参数与动作入口)。
|
||||
*
|
||||
* 字段说明:
|
||||
* byClass - 菜单层级(0 表示根层,其它值表示子层级深度)
|
||||
* byName - 菜单显示名称(UTF-8 字节数组)
|
||||
* byTip - 菜单提示信息(UTF-8 字节数组)
|
||||
* byAttrib - 菜单属性标志(读写权限/类型等)
|
||||
* wPassword - 菜单访问密码或权限码
|
||||
* wPara - 菜单动作参数(业务扩展字段)
|
||||
* pfnWinProc - 菜单动作回调函数(叶子节点触发)
|
||||
* ------------------------------------------------------------------------- */
|
||||
typedef struct tagMenuModel
|
||||
{
|
||||
uint8_t byClass;
|
||||
uint8_t byName[50];
|
||||
uint8_t byTip[50];
|
||||
uint8_t byAttrib;
|
||||
uint16_t wPassword;
|
||||
uint16_t wPara;
|
||||
FUNCPTR pfnWinProc;
|
||||
} tagMenuModel;
|
||||
|
||||
/* -------------------------------------------------------------------------
|
||||
* 结构体名: MenuLinks
|
||||
* 功能:
|
||||
* 菜单运行时节点的四向链路关系,描述父子与同层前后关系。
|
||||
*
|
||||
* 字段说明:
|
||||
* higher - 指向父节点
|
||||
* lower - 指向第一个子节点
|
||||
* before - 指向同层前一个节点
|
||||
* behind - 指向同层后一个节点
|
||||
* ------------------------------------------------------------------------- */
|
||||
typedef struct
|
||||
{
|
||||
MenuItem *higher;
|
||||
MenuItem *lower;
|
||||
MenuItem *before;
|
||||
MenuItem *behind;
|
||||
} MenuLinks;
|
||||
|
||||
/* -------------------------------------------------------------------------
|
||||
* 结构体名: MenuRect
|
||||
* 功能:
|
||||
* 菜单项/菜单框的布局计算结果,供 View 层绘制与反显定位使用。
|
||||
*
|
||||
* 字段说明:
|
||||
* wPos - 当前项在本层中的行号(从 0 开始)
|
||||
* wNum - 当前层可见项数量
|
||||
* wSPosX - 绘制区域左上角 X
|
||||
* wSPosY - 绘制区域左上角 Y
|
||||
* wEPosX - 绘制区域右下角 X
|
||||
* wEPosY - 绘制区域右下角 Y
|
||||
* ------------------------------------------------------------------------- */
|
||||
typedef struct
|
||||
{
|
||||
uint16_t wPos; /* 当前项在本层中的行号(从 0 开始) */
|
||||
uint16_t wNum;
|
||||
uint16_t wSPosX;
|
||||
uint16_t wSPosY;
|
||||
uint16_t wEPosX;
|
||||
uint16_t wEPosY;
|
||||
} MenuRect;
|
||||
|
||||
/* -------------------------------------------------------------------------
|
||||
* 结构体名: MenuItem
|
||||
* 功能:
|
||||
* 菜单运行时节点,聚合链路关系、业务定义与布局结果。
|
||||
*
|
||||
* 字段说明:
|
||||
* links - 节点拓扑关系(父子/同层)
|
||||
* menuDef - 节点业务定义(名称、属性、回调等)
|
||||
* rect - 节点绘制位置与层级布局信息
|
||||
* ------------------------------------------------------------------------- */
|
||||
struct MenuItem
|
||||
{
|
||||
MenuLinks links;
|
||||
tagMenuModel menuDef;
|
||||
MenuRect rect;
|
||||
};
|
||||
|
||||
/* -------------------------------------------------------------------------
|
||||
* 结构体名: tagMenuCtrl
|
||||
* 功能:
|
||||
* 菜单导航与渲染过程中的运行时控制上下文(Presenter 持有)。
|
||||
*
|
||||
* 字段说明:
|
||||
* ptHead - 菜单根节点
|
||||
* ptCurrent - 当前选中节点
|
||||
* ptRoute - 当前路径路由(按层级保存各级节点,固定深度 4)
|
||||
* ptCurBak - 上一次选中节点(用于差分刷新)
|
||||
* pt0Level - 当前顶层节点缓存(用于判断是否需要整页刷新)
|
||||
* ------------------------------------------------------------------------- */
|
||||
typedef struct
|
||||
{
|
||||
tagPMenuItem ptHead;
|
||||
tagPMenuItem ptCurrent;
|
||||
tagPMenuItem ptRoute[4];
|
||||
tagPMenuItem ptCurBak;
|
||||
tagPMenuItem pt0Level;
|
||||
} tagMenuCtrl;
|
||||
|
||||
#endif
|
||||
138
src/Drv/pages/menu/display.h
Normal file
138
src/Drv/pages/menu/display.h
Normal file
@@ -0,0 +1,138 @@
|
||||
#ifndef __DISPLAY__H__
|
||||
#define __DISPLAY__H__
|
||||
|
||||
#include "types.h"
|
||||
|
||||
#define CN_COP_PWD (321)
|
||||
#define CN_USER_PWD (700)
|
||||
#define CN_SUPER_PWD (620)
|
||||
|
||||
enum _REV_TYPE_
|
||||
{
|
||||
EN_REV_FREE,
|
||||
EN_REV_FORCE
|
||||
};
|
||||
|
||||
enum _SET_SIDE_TYPE_
|
||||
{
|
||||
EN_SIDE_START = 0,
|
||||
EN_SIDE_BASIC,
|
||||
EN_SIDE_DEVINF,
|
||||
EN_SIDE_COP,
|
||||
EN_SIDE_MATRIX,
|
||||
EN_SIDE_ALL,
|
||||
EN_SIDE_HIGH,
|
||||
EN_SIDE_MED1,
|
||||
EN_SIDE_MED2,
|
||||
EN_SIDE_LOW1,
|
||||
EN_SIDE_LOW2,
|
||||
EN_SIDE_LK,
|
||||
EN_SIDE_Z,
|
||||
EN_SIDE_DEF,
|
||||
EN_SIDE_NONE = 0xFF
|
||||
};
|
||||
|
||||
enum _SOFT_TYPE_NUMBER
|
||||
{
|
||||
EN_SOFT_PRO = 0,
|
||||
EN_SOFT_GOOSE,
|
||||
EN_SOFT_MU,
|
||||
EN_SOFT_SWITCH,
|
||||
EN_SOFT_BAK,
|
||||
EN_SOFT_TYPE_END
|
||||
};
|
||||
|
||||
enum _MEA_TYPE_
|
||||
{
|
||||
EN_MEA_RLY,
|
||||
EN_MEA_RLY2,
|
||||
EN_MEA_RLY3,
|
||||
EN_MEA_ANA,
|
||||
EN_MEA_ANA2,
|
||||
EN_MEA_ANA3,
|
||||
EN_MEA_AC,
|
||||
EN_MEA_DC,
|
||||
EN_MEA_SYN,
|
||||
EN_MEA_POWER,
|
||||
EN_MEA_DD,
|
||||
EN_MEA_JLYC,
|
||||
EN_MEA_GEAR,
|
||||
EN_MEA_TQ,
|
||||
EN_MEA_INPUT1,
|
||||
EN_MEA_INPUT2,
|
||||
EN_MEA_INPUT3,
|
||||
EN_INPUT_RLY_ALL,
|
||||
EN_INPUT_RLY_FAULT,
|
||||
EN_INPUT_RLY_OTHER,
|
||||
EN_INPUT_BS_ALL,
|
||||
EN_INPUT_BS_FAULT,
|
||||
EN_INPUT_BS_OTHER,
|
||||
EN_MEA_ADJ,
|
||||
EN_MEA_YX,
|
||||
EN_OUTPUT_TRIP,
|
||||
EN_OUTPUT_SIGN,
|
||||
EN_MEA_LS,
|
||||
EN_MEA_SCRLY
|
||||
};
|
||||
|
||||
enum _INDEX_VALUE_TYPE_
|
||||
{
|
||||
EN_SOFT_SET,
|
||||
EN_FIGURE_SET
|
||||
};
|
||||
|
||||
enum _ANA_PARA_
|
||||
{
|
||||
EN_ANA_0 = 1,
|
||||
EN_ANA_1
|
||||
};
|
||||
|
||||
enum _INPUT_PARA_
|
||||
{
|
||||
EN_INPUT_0 = 1,
|
||||
EN_INPUT_1
|
||||
};
|
||||
|
||||
enum _ANA_TYPE_
|
||||
{
|
||||
EN_TYPE_DIF_CURRENT = 0,
|
||||
EN_TYPE_UNIT_CURRENT,
|
||||
EN_TYPE_UNIT_VOLTAGE
|
||||
};
|
||||
|
||||
enum _NO_USER_PASSWORD_
|
||||
{
|
||||
EN_NO_USER_PWD = 0x55
|
||||
};
|
||||
enum _FACTORY_PASSWORD_
|
||||
{
|
||||
EN_FACTORY_PASSWORD = 0x55
|
||||
};
|
||||
|
||||
enum _REC_TYPE_
|
||||
{
|
||||
EN_ACT_REC = 0,
|
||||
EN_ALM_REC,
|
||||
EN_CHK_REC,
|
||||
EN_SOE_REC,
|
||||
EN_COS_REC,
|
||||
EN_LOCK_REC,
|
||||
EN_OVER_REC,
|
||||
EN_START_REC,
|
||||
EN_RUN_REC,
|
||||
EN_INPUT_REC,
|
||||
EN_ONOFF_REC,
|
||||
EN_OPT_REC,
|
||||
EN_YK_REC,
|
||||
EN_SC_REC,
|
||||
EN_SCSTEPINFO_REC,
|
||||
EN_FAULT_REC,
|
||||
EN_ACTWAVE_REC,
|
||||
EN_STARTWAVE_REC,
|
||||
EN_HANDWAVE_REC,
|
||||
EN_FAULT_NO,
|
||||
EN_ALL_REC = 0xFF,
|
||||
EN_NO_REC = 0xFFFF
|
||||
};
|
||||
|
||||
#endif
|
||||
69
src/Drv/pages/menu/menu_model.h
Normal file
69
src/Drv/pages/menu/menu_model.h
Normal file
@@ -0,0 +1,69 @@
|
||||
#ifndef MENU_MODEL_H
|
||||
#define MENU_MODEL_H
|
||||
|
||||
#include "display.h"
|
||||
|
||||
typedef struct tagMenuModel
|
||||
{
|
||||
uint8_t byClass;
|
||||
uint8_t byName[50];
|
||||
uint8_t byTip[50];
|
||||
uint8_t byAttrib;
|
||||
uint16_t wPassword;
|
||||
uint16_t wPara;
|
||||
FUNCPTR pfnWinProc;
|
||||
} tagMenuModel;
|
||||
|
||||
typedef struct MenuItem MenuItem;
|
||||
typedef MenuItem *tagPMenuItem;
|
||||
typedef MenuItem tagMenuItem;
|
||||
|
||||
typedef struct
|
||||
{
|
||||
MenuItem *higher;
|
||||
MenuItem *lower;
|
||||
MenuItem *before;
|
||||
MenuItem *behind;
|
||||
} MenuLinks;
|
||||
|
||||
typedef struct
|
||||
{
|
||||
uint16_t wPos;
|
||||
uint16_t wNum;
|
||||
uint16_t wSPosX;
|
||||
uint16_t wSPosY;
|
||||
uint16_t wEPosX;
|
||||
uint16_t wEPosY;
|
||||
} MenuRect;
|
||||
|
||||
struct MenuItem
|
||||
{
|
||||
MenuLinks links;
|
||||
tagMenuModel menuDef;
|
||||
MenuRect rect;
|
||||
};
|
||||
|
||||
|
||||
|
||||
typedef struct
|
||||
{
|
||||
uint8_t by0LevelNum;
|
||||
tagPMenuItem ptHead;
|
||||
tagPMenuItem ptCurrent;
|
||||
tagPMenuItem ptRoute[4];
|
||||
tagPMenuItem ptCurBak;
|
||||
tagPMenuItem pt0Level;
|
||||
} tagMenuCtrl;
|
||||
|
||||
typedef struct menu_model_t menu_model_t;
|
||||
|
||||
struct menu_model_t
|
||||
{
|
||||
uint16_t maxItem;
|
||||
tagMenuItem menuItems[300];
|
||||
uint16_t by0LevelNum;
|
||||
};
|
||||
|
||||
void MenuModel_Init(menu_model_t *model);
|
||||
|
||||
#endif
|
||||
891
src/Drv/pages/menu/model.c
Normal file
891
src/Drv/pages/menu/model.c
Normal file
@@ -0,0 +1,891 @@
|
||||
#include "model.h"
|
||||
|
||||
#include <string.h>
|
||||
|
||||
#include "../../menu/app/menu.h"
|
||||
#include "utf8.h"
|
||||
|
||||
/* -------------------------------------------------------------------------
|
||||
* 函数名: MenuModel_Utf8LenCal
|
||||
* 功能:
|
||||
* 计算 UTF-8 字符串在当前菜单显示规则下的“显示宽度”。
|
||||
*
|
||||
* 参数:
|
||||
* str - 待计算的 UTF-8 字符串
|
||||
*
|
||||
* 边界处理:
|
||||
* - 本函数不做空指针校验,调用方需保证 `str` 非空且为 `\0` 结尾。
|
||||
* - 依赖 `utf8_next()` 逐字符解析 UTF-8 编码;若输入非法,结果由底层解析行为决定。
|
||||
*
|
||||
* 说明:
|
||||
* - 当前显示规则约定:
|
||||
* 1) ASCII/单字节字符宽度记为 1
|
||||
* 2) 多字节 UTF-8 字符(如中文)宽度记为 2
|
||||
* - 本函数返回的是“显示占位宽度”,不是原始字节长度。
|
||||
* - 该结果会被菜单布局函数用于计算菜单框宽度与字符串显示长度。
|
||||
*
|
||||
* 返回值:
|
||||
* - UTF-8 字符串的显示宽度
|
||||
* ------------------------------------------------------------------------- */
|
||||
uint8_t MenuModel_Utf8LenCal(uint8_t *str)
|
||||
{
|
||||
uint8_t strLen = 0;
|
||||
uint32_t unicode;
|
||||
uint8_t index = 0;
|
||||
uint8_t n = 0;
|
||||
|
||||
while (str[index] != '\0')
|
||||
{
|
||||
n = utf8_next(str + index, &unicode);
|
||||
strLen += (n > 1) ? 2 : 1;
|
||||
index += n;
|
||||
}
|
||||
return strLen;
|
||||
}
|
||||
|
||||
/* -------------------------------------------------------------------------
|
||||
* 函数名: MenuLayout_ItemDisplayLen
|
||||
* 功能:
|
||||
* 补充右箭头并计算单个菜单项最终用于布局的显示宽度。
|
||||
*
|
||||
* 参数:
|
||||
* item - 菜单项节点指针
|
||||
*
|
||||
* 边界处理:
|
||||
* - 本函数不做空指针校验,调用方需保证 `item` 有效。
|
||||
* - 菜单名称最大扫描长度固定为 50,与 `byName` 缓冲区容量保持一致。
|
||||
*
|
||||
* 说明:
|
||||
* - 先通过 `MenuModel_Utf8LenCal()` 计算菜单名称本身的显示宽度。
|
||||
* - 若该菜单项存在子节点(`links.lower != NULL`),则该项在界面上通常会显示
|
||||
* 一个“可下钻”装饰符(例如 `\x10` 右箭头)。
|
||||
* - 如果当前名称末尾尚未包含该装饰符,则额外为布局宽度加 1,
|
||||
* 以保证菜单框宽度计算时为装饰符预留空间。
|
||||
* - 本函数只做宽度估算,不修改菜单项内容。
|
||||
*
|
||||
* 返回值:
|
||||
* - 该菜单项的显示宽度
|
||||
* ------------------------------------------------------------------------- */
|
||||
static uint8_t MenuLayout_ItemDisplayLen(const MenuItem *item)
|
||||
{
|
||||
uint8_t displayLen;
|
||||
displayLen = MenuModel_Utf8LenCal((uint8_t *)item->menuDef.byName);
|
||||
if (item->links.lower != NULL)
|
||||
{
|
||||
uint8_t byteLen = 0;
|
||||
while ((byteLen < 50) && (item->menuDef.byName[byteLen] != '\0'))
|
||||
{
|
||||
byteLen++;
|
||||
}
|
||||
if ((byteLen == 0) || (item->menuDef.byName[byteLen - 1] != '\x10'))
|
||||
{
|
||||
displayLen += 1;
|
||||
}
|
||||
}
|
||||
return displayLen;
|
||||
}
|
||||
/* -------------------------------------------------------------------------
|
||||
* 函数名: MenuModel_GetMenuMaxDisplayLen
|
||||
* 功能:
|
||||
* 统计当前层级菜单项的最大显示宽度,并返回当前层级菜单项中的最大显示宽度。
|
||||
*
|
||||
* 参数:
|
||||
* ptFirst - 当前层的首节点指针
|
||||
*
|
||||
* 边界处理:
|
||||
* - 本函数不做空指针校验,调用方需保证 `ptFirst` 有效。
|
||||
*
|
||||
* 说明:
|
||||
* - 本函数只负责遍历下一层菜单项,并调用 `MenuLayout_ItemDisplayLen()`
|
||||
* 计算每个节点的显示宽度。
|
||||
*
|
||||
* 返回值:
|
||||
* - 当前层级菜单项中的最大显示宽度
|
||||
* ------------------------------------------------------------------------- */
|
||||
uint8_t MenuModel_GetMenuMaxDisplayLen(tagPMenuItem ptFirst)
|
||||
{
|
||||
uint8_t displayLen;
|
||||
uint8_t byMaxLen = 0;
|
||||
tagPMenuItem ptIndex = ptFirst;
|
||||
|
||||
for (uint16_t index = 0; index < MENU_MODEL_MAX_ITEM; index++)
|
||||
{
|
||||
displayLen = MenuLayout_ItemDisplayLen(ptIndex);
|
||||
|
||||
if (byMaxLen < displayLen)
|
||||
{
|
||||
byMaxLen = displayLen;
|
||||
}
|
||||
|
||||
ptIndex = ptIndex->links.behind;
|
||||
if (ptIndex == ptFirst)
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
return byMaxLen;
|
||||
}
|
||||
/* -------------------------------------------------------------------------
|
||||
* 函数名: MenuLayout_MapMenuPos
|
||||
* 功能:
|
||||
* 根据当前菜单项在本层中的原始序号,以及当前层可显示的最大行数,
|
||||
* 计算该菜单项在菜单框中的实际显示行号。
|
||||
*
|
||||
* 参数:
|
||||
* menuPos - 当前菜单项在本层中的原始位置序号(从 0 开始)
|
||||
* itemNum - 当前层菜单项总数
|
||||
* maxNum - 当前菜单框在可视区域内最多可容纳的显示行数
|
||||
*
|
||||
* 边界处理:
|
||||
* - 本函数不做参数合法性校验,调用方需保证 `menuPos / itemNum / maxNum` 有效。
|
||||
* - 若 `itemNum <= maxNum`,说明当前层菜单可完整显示,直接返回原始序号。
|
||||
* - 若 `itemNum > maxNum` 且 `menuPos < maxNum`,说明当前项仍落在可直接显示区域,
|
||||
* 直接返回原始序号。
|
||||
*
|
||||
* 说明:
|
||||
* - 当当前层菜单项总数未超过窗口容量时,菜单项在框内的显示行号与其原始序号一致。
|
||||
* - 当菜单项总数超过窗口容量时,需要把靠后的菜单项映射到有限的可视行范围内。
|
||||
* - 当前实现策略为:
|
||||
* 1) 若当前项仍位于前 `maxNum - 1` 个可显示位置内,则保持不变
|
||||
* 2) 若当前项已落到窗口容量之外,则将其映射到靠近窗口底部的可视区域
|
||||
* - 该函数的作用是为后续 Y 坐标计算提供“菜单框内部显示行号”,
|
||||
* 避免菜单项过多时直接按原始序号计算导致超出可显示范围。
|
||||
*
|
||||
* 返回值:
|
||||
* - 当前菜单项在菜单框中的实际显示行号
|
||||
* ------------------------------------------------------------------------- */
|
||||
static uint16_t MenuLayout_MapMenuPos(uint16_t menuPos, uint16_t itemNum, uint8_t maxNum)
|
||||
{
|
||||
if ((itemNum > maxNum) && (menuPos >= maxNum))
|
||||
{
|
||||
return (uint16_t)(maxNum - (itemNum - menuPos));
|
||||
}
|
||||
return menuPos;
|
||||
}
|
||||
|
||||
/* -------------------------------------------------------------------------
|
||||
* 函数名: MenuLayout_TryFlipUp
|
||||
* 功能:
|
||||
* 当子菜单默认向下展开可能越界时,尝试将菜单框改为“向上展开”。
|
||||
*
|
||||
* 参数:
|
||||
* startY - 菜单框起始 Y 坐标(输入/输出)
|
||||
* endY - 菜单框结束 Y 坐标(输入/输出)
|
||||
* itemNum - 当前子菜单项数量
|
||||
* flipMinY - 允许上翻后的最小 Y 边界
|
||||
*
|
||||
* 边界处理:
|
||||
* - 本函数不做空指针校验,调用方需保证 `startY / endY` 有效。
|
||||
* - 若根据 `itemNum` 推导出的上翻后起点小于等于 `flipMinY`,
|
||||
* 则认为顶部空间不足,上翻失败。
|
||||
* - 若候选起点超出 `MENU_YMAX`,则视为异常坐标,上翻失败。
|
||||
*
|
||||
* 说明:
|
||||
* - 本函数的目标是在“尽量保持父子菜单行对齐”的前提下,
|
||||
* 把原本会向下越界的子菜单框整体上移。
|
||||
* - 计算步骤:
|
||||
* 1) 以当前 `startY` 为锚点,反推出上翻后的候选顶部 `candidateEndY`
|
||||
* 2) 判断该候选顶部是否落在允许范围 `(flipMinY, MENU_YMAX)` 内
|
||||
* 3) 若可上翻,则将 `startY` 更新为候选顶部
|
||||
* 4) 同时使用旧 `startY` 反推出新的 `endY`
|
||||
* - 返回成功后,调用方可直接使用更新后的 `startY / endY`;
|
||||
* 若失败,则由调用方继续执行 clamp/probe 等后备布局策略。
|
||||
*
|
||||
* 返回值:
|
||||
* - 1 : 上翻成功
|
||||
* - 0 : 上翻失败
|
||||
* ------------------------------------------------------------------------- */
|
||||
static uint8_t MenuLayout_TryFlipUp(uint16_t *startY, uint16_t *endY, uint16_t itemNum, uint16_t flipMinY)
|
||||
{
|
||||
uint16_t candidateEndY;
|
||||
uint16_t temp;
|
||||
|
||||
candidateEndY = (uint16_t)(*startY - (itemNum - 1) * LINE_HEIGHT - MENU_YADD);
|
||||
if ((candidateEndY > flipMinY) && (candidateEndY < MENU_YMAX))
|
||||
{
|
||||
temp = *startY;
|
||||
*startY = candidateEndY;
|
||||
*endY = (uint16_t)(temp + LINE_HEIGHT);
|
||||
return 1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
/* -------------------------------------------------------------------------
|
||||
* 函数名: MenuLayout_FallbackProbeUp
|
||||
* 功能:
|
||||
* 当子菜单无法直接放入可视区域时,按“逐行向上试探”的方式寻找可容纳位置。
|
||||
*
|
||||
* 参数:
|
||||
* ptIndex - 当前菜单路径节点数组(输入)
|
||||
* itemNum - 当前子菜单项数量
|
||||
* startY - 菜单框起始 Y 坐标(输出)
|
||||
* endY - 菜单框结束 Y 坐标(输出)
|
||||
*
|
||||
* 边界处理:
|
||||
* - 本函数不做空指针校验,调用方需保证 `ptIndex / startY / endY` 有效。
|
||||
* - 默认要求 `ptIndex[0]` 已指向 0 级菜单节点,且其 `rect.wSPosY` 已完成布局计算。
|
||||
* - 当试探上移后 `startY` 小于 1 行高度时,直接钳制为 0,避免出现负坐标。
|
||||
* - 若多次上移后仍无法完全放入 `MENU_YMAX` 范围,本函数保留最后一次试探结果,
|
||||
* 由调用方或后续绘制逻辑继续处理。
|
||||
*
|
||||
* 说明:
|
||||
* - 该函数是介于“直接上翻失败”和“强制贴顶”之间的一种更平滑兜底策略。
|
||||
* - 处理步骤:
|
||||
* 1) 先以 0 级菜单顶部 `ptIndex[0]->rect.wSPosY` 作为初始起点
|
||||
* 2) 按 `itemNum * LINE_HEIGHT + MENU_YADD` 计算当前理论底部
|
||||
* 3) 若底部仍超出 `MENU_YMAX`,则每次向上移动 1 个 `LINE_HEIGHT`
|
||||
* 4) 每移动一次都重新计算 `endY`,直到菜单框进入可显示区域或试探结束
|
||||
* - 这种策略比直接贴顶更保守,尽量保留与原始父菜单位置的相对关系,
|
||||
* 同时逐步寻找一个更自然的显示位置。
|
||||
*
|
||||
* 返回值:
|
||||
* - 无
|
||||
* ------------------------------------------------------------------------- */
|
||||
static void MenuLayout_FallbackProbeUp(uint16_t w0LevelSPosY, uint16_t itemNum, uint16_t *startY, uint16_t *endY)
|
||||
{
|
||||
*startY = w0LevelSPosY;
|
||||
*endY = (uint16_t)(*startY + itemNum * LINE_HEIGHT + MENU_YADD);
|
||||
for (uint16_t i = 1; i < (uint16_t)(w0LevelSPosY / LINE_HEIGHT + 1); i++)
|
||||
{
|
||||
if (*endY > MENU_YMAX)
|
||||
{
|
||||
*startY = (uint16_t)(w0LevelSPosY - LINE_HEIGHT * i);
|
||||
if (*startY < LINE_HEIGHT)
|
||||
{
|
||||
*startY = 0;
|
||||
}
|
||||
*endY = (uint16_t)(*startY + itemNum * LINE_HEIGHT + MENU_YADD);
|
||||
}
|
||||
else
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* -------------------------------------------------------------------------
|
||||
* 函数名: MenuView_SubPosCal
|
||||
* 功能:
|
||||
* 以递归方式计算从当前层首节点开始的所有子菜单框位置,
|
||||
* 完成当前层以及更深层级菜单的矩形布局。
|
||||
*
|
||||
* 参数:
|
||||
* ptFirst - 当前层首节点指针
|
||||
* ptParent - 当前层所属父菜单框节点
|
||||
* w0LevelSPosY - 0 级菜单框顶部 Y 坐标,用于越界时的回退布局基准
|
||||
*
|
||||
* 边界处理:
|
||||
* - 若 `ptFirst == NULL`,说明当前层不存在有效菜单链,直接返回。
|
||||
* - 本函数不对 `ptParent` 做空指针校验,调用方需保证其有效,
|
||||
* 且其 `rect` 已完成布局计算。
|
||||
* - 当前层遍历仍使用 `MENU_MODEL_MAX_ITEM` 作为保护上限,
|
||||
* 防止异常链表导致死循环。
|
||||
* - 当子菜单框底部超出 `MENU_YMAX` 时,优先尝试上翻;
|
||||
* 若上翻失败,则退化到 `MenuLayout_FallbackProbeUp()` 继续寻找可显示位置。
|
||||
*
|
||||
* 说明:
|
||||
* - 本函数是子菜单布局的递归核心,处理流程如下:
|
||||
* 1) 以 `ptFirst` 为起点遍历当前层所有节点
|
||||
* 2) 对每个存在子菜单的节点,先取其下一层首节点 `ptNextFirst`
|
||||
* 3) 通过 `MenuModel_GetMenuMaxDisplayLen()` 计算该下一层菜单的最大显示宽度
|
||||
* 4) 根据父菜单框右边界,计算当前子菜单框的 X 坐标
|
||||
* 5) 根据当前节点在本层中的 0 基序号 `wPos`、父层项数 `wNum`,
|
||||
* 计算当前子菜单框的理论 Y 坐标
|
||||
* 6) 若理论底部越界,则尝试上翻或执行回退探测
|
||||
* 7) 当前节点对应子菜单框位置确定后,
|
||||
* 再递归处理其更深一层的子菜单布局
|
||||
* - `ptParent->rect.wNum` 表示父节点直接子节点总数,
|
||||
* 用于把当前节点的原始层内 0 基序号映射到菜单框可显示行号。
|
||||
* - `w0LevelSPosY` 作为递归过程中统一的顶部参考,
|
||||
* 保证深层菜单在空间不足时仍能回退到稳定的显示区域。
|
||||
*
|
||||
* 返回值:
|
||||
* - 无
|
||||
* ------------------------------------------------------------------------- */
|
||||
static void MenuView_SubPosCal(tagPMenuItem ptFirst, tagPMenuItem ptParent, uint16_t w0LevelSPosY)
|
||||
{
|
||||
uint16_t wSPosY;
|
||||
uint16_t wEPosY;
|
||||
uint8_t byMaxLen;
|
||||
uint8_t byMaxNum;
|
||||
uint16_t byMenuPos;
|
||||
uint16_t byItemNum;
|
||||
|
||||
tagPMenuItem ptNextFirst;
|
||||
tagPMenuItem ptIndex;
|
||||
|
||||
|
||||
if (ptFirst == NULL)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
byMaxNum = (MENU_YMAX - MENU_YMIN) / LINE_HEIGHT;
|
||||
ptIndex = ptFirst;
|
||||
for (uint16_t wLoop = 0; wLoop < MENU_MODEL_MAX_ITEM; wLoop++)
|
||||
{
|
||||
if (ptIndex->links.lower != NULL)
|
||||
{
|
||||
ptNextFirst = ptIndex->links.lower; /* 保存下一层的第一个节点 */
|
||||
|
||||
byMaxLen = MenuModel_GetMenuMaxDisplayLen(ptNextFirst); /* 获取下一层菜单项中的最大显示宽度 */
|
||||
|
||||
/* 计算子菜单框的 X 坐标 */
|
||||
ptIndex->rect.wSPosX = ptParent->rect.wEPosX; /* 子菜单框左边界紧贴父菜单框右边界 */
|
||||
/* 子菜单框右边界由最大显示宽度 * 单字符宽度 + 额外边距推导 */
|
||||
ptIndex->rect.wEPosX = (uint16_t)(ptIndex->rect.wSPosX + byMaxLen * MENU_WITDTH + MENU_XADD);
|
||||
|
||||
|
||||
/* 计算子菜单框的 Y 坐标 */
|
||||
byMenuPos = ptIndex->rect.wPos; /* 当前菜单项的 0 基序号 */
|
||||
byItemNum = ptParent->rect.wNum; /* 父菜单项的项数 */
|
||||
byMenuPos = MenuLayout_MapMenuPos(byMenuPos, byItemNum, byMaxNum);
|
||||
|
||||
wSPosY = ptParent->rect.wSPosY + byMenuPos * LINE_HEIGHT;
|
||||
byItemNum = ptIndex->rect.wNum;
|
||||
wEPosY = wSPosY + byItemNum * LINE_HEIGHT + MENU_YADD;
|
||||
|
||||
if (wEPosY < MENU_YMAX)
|
||||
{
|
||||
ptIndex->rect.wSPosY = wSPosY;
|
||||
ptIndex->rect.wEPosY = wEPosY;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!MenuLayout_TryFlipUp(&wSPosY, &wEPosY, byItemNum, MENU_YMIN))
|
||||
{
|
||||
MenuLayout_FallbackProbeUp(w0LevelSPosY, byItemNum, &wSPosY, &wEPosY);
|
||||
}
|
||||
ptIndex->rect.wSPosY = wSPosY;
|
||||
ptIndex->rect.wEPosY = wEPosY;
|
||||
}
|
||||
MenuView_SubPosCal(ptNextFirst, ptIndex, w0LevelSPosY);
|
||||
}
|
||||
ptIndex = ptIndex->links.behind;
|
||||
if (ptIndex == ptFirst)
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
/* -------------------------------------------------------------------------
|
||||
* 函数名: MenuModel_PositionCal
|
||||
* 功能:
|
||||
* 计算整棵菜单树的初始显示位置,完成 0 级菜单以及后续子菜单层级的矩形布局。
|
||||
*
|
||||
* 参数:
|
||||
* ptMenuHead - 菜单头节点(0 级起点)
|
||||
* by0LevelNum - 0 级菜单数量
|
||||
*
|
||||
* 边界处理:
|
||||
* - 若 ptMenuHead 为 NULL、config 为 NULL 或 by0LevelNum 为 0,则直接返回。
|
||||
* - 若 by0LevelNum 超过本函数内部固定工作数组容量 4,则直接返回。
|
||||
* - 当 0 级菜单数量大于 LCD 宽度时,byInterval 可能被截断为 0;
|
||||
* 当前通过断言暴露异常输入,并保持返回保护。
|
||||
*
|
||||
* 说明:
|
||||
* - 本函数是菜单布局计算入口:
|
||||
* 1) 先建立 0 级菜单的遍历起点、当前位置与 0 基序号
|
||||
* 2) 根据 lcdSizeX / by0LevelNum 计算 0 级菜单的水平分布间隔
|
||||
* 3) 逐个遍历 0 级菜单项
|
||||
* 4) 对存在子菜单的节点,计算其子菜单框体矩形
|
||||
* 5) 递归调用 MenuView_Sub1PosCal() 继续完成更深层布局
|
||||
* - 本函数只计算 rect,不负责实际绘制。
|
||||
*
|
||||
* 返回值:
|
||||
* - 无
|
||||
* ------------------------------------------------------------------------- */
|
||||
void MenuModel_PositionCal(tagPMenuItem ptMenuHead, uint16_t by0LevelNum)
|
||||
{
|
||||
tagPMenuItem ptFirst = ptMenuHead;
|
||||
tagPMenuItem ptIndex = ptMenuHead;
|
||||
uint8_t byMaxLen;
|
||||
uint8_t byInterval;
|
||||
tagPMenuItem ptNextFirst;
|
||||
|
||||
ASSERT((ptMenuHead == NULL) || (by0LevelNum == 0));
|
||||
if ((ptMenuHead == NULL) || (by0LevelNum == 0))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
byInterval = MENU_SIZE_X / by0LevelNum;
|
||||
|
||||
for (uint16_t wLoop = 0; wLoop < by0LevelNum; wLoop++)
|
||||
{
|
||||
if (ptIndex->links.lower != NULL)
|
||||
{
|
||||
ptNextFirst = ptIndex->links.lower; /* 保存下一层的第一个节点 */
|
||||
byMaxLen = MenuModel_GetMenuMaxDisplayLen(ptNextFirst);
|
||||
ptIndex->rect.wSPosX = ptIndex->rect.wPos * byInterval;
|
||||
ptIndex->rect.wSPosY = MENU_SIZE_Y - LINE_HEIGHT - ptIndex->rect.wNum * LINE_HEIGHT - MENU_YADD;
|
||||
ptIndex->rect.wEPosX = ptIndex->rect.wPos * byInterval + byMaxLen * MENU_WITDTH + MENU_XADD;
|
||||
ptIndex->rect.wEPosY = MENU_SIZE_Y - LINE_HEIGHT;
|
||||
|
||||
MenuView_SubPosCal(ptNextFirst, ptIndex, ptIndex->rect.wSPosY);
|
||||
}
|
||||
ptIndex = ptIndex->links.behind;
|
||||
if (ptIndex == ptFirst)
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
enum _STR_FLAG_ {
|
||||
EN_STR_FLAG_FUNSET = 0,
|
||||
EN_STR_FLAG_OKCANCEL,
|
||||
EN_VALUE_FLAG_SLOTTYPE,
|
||||
EN_VALUE_FLAG_YESNO,
|
||||
EN_VALUE_FLAG_CHN_NUM,
|
||||
EN_VALUE_FLAG_YXSET,
|
||||
EN_VALUE_FLAG_YKSTEP,
|
||||
EN_VALUE_FLAG_YKACTION,
|
||||
EN_VALUE_FLAG_CHK,
|
||||
EN_VALUE_FLAG_BEUSED,
|
||||
EN_VALUE_FLAG_PORTTYPE,
|
||||
EN_VALUE_FLAG_MAPNAME,
|
||||
EN_VALUE_FLAG_RECTITLE,
|
||||
EN_VALUE_FLAG_BHINFO,
|
||||
EN_VALUE_FLAG_BHSET,
|
||||
EN_VALUE_FLAG_OKCANCEL,
|
||||
EN_VALUE_FLAG_FILETYPE,
|
||||
EN_VALUE_FLAG_ROLE,
|
||||
EN_VALUE_FLAG_COMMSTATE,
|
||||
EN_VALUE_FLAG_ALARM,
|
||||
EN_VALUE_FLAG_YXTYPE,
|
||||
EN_STR_FLAG_MENUALAUTO,
|
||||
EN_STR_FLAG_LINKBREAK,
|
||||
EN_STR_FLAG_DONEUNDONE,
|
||||
EN_STR_FLAG_ACTBACK,
|
||||
EN_STR_FLAG_RESUMEBREAK,
|
||||
EN_STR_FLAG_USESTOP,
|
||||
EN_STR_FLAG_BAUDRATE,
|
||||
EN_STR_FLAG_VERIFY,
|
||||
EN_STR_FLAG_PROTOL,
|
||||
EN_STR_FLAG_RECORD,
|
||||
EN_STR_FLAG_MESSAGE,
|
||||
EN_STR_FLAG_COM,
|
||||
EN_STR_FLAG_MODE,
|
||||
EN_STR_FLAG_COMBAUD,
|
||||
EN_STR_FLAG_DATABIT,
|
||||
EN_STR_FLAG_STOPBIT,
|
||||
EN_STR_FLAG_COMTYPE,
|
||||
EN_STR_FLAG_SAMEV,
|
||||
EN_STR_FLAG_PT,
|
||||
EN_STR_FLAG_CT,
|
||||
EN_STR_FLAG_TRANSTYPE,
|
||||
EN_STR_FLAG_LINEPHS,
|
||||
EN_STR_FLAG_RETOUT,
|
||||
EN_STR_FLAG_YXDI,
|
||||
EN_STR_FLAG_YKPUL,
|
||||
EN_STR_FLAG_WAVE,
|
||||
EN_STR_FLAG_MEMTYPE,
|
||||
EN_STR_FLAG_CHANNEL,
|
||||
EN_STR_FLAG_POWERTYPE,
|
||||
EN_STR_FLAG_ZEROTYPE,
|
||||
EN_STR_FLAG_YCCOMMTYPE,
|
||||
EN_STR_FLAG_YXCOMMTYPE,
|
||||
EN_STR_FLAG_CTRLWORD,
|
||||
EN_STR_FLAG_BUSNAME,
|
||||
EN_STR_FLAG_UNITSHOWMODE,
|
||||
EN_STR_FLAG_BAYMODE,
|
||||
EN_STR_FLAG_BASEANA,
|
||||
EN_STR_FLAG_BASEPHASE,
|
||||
EN_STR_FLAG_BAYCODE,
|
||||
EN_STR_FLAG_SET_NUM,
|
||||
EN_STR_FLAG_DCTYPE,
|
||||
EN_STR_FLAG_DCINTYPE,
|
||||
EN_STR_FLAG_DWINTYPE,
|
||||
EN_STR_FLAG_PTCT,
|
||||
EN_STR_FLAG_HARMOTYPE,
|
||||
EN_STR_FLAG_YXSIN_DI,
|
||||
EN_STR_FLAG_SHOWANATYPE,
|
||||
EN_STR_FLAG_ANAPOLARTYPE,
|
||||
EN_STR_FLAG_WIRDIAGRAMTYPE
|
||||
} enumStrType;
|
||||
|
||||
const tagMenuModel menuTab[] =
|
||||
{
|
||||
{ 0, "主界面", "", 0, 0x0000, 0, (FUNCPTR)Menu_NonPfunc },
|
||||
{ 1, "装置信息", "查看装置信息", 0, 0x0000, EN_ANA_0, (FUNCPTR)MenuProc_See_AppInfo },
|
||||
{ 1, "实时数据", "装置实时数据", 0, 0x0000, 0, (FUNCPTR)Menu_NonPfunc },
|
||||
{ 2, "交流量", "查看遥测交流量", EN_MEA_AC, 0x0000, EN_ANA_0, (FUNCPTR)MenuProc_See_YC },
|
||||
{ 2, "直流量", "查看遥测直流量", EN_MEA_DC, 0x0000, EN_ANA_0, (FUNCPTR)MenuProc_See_YC },
|
||||
{ 2, "遥信量", "查看遥信开入量", EN_INPUT_RLY_ALL, 0x0000, EN_INPUT_0, (FUNCPTR)MenuProc_See_Input },
|
||||
{ 1, "参数定值", "保护参数查看与修改", 0, 0x0000, 0, (FUNCPTR)Menu_NonPfunc },
|
||||
{ 2, "整定", "整定装置保护参数", 0, 0x0000, 0, (FUNCPTR)Menu_NonPfunc },
|
||||
{ 3, "参数", "查看设备参数定值", EN_FIGURE_SET, CN_USER_PWD, EN_SIDE_BASIC, (FUNCPTR)MenuProc_Set_Value },
|
||||
{ 3, "定值", "设置装置数值定值", EN_FIGURE_SET, CN_USER_PWD, EN_SIDE_DEF, (FUNCPTR)MenuProc_Set_Value },
|
||||
{ 3, "控制字", "设置装置控制字", EN_SOFT_SET, CN_USER_PWD, EN_SIDE_DEF, (FUNCPTR)MenuProc_Set_Value },
|
||||
{ 3, "软压板", "设置软压板", 0, CN_USER_PWD, EN_SOFT_PRO, (FUNCPTR)MenuProc_Set_Soft },
|
||||
{ 2, "查看", "查看装置保护参数", 0, 0x0000, 0, (FUNCPTR)Menu_NonPfunc },
|
||||
{ 3, "参数", "设置设备参数定值", EN_FIGURE_SET, 0x0000, EN_SIDE_BASIC, (FUNCPTR)MenuProc_See_Set },
|
||||
{ 3, "定值", "查看数值型定值", EN_FIGURE_SET, 0x0000, EN_SIDE_DEF, (FUNCPTR)MenuProc_See_Set },
|
||||
{ 3, "控制字", "查看控制字定值", EN_SOFT_SET, 0x0000, EN_SIDE_DEF, (FUNCPTR)MenuProc_See_Set },
|
||||
{ 3, "软压板", "查看软压板", 0, 0x0000, EN_SOFT_PRO, (FUNCPTR)MenuProc_See_Soft },
|
||||
{ 1, "三遥设置", "", 0, 0x0000, 0, (FUNCPTR)Menu_NonPfunc },
|
||||
{ 2, "遥测死区", "设置遥测量死区门槛", 0, CN_USER_PWD, 0, (FUNCPTR)MenuProc_YC_SetSqValue },
|
||||
{ 2, "遥测系数", "设置遥测量微调系数", EN_MEA_ADJ, CN_USER_PWD, 0, (FUNCPTR)MenuProc_YC_SetAdjCoe },
|
||||
{ 2, "遥信类型", "设置遥信类型", 0, CN_USER_PWD, 0, (FUNCPTR)MenuProc_YX_SetCommType },
|
||||
{ 2, "遥信防抖", "设置遥信防抖时间", 0, CN_USER_PWD, 0, (FUNCPTR)MenuProc_YX_SetWidth },
|
||||
{ 2, "双点遥信", "设置双点遥信虚端子", 0, CN_USER_PWD, 0, (FUNCPTR)MenuProc_YX_SetTwin },
|
||||
{ 1, "装置维护", "", 0, 0x0000, 0, (FUNCPTR)Menu_NonPfunc },
|
||||
{ 2, "时钟设置", "设置系统时钟", 0, CN_USER_PWD, 0, (FUNCPTR)MenuProc_Cfg_Time },
|
||||
{ 2, "强制复归", "可复归未返回事件", EN_REV_FORCE, CN_USER_PWD, 0, (FUNCPTR)MenuProc_Cfg_RevEvent },
|
||||
{ 2, "手动录波", "启动手动录波", 0, CN_USER_PWD, 0, (FUNCPTR)MenuProc_Cfg_ManualWave },
|
||||
{ 2, "清除记录", "", 0, CN_USER_PWD, 0, (FUNCPTR)MenuProc_Cfg_ClrRec },
|
||||
{ 1, "通讯参数", "", 0, 0x0000, 0, (FUNCPTR)Menu_NonPfunc },
|
||||
{ 2, "通讯设置", "外部通讯设置", 0, CN_USER_PWD, 0, (FUNCPTR)MenuProc_Cfg_ComPara },
|
||||
{ 2, "网口设置", "", 0, CN_USER_PWD, 0, (FUNCPTR)MenuProc_Cfg_EditIP },
|
||||
{ 2, "SNTP设置", "", 0, CN_USER_PWD, 0, (FUNCPTR)MenuProc_Cfg_EditSntp },
|
||||
{ 1, "记录查询", "查看各种装置记录", 0, 0x0000, 0, (FUNCPTR)Menu_NonPfunc },
|
||||
{ 2, "SOE记录", "", 0, 0x0000, 0, (FUNCPTR)MenuProc_See_RecSOE },
|
||||
{ 2, "事故记录", "", 0, 0x0000, 0, (FUNCPTR)MenuProc_See_RecAct },
|
||||
{ 2, "操作记录", "", 0, 0x0000, 0, (FUNCPTR)MenuProc_See_RecOpt },
|
||||
{ 2, "保护告警", "", 0, 0x0000, 0, (FUNCPTR)MenuProc_See_RecAlm },
|
||||
{ 2, "保护启动", "", 0, 0x0000, 0, (FUNCPTR)MenuProc_See_RecStart },
|
||||
{ 2, "遥控记录", "", 0, 0x0000, 0, (FUNCPTR)MenuProc_See_RecYK },
|
||||
{ 2, "自检记录", "", 0, 0x0000, 0, (FUNCPTR)MenuProc_See_RecChk },
|
||||
{ 2, "运行记录", "", 0, 0x0000, 0, (FUNCPTR)MenuProc_See_RecRun },
|
||||
{ 2, "运行报告", "", 0, 0x0000, 0, (FUNCPTR)MenuProc_See_RecFault },
|
||||
{ 0, "厂家设置", "设置装置相关参数", 0, CN_COP_PWD, 0, (FUNCPTR)Menu_NonPfunc },
|
||||
{ 1, "元件配置", "配置元件配置", 0, CN_COP_PWD, EN_FACTORY_PASSWORD,(FUNCPTR)MenuProc_Cfg_CellConf },
|
||||
{ 1, "恢复默认", "恢复默认元件定值参数", 0, CN_COP_PWD, 0, (FUNCPTR)Menu_NonPfunc },
|
||||
{ 2, "全部恢复", "全部参数恢复默认", 0, CN_COP_PWD, EN_NO_USER_PWD, (FUNCPTR)MenuProc_AllInf_Default },
|
||||
{ 2, "默认参数", "当前参数恢复默认", 0, CN_COP_PWD, EN_NO_USER_PWD, (FUNCPTR)MenuProc_Para_Default },
|
||||
{ 2, "默认定值", "当前定值区恢复默认", 0, CN_COP_PWD, EN_NO_USER_PWD, (FUNCPTR)MenuProc_Set_Default },
|
||||
{ 2, "软压板", "当前软压板恢复默认", 0, CN_COP_PWD, EN_NO_USER_PWD, (FUNCPTR)MenuProc_Resume_Soft },
|
||||
{ 2, "元件配置", "元件配置恢复默认", 0, CN_COP_PWD, EN_NO_USER_PWD, (FUNCPTR)MenuProc_Cfg_CellDef },
|
||||
{ 1, "交流显示", "交流显示方式设置", 0, CN_COP_PWD, 0, (FUNCPTR)MenuProc_Cfg_ShowAnaType },
|
||||
{ 1, "装置调试", "调试装置", 0, 0x0000, 0, (FUNCPTR)Menu_NonPfunc },
|
||||
{ 2, "虚拟遥信", "设置虚拟遥信值", 0, CN_COP_PWD, 0, (FUNCPTR)MenuProc_Dbg_XuYX },
|
||||
{ 2, "交流虚遥测", "设置虚拟交流遥测值", EN_MEA_AC, CN_COP_PWD, EN_ANA_0, (FUNCPTR)MenuProc_Dbg_XuYC },
|
||||
{ 2, "直流虚遥测", "设置虚拟直流遥测值", EN_MEA_DC, CN_COP_PWD, EN_ANA_0, (FUNCPTR)MenuProc_Dbg_XuYC },
|
||||
{ 2, "电度虚遥测", "设置虚拟电度遥测值", EN_MEA_POWER, CN_COP_PWD, EN_ANA_0, (FUNCPTR)MenuProc_Dbg_XuYC },
|
||||
{ 2, "动作虚事件", "设置虚拟动作事件", EN_ACT_REC, CN_COP_PWD, 0, (FUNCPTR)MenuProc_Dbg_XuEvent },
|
||||
{ 2, "告警虚事件", "设置虚拟告警事件", EN_ALM_REC, CN_COP_PWD, 0, (FUNCPTR)MenuProc_Dbg_XuEvent },
|
||||
{ 2, "动作出口", "进入此菜单保护退出", EN_OUTPUT_TRIP, CN_COP_PWD, EN_INPUT_0, (FUNCPTR)MenuProc_Dbg_Relay },
|
||||
{ 2, "信号出口", "进入此菜单保护退出", EN_OUTPUT_SIGN, CN_COP_PWD, 0, (FUNCPTR)MenuProc_Dbg_Relay },
|
||||
{ 1, "版本信息", "查看板件版本信息", 0, CN_COP_PWD, 0, (FUNCPTR)MenuProc_See_VersionBoard },
|
||||
};
|
||||
|
||||
/* -------------------------------------------------------------------------
|
||||
* 函数名: MenuModel_CountTopLevel
|
||||
* 功能:
|
||||
* 统计静态菜单定义表中 0 级菜单项的数量,用于后续顶部菜单布局计算。
|
||||
*
|
||||
* 参数:
|
||||
* maxItem - 需要参与统计的菜单项总数
|
||||
*
|
||||
* 边界处理:
|
||||
* - 本函数默认 `maxItem` 不超过 `menuTab` 实际长度,调用方需保证该前提成立。
|
||||
* - 当 `maxItem == 0` 时,for 循环不进入,函数返回 0。
|
||||
*
|
||||
* 说明:
|
||||
* - 在当前菜单模型中,`byClass == 0` 表示顶层菜单节点。
|
||||
* - 本函数通过顺序扫描 `menuTab[0 ~ maxItem-1]`,
|
||||
* 统计所有 0 级节点数量,并将结果返回给布局初始化流程。
|
||||
* - 返回值通常会被用于计算顶层菜单在 LCD 上的水平分布间隔。
|
||||
*
|
||||
* 返回值:
|
||||
* - 静态菜单表中的 0 级菜单数量
|
||||
* ------------------------------------------------------------------------- */
|
||||
uint16_t MenuModel_CountTopLevel(uint16_t maxItem)
|
||||
{
|
||||
uint8_t by0LevelNum = 0;
|
||||
uint32_t wLoop;
|
||||
|
||||
for (wLoop = 0; wLoop < maxItem; wLoop++)
|
||||
{
|
||||
if (menuTab[wLoop].byClass == 0)
|
||||
{
|
||||
by0LevelNum++;
|
||||
}
|
||||
}
|
||||
return by0LevelNum;
|
||||
}
|
||||
|
||||
/* -------------------------------------------------------------------------
|
||||
* 函数名: MenuModel_BuildTree
|
||||
* 功能:
|
||||
* 根据按层级顺序排列的静态菜单定义表,构建菜单运行时树,并建立
|
||||
* `higher / lower / before / behind` 四类链路关系。
|
||||
*
|
||||
* 参数:
|
||||
* menuItems - 运行时菜单节点数组(输出)
|
||||
* modelTab - 静态菜单定义表(输入)
|
||||
* modelCount - 菜单项总数量
|
||||
*
|
||||
* 边界处理:
|
||||
* - 本函数不做 menuItems/modelTab 空指针校验,调用方需保证输入有效。
|
||||
* - 默认要求 modelCount > 0,且 `modelTab` 已按菜单显示顺序正确排列:
|
||||
* 同层节点连续,子节点紧跟在父节点之后。
|
||||
* - 当前函数内部使用固定深度数组 `ptFirst[4] / ptLast[4]`,
|
||||
* 因此默认支持的最大层级索引为 3。
|
||||
*
|
||||
* 说明:
|
||||
* - 构建流程:
|
||||
* 1) 先初始化 `menuItems[0]`,作为整棵树的起点
|
||||
* 2) 使用 `ptFirst[level] / ptLast[level]` 记录每一层当前的首节点和尾节点
|
||||
* 3) 依次读取后续静态定义项,复制 `menuDef` 到运行时节点
|
||||
* 4) 比较“当前节点层级”和“下一个节点层级”,决定链路挂接方式:
|
||||
* a) byCurClass < byNextClass
|
||||
* 表示进入更深一层,`ptNextNode` 是 `ptCurrent` 的第一个子节点:
|
||||
* - `ptCurrent->links.lower = ptNextNode`
|
||||
* - `ptNextNode->links.higher = ptCurrent`
|
||||
* - 该层的 `ptFirst/ptLast` 都初始化为 `ptNextNode`
|
||||
* b) byCurClass == byNextClass
|
||||
* 表示接入同层兄弟节点:
|
||||
* - `ptNextNode->links.before = ptCurrent`
|
||||
* - `ptCurrent->links.behind = ptNextNode`
|
||||
* - `ptNextNode->links.higher` 继承当前节点的父节点
|
||||
* - 更新该层 `ptLast`
|
||||
* c) byCurClass > byNextClass
|
||||
* 表示从更深层回退到某个上层后,再接入该层的新兄弟节点:
|
||||
* - `ptNextNode->links.before = ptLast[byNextClass]`
|
||||
* - `ptNextNode->links.higher = ptFirst[byNextClass]->links.higher`
|
||||
* 即:新节点的父节点与该层第一个节点保持一致
|
||||
* - 将原尾节点的 `behind` 指向新节点,并更新该层 `ptLast`
|
||||
* - 对回退过程中已经结束的更深层链表做首尾闭环
|
||||
* 5) 全部节点处理完成后,再将 `0 ~ byCurClass` 各层链表统一闭环
|
||||
* - 构建结果:
|
||||
* `higher` - 父节点
|
||||
* `lower` - 第一个子节点
|
||||
* `before` - 同层前一个节点
|
||||
* `behind` - 同层后一个节点
|
||||
* - 同层链表最终被组织为环形结构,便于菜单上下移动时循环导航。
|
||||
* - 本函数只负责树与链表关系构建,不负责菜单位置计算和显示名称装饰。
|
||||
*
|
||||
* 返回值:
|
||||
* - 无
|
||||
* ------------------------------------------------------------------------- */
|
||||
void MenuModel_BuildTree(tagMenuItem *menuItems, const tagMenuModel *modelTab, uint32_t modelCount)
|
||||
{
|
||||
tagPMenuItem ptFirst[MENU_MODEL_MAX_LEVEL]; /* 每层第一个节点 */
|
||||
tagPMenuItem ptLast[MENU_MODEL_MAX_LEVEL]; /* 每层最后一个节点 */
|
||||
tagPMenuItem ptCurrent; /* 当前节点 */
|
||||
tagPMenuItem ptNextNode; /* 下一个节点 */
|
||||
|
||||
uint8_t byCurClass; /* 当前节点层级 */
|
||||
uint8_t byNextClass; /* 下一个节点层级 */
|
||||
|
||||
/* 初始化第 0 个节点,作为整棵树的起点 */
|
||||
memset(&menuItems[0], 0, sizeof(menuItems[0]));
|
||||
/* 初始化第 0 个节点的业务字段 */
|
||||
memcpy(&menuItems[0].menuDef, &modelTab[0], sizeof(menuItems[0].menuDef));
|
||||
|
||||
/* 初始化第 0 个节点的链路 */
|
||||
ptFirst[0] = &menuItems[0];
|
||||
ptLast[0] = ptFirst[0];
|
||||
ptCurrent = &menuItems[0];
|
||||
|
||||
/* 构建菜单树 */
|
||||
for (uint16_t index = 1; index < modelCount; index++)
|
||||
{
|
||||
/* 初始化第 index 个节点的业务字段 */
|
||||
ptNextNode = &menuItems[index];
|
||||
memset(ptNextNode, 0, sizeof(*ptNextNode));
|
||||
memcpy(&ptNextNode->menuDef, &modelTab[index], sizeof(ptNextNode->menuDef));
|
||||
|
||||
byCurClass = ptCurrent->menuDef.byClass;
|
||||
byNextClass = ptNextNode->menuDef.byClass;
|
||||
|
||||
if (byCurClass < byNextClass) /* 当前节点层级小于下一个节点层级 */
|
||||
{
|
||||
ptCurrent->links.lower = ptNextNode;
|
||||
ptNextNode->links.higher = ptCurrent;
|
||||
/* 更新该层 first/last */
|
||||
ptFirst[byNextClass] = ptNextNode;
|
||||
ptLast[byNextClass] = ptNextNode;
|
||||
}
|
||||
else if (byCurClass == byNextClass) /* 当前节点层级等于下一个节点层级 */
|
||||
{
|
||||
ptNextNode->links.before = ptCurrent;
|
||||
ptNextNode->links.higher = ptCurrent->links.higher;
|
||||
ptCurrent->links.behind = ptNextNode;
|
||||
/* 更新该层 last */
|
||||
ptLast[byNextClass] = ptNextNode;
|
||||
}
|
||||
else /* 当前节点层级大于下一个节点层级,回退层级*/
|
||||
{
|
||||
ptNextNode->links.before = ptLast[byNextClass];
|
||||
ptNextNode->links.higher = ptFirst[byNextClass]->links.higher;
|
||||
/* 更新该层 last 的 behind */
|
||||
ptLast[byNextClass]->links.behind = ptNextNode;
|
||||
ptLast[byNextClass] = ptNextNode;
|
||||
|
||||
/* 形成回环链表 */
|
||||
for (uint16_t index2 = byCurClass; index2 > byNextClass; index2--)
|
||||
{
|
||||
ptLast[index2]->links.behind = ptFirst[index2];
|
||||
ptFirst[index2]->links.before = ptLast[index2];
|
||||
}
|
||||
}
|
||||
/* 更新当前节点 */
|
||||
ptCurrent = ptNextNode;
|
||||
}
|
||||
/* 形成回环链表 */
|
||||
byCurClass = ptCurrent->menuDef.byClass;
|
||||
for (uint16_t index = 0; index <= byCurClass; index++)
|
||||
{
|
||||
ptLast[index]->links.behind = ptFirst[index];
|
||||
ptFirst[index]->links.before = ptLast[index];
|
||||
}
|
||||
}
|
||||
|
||||
/* -------------------------------------------------------------------------
|
||||
* 函数名: MenuModel_DecorateDisplayNames
|
||||
* 功能:
|
||||
* 为存在子菜单的菜单项名称追加显示装饰符,提示该节点可继续进入下一级。
|
||||
*
|
||||
* 参数:
|
||||
* menuItems - 菜单项数组首地址
|
||||
* modelCount - 菜单项总数量
|
||||
*
|
||||
* 边界处理:
|
||||
* - 本函数不做 menuItems 空指针校验,调用方需保证数组有效。
|
||||
* - 若菜单项没有子节点(links.lower == NULL),直接跳过,不追加装饰符。
|
||||
* - 通过 `sizeof(byName) - 1` 预留字符串结尾 `\0`,避免越界写入。
|
||||
* - 若名称已以 `\x10` 结尾,则不重复追加。
|
||||
*
|
||||
* 说明:
|
||||
* - `\x10` 在当前菜单显示逻辑中作为“右箭头/可下钻”标记,
|
||||
* 用于提示用户该项后面还有子菜单。
|
||||
* - 处理流程:
|
||||
* 1) 遍历所有菜单项
|
||||
* 2) 判断是否存在子菜单
|
||||
* 3) 统计当前名称长度(不超过缓冲区上限)
|
||||
* 4) 若末尾尚未追加装饰符,则写入 `\x10` 和字符串结束符 `\0`
|
||||
* - 该函数只修改显示名称,不改变菜单树链路关系和业务属性。
|
||||
*
|
||||
* 返回值:
|
||||
* - 无
|
||||
* ------------------------------------------------------------------------- */
|
||||
void MenuModel_DecorateDisplayNames(tagMenuItem *menuItems, uint32_t modelCount)
|
||||
{
|
||||
for (uint32_t i = 0; i < modelCount; i++)
|
||||
{
|
||||
uint16_t nameCapacity = (uint16_t)sizeof(menuItems[i].menuDef.byName);
|
||||
uint16_t maxTextLenWithoutNull = (uint16_t)(nameCapacity - 1);
|
||||
uint16_t len = 0;
|
||||
/* 如果没有子菜单,直接跳过 */
|
||||
if (menuItems[i].links.lower == NULL)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
/* 计算名称长度 */
|
||||
while ((len < maxTextLenWithoutNull) && (menuItems[i].menuDef.byName[len] != '\0'))
|
||||
{
|
||||
len++;
|
||||
}
|
||||
/* 补充右箭头 */
|
||||
if ((len == 0) || (menuItems[i].menuDef.byName[len - 1] != '\x10'))
|
||||
{
|
||||
if (len < maxTextLenWithoutNull)
|
||||
{
|
||||
menuItems[i].menuDef.byName[len] = '\x10';
|
||||
menuItems[i].menuDef.byName[len + 1] = '\0';
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
/* -------------------------------------------------------------------------
|
||||
* 函数名: MenuModel_IndexMenuItems
|
||||
* 功能:
|
||||
* 统计整棵菜单树中所有菜单项的 `wPos` 与 `wNum`。
|
||||
*
|
||||
* 参数:
|
||||
* menuItems - 菜单项数组
|
||||
* modelCount - 菜单项总数
|
||||
*
|
||||
* 边界处理:
|
||||
* - 若 `menuItems == NULL` 或 `modelCount == 0`,则直接返回。
|
||||
*
|
||||
* 说明:
|
||||
* - 本函数分两部分初始化:
|
||||
* 1) 先遍历 0 级同层链表,为所有顶层节点写入 `rect.wPos`
|
||||
* 2) 再遍历每个节点:
|
||||
* a) 若无子节点,则其 `rect.wNum = 0`
|
||||
* b) 若有子节点,则沿子层 `behind` 链统计子节点总数
|
||||
* c) 同时为该组子节点写入各自的 `rect.wPos`
|
||||
* d) 最后把子节点总数写回当前父节点的 `rect.wNum`
|
||||
* - 因此:
|
||||
* `wPos` 表示“当前节点在本层中的 0 基序号”
|
||||
* `wNum` 表示“当前节点拥有的直接子节点总数”
|
||||
*
|
||||
* 返回值:
|
||||
* - 无
|
||||
* ------------------------------------------------------------------------- */
|
||||
void MenuModel_IndexMenuItems(tagMenuItem *menuItems, uint32_t modelCount)
|
||||
{
|
||||
tagPMenuItem current;
|
||||
tagPMenuItem parent;
|
||||
tagPMenuItem child;
|
||||
uint16_t count;
|
||||
|
||||
if ((menuItems == NULL) || (modelCount == 0))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
/* 1. 记录 0 级节点的位置和子节点数量 */
|
||||
current = &menuItems[0];
|
||||
count = 0;
|
||||
for (uint16_t index = 0; index < modelCount; index++)
|
||||
{
|
||||
current->rect.wPos = count;
|
||||
current = current->links.behind;
|
||||
if ((current == NULL) || (current == &menuItems[0]))
|
||||
{
|
||||
break;
|
||||
}
|
||||
count++;
|
||||
}
|
||||
|
||||
/* 2. 记录其他层级的位置和子节点数量 */
|
||||
for (uint32_t i = 0; i < modelCount; i++)
|
||||
{
|
||||
parent = &menuItems[i];
|
||||
current = parent->links.lower;
|
||||
/* a. 如果当前节点没有子节点,说明是最深一层,记录当前节点子节点数量为 0 */
|
||||
if (current == NULL)
|
||||
{
|
||||
parent->rect.wNum = 0;
|
||||
continue;
|
||||
}
|
||||
|
||||
/*b. 若有子节点,则沿子层 `behind` 链统计子节点总数 */
|
||||
child = current;
|
||||
count = 0;
|
||||
|
||||
for (uint16_t index = 0; index < MENU_MODEL_MAX_ITEM; index++)
|
||||
{
|
||||
/*c. 为该组子节点写入各自的 `rect.wPos` */
|
||||
child->rect.wPos = count;
|
||||
count++;
|
||||
|
||||
child = child->links.behind;
|
||||
if ((child == NULL) || (child == current))
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
/* d. 最后把子节点总数写回当前父节点的 `rect.wNum` */
|
||||
parent->rect.wNum = count;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void MenuModel_Init(menu_model_t *model)
|
||||
{
|
||||
uint16_t by0LevelNum;
|
||||
model->maxItem = (uint16_t)(sizeof(menuTab) / sizeof(tagMenuModel));
|
||||
memset(model->menuItems, 0, sizeof(model->menuItems));
|
||||
|
||||
by0LevelNum = MenuModel_CountTopLevel(model->maxItem);
|
||||
MenuModel_BuildTree(model->menuItems, menuTab, model->maxItem);
|
||||
MenuModel_DecorateDisplayNames(model->menuItems, model->maxItem);
|
||||
MenuModel_IndexMenuItems(model->menuItems, model->maxItem);
|
||||
MenuModel_PositionCal(&model->menuItems[0], by0LevelNum);
|
||||
}
|
||||
36
src/Drv/pages/menu/model.h
Normal file
36
src/Drv/pages/menu/model.h
Normal file
@@ -0,0 +1,36 @@
|
||||
#ifndef MENU_MODEL_H
|
||||
#define MENU_MODEL_H
|
||||
|
||||
#include "display.h"
|
||||
#include "def.h"
|
||||
|
||||
#define MENU_MODEL_MAX_ITEM 300 /* 最大菜单项数 */
|
||||
#define MENU_MODEL_MAX_LEVEL 4 /* 最大菜单层级 */
|
||||
|
||||
typedef struct menu_model_t menu_model_t;
|
||||
|
||||
struct menu_model_t
|
||||
{
|
||||
uint16_t maxItem;
|
||||
tagMenuItem menuItems[MENU_MODEL_MAX_ITEM];
|
||||
};
|
||||
|
||||
|
||||
|
||||
void MenuModel_Init(menu_model_t *model);
|
||||
|
||||
|
||||
|
||||
/* whitebox test hooks */
|
||||
#ifdef UNIT_TEST
|
||||
void MenuModel_IndexMenuItems(tagMenuItem *menuItems, uint32_t modelCount);
|
||||
void MenuModel_PositionCal(tagPMenuItem ptMenuHead, uint16_t by0LevelNum);
|
||||
uint8_t MenuModel_Utf8LenCal(uint8_t *str);
|
||||
uint8_t MenuModel_GetMenuMaxDisplayLen(tagPMenuItem ptFirst);
|
||||
uint16_t MenuModel_CountTopLevel(uint16_t maxItem);
|
||||
void MenuModel_BuildTree(tagMenuItem *menuItems, const tagMenuModel *modelTab, uint32_t modelCount);
|
||||
void MenuModel_DecorateDisplayNames(tagMenuItem *menuItems, uint32_t modelCount);
|
||||
#endif
|
||||
|
||||
|
||||
#endif
|
||||
255
src/Drv/pages/menu/page.c
Normal file
255
src/Drv/pages/menu/page.c
Normal file
@@ -0,0 +1,255 @@
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
|
||||
#include "page.h"
|
||||
|
||||
#include "display.h"
|
||||
#include "presenter.h"
|
||||
|
||||
|
||||
/* -------------------------------------------------------------------------
|
||||
* 模块内静态对象说明:
|
||||
* s_model - 菜单页 Model 实例(菜单数据与运行时结构)
|
||||
* s_view - 菜单页 View 实例(布局与渲染能力)
|
||||
* s_presenter - 菜单页 Presenter 实例(输入处理与状态驱动)
|
||||
* s_menuPage - 页面管理器可注册的 page_t 描述对象
|
||||
*
|
||||
* 说明:
|
||||
* - 以上对象均为文件内静态单例,生命周期覆盖进程运行期。
|
||||
* - 通过 MenuPage_GetInstance() 暴露 s_menuPage 给 PageManager 注册。
|
||||
* ------------------------------------------------------------------------- */
|
||||
static menu_model_t s_model;
|
||||
static menu_view_t s_view;
|
||||
static menu_presenter_t s_presenter;
|
||||
static page_t s_menuPage;
|
||||
|
||||
|
||||
/* -------------------------------------------------------------------------
|
||||
* 函数名: MenuPage_OnEnter
|
||||
* 功能:
|
||||
* 页面进入回调:将菜单渲染标记为“首帧全量刷新”,并立即触发一次刷新。
|
||||
*
|
||||
* 参数:
|
||||
* page - 当前页面对象指针(本实现未直接使用)
|
||||
*
|
||||
* 边界处理:
|
||||
* - 本函数不依赖 page 内容,统一转为 (void)page 消除未使用告警。
|
||||
*
|
||||
* 说明:
|
||||
* - 通过 dspCtrl.bFirst = 1 告知 Presenter 下一次刷新走首帧路径。
|
||||
* - 进入页面后立即刷新,确保界面可见状态与内部状态同步。
|
||||
*
|
||||
* 返回值:
|
||||
* - 无
|
||||
* ------------------------------------------------------------------------- */
|
||||
static void MenuPage_OnEnter(page_t *page)
|
||||
{
|
||||
(void)page;
|
||||
s_presenter.dspCtrl.bFirst = 1;
|
||||
MenuPresenter_Refresh(&s_presenter);
|
||||
}
|
||||
|
||||
/* -------------------------------------------------------------------------
|
||||
* 函数名: MenuPage_OnExit
|
||||
* 功能:
|
||||
* 页面退出回调占位点;当前版本无额外退出动作。
|
||||
*
|
||||
* 参数:
|
||||
* page - 当前页面对象指针(本实现未直接使用)
|
||||
*
|
||||
* 边界处理:
|
||||
* - 使用 (void)page 防止未使用参数告警。
|
||||
*
|
||||
* 说明:
|
||||
* - 预留给后续扩展(如停止定时任务、冻结动画、保存瞬时 UI 状态等)。
|
||||
*
|
||||
* 返回值:
|
||||
* - 无
|
||||
* ------------------------------------------------------------------------- */
|
||||
static void MenuPage_OnExit(page_t *page)
|
||||
{
|
||||
(void)page;
|
||||
}
|
||||
|
||||
/* -------------------------------------------------------------------------
|
||||
* 函数名: MenuPage_OnDestroy
|
||||
* 功能:
|
||||
* 页面销毁回调:清空菜单页内部三层对象(Model/View/Presenter)状态。
|
||||
*
|
||||
* 参数:
|
||||
* page - 当前页面对象指针(本实现未直接使用)
|
||||
*
|
||||
* 边界处理:
|
||||
* - 使用 memset 全量清零静态对象,避免残留状态影响后续重建。
|
||||
*
|
||||
* 说明:
|
||||
* - 与 is_cached 策略配合:当页面被标记为非缓存并弹栈销毁时,该函数用于复位。
|
||||
* - page_t 元信息不在此函数复位,由页面生命周期创建阶段重新赋值。
|
||||
*
|
||||
* 返回值:
|
||||
* - 无
|
||||
* ------------------------------------------------------------------------- */
|
||||
static void MenuPage_OnDestroy(page_t *page)
|
||||
{
|
||||
(void)page;
|
||||
memset(&s_model, 0, sizeof(s_model));
|
||||
memset(&s_view, 0, sizeof(s_view));
|
||||
memset(&s_presenter, 0, sizeof(s_presenter));
|
||||
}
|
||||
|
||||
/* -------------------------------------------------------------------------
|
||||
* 函数名: MenuPage_OnEvent
|
||||
* 功能:
|
||||
* 页面事件回调:校验输入事件后,将按键转交 Presenter 处理。
|
||||
*
|
||||
* 参数:
|
||||
* page - 当前页面对象指针(本实现未直接使用)
|
||||
* event - 输入事件指针
|
||||
*
|
||||
* 边界处理:
|
||||
* - event 为 NULL 时返回 EVENT_UNHANDLED。
|
||||
* - 仅处理 PAGE_EVENT_KEY 事件类型,其它类型返回 EVENT_UNHANDLED。
|
||||
* - keyVal 为 0 视为无效按键,返回 EVENT_UNHANDLED。
|
||||
*
|
||||
* 说明:
|
||||
* - 事件有效时调用 MenuPresenter_HandleInput() 执行业务输入流转。
|
||||
* - 返回 EVENT_HANDLED,表示该事件已被菜单页消费,不再交给上层页面逻辑。
|
||||
*
|
||||
* 返回值:
|
||||
* - EVENT_HANDLED : 事件已处理
|
||||
* - EVENT_UNHANDLED : 事件无效或不属于本页处理范围
|
||||
* ------------------------------------------------------------------------- */
|
||||
static event_result_t MenuPage_OnEvent(page_t *page, input_event_t *event)
|
||||
{
|
||||
(void)page;
|
||||
if ((event == NULL) || (event->type != PAGE_EVENT_KEY) || (event->keyVal == 0))
|
||||
{
|
||||
return EVENT_UNHANDLED;
|
||||
}
|
||||
|
||||
MenuPresenter_HandleInput(&s_presenter, event->keyVal);
|
||||
return EVENT_HANDLED;
|
||||
}
|
||||
|
||||
/* -------------------------------------------------------------------------
|
||||
* 函数名: MenuPage_OnLoop
|
||||
* 功能:
|
||||
* 页面循环回调:周期性驱动 Presenter 执行刷新逻辑。
|
||||
*
|
||||
* 参数:
|
||||
* page - 当前页面对象指针(本实现未直接使用)
|
||||
*
|
||||
* 边界处理:
|
||||
* - 使用 (void)page 防止未使用参数告警。
|
||||
*
|
||||
* 说明:
|
||||
* - 实际刷新策略(全量/增量)由 Presenter 内部状态控制。
|
||||
* - 该函数通常由 PageManager_Loop() 在主循环节拍中调用。
|
||||
*
|
||||
* 返回值:
|
||||
* - 无
|
||||
* ------------------------------------------------------------------------- */
|
||||
static void MenuPage_OnLoop(page_t *page)
|
||||
{
|
||||
(void)page;
|
||||
MenuPresenter_Refresh(&s_presenter);
|
||||
}
|
||||
|
||||
/* -------------------------------------------------------------------------
|
||||
* 函数名: MenuPage_OnCreate
|
||||
* 功能:
|
||||
* 页面创建回调:按 Model -> View -> Presenter 顺序完成菜单页运行时装配,
|
||||
* 并初始化 page_t 描述对象字段。
|
||||
*
|
||||
* 参数:
|
||||
* page - 当前页面对象指针(由 PageManager 传入)
|
||||
*
|
||||
* 边界处理:
|
||||
* - 先清零 s_menuPage,再统一重建其元信息与回调绑定,避免脏状态遗留。
|
||||
* - 假定 page 非空且来自 PageManager 生命周期调用链。
|
||||
*
|
||||
* 说明:
|
||||
* - 初始化顺序固定:
|
||||
* 1) MenuModel_Init(&s_model)
|
||||
* 2) MenuView_Init(&s_view)
|
||||
* 3) MenuPresenter_Init(&s_presenter, &s_model, &s_view)
|
||||
* - 将 model/presenter/view 回填到 page 与 s_menuPage,便于调试与统一访问。
|
||||
* - s_menuPage 作为静态页面实例,对外由 MenuPage_GetInstance() 返回。
|
||||
*
|
||||
* 返回值:
|
||||
* - 无
|
||||
* ------------------------------------------------------------------------- */
|
||||
static void MenuPage_OnCreate(page_t *page)
|
||||
{
|
||||
/* 1) model init */
|
||||
MenuModel_Init(&s_model);
|
||||
|
||||
/* 2) view init */
|
||||
MenuView_Init(&s_view);
|
||||
|
||||
/* 3) presenter setup + runtime build */
|
||||
MenuPresenter_Init(&s_presenter, &s_model, &s_view);
|
||||
|
||||
|
||||
page->model = &s_model;
|
||||
page->presenter = &s_presenter;
|
||||
page->view = &s_view;
|
||||
|
||||
s_menuPage.presenter = &s_presenter;
|
||||
s_menuPage.view = &s_view;
|
||||
s_menuPage.model = &s_model;
|
||||
}
|
||||
|
||||
/* -------------------------------------------------------------------------
|
||||
* 函数名: MenuPage_GetInstance
|
||||
* 功能:
|
||||
* 获取菜单页静态实例指针,供 PageManager_Register() 注册使用。
|
||||
*
|
||||
* 参数:
|
||||
* 无
|
||||
*
|
||||
* 边界处理:
|
||||
* - 返回文件内静态对象地址,无空指针分支。
|
||||
*
|
||||
* 说明:
|
||||
* - 该函数仅暴露页面入口,不负责初始化;初始化由 on_create 生命周期完成。
|
||||
*
|
||||
* 返回值:
|
||||
* - 指向静态页面对象 s_menuPage 的 page_t* 指针
|
||||
* ------------------------------------------------------------------------- */
|
||||
page_t *MenuPage_GetInstance(void)
|
||||
{
|
||||
/* 确保在注册到 PageManager 前,页面生命周期回调已就绪 */
|
||||
memset(&s_menuPage, 0, sizeof(s_menuPage));
|
||||
s_menuPage.page_id = PAGE_ID_MENU;
|
||||
s_menuPage.is_cached = 1;
|
||||
s_menuPage.on_create = MenuPage_OnCreate;
|
||||
s_menuPage.on_enter = MenuPage_OnEnter;
|
||||
s_menuPage.on_exit = MenuPage_OnExit;
|
||||
s_menuPage.on_destroy = MenuPage_OnDestroy;
|
||||
s_menuPage.on_event = MenuPage_OnEvent;
|
||||
s_menuPage.on_loop = MenuPage_OnLoop;
|
||||
return &s_menuPage;
|
||||
}
|
||||
|
||||
/* -------------------------------------------------------------------------
|
||||
* 函数名: MenuPage_TriggerCurrentAction
|
||||
* 功能:
|
||||
* 触发当前菜单项动作的占位接口(当前版本仅输出调试日志)。
|
||||
*
|
||||
* 参数:
|
||||
* 无
|
||||
*
|
||||
* 边界处理:
|
||||
* - 当前实现不依赖外部输入,不涉及参数校验。
|
||||
*
|
||||
* 说明:
|
||||
* - 现阶段用于保留动作触发扩展点,后续可接入真实业务动作执行链路。
|
||||
*
|
||||
* 返回值:
|
||||
* - 无
|
||||
* ------------------------------------------------------------------------- */
|
||||
void MenuPage_TriggerCurrentAction(void)
|
||||
{
|
||||
printf("MenuPage_TriggerCurrentAction\n");
|
||||
}
|
||||
10
src/Drv/pages/menu/page.h
Normal file
10
src/Drv/pages/menu/page.h
Normal file
@@ -0,0 +1,10 @@
|
||||
#ifndef MENU_PAGE_H
|
||||
#define MENU_PAGE_H
|
||||
|
||||
#include "view.h"
|
||||
#include "../page.h"
|
||||
|
||||
page_t *MenuPage_GetInstance(void);
|
||||
void MenuPage_TriggerCurrentAction(void);
|
||||
|
||||
#endif
|
||||
272
src/Drv/pages/menu/presenter.c
Normal file
272
src/Drv/pages/menu/presenter.c
Normal file
@@ -0,0 +1,272 @@
|
||||
#include "presenter.h"
|
||||
|
||||
#include <stddef.h>
|
||||
#include <string.h>
|
||||
|
||||
#include "../../key.h"
|
||||
|
||||
typedef struct
|
||||
{
|
||||
tagPMenuItem ptHead;
|
||||
tagPMenuItem ptCurrent;
|
||||
tagPMenuItem ptRoute[4];
|
||||
tagPMenuItem ptCurBak;
|
||||
tagPMenuItem pt0Level;
|
||||
} MenuNavState;
|
||||
|
||||
typedef struct
|
||||
{
|
||||
uint8_t needRefresh;
|
||||
uint8_t skipRenderThisRound;
|
||||
} MenuNavResult;
|
||||
|
||||
static MenuNavResult MenuNavigator_ProcessKey(MenuNavState *navState, uint8_t keyVal)
|
||||
{
|
||||
MenuNavResult result;
|
||||
tagPMenuItem ptCurrent;
|
||||
tagPMenuItem ptHead;
|
||||
tagPMenuItem *ptRoute;
|
||||
|
||||
result.needRefresh = 0;
|
||||
result.skipRenderThisRound = 0;
|
||||
|
||||
ptCurrent = navState->ptCurrent;
|
||||
ptHead = navState->ptHead;
|
||||
ptRoute = navState->ptRoute;
|
||||
|
||||
switch (keyVal)
|
||||
{
|
||||
case KEY_F1:
|
||||
case KEY_F2:
|
||||
break;
|
||||
case KEY_U:
|
||||
ptCurrent = ptCurrent->links.before;
|
||||
result.needRefresh = 1;
|
||||
break;
|
||||
case KEY_D:
|
||||
ptCurrent = ptCurrent->links.behind;
|
||||
result.needRefresh = 1;
|
||||
break;
|
||||
case KEY_L:
|
||||
ptCurrent = ptRoute[ptCurrent->menuDef.byClass - 1];
|
||||
if (ptCurrent->menuDef.byClass == 0)
|
||||
{
|
||||
if (ptCurrent->rect.wPos == 1)
|
||||
{
|
||||
ptCurrent = ptCurrent->links.before->links.before;
|
||||
}
|
||||
else
|
||||
{
|
||||
ptCurrent = ptCurrent->links.before;
|
||||
}
|
||||
ptCurrent = ptCurrent->links.lower;
|
||||
}
|
||||
result.needRefresh = 1;
|
||||
break;
|
||||
case KEY_R:
|
||||
case KEY_ENT:
|
||||
if (ptCurrent->links.lower != NULL)
|
||||
{
|
||||
ptCurrent = ptCurrent->links.lower;
|
||||
result.needRefresh = 1;
|
||||
}
|
||||
else if (ptCurrent->menuDef.pfnWinProc != NULL)
|
||||
{
|
||||
ptCurrent->menuDef.pfnWinProc();
|
||||
}
|
||||
break;
|
||||
case KEY_ESC:
|
||||
if (ptCurrent->menuDef.byClass == 1)
|
||||
{
|
||||
navState->pt0Level = ptHead;
|
||||
navState->ptRoute[0] = ptHead;
|
||||
navState->ptCurrent = ptHead->links.lower;
|
||||
navState->ptCurBak = navState->ptCurrent;
|
||||
navState->ptRoute[1] = navState->ptCurrent;
|
||||
result.skipRenderThisRound = 1;
|
||||
return result;
|
||||
}
|
||||
ptCurrent = ptRoute[ptCurrent->menuDef.byClass - 1];
|
||||
result.needRefresh = 1;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
navState->ptCurrent = ptCurrent;
|
||||
return result;
|
||||
}
|
||||
|
||||
static void MenuNavigator_RebuildRoute(MenuNavState *navState, uint32_t maxItem)
|
||||
{
|
||||
tagPMenuItem ptIndex;
|
||||
|
||||
if (navState->ptCurBak == navState->ptCurrent)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
ptIndex = navState->ptCurrent;
|
||||
for (uint32_t index = 0; index < maxItem; index++)
|
||||
{
|
||||
if (ptIndex->links.higher == NULL)
|
||||
{
|
||||
ptIndex = ptIndex->links.before;
|
||||
}
|
||||
else
|
||||
{
|
||||
ptIndex = ptIndex->links.higher;
|
||||
navState->ptRoute[ptIndex->menuDef.byClass] = ptIndex;
|
||||
}
|
||||
if (ptIndex->menuDef.byClass == 0)
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
navState->ptRoute[navState->ptCurrent->menuDef.byClass] = navState->ptCurrent;
|
||||
}
|
||||
|
||||
static void MenuPresenter_FillNavState(const tagMenuCtrl *ctrl, MenuNavState *nav)
|
||||
{
|
||||
nav->ptHead = ctrl->ptHead;
|
||||
nav->ptCurrent = ctrl->ptCurrent;
|
||||
nav->ptRoute[0] = ctrl->ptRoute[0];
|
||||
nav->ptRoute[1] = ctrl->ptRoute[1];
|
||||
nav->ptRoute[2] = ctrl->ptRoute[2];
|
||||
nav->ptRoute[3] = ctrl->ptRoute[3];
|
||||
nav->ptCurBak = ctrl->ptCurBak;
|
||||
nav->pt0Level = ctrl->pt0Level;
|
||||
}
|
||||
|
||||
static void MenuPresenter_ApplyNavState(tagMenuCtrl *ctrl, const MenuNavState *nav)
|
||||
{
|
||||
ctrl->ptHead = nav->ptHead;
|
||||
ctrl->ptCurrent = nav->ptCurrent;
|
||||
ctrl->ptRoute[0] = nav->ptRoute[0];
|
||||
ctrl->ptRoute[1] = nav->ptRoute[1];
|
||||
ctrl->ptRoute[2] = nav->ptRoute[2];
|
||||
ctrl->ptRoute[3] = nav->ptRoute[3];
|
||||
ctrl->ptCurBak = nav->ptCurBak;
|
||||
ctrl->pt0Level = nav->pt0Level;
|
||||
}
|
||||
|
||||
/* -------------------------------------------------------------------------
|
||||
* 函数名: MenuPresenter_RenderByState
|
||||
* 功能:
|
||||
* 根据当前菜单状态差异选择渲染策略(整页刷新 / 同层局部刷新 / 单层补绘)。
|
||||
*
|
||||
* 参数:
|
||||
* presenter - 菜单 Presenter 实例,内部持有 menuCtrl 与 view 接口
|
||||
*
|
||||
* 边界处理:
|
||||
* - 本函数不做空指针校验,默认由调用链保证 presenter/view 有效。
|
||||
* - 当菜单路由发生跨 0 级变化时,强制走整页刷新,避免多层残影。
|
||||
* - 仅在“同层且同父”场景使用局部反显更新,其他变化按层级关系降级处理。
|
||||
*
|
||||
* 说明:
|
||||
* - 该函数是 Presenter 的渲染决策核心,不直接参与菜单导航计算。
|
||||
* - 决策顺序如下:
|
||||
* 1) 若 pt0Level 与 route[0] 不一致:
|
||||
* 说明当前顶层上下文已切换,先同步 pt0Level/ptCurBak,再 full_refresh。
|
||||
* 2) 否则若 ptCurBak 与 ptCurrent 不一致:
|
||||
* a) 同层 + 同父:调用 update_selection_same_level,执行“旧选中恢复 + 新选中高亮”
|
||||
* b) 新层级 >= 旧层级:调用 show_other_level,仅补绘受影响层
|
||||
* c) 其他(通常是回退到更高层):调用 full_refresh,保证界面一致性
|
||||
* 3) 渲染后统一将 ptCurBak 更新为 ptCurrent,作为下一轮差异比较基准。
|
||||
*
|
||||
* 返回值:
|
||||
* - 无
|
||||
* ------------------------------------------------------------------------- */
|
||||
static void MenuPresenter_RenderByState(MenuPresenter *presenter)
|
||||
{
|
||||
tagMenuCtrl *menuCtrl = &presenter->menuCtrl;
|
||||
MenuView *view = presenter->view;
|
||||
|
||||
if (menuCtrl->pt0Level != menuCtrl->ptRoute[0])
|
||||
{
|
||||
menuCtrl->pt0Level = menuCtrl->ptRoute[0];
|
||||
menuCtrl->ptCurBak = menuCtrl->ptCurrent;
|
||||
presenter->view->full_refresh(presenter->view, menuCtrl, MODE_NONE);
|
||||
}
|
||||
else if (menuCtrl->ptCurBak != menuCtrl->ptCurrent)
|
||||
{
|
||||
if ((menuCtrl->ptCurrent->menuDef.byClass == menuCtrl->ptCurBak->menuDef.byClass) &&
|
||||
(menuCtrl->ptCurrent->menuDef.byClass > 0) &&
|
||||
(menuCtrl->ptCurrent->links.higher == menuCtrl->ptCurBak->links.higher))
|
||||
{
|
||||
view->update_selection_same_level(view, menuCtrl->ptCurBak, menuCtrl->ptCurrent);
|
||||
}
|
||||
else if (menuCtrl->ptCurrent->menuDef.byClass >= menuCtrl->ptCurBak->menuDef.byClass)
|
||||
{
|
||||
view->update_selection_new_level(view, menuCtrl->ptRoute[menuCtrl->ptCurrent->menuDef.byClass - 1], menuCtrl->ptRoute[menuCtrl->ptCurrent->menuDef.byClass]);
|
||||
}
|
||||
else
|
||||
{
|
||||
presenter->view->full_refresh(presenter->view, menuCtrl, MODE_NONE);
|
||||
}
|
||||
menuCtrl->ptCurBak = menuCtrl->ptCurrent;
|
||||
}
|
||||
}
|
||||
|
||||
void MenuPresenter_Init(MenuPresenter *presenter, menu_model_t *model, menu_view_t *view)
|
||||
{
|
||||
presenter->dspCtrl.bFirst = 0;
|
||||
memset(&presenter->menuCtrl, 0, sizeof(presenter->menuCtrl));
|
||||
presenter->model = model;
|
||||
presenter->view = view;
|
||||
|
||||
|
||||
presenter->dspCtrl.bFirst = 1;
|
||||
presenter->menuCtrl.ptHead = &presenter->model->menuItems[0];
|
||||
presenter->menuCtrl.pt0Level = &presenter->model->menuItems[0];
|
||||
presenter->menuCtrl.ptCurrent = presenter->menuCtrl.ptHead->links.lower;
|
||||
presenter->menuCtrl.ptCurBak = presenter->menuCtrl.ptCurrent;
|
||||
|
||||
presenter->menuCtrl.ptRoute[0] = &presenter->model->menuItems[0];
|
||||
presenter->menuCtrl.ptRoute[1] = presenter->menuCtrl.ptCurrent;
|
||||
presenter->menuCtrl.ptRoute[2] = presenter->menuCtrl.ptCurrent;
|
||||
presenter->menuCtrl.ptRoute[3] = presenter->menuCtrl.ptCurrent;
|
||||
}
|
||||
|
||||
void MenuPresenter_Refresh(MenuPresenter *presenter)
|
||||
{
|
||||
if (presenter->dspCtrl.bFirst)
|
||||
{
|
||||
presenter->dspCtrl.bFirst = 0;
|
||||
presenter->view->full_refresh(presenter->view, &presenter->menuCtrl, MODE_NONE);
|
||||
}
|
||||
else
|
||||
{
|
||||
MenuPresenter_RenderByState(presenter);
|
||||
}
|
||||
}
|
||||
|
||||
void MenuPresenter_HandleInput(MenuPresenter *presenter, uint8_t keyVal)
|
||||
{
|
||||
MenuNavState navState;
|
||||
MenuNavResult navResult;
|
||||
|
||||
if (presenter->dspCtrl.bFirst)
|
||||
{
|
||||
MenuPresenter_Refresh(presenter);
|
||||
return;
|
||||
}
|
||||
|
||||
MenuPresenter_FillNavState(&presenter->menuCtrl, &navState);
|
||||
navResult = MenuNavigator_ProcessKey(&navState, keyVal);
|
||||
MenuPresenter_ApplyNavState(&presenter->menuCtrl, &navState);
|
||||
|
||||
if (navResult.skipRenderThisRound)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (navResult.needRefresh)
|
||||
{
|
||||
MenuNavState rebuildState;
|
||||
MenuPresenter_FillNavState(&presenter->menuCtrl, &rebuildState);
|
||||
MenuNavigator_RebuildRoute(&rebuildState, presenter->model->maxItem);
|
||||
MenuPresenter_ApplyNavState(&presenter->menuCtrl, &rebuildState);
|
||||
MenuPresenter_Refresh(presenter);
|
||||
}
|
||||
}
|
||||
33
src/Drv/pages/menu/presenter.h
Normal file
33
src/Drv/pages/menu/presenter.h
Normal file
@@ -0,0 +1,33 @@
|
||||
#ifndef MENU_PRESENTER_H
|
||||
#define MENU_PRESENTER_H
|
||||
|
||||
#include "display.h"
|
||||
#include "model.h"
|
||||
#include "view.h"
|
||||
|
||||
typedef struct menu_presenter_t menu_presenter_t;
|
||||
|
||||
typedef struct
|
||||
{
|
||||
uint8_t bFirst;
|
||||
} tagDspCtrl;
|
||||
|
||||
|
||||
struct menu_presenter_t
|
||||
{
|
||||
tagDspCtrl dspCtrl;
|
||||
tagMenuCtrl menuCtrl;
|
||||
menu_model_t *model;
|
||||
menu_view_t *view;
|
||||
};
|
||||
|
||||
typedef menu_presenter_t MenuPresenter;
|
||||
|
||||
void MenuPresenter_Init(MenuPresenter *presenter,
|
||||
menu_model_t *model,
|
||||
menu_view_t *view);
|
||||
|
||||
void MenuPresenter_HandleInput(MenuPresenter *presenter, uint8_t keyVal);
|
||||
void MenuPresenter_Refresh(MenuPresenter *presenter);
|
||||
|
||||
#endif
|
||||
313
src/Drv/pages/menu/view.c
Normal file
313
src/Drv/pages/menu/view.c
Normal file
@@ -0,0 +1,313 @@
|
||||
#include <stddef.h>
|
||||
#include <string.h>
|
||||
#include "view.h"
|
||||
|
||||
|
||||
static const PageRenderPort *s_port = NULL;
|
||||
/* -------------------------------------------------------------------------
|
||||
* 函数名: MenuView_DrawMeitou
|
||||
* 功能:
|
||||
* 绘制菜单标题装饰线(“眉头”样式):中间横线 + 左右两端斜线。
|
||||
*
|
||||
* 参数:
|
||||
* view - 菜单视图对象,提供底层渲染端口
|
||||
* yStart - 装饰线基准 Y 坐标(横线所在行)
|
||||
* width - 线宽参数(传递给 line_h/line)
|
||||
*
|
||||
* 边界处理:
|
||||
* - 本函数不做空指针判定,调用方需保证 view 与 s_port 有效。
|
||||
* - 坐标范围合法性由上层布局计算保证。
|
||||
*
|
||||
* 说明:
|
||||
* - 颜色统一使用字体前景色(get_color_font),确保装饰与文字风格一致。
|
||||
* - 绘制顺序:
|
||||
* 1) 中间横线(x:16~144)
|
||||
* 2) 左斜线(8, yStart-8 -> 16, yStart)
|
||||
* 3) 右斜线(144, yStart -> 152, yStart-8)
|
||||
* - 该函数用于视觉分隔与层次强调,不改变菜单状态数据。
|
||||
*
|
||||
* 返回值:
|
||||
* - 无
|
||||
* ------------------------------------------------------------------------- */
|
||||
static void MenuView_DrawMeitou(MenuView *view, uint16_t yStart, uint16_t width)
|
||||
{
|
||||
uint8_t fontColor = s_port->get_color_font();
|
||||
s_port->line_h(16, 144, yStart, width, fontColor);
|
||||
s_port->line(8, yStart - 8, 16, yStart, width, fontColor);
|
||||
s_port->line(144, yStart, 152, yStart - 8, width, fontColor);
|
||||
}
|
||||
|
||||
/* -------------------------------------------------------------------------
|
||||
* 函数名: MenuView_DrawBoundaryBox
|
||||
* 功能:
|
||||
* 绘制矩形边界框:先清空内部区域,再绘制四条边线。
|
||||
*
|
||||
* 参数:
|
||||
* view - 菜单视图对象,提供像素与线段绘制接口
|
||||
* leftX - 左边界 X
|
||||
* topY - 上边界 Y
|
||||
* rightX - 右边界 X
|
||||
* bottomY - 下边界 Y
|
||||
*
|
||||
* 边界处理:
|
||||
* - 本函数不校验 view/port 空指针,调用方需保证有效。
|
||||
* - 默认要求 leftX <= rightX、topY <= bottomY;
|
||||
* 若参数异常,像素循环或画线结果由底层驱动行为决定。
|
||||
*
|
||||
* 说明:
|
||||
* - 背景填充:逐像素将内部区域置为背景色(get_color_back),
|
||||
* 避免残影或旧内容叠加。
|
||||
* - 边框绘制:统一使用字体前景色(get_color_font),绘制上/左/下/右四边。
|
||||
* - 下边框使用 rightX + 1,目的是与其它边界在视觉上闭合对齐。
|
||||
*
|
||||
* 返回值:
|
||||
* - 无
|
||||
* ------------------------------------------------------------------------- */
|
||||
static void MenuView_DrawBoundaryBox(MenuView *view, uint16_t leftX, uint16_t topY, uint16_t rightX, uint16_t bottomY)
|
||||
{
|
||||
uint8_t backColor = s_port->get_color_back();
|
||||
uint8_t fontColor = s_port->get_color_font();
|
||||
for (uint16_t y = topY; y < bottomY; y++)
|
||||
{
|
||||
for (uint16_t x = leftX; x < rightX; x++)
|
||||
{
|
||||
s_port->set_pixel(x, y, backColor);
|
||||
}
|
||||
}
|
||||
|
||||
s_port->line_h(leftX, rightX, topY, 1, fontColor);
|
||||
s_port->line_v(topY, bottomY, leftX, 1, fontColor);
|
||||
s_port->line_h(leftX, rightX + 1, bottomY, 1, fontColor);
|
||||
s_port->line_v(topY, bottomY, rightX, 1, fontColor);
|
||||
}
|
||||
|
||||
/* -------------------------------------------------------------------------
|
||||
* 函数名: MenuView_UpdateSelectionNewLevel
|
||||
* 功能:
|
||||
* 在“进入新层级”场景下,重绘目标层级窗口内容(边框 + 文本)
|
||||
* 并对当前选中项执行反显高亮。
|
||||
*
|
||||
* 参数:
|
||||
* view - 菜单视图对象,提供底层渲染接口
|
||||
* currentLevel - 当前层级的“层级框拥有者”节点,提供窗口矩形与数量信息
|
||||
* nextLevel - 当前层级内的选中节点(也是该层列表起始遍历节点)
|
||||
*
|
||||
* 边界处理:
|
||||
* - 本函数不做空指针判定,调用方需保证 view/currentLevel/nextLevel 有效。
|
||||
* - 默认要求 currentLevel 与 nextLevel 来自同一层级链路,且 behind 链可遍历。
|
||||
* - 当 currentLevel->rect.wNum 为 0 时,文本循环不执行,
|
||||
* 但边框仍会被绘制。
|
||||
*
|
||||
* 说明:
|
||||
* - 绘制步骤:
|
||||
* 1) 使用 currentLevel->rect 绘制层级边框区域
|
||||
* 2) 从 nextLevel 起,按 wNum 数量沿 behind 链逐项绘制名称
|
||||
* 3) 基于 nextLevel 的 wPos 计算行区域并执行 invert 高亮
|
||||
* - 坐标策略:
|
||||
* wPosX = currentLevel->rect.wSPosX + 4(文本左内边距)
|
||||
* wPosY = currentLevel->rect.wSPosY + item->rect.wPos * LINE_HEIGHT + 3
|
||||
* - 该函数对应“新层级内容补绘”路径,与同层移动的局部反显函数职责分离。
|
||||
*
|
||||
* 返回值:
|
||||
* - 无
|
||||
* ------------------------------------------------------------------------- */
|
||||
|
||||
static void MenuView_UpdateSelectionNewLevel(MenuView *view, const tagPMenuItem currentLevel, const tagPMenuItem nextLevel)
|
||||
{
|
||||
tagPMenuItem ptIndex;
|
||||
|
||||
uint16_t wPosX;
|
||||
uint16_t wPosY;
|
||||
/* 1. 绘制当前层级的边界框 */
|
||||
MenuView_DrawBoundaryBox(view, currentLevel->rect.wSPosX, currentLevel->rect.wSPosY, currentLevel->rect.wEPosX, currentLevel->rect.wEPosY);
|
||||
/*2. 获取当前层级选中菜单项,并计算X起始坐标*/
|
||||
ptIndex = nextLevel;
|
||||
/* 计算当前菜单项的 X 坐标,对于下一层级起始 X 坐标都是一致的 */
|
||||
wPosX = currentLevel->rect.wSPosX + 4;
|
||||
|
||||
/* 3. 绘制当前层级的所有菜单项 */
|
||||
for (uint16_t index = 0; index < currentLevel->rect.wNum; index++)
|
||||
{
|
||||
wPosY = currentLevel->rect.wSPosY + ptIndex->rect.wPos * LINE_HEIGHT + 3;
|
||||
s_port->show_str(wPosX, wPosY, ptIndex->menuDef.byName);
|
||||
/* 移动到下一个菜单项 */
|
||||
ptIndex = ptIndex->links.behind;
|
||||
}
|
||||
|
||||
/* 4. 绘制当前选中项的反显 */
|
||||
wPosY = currentLevel->rect.wSPosY + nextLevel->rect.wPos * LINE_HEIGHT + 3;
|
||||
s_port->invert(currentLevel->rect.wSPosX + 2, wPosY - 1, currentLevel->rect.wEPosX - 2, wPosY + LINE_HEIGHT);
|
||||
}
|
||||
|
||||
/* -------------------------------------------------------------------------
|
||||
* 函数名: MenuView_ShowTopLevel
|
||||
* 功能:
|
||||
* 绘制菜单页顶部区域(0 级标题栏):清空标题背景、绘制装饰线,
|
||||
* 并按传入模式显示顶部提示文案。
|
||||
*
|
||||
* 参数:
|
||||
* view - 菜单视图对象,提供渲染端口能力
|
||||
* mode - 当前需要显示的运行模式(MenuMode)
|
||||
*
|
||||
* 边界处理:
|
||||
* - 本函数不做 view/port 空指针校验,调用方需保证对象已正确初始化。
|
||||
* - 模式值若不在已定义枚举分支内,走默认分支显示“当前模式: 无模式”。
|
||||
*
|
||||
* 说明:
|
||||
* - 先清空顶部区域(y: 0~32),避免旧文案残留。
|
||||
* - 再调用 MenuView_DrawMeitou() 绘制标题装饰线,保持页面视觉一致性。
|
||||
* - 模式文案映射:
|
||||
* MODE_OVERFLOW_PROTECTION -> "当前模式: 过流保护"
|
||||
* MODE_LOCAL_FEEDER_SEGMENT -> "就地馈线: 分段模式"
|
||||
* MODE_LOCAL_FEEDER_CONTACT -> "就地馈线: 联络模式"
|
||||
* 其他/未设置 -> "当前模式: 无模式"
|
||||
* - 本函数只负责“顶部区域”渲染,不处理菜单列表区内容刷新。
|
||||
*
|
||||
* 返回值:
|
||||
* - 无
|
||||
* ------------------------------------------------------------------------- */
|
||||
static void MenuView_ShowTopLevel(MenuView *view, MenuMode mode)
|
||||
{
|
||||
uint16_t lcdSizeX = s_port->get_size_x();
|
||||
s_port->fill_rect(0, 0, lcdSizeX - 1, 32, s_port->get_color_back());
|
||||
MenuView_DrawMeitou(view, 16, 2);
|
||||
/* 在 0 级菜单标题栏下方显示当前运行模式提示文字 */
|
||||
if (mode == MODE_OVERFLOW_PROTECTION)
|
||||
{
|
||||
s_port->show_str(16, 20, (uint8_t *)"当前模式: 过流保护");
|
||||
}
|
||||
else if (mode == MODE_LOCAL_FEEDER_SEGMENT)
|
||||
{
|
||||
s_port->show_str(16, 20, (uint8_t *)"就地馈线: 分段模式");
|
||||
}
|
||||
else if (mode == MODE_LOCAL_FEEDER_CONTACT)
|
||||
{
|
||||
s_port->show_str(16, 20, (uint8_t *)"就地馈线: 联络模式");
|
||||
}
|
||||
else
|
||||
{
|
||||
s_port->show_str(16, 20, (uint8_t *)"当前模式: 无模式");
|
||||
}
|
||||
}
|
||||
|
||||
/* -------------------------------------------------------------------------
|
||||
* 函数名: MenuView_UpdateSelectionSameLevel
|
||||
* 功能:
|
||||
* 在“同层同父节点”移动场景下执行局部刷新,仅更新旧/新选中行的反显状态,
|
||||
* 避免整块区域重绘带来的闪烁。
|
||||
*
|
||||
* 参数:
|
||||
* view - 菜单视图对象,提供底层 invert 绘制能力
|
||||
* oldItem - 变更前的选中菜单项(旧焦点)
|
||||
* newItem - 变更后的选中菜单项(新焦点)
|
||||
*
|
||||
* 边界处理:
|
||||
* - 本函数不做空指针与父链校验,调用方需保证:
|
||||
* 1) view/s_port 有效
|
||||
* 2) oldItem/newItem 非空
|
||||
* 3) oldItem->links.higher 非空且与 newItem 属于同一父节点
|
||||
* - 若上述前置条件不满足,坐标计算与反显结果将不可预期。
|
||||
*
|
||||
* 说明:
|
||||
* - 该函数是 Presenter 在“同层同父移动”场景(典型 U/D)的优化渲染路径。
|
||||
* - 通过两次 invert 完成“旧选中恢复 + 新选中高亮”:
|
||||
* 1) 对旧行区域 invert:撤销旧高亮
|
||||
* 2) 对新行区域 invert:施加新高亮
|
||||
* - 坐标基准来自 oldItem 的父节点(levelRectOwner = oldItem->links.higher):
|
||||
* posY = levelRectOwner->rect.wSPosY + (item->rect.wPos - 1) * LINE_HEIGHT + 3
|
||||
* - X 方向使用边框内缩(+2/-2),避免覆盖边框线条,保持视觉完整性。
|
||||
*
|
||||
* 返回值:
|
||||
* - 无
|
||||
* ------------------------------------------------------------------------- */
|
||||
static void MenuView_UpdateSelectionSameLevel(MenuView *view, const tagPMenuItem oldItem, const tagPMenuItem newItem)
|
||||
{
|
||||
tagPMenuItem levelRectOwner = oldItem->links.higher;
|
||||
|
||||
uint16_t leftX = (uint16_t)(levelRectOwner->rect.wSPosX + 2);
|
||||
uint16_t rightX = (uint16_t)(levelRectOwner->rect.wEPosX - 2);
|
||||
uint16_t oldPosY = (uint16_t)(levelRectOwner->rect.wSPosY + oldItem->rect.wPos * LINE_HEIGHT + 3);
|
||||
uint16_t newPosY = (uint16_t)(levelRectOwner->rect.wSPosY + newItem->rect.wPos * LINE_HEIGHT + 3);
|
||||
|
||||
/* 旧选中行反显恢复 + 新选中行反显高亮 */
|
||||
s_port->invert(leftX, (uint16_t)(oldPosY - 1), rightX, (uint16_t)(oldPosY + LINE_HEIGHT));
|
||||
s_port->invert(leftX, (uint16_t)(newPosY - 1), rightX, (uint16_t)(newPosY + LINE_HEIGHT));
|
||||
}
|
||||
|
||||
/* -------------------------------------------------------------------------
|
||||
* 函数名: MenuView_FullRefresh
|
||||
* 功能:
|
||||
* 执行菜单页整页刷新:清空工作区、重绘顶部标题区域、按当前层级路径重绘各级菜单框。
|
||||
*
|
||||
* 参数:
|
||||
* view - 菜单视图对象,提供布局参数与绘制能力
|
||||
* menuCtrl - 菜单运行时控制结构,提供当前层级与路由链信息
|
||||
* mode - 顶部标题显示使用的运行模式
|
||||
*
|
||||
* 边界处理:
|
||||
* - 本函数不做空指针校验,调用方需保证 view/menuCtrl/s_port 均有效。
|
||||
* - 当 byClass 为 0 时,for 循环不进入,仅刷新顶部区域。
|
||||
* - 默认要求 ptRoute[index] 与 ptRoute[index + 1] 在有效层级范围内可访问。
|
||||
*
|
||||
* 说明:
|
||||
* - 刷新步骤:
|
||||
* 1) 使用背景色清空菜单工作区域(MENU_YMIN ~ menuYMax)
|
||||
* 2) 调用 MenuView_ShowTopLevel() 重绘顶部模式文案与装饰
|
||||
* 3) 按当前菜单层级(byClass)逐层调用 MenuView_UpdateSelectionNewLevel()
|
||||
* 重绘各层边框、文本和选中态
|
||||
* - 该函数用于“整页一致性恢复”场景(如跨层回退、顶层切换等)。
|
||||
*
|
||||
* 返回值:
|
||||
* - 无
|
||||
* ------------------------------------------------------------------------- */
|
||||
static void MenuView_FullRefresh(MenuView *view, const tagMenuCtrl *menuCtrl, MenuMode mode)
|
||||
{
|
||||
uint8_t backColor = s_port->get_color_back();
|
||||
uint16_t lcdSizeX = s_port->get_size_x();
|
||||
|
||||
s_port->fill_rect(0, MENU_YMIN, lcdSizeX - 1, MENU_YMAX, backColor);
|
||||
|
||||
MenuView_ShowTopLevel(view, mode);
|
||||
for (uint8_t index = 0; index < menuCtrl->ptCurrent->menuDef.byClass; index++)
|
||||
{
|
||||
MenuView_UpdateSelectionNewLevel(view, menuCtrl->ptRoute[index], menuCtrl->ptRoute[index + 1]);
|
||||
}
|
||||
}
|
||||
/* -------------------------------------------------------------------------
|
||||
* 函数名: MenuView_Init
|
||||
* 功能:
|
||||
* 初始化菜单视图对象 MenuView,准备渲染端口引用与布局参数,
|
||||
* 并设置当前模式状态的默认值以及渲染入口函数指针。
|
||||
*
|
||||
* 参数:
|
||||
* view - 菜单视图实例地址(由调用方分配,函数内填充其字段)
|
||||
*
|
||||
* 边界处理:
|
||||
* - 本函数不校验 view 空指针,调用方需保证 view 非空。
|
||||
* - 渲染端口的物理 init 不在视图内执行;
|
||||
* 由入口(main)统一完成屏幕初始化(符合 PageRenderPort 共享策略)。
|
||||
*
|
||||
* 说明:
|
||||
* - 初始化内容包含:
|
||||
* 1) s_port = PageRenderer_Lcd():绑定 LCD 渲染端口表指针
|
||||
* 4) 设置当前模式为 MODE_NONE,避免进入绘制前读取未定义值
|
||||
* 5) 视图层仅保留绘制原子动作,刷新策略由 Presenter 调度
|
||||
*
|
||||
* 返回值:
|
||||
* - 无
|
||||
* ------------------------------------------------------------------------- */
|
||||
void MenuView_Init(MenuView *view)
|
||||
{
|
||||
s_port = PageRenderer_Lcd();
|
||||
view->update_selection_new_level = MenuView_UpdateSelectionNewLevel;
|
||||
view->update_selection_same_level = MenuView_UpdateSelectionSameLevel;
|
||||
view->full_refresh = MenuView_FullRefresh;
|
||||
}
|
||||
|
||||
#ifdef UNIT_TEST
|
||||
const PageRenderPort *MenuView_GetPortForTest(void)
|
||||
{
|
||||
return s_port;
|
||||
}
|
||||
#endif
|
||||
|
||||
44
src/Drv/pages/menu/view.h
Normal file
44
src/Drv/pages/menu/view.h
Normal file
@@ -0,0 +1,44 @@
|
||||
#ifndef MENU_VIEW_H
|
||||
#define MENU_VIEW_H
|
||||
|
||||
#include "types.h"
|
||||
#include "display.h"
|
||||
#include "def.h"
|
||||
#include "../global/renderer_lcd.h"
|
||||
|
||||
|
||||
|
||||
|
||||
typedef struct menu_view_t menu_view_t;
|
||||
|
||||
/* -------------------------------------------------------------------------
|
||||
* 结构体名: menu_view_t
|
||||
* 功能:
|
||||
* 菜单页面 View 层对象,封装对外绘制接口函数指针。
|
||||
*
|
||||
* 字段说明:
|
||||
* update_selection_new_level - 新层级切换时的层级窗口重绘与高亮更新
|
||||
* update_selection_same_level - 同层移动时的局部反显更新(降低闪烁)
|
||||
* full_refresh - 整页刷新入口(顶部 + 多层菜单内容)
|
||||
*
|
||||
* 说明:
|
||||
* - Presenter 通过该结构体调用 View 能力,避免直接依赖 view.c 内部实现细节。
|
||||
* - 函数指针在 MenuView_Init() 中完成绑定。
|
||||
* ------------------------------------------------------------------------- */
|
||||
struct menu_view_t
|
||||
{
|
||||
void (*update_selection_new_level)(struct menu_view_t *self, const tagPMenuItem currentLevel, const tagPMenuItem nextLevel);
|
||||
void (*update_selection_same_level)(struct menu_view_t *self, const tagPMenuItem oldItem, const tagPMenuItem newItem);
|
||||
void (*full_refresh)(struct menu_view_t *self, const tagMenuCtrl *menuCtrl, MenuMode mode);
|
||||
};
|
||||
|
||||
typedef menu_view_t MenuView;
|
||||
|
||||
|
||||
void MenuView_Init(MenuView *view);
|
||||
|
||||
#ifdef UNIT_TEST
|
||||
const PageRenderPort *MenuView_GetPortForTest(void);
|
||||
#endif
|
||||
|
||||
#endif
|
||||
125
src/Drv/pages/page.h
Normal file
125
src/Drv/pages/page.h
Normal file
@@ -0,0 +1,125 @@
|
||||
#ifndef PAGE_H
|
||||
#define PAGE_H
|
||||
|
||||
#include "types.h"
|
||||
/* -------------------------------------------------------------------------
|
||||
* 枚举名: page_id_t
|
||||
* 作用:
|
||||
* 定义系统内可导航页面的逻辑标识(Page ID)。
|
||||
*
|
||||
* 取值说明:
|
||||
* PAGE_ID_NONE - 无效页面 ID / 未初始化占位值
|
||||
* PAGE_ID_MENU - 菜单页 ID(当前主运行页)
|
||||
* PAGE_ID_APP_INFO - 预留页面 ID(当前版本可注册与否由上层决定)
|
||||
* PAGE_ID_MAX - 上界哨兵,不可作为有效页面 ID 使用
|
||||
*
|
||||
* 使用约束:
|
||||
* - PageManager_Register() 会校验 page_id,PAGE_ID_NONE 与 >= PAGE_ID_MAX
|
||||
* 均视为非法值。
|
||||
* ------------------------------------------------------------------------- */
|
||||
typedef enum
|
||||
{
|
||||
PAGE_ID_NONE = 0,
|
||||
PAGE_ID_MENU = 1,
|
||||
PAGE_ID_APP_INFO = 2, /* 预留ID:当前版本未注册运行 */
|
||||
PAGE_ID_MAX
|
||||
} page_id_t;
|
||||
|
||||
/* -------------------------------------------------------------------------
|
||||
* 枚举名: page_event_type_t
|
||||
* 作用:
|
||||
* 定义页面层可分发的事件类型。
|
||||
*
|
||||
* 当前约定:
|
||||
* PAGE_EVENT_KEY - 按键输入事件
|
||||
*
|
||||
* 扩展说明:
|
||||
* - 后续可按需扩展触摸、定时器、通信消息等事件类型。
|
||||
* ------------------------------------------------------------------------- */
|
||||
typedef enum
|
||||
{
|
||||
PAGE_EVENT_KEY = 1
|
||||
} page_event_type_t;
|
||||
|
||||
/* -------------------------------------------------------------------------
|
||||
* 结构体名: input_event_t
|
||||
* 作用:
|
||||
* 页面事件分发的数据载体,由 PageManager_DispatchEvent() 传入页面 on_event。
|
||||
*
|
||||
* 字段说明:
|
||||
* type - 事件类型,取值来自 page_event_type_t
|
||||
* keyVal - 按键值(当 type 为 PAGE_EVENT_KEY 时有效)
|
||||
*
|
||||
* 使用约束:
|
||||
* - 调用方应保证 type/keyVal 与事件来源一致;
|
||||
* - 页面 on_event 可基于该结构返回 EVENT_HANDLED / EVENT_UNHANDLED。
|
||||
* ------------------------------------------------------------------------- */
|
||||
typedef struct
|
||||
{
|
||||
uint8_t type;
|
||||
uint8_t keyVal;
|
||||
} input_event_t;
|
||||
|
||||
/* -------------------------------------------------------------------------
|
||||
* 枚举名: event_result_t
|
||||
* 作用:
|
||||
* 定义页面事件处理结果,用于控制事件链是否继续兜底处理。
|
||||
*
|
||||
* 取值说明:
|
||||
* EVENT_UNHANDLED - 页面未消费事件;允许交由管理器执行全局兜底逻辑
|
||||
* EVENT_HANDLED - 页面已消费事件;事件链终止
|
||||
* ------------------------------------------------------------------------- */
|
||||
typedef enum
|
||||
{
|
||||
EVENT_UNHANDLED = 0,
|
||||
EVENT_HANDLED = 1
|
||||
} event_result_t;
|
||||
|
||||
/* 前置声明:支持在回调签名中使用 page_t* */
|
||||
typedef struct page_t page_t;
|
||||
|
||||
/* -------------------------------------------------------------------------
|
||||
* 结构体名: page_t
|
||||
* 作用:
|
||||
* 页面抽象基元,描述“一个可被 PageManager 管理的页面实例”。
|
||||
*
|
||||
* 字段分组:
|
||||
* 1) 基础元信息
|
||||
* page_id - 页面逻辑标识
|
||||
* is_cached - 是否缓存页(1: Pop 后不销毁;0: Pop 后可销毁)
|
||||
* is_created - 是否已执行过 on_create(由管理器维护)
|
||||
*
|
||||
* 2) 生命周期与事件回调
|
||||
* on_create - 首次创建时调用
|
||||
* on_enter - 页面进入前台时调用
|
||||
* on_exit - 页面离开前台时调用
|
||||
* on_destroy - 页面销毁时调用(常用于非缓存页)
|
||||
* on_event - 输入事件处理回调
|
||||
* on_loop - 周期循环回调
|
||||
*
|
||||
* 3) 三层对象挂载指针(MVP)
|
||||
* presenter / view / model - 页面内部层对象地址(可为空,按页面实现决定)
|
||||
*
|
||||
* 生命周期约定(由 PageManager 驱动):
|
||||
* - Push 到新页面时:旧页 on_exit -> 新页(必要时 on_create)-> 新页 on_enter
|
||||
* - Pop 回退时:当前页 on_exit ->(非缓存页可 on_destroy)-> 新栈顶 on_enter
|
||||
* ------------------------------------------------------------------------- */
|
||||
struct page_t
|
||||
{
|
||||
page_id_t page_id;
|
||||
uint8_t is_cached;
|
||||
uint8_t is_created;
|
||||
|
||||
void (*on_create)(page_t *page);
|
||||
void (*on_enter)(page_t *page);
|
||||
void (*on_exit)(page_t *page);
|
||||
void (*on_destroy)(page_t *page);
|
||||
event_result_t (*on_event)(page_t *page, input_event_t *event);
|
||||
void (*on_loop)(page_t *page);
|
||||
|
||||
void *presenter;
|
||||
void *view;
|
||||
void *model;
|
||||
};
|
||||
|
||||
#endif
|
||||
448
src/Drv/pages/page_manager.c
Normal file
448
src/Drv/pages/page_manager.c
Normal file
@@ -0,0 +1,448 @@
|
||||
#include <stddef.h>
|
||||
#include <string.h>
|
||||
|
||||
#include "page_manager.h"
|
||||
#include "../key.h"
|
||||
|
||||
/* 默认全局页面管理器实例(便于主流程直接使用) */
|
||||
static page_manager_t s_pageManager;
|
||||
|
||||
/* -------------------------------------------------------------------------
|
||||
* 函数名: PageManager_GlobalEventHandle
|
||||
* 功能:
|
||||
* 页面管理器的全局事件兜底处理入口,用于处理未被页面消费的通用按键逻辑。
|
||||
*
|
||||
* 参数:
|
||||
* event - 输入事件指针
|
||||
*
|
||||
* 边界处理:
|
||||
* - 若 event 为 NULL,函数直接返回。
|
||||
* - 若事件类型不是 PAGE_EVENT_KEY,函数直接返回,不做处理。
|
||||
* - 仅当 keyVal 为 KEY_ESC 时触发返回上一页,其它按键忽略。
|
||||
*
|
||||
* 说明:
|
||||
* - 该函数通常由 PageManager_DispatchEvent 在页面返回
|
||||
* EVENT_UNHANDLED 时调用。
|
||||
* - 当前全局策略为“ESC = 返回”:内部通过 PageManager_Pop()
|
||||
* 执行页面栈回退。
|
||||
* - 设计目标是在不侵入页面私有逻辑的前提下,提供统一系统级快捷行为。
|
||||
*
|
||||
* 返回值:
|
||||
* - 无
|
||||
* ------------------------------------------------------------------------- */
|
||||
static void PageManager_GlobalEventHandle(input_event_t *event)
|
||||
{
|
||||
ASSERT((event == NULL) || (event->type != PAGE_EVENT_KEY));
|
||||
if ((event == NULL) || (event->type != PAGE_EVENT_KEY))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (event->keyVal == KEY_ESC)
|
||||
{
|
||||
(void)PageManager_Pop();
|
||||
}
|
||||
}
|
||||
|
||||
/* -------------------------------------------------------------------------
|
||||
* 函数名: PageManager_Init
|
||||
* 功能:
|
||||
* 初始化单例页面管理器 s_pageManager,使其进入“空栈、可注册”的初始状态。
|
||||
*
|
||||
* 参数:
|
||||
* 无
|
||||
*
|
||||
* 边界处理:
|
||||
* - 本函数无外部入参,不涉及空指针校验。
|
||||
* - 通过 memset 全量清零,确保历史运行残留状态(栈、注册表、计数)被彻底复位。
|
||||
*
|
||||
* 说明:
|
||||
* - stack_top 采用 -1 表示页面栈为空,这是后续 Push/GetTop/Depth 的统一判定基准。
|
||||
* - 该函数通常在系统启动阶段调用一次;若重复调用,将清空当前页面管理上下文。
|
||||
*
|
||||
* 返回值:
|
||||
* - 无
|
||||
* ------------------------------------------------------------------------- */
|
||||
void PageManager_Init(void)
|
||||
{
|
||||
memset(&s_pageManager, 0, sizeof(s_pageManager));
|
||||
s_pageManager.stack_top = -1;
|
||||
}
|
||||
|
||||
/* -------------------------------------------------------------------------
|
||||
* 函数名: PageManager_Register
|
||||
* 功能:
|
||||
* 将页面对象注册到页面管理器的注册表中,供后续按 page_id 查找与导航。
|
||||
*
|
||||
* 参数:
|
||||
* page - 待注册页面指针
|
||||
*
|
||||
* 边界处理:
|
||||
* - 若 page 为 NULL,返回 PAGE_MANAGER_ERR_NULL_PARAM,不执行注册。
|
||||
* - 若 page_id 非法(PAGE_ID_NONE 或超出 PAGE_ID_MAX),返回 PAGE_MANAGER_ERR_INVALID_ID。
|
||||
* - 若注册表已满(page_count >= MAX_PAGE_COUNT),返回 PAGE_MANAGER_ERR_REGISTRY_FULL。
|
||||
*
|
||||
* 说明:
|
||||
* - 若检测到相同 page_id 已存在,不新增条目,而是覆盖旧指针(热更新语义)。
|
||||
* - 新页面按 page_count 尾插,成功后 page_count 自增 1。
|
||||
*
|
||||
* 返回值:
|
||||
* - PAGE_MANAGER_OK : 注册成功(含覆盖更新成功)
|
||||
* - PAGE_MANAGER_ERR_NULL_PARAM : 参数无效
|
||||
* - PAGE_MANAGER_ERR_INVALID_ID : page_id 非法
|
||||
* - PAGE_MANAGER_ERR_REGISTRY_FULL : 注册表容量不足
|
||||
* ------------------------------------------------------------------------- */
|
||||
page_manager_result_t PageManager_Register(page_t *page)
|
||||
{
|
||||
ASSERT(page == NULL);
|
||||
if (page == NULL)
|
||||
{
|
||||
return PAGE_MANAGER_ERR_NULL_PARAM;
|
||||
}
|
||||
ASSERT((page->page_id == PAGE_ID_NONE) || (page->page_id >= PAGE_ID_MAX));
|
||||
if ((page->page_id == PAGE_ID_NONE) || (page->page_id >= PAGE_ID_MAX))
|
||||
{
|
||||
return PAGE_MANAGER_ERR_INVALID_ID;
|
||||
}
|
||||
|
||||
for (uint8_t i = 0; i < s_pageManager.page_count; i++)
|
||||
{
|
||||
/* 支持重复注册:同 page_id 视为更新页面指针 */
|
||||
if (s_pageManager.page_registry[i]->page_id == page->page_id)
|
||||
{
|
||||
s_pageManager.page_registry[i] = page;
|
||||
return PAGE_MANAGER_OK;
|
||||
}
|
||||
}
|
||||
|
||||
ASSERT(s_pageManager.page_count >= MAX_PAGE_COUNT);
|
||||
if (s_pageManager.page_count >= MAX_PAGE_COUNT)
|
||||
{
|
||||
return PAGE_MANAGER_ERR_REGISTRY_FULL;
|
||||
}
|
||||
|
||||
s_pageManager.page_registry[s_pageManager.page_count] = page;
|
||||
s_pageManager.page_count++;
|
||||
return PAGE_MANAGER_OK;
|
||||
}
|
||||
|
||||
/* -------------------------------------------------------------------------
|
||||
* 函数名: PageManager_Find
|
||||
* 功能:
|
||||
* 在管理器的页面注册表中按 page_id 线性查找,返回对应的 page_t 指针。
|
||||
*
|
||||
* 参数:
|
||||
* pageId - 待查找的页面标识
|
||||
*
|
||||
* 边界处理:
|
||||
* - 若注册表为空(page_count 为 0),循环不执行,返回 NULL。
|
||||
* - 若不存在匹配的 page_id,返回 NULL。
|
||||
*
|
||||
* 说明:
|
||||
* - 查找顺序与注册顺序一致;同 id 在 Register 中已保证唯一或覆盖更新。
|
||||
* - 常与 PageManager_Navigate 配合:先 Find 再 Push,上层也可直接 Find 获取页面对象。
|
||||
*
|
||||
* 返回值:
|
||||
* - 命中时返回注册表中的 page_t 指针
|
||||
* - 未命中返回 NULL
|
||||
* ------------------------------------------------------------------------- */
|
||||
page_t *PageManager_Find(page_id_t pageId)
|
||||
{
|
||||
for (uint8_t i = 0; i < s_pageManager.page_count; i++)
|
||||
{
|
||||
if (s_pageManager.page_registry[i]->page_id == pageId)
|
||||
{
|
||||
return s_pageManager.page_registry[i];
|
||||
}
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/* -------------------------------------------------------------------------
|
||||
* 函数名: PageManager_GetTop
|
||||
* 功能:
|
||||
* 返回当前页面栈栈顶元素,即当前“前台/可见”页面对应的 page_t 指针。
|
||||
*
|
||||
* 参数:
|
||||
* 无
|
||||
*
|
||||
* 边界处理:
|
||||
* - stack_top < 0 表示空栈,返回 NULL。
|
||||
* - 正常栈顶索引由 Push/Pop 维护,假定栈槽位与 stack_top 一致。
|
||||
*
|
||||
* 说明:
|
||||
* - DispatchEvent、Loop、Navigate 等逻辑均以栈顶页为“当前页”。
|
||||
* - 不修改栈状态,仅查询。
|
||||
*
|
||||
* 返回值:
|
||||
* - 非空栈时返回栈顶 page_t 指针
|
||||
* - 空栈时返回 NULL
|
||||
* ------------------------------------------------------------------------- */
|
||||
page_t *PageManager_GetTop(void)
|
||||
{
|
||||
if (s_pageManager.stack_top < 0)
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
return s_pageManager.page_stack[s_pageManager.stack_top];
|
||||
}
|
||||
|
||||
/* -------------------------------------------------------------------------
|
||||
* 函数名: PageManager_Push
|
||||
* 功能:
|
||||
* 将指定页面压入页面栈并切换为当前页:必要时触发旧页 on_exit、新页 on_create/on_enter。
|
||||
*
|
||||
* 参数:
|
||||
* newPage - 要入栈并成为栈顶的目标页面指针
|
||||
*
|
||||
* 边界处理:
|
||||
* - 若 newPage 为 NULL,返回 PAGE_MANAGER_ERR_NULL_PARAM。
|
||||
* - 若栈已满(stack_top >= MAX_PAGE_STACK_DEPTH - 1),返回 PAGE_MANAGER_ERR_STACK_FULL。
|
||||
* - 若当前栈顶与 newPage 的 page_id 相同,视为重复导航,直接返回 PAGE_MANAGER_OK,
|
||||
* 不再次调用 on_exit/on_enter(幂等)。
|
||||
*
|
||||
* 说明:
|
||||
* - 切换顺序:旧栈顶 on_exit(若存在)-> 新页入栈 ->
|
||||
* 若 is_created 为 0 则 on_create 并置 is_created,再 on_enter。
|
||||
* - 页面“创建一次”语义由 is_created 控制;非缓存页在 Pop 时可销毁并重置 is_created。
|
||||
*
|
||||
* 返回值:
|
||||
* - PAGE_MANAGER_OK : 成功(含幂等重复入同一逻辑页)
|
||||
* - PAGE_MANAGER_ERR_NULL_PARAM : 参数无效
|
||||
* - PAGE_MANAGER_ERR_STACK_FULL : 栈满
|
||||
* ------------------------------------------------------------------------- */
|
||||
page_manager_result_t PageManager_Push(page_t *newPage)
|
||||
{
|
||||
page_t *currentTop;
|
||||
ASSERT(newPage == NULL);
|
||||
if (newPage == NULL)
|
||||
{
|
||||
return PAGE_MANAGER_ERR_NULL_PARAM;
|
||||
}
|
||||
ASSERT(s_pageManager.stack_top >= (MAX_PAGE_STACK_DEPTH - 1));
|
||||
if (s_pageManager.stack_top >= (MAX_PAGE_STACK_DEPTH - 1))
|
||||
{
|
||||
return PAGE_MANAGER_ERR_STACK_FULL;
|
||||
}
|
||||
|
||||
currentTop = PageManager_GetTop();
|
||||
ASSERT((currentTop != NULL) && (currentTop->page_id == newPage->page_id));
|
||||
if ((currentTop != NULL) && (currentTop->page_id == newPage->page_id))
|
||||
{
|
||||
/* 重复导航到当前页面:按幂等处理 */
|
||||
return PAGE_MANAGER_OK;
|
||||
}
|
||||
ASSERT((currentTop != NULL) && (currentTop->on_exit != NULL));
|
||||
if ((currentTop != NULL) && (currentTop->on_exit != NULL))
|
||||
{
|
||||
currentTop->on_exit(currentTop);
|
||||
}
|
||||
|
||||
s_pageManager.stack_top++;
|
||||
s_pageManager.page_stack[s_pageManager.stack_top] = newPage;
|
||||
|
||||
if (!newPage->is_created)
|
||||
{
|
||||
/* 页面只创建一次(除非在 Pop 时被销毁并重置) */
|
||||
if (newPage->on_create != NULL)
|
||||
{
|
||||
newPage->on_create(newPage);
|
||||
}
|
||||
newPage->is_created = 1;
|
||||
}
|
||||
|
||||
if (newPage->on_enter != NULL)
|
||||
{
|
||||
newPage->on_enter(newPage);
|
||||
}
|
||||
return PAGE_MANAGER_OK;
|
||||
}
|
||||
|
||||
/* -------------------------------------------------------------------------
|
||||
* 函数名: PageManager_Pop
|
||||
* 功能:
|
||||
* 弹出当前栈顶页面并回到下一层:触发当前页 on_exit,非缓存页可 on_destroy,
|
||||
* 再对新栈顶调用 on_enter。
|
||||
*
|
||||
* 参数:
|
||||
* 无
|
||||
*
|
||||
* 边界处理:
|
||||
* - 若 stack_top <= 0(仅余底页或空栈),不允许继续弹出,
|
||||
* 返回 PAGE_MANAGER_ERR_STACK_BOTTOM。
|
||||
* - 当前页指针为 NULL 时跳过对应回调(防御性,正常不应出现)。
|
||||
*
|
||||
* 说明:
|
||||
* - 设计为至少保留栈底一页:不能无限 Pop 到空栈,故 stack_top <= 0 即失败。
|
||||
* - 非缓存页(is_cached == 0)在 Pop 后会 on_destroy 且 is_created 清零,下次 Push 会重新 on_create。
|
||||
* - 缓存页仅 on_exit,不销毁,便于保留状态。
|
||||
* - 全局 ESC 兜底通过本函数回退上一页。
|
||||
*
|
||||
* 返回值:
|
||||
* - PAGE_MANAGER_OK : 出栈成功,新栈顶已 on_enter(若实现)
|
||||
* - PAGE_MANAGER_ERR_STACK_BOTTOM : 无法弹出(已到底或空栈)
|
||||
* ------------------------------------------------------------------------- */
|
||||
page_manager_result_t PageManager_Pop(void)
|
||||
{
|
||||
page_t *currentPage;
|
||||
page_t *newTop;
|
||||
|
||||
ASSERT(s_pageManager.stack_top <= 0);
|
||||
if (s_pageManager.stack_top <= 0)
|
||||
{
|
||||
return PAGE_MANAGER_ERR_STACK_BOTTOM;
|
||||
}
|
||||
|
||||
currentPage = s_pageManager.page_stack[s_pageManager.stack_top];
|
||||
ASSERT((currentPage != NULL) && (currentPage->on_exit != NULL));
|
||||
if ((currentPage != NULL) && (currentPage->on_exit != NULL))
|
||||
{
|
||||
currentPage->on_exit(currentPage);
|
||||
}
|
||||
|
||||
if ((currentPage != NULL) && !currentPage->is_cached)
|
||||
{
|
||||
/* 非缓存页离开即销毁,下次进入会重新 on_create */
|
||||
if (currentPage->on_destroy != NULL)
|
||||
{
|
||||
currentPage->on_destroy(currentPage);
|
||||
}
|
||||
currentPage->is_created = 0;
|
||||
}
|
||||
|
||||
s_pageManager.page_stack[s_pageManager.stack_top] = NULL;
|
||||
s_pageManager.stack_top--;
|
||||
|
||||
newTop = s_pageManager.page_stack[s_pageManager.stack_top];
|
||||
ASSERT((newTop != NULL) && (newTop->on_enter != NULL));
|
||||
if ((newTop != NULL) && (newTop->on_enter != NULL))
|
||||
{
|
||||
newTop->on_enter(newTop);
|
||||
}
|
||||
return PAGE_MANAGER_OK;
|
||||
}
|
||||
|
||||
/* -------------------------------------------------------------------------
|
||||
* 函数名: PageManager_Navigate
|
||||
* 功能:
|
||||
* 根据目标 page_id 执行页面导航:先在注册表中查找页面,再将其压入页面栈。
|
||||
*
|
||||
* 参数:
|
||||
* pageId - 目标页面标识
|
||||
*
|
||||
* 边界处理:
|
||||
* - 若注册表中未找到对应 pageId,返回 PAGE_MANAGER_ERR_NOT_FOUND,
|
||||
* 不执行页面切换。
|
||||
* - 目标页找到后,Push 过程中的边界条件(如栈满、重复入栈等)
|
||||
* 由 PageManager_Push 统一处理并返回对应错误码。
|
||||
*
|
||||
* 说明:
|
||||
* - 本函数是“按 ID 导航”的统一入口,避免上层直接操作 page_t 指针。
|
||||
* - 导航成功后将触发标准生命周期链路:
|
||||
* 旧页 on_exit -> 新页(必要时 on_create)-> 新页 on_enter。
|
||||
*
|
||||
* 返回值:
|
||||
* - PAGE_MANAGER_OK : 导航成功
|
||||
* - PAGE_MANAGER_ERR_NOT_FOUND : 未找到目标页面
|
||||
* - 其他 : 透传 PageManager_Push 的返回码(如栈满等)
|
||||
* ------------------------------------------------------------------------- */
|
||||
page_manager_result_t PageManager_Navigate(page_id_t pageId)
|
||||
{
|
||||
page_t *target;
|
||||
target = PageManager_Find(pageId);
|
||||
ASSERT(target == NULL);
|
||||
if (target == NULL)
|
||||
{
|
||||
return PAGE_MANAGER_ERR_NOT_FOUND;
|
||||
}
|
||||
return PageManager_Push(target);
|
||||
}
|
||||
|
||||
/* -------------------------------------------------------------------------
|
||||
* 函数名: PageManager_DispatchEvent
|
||||
* 功能:
|
||||
* 将输入事件分发到当前栈顶页面,并在页面未处理时执行全局兜底处理。
|
||||
*
|
||||
* 参数:
|
||||
* event - 待分发的输入事件指针(键值、事件类型等)
|
||||
*
|
||||
* 边界处理:
|
||||
* - 若 event 为 NULL,返回 PAGE_MANAGER_ERR_NULL_PARAM,不进行任何处理。
|
||||
* - 若当前页面栈为空(GetTop 返回 NULL),返回 PAGE_MANAGER_ERR_NO_ACTIVE_PAGE。
|
||||
* - 若栈顶页面未实现 on_event 回调,则视为未处理并进入兜底判定流程。
|
||||
*
|
||||
* 说明:
|
||||
* - 分发优先级:页面私有事件处理 > 管理器全局兜底处理。
|
||||
* - top->on_event 返回 EVENT_UNHANDLED 时,才调用
|
||||
* PageManager_GlobalEventHandle(event)。
|
||||
* - 该机制允许页面覆盖默认按键行为,同时保留统一的系统级快捷逻辑
|
||||
* (例如 ESC 返回上页)。
|
||||
*
|
||||
* 返回值:
|
||||
* - PAGE_MANAGER_OK : 分发流程执行完成
|
||||
* - PAGE_MANAGER_ERR_NULL_PARAM : 事件参数为空
|
||||
* - PAGE_MANAGER_ERR_NO_ACTIVE_PAGE : 当前无活动页面
|
||||
* ------------------------------------------------------------------------- */
|
||||
page_manager_result_t PageManager_DispatchEvent(input_event_t *event)
|
||||
{
|
||||
page_t *top;
|
||||
event_result_t result;
|
||||
|
||||
ASSERT(event == NULL);
|
||||
if (event == NULL)
|
||||
{
|
||||
return PAGE_MANAGER_ERR_NULL_PARAM;
|
||||
}
|
||||
|
||||
top = PageManager_GetTop();
|
||||
ASSERT(top == NULL);
|
||||
if (top == NULL)
|
||||
{
|
||||
return PAGE_MANAGER_ERR_NO_ACTIVE_PAGE;
|
||||
}
|
||||
|
||||
result = EVENT_UNHANDLED;
|
||||
if (top->on_event != NULL)
|
||||
{
|
||||
result = top->on_event(top, event);
|
||||
}
|
||||
|
||||
/* 页面未处理时交给管理器处理全局快捷行为 */
|
||||
if (result == EVENT_UNHANDLED)
|
||||
{
|
||||
PageManager_GlobalEventHandle(event);
|
||||
}
|
||||
return PAGE_MANAGER_OK;
|
||||
}
|
||||
|
||||
/* -------------------------------------------------------------------------
|
||||
* 函数名: PageManager_Loop
|
||||
* 功能:
|
||||
* 驱动当前栈顶页面的循环回调(on_loop),用于执行周期性任务。
|
||||
*
|
||||
* 参数:
|
||||
* 无
|
||||
*
|
||||
* 边界处理:
|
||||
* - 若当前页面栈为空(GetTop 返回 NULL),函数直接返回。
|
||||
* - 若栈顶页面未实现 on_loop 回调,函数直接返回。
|
||||
* - 通过双重判空避免空指针调用导致崩溃。
|
||||
*
|
||||
* 说明:
|
||||
* - 本函数只处理“当前可见页面”(栈顶页),不遍历其它页面。
|
||||
* - 典型调用位置是主循环中的固定节拍点(例如每 N ms 调用一次)。
|
||||
* - 页面切换、事件分发不在本函数处理范围内,分别由 Navigate/Push/Pop、
|
||||
* DispatchEvent 负责。
|
||||
*
|
||||
* 返回值:
|
||||
* - 无
|
||||
* ------------------------------------------------------------------------- */
|
||||
void PageManager_Loop(void)
|
||||
{
|
||||
page_t *top = PageManager_GetTop();
|
||||
ASSERT((top == NULL) || (top->on_loop == NULL));
|
||||
if ((top != NULL) && (top->on_loop != NULL))
|
||||
{
|
||||
top->on_loop(top);
|
||||
}
|
||||
}
|
||||
77
src/Drv/pages/page_manager.h
Normal file
77
src/Drv/pages/page_manager.h
Normal file
@@ -0,0 +1,77 @@
|
||||
#ifndef PAGE_MANAGER_H
|
||||
#define PAGE_MANAGER_H
|
||||
|
||||
#include "page.h"
|
||||
|
||||
/* -------------------------------------------------------------------------
|
||||
* 结构体名: page_manager_t
|
||||
* 作用:
|
||||
* 页面管理器运行时上下文,维护“页面栈 + 页面注册表”两套核心状态。
|
||||
*
|
||||
* 设计说明:
|
||||
* - page_stack:
|
||||
* 运行时导航栈,表示当前页面跳转路径。
|
||||
* 栈顶元素是当前可见/可交互页面。
|
||||
* - stack_top:
|
||||
* 栈顶索引。约定 -1 表示空栈。
|
||||
* 当前栈深可由 (stack_top + 1) 推导。
|
||||
* - page_registry:
|
||||
* 可导航页面的注册表,按 page_id 查找目标页面时使用。
|
||||
* - page_count:
|
||||
* 当前注册表有效页面数量,范围 [0, MAX_PAGE_COUNT]。
|
||||
*
|
||||
* 约束关系:
|
||||
* - page_stack 最大深度由 MAX_PAGE_STACK_DEPTH 限制;
|
||||
* - page_registry 最大容量由 MAX_PAGE_COUNT 限制;
|
||||
* - 调用方需通过 PageManager_Init 初始化后再使用。
|
||||
* ------------------------------------------------------------------------- */
|
||||
#define MAX_PAGE_STACK_DEPTH 5
|
||||
#define MAX_PAGE_COUNT 16
|
||||
|
||||
/* -------------------------------------------------------------------------
|
||||
* 枚举名: page_manager_result_t
|
||||
* 作用:
|
||||
* 定义 PageManager 相关接口的统一返回码,避免使用无语义的裸整数。
|
||||
*
|
||||
* 取值说明:
|
||||
* PAGE_MANAGER_OK - 操作成功
|
||||
* PAGE_MANAGER_ERR_NULL_PARAM - 传入参数为空
|
||||
* PAGE_MANAGER_ERR_INVALID_ID - 页面 ID 非法
|
||||
* PAGE_MANAGER_ERR_NOT_FOUND - 目标页面未注册/未找到
|
||||
* PAGE_MANAGER_ERR_STACK_FULL - 页面栈已满,无法继续压栈
|
||||
* PAGE_MANAGER_ERR_STACK_BOTTOM - 页面栈已到栈底,无法继续弹栈
|
||||
* PAGE_MANAGER_ERR_REGISTRY_FULL - 页面注册表已满
|
||||
* PAGE_MANAGER_ERR_NO_ACTIVE_PAGE - 当前无活动页面可供分发/循环驱动
|
||||
* ------------------------------------------------------------------------- */
|
||||
typedef enum
|
||||
{
|
||||
PAGE_MANAGER_OK = 0,
|
||||
PAGE_MANAGER_ERR_NULL_PARAM = -1,
|
||||
PAGE_MANAGER_ERR_INVALID_ID = -2,
|
||||
PAGE_MANAGER_ERR_NOT_FOUND = -3,
|
||||
PAGE_MANAGER_ERR_STACK_FULL = -4,
|
||||
PAGE_MANAGER_ERR_STACK_BOTTOM = -5,
|
||||
PAGE_MANAGER_ERR_REGISTRY_FULL = -6,
|
||||
PAGE_MANAGER_ERR_NO_ACTIVE_PAGE = -7
|
||||
} page_manager_result_t;
|
||||
|
||||
typedef struct
|
||||
{
|
||||
page_t *page_stack[MAX_PAGE_STACK_DEPTH]; /* 页面导航栈 */
|
||||
int8_t stack_top; /* 栈顶索引(-1 表示空栈) */
|
||||
page_t *page_registry[MAX_PAGE_COUNT]; /* 页面注册表 */
|
||||
uint8_t page_count; /* 注册页面数量 */
|
||||
} page_manager_t;
|
||||
|
||||
void PageManager_Init(void);
|
||||
page_manager_result_t PageManager_Register(page_t *page);
|
||||
page_manager_result_t PageManager_Navigate(page_id_t pageId);
|
||||
page_manager_result_t PageManager_DispatchEvent(input_event_t *event);
|
||||
|
||||
page_t *PageManager_GetTop(void);
|
||||
page_manager_result_t PageManager_Push(page_t *newPage);
|
||||
page_manager_result_t PageManager_Pop(void);
|
||||
page_t *PageManager_Find(page_id_t pageId);
|
||||
void PageManager_Loop(void);
|
||||
|
||||
#endif
|
||||
22
src/main.c
22
src/main.c
@@ -30,10 +30,12 @@ static int getch(void)
|
||||
}
|
||||
#endif
|
||||
|
||||
#include "Drv/menu/app/menu.h"
|
||||
#include "Drv/pages/page_manager.h"
|
||||
#include "Drv/pages/menu/page.h"
|
||||
#include "TCP/tcp.h"
|
||||
#include "remoteDisplay.h"
|
||||
#include "Drv/key.h"
|
||||
#include "Drv/lcd/lcd.h"
|
||||
#include "thread_utils.h"
|
||||
|
||||
/* ----------------------------------------------------------------------------
|
||||
@@ -42,6 +44,7 @@ static int getch(void)
|
||||
int main(void)
|
||||
{
|
||||
uint8_t count = 0;
|
||||
input_event_t event;
|
||||
|
||||
#ifdef _WIN32
|
||||
/* Windows:将控制台代码页设为 UTF-8,避免中文乱码(如“监听”“退出”等) */
|
||||
@@ -61,9 +64,12 @@ int main(void)
|
||||
server_arg.p_server_sock = &server_sock;
|
||||
server_arg.p_quit = &server_quit;
|
||||
|
||||
printf("开始初始化菜单树...\n");
|
||||
printf("开始初始化页面管理器与菜单页面...\n");
|
||||
|
||||
MenuApp_Init(); /* 初始化菜单应用(Model + View + Presenter) */
|
||||
PageManager_Init();
|
||||
Lcd_Init(); /* 初始化屏幕显存:由入口统一完成 */
|
||||
(void)PageManager_Register(MenuPage_GetInstance());
|
||||
(void)PageManager_Navigate(PAGE_ID_MENU);
|
||||
Key_Init(); /* 初始化按键 */
|
||||
printf("PC 端 HMI 菜单模拟启动(TCP 服务在单独线程,端口 7003)。\n");
|
||||
/* 7003 为 RemoDispBus 默认端口,与 remo_disp_server.py 一致 */
|
||||
@@ -73,13 +79,19 @@ int main(void)
|
||||
}
|
||||
while(1)
|
||||
{
|
||||
MenuApp_PollInput();
|
||||
uint8_t keyVal = Key_Read();
|
||||
if (keyVal != KEY_NONE)
|
||||
{
|
||||
event.type = PAGE_EVENT_KEY;
|
||||
event.keyVal = keyVal;
|
||||
PageManager_DispatchEvent(&event);
|
||||
}
|
||||
Sleep(20);
|
||||
count++;
|
||||
if(count > 50)
|
||||
{
|
||||
count = 0;
|
||||
MenuApp_Render();
|
||||
PageManager_Loop();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -7,7 +7,6 @@ set(DTU_TEST_COMMON_SOURCES
|
||||
"${CMAKE_SOURCE_DIR}/src/Drv/lcd/lcd.c"
|
||||
"${CMAKE_SOURCE_DIR}/src/Drv/key.c"
|
||||
"${CMAKE_SOURCE_DIR}/src/Drv/menu/app/menu.c"
|
||||
"${CMAKE_SOURCE_DIR}/src/Drv/menu/model/display.c"
|
||||
"${CMAKE_SOURCE_DIR}/src/TCP/tcp.c"
|
||||
"${CMAKE_SOURCE_DIR}/src/thread_utils.c"
|
||||
"${CMAKE_SOURCE_DIR}/src/remoteDisplay.c"
|
||||
@@ -70,11 +69,10 @@ add_dtu_test(
|
||||
# ------------------------------------------------------------
|
||||
# P1:业务核心计算/状态流转测试
|
||||
# ------------------------------------------------------------
|
||||
add_dtu_test(
|
||||
test_p1_key
|
||||
test_p1_key.c
|
||||
"${CMAKE_SOURCE_DIR}/src/Drv/key.c"
|
||||
)
|
||||
|
||||
# ------------------------------------------------------------
|
||||
# P1:lcd基本测试
|
||||
# ------------------------------------------------------------
|
||||
add_dtu_test(
|
||||
test_p1_lcd_basic
|
||||
test_p1_lcd_basic.c
|
||||
@@ -83,40 +81,40 @@ add_dtu_test(
|
||||
"${CMAKE_SOURCE_DIR}/src/Drv/lcd/lcd_text.c"
|
||||
"${CMAKE_SOURCE_DIR}/src/Drv/lcd/ascii.c"
|
||||
)
|
||||
# ------------------------------------------------------------
|
||||
# P1:菜单管理器测试
|
||||
# ------------------------------------------------------------
|
||||
add_dtu_test(
|
||||
test_p1_page_manager
|
||||
test_p1_page_manager.c
|
||||
"${CMAKE_SOURCE_DIR}/src/Drv/pages/page_manager.c"
|
||||
)
|
||||
# ------------------------------------------------------------
|
||||
# P1:菜单测试
|
||||
# ------------------------------------------------------------
|
||||
add_dtu_test(
|
||||
test_p1_menu
|
||||
test_p1_menu.c
|
||||
"${CMAKE_SOURCE_DIR}/src/Drv/menu/model/menu_tree_builder.c"
|
||||
"${CMAKE_SOURCE_DIR}/src/Drv/menu/view/menu_layout.c"
|
||||
"${CMAKE_SOURCE_DIR}/src/Drv/lcd/lcd_text.c"
|
||||
"${CMAKE_SOURCE_DIR}/src/Drv/lcd/lcd.c"
|
||||
"${CMAKE_SOURCE_DIR}/src/Drv/lcd/ascii.c"
|
||||
)
|
||||
add_dtu_test(
|
||||
test_p1_menu_nav_legacy
|
||||
test_p1_menu_nav_legacy.c
|
||||
"${CMAKE_SOURCE_DIR}/src/Drv/menu/presenter/menu_navigator.c"
|
||||
)
|
||||
add_dtu_test(
|
||||
test_p1_menu_navigator
|
||||
test_p1_menu_navigator.c
|
||||
"${CMAKE_SOURCE_DIR}/src/Drv/menu/presenter/menu_navigator.c"
|
||||
)
|
||||
add_dtu_test(
|
||||
test_p1_menu_tree_builder
|
||||
test_p1_menu_tree_builder.c
|
||||
"${CMAKE_SOURCE_DIR}/src/Drv/menu/model/menu_tree_builder.c"
|
||||
)
|
||||
add_dtu_test(
|
||||
test_p1_menu_layout
|
||||
test_p1_menu_layout.c
|
||||
"${CMAKE_SOURCE_DIR}/src/Drv/menu/view/menu_layout.c"
|
||||
"${CMAKE_SOURCE_DIR}/src/Drv/menu/app/menu.c"
|
||||
"${CMAKE_SOURCE_DIR}/src/Drv/pages/menu/model.c"
|
||||
"${CMAKE_SOURCE_DIR}/src/Drv/pages/menu/view.c"
|
||||
"${CMAKE_SOURCE_DIR}/src/Drv/pages/menu/presenter.c"
|
||||
"${CMAKE_SOURCE_DIR}/src/Drv/pages/menu/page.c"
|
||||
"${CMAKE_SOURCE_DIR}/src/Drv/pages/global/renderer_lcd.c"
|
||||
"${CMAKE_SOURCE_DIR}/src/Drv/lcd/lcd_text.c"
|
||||
"${CMAKE_SOURCE_DIR}/src/Drv/lcd/lcd.c"
|
||||
"${CMAKE_SOURCE_DIR}/src/Drv/lcd/lcd_draw.c"
|
||||
"${CMAKE_SOURCE_DIR}/src/Drv/lcd/ascii.c"
|
||||
)
|
||||
|
||||
target_compile_definitions(test_p1_menu PRIVATE UNIT_TEST)
|
||||
# ------------------------------------------------------------
|
||||
# P1:key测试
|
||||
# ------------------------------------------------------------
|
||||
add_dtu_test(
|
||||
test_p1_key
|
||||
test_p1_key.c
|
||||
"${CMAKE_SOURCE_DIR}/src/Drv/key.c"
|
||||
)
|
||||
# ------------------------------------------------------------
|
||||
# P2:集成测试(网络回环等)
|
||||
# ------------------------------------------------------------
|
||||
@@ -126,21 +124,3 @@ add_dtu_test(
|
||||
"${CMAKE_SOURCE_DIR}/src/TCP/tcp.c"
|
||||
"${CMAKE_SOURCE_DIR}/src/thread_utils.c"
|
||||
)
|
||||
add_dtu_test(
|
||||
test_p2_menu_runtime_startup
|
||||
test_p2_menu_runtime_startup.c
|
||||
"${CMAKE_SOURCE_DIR}/src/Drv/menu/app/menu.c"
|
||||
"${CMAKE_SOURCE_DIR}/src/Drv/menu/model/display.c"
|
||||
"${CMAKE_SOURCE_DIR}/src/Drv/menu/model/menu_model.c"
|
||||
"${CMAKE_SOURCE_DIR}/src/Drv/menu/view/menu_view.c"
|
||||
"${CMAKE_SOURCE_DIR}/src/Drv/menu/presenter/menu_presenter.c"
|
||||
"${CMAKE_SOURCE_DIR}/src/Drv/menu/model/menu_tree_builder.c"
|
||||
"${CMAKE_SOURCE_DIR}/src/Drv/menu/view/menu_layout.c"
|
||||
"${CMAKE_SOURCE_DIR}/src/Drv/menu/presenter/menu_navigator.c"
|
||||
"${CMAKE_SOURCE_DIR}/src/Drv/menu/view/menu_renderer_lcd.c"
|
||||
"${CMAKE_SOURCE_DIR}/src/Drv/lcd/lcd.c"
|
||||
"${CMAKE_SOURCE_DIR}/src/Drv/lcd/lcd_draw.c"
|
||||
"${CMAKE_SOURCE_DIR}/src/Drv/lcd/lcd_text.c"
|
||||
"${CMAKE_SOURCE_DIR}/src/Drv/lcd/ascii.c"
|
||||
"${CMAKE_SOURCE_DIR}/src/Drv/key.c"
|
||||
)
|
||||
|
||||
@@ -2,46 +2,199 @@
|
||||
|
||||
#include "test_common.h"
|
||||
|
||||
#include "../src/Drv/menu/view/menu_layout.h"
|
||||
#include "../src/Drv/menu/model/menu_tree_builder.h"
|
||||
|
||||
static int noop_proc(void)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
#include "../src/Drv/pages/menu/view.h"
|
||||
#include "../src/Drv/pages/menu/model.h"
|
||||
#include "../src/Drv/pages/menu/page.h"
|
||||
|
||||
/* -------------------------------------------------------------------------
|
||||
* 函数名: main
|
||||
* 功能:
|
||||
* 覆盖菜单模块的白盒/黑盒关键能力,验证:
|
||||
* 1) Model 初始化与辅助函数
|
||||
* 2) 菜单树构建与显示名称装饰
|
||||
* 3) View 初始化后的端口与接口绑定
|
||||
* 4) MenuPage 生命周期入口是否完整绑定
|
||||
*
|
||||
* 参数:
|
||||
* 无
|
||||
*
|
||||
* 返回值:
|
||||
* - 0 : 测试通过
|
||||
* ------------------------------------------------------------------------- */
|
||||
int main(void)
|
||||
{
|
||||
tagMenuCtrl ctrl;
|
||||
tagMenuItem items[4];
|
||||
uint8_t menu_num[4] = {0};
|
||||
/* 用于 MenuView_CharLenCal / Position 相关白盒测试的临时数组 */
|
||||
tagPMenuItem first[4] = {0};
|
||||
tagPMenuItem index[4] = {0};
|
||||
uint8_t max_len;
|
||||
|
||||
const tagMenuModel model[4] = {
|
||||
{0, "Root", "", 0, 0, 0, (FUNCPTR)noop_proc},
|
||||
{1, "设置", "", 0, 0, 0, (FUNCPTR)noop_proc},
|
||||
{2, "子项", "", 0, 0, 0, (FUNCPTR)noop_proc},
|
||||
{1, "查看", "", 0, 0, 0, (FUNCPTR)noop_proc},
|
||||
/* 页面实例:用于验证 MenuPage_GetInstance 生命周期回调绑定 */
|
||||
page_t *menuPage;
|
||||
|
||||
/* 真实菜单模型对象:用于测试 MenuModel_Init 及其派生结果 */
|
||||
menu_model_t menuModel;
|
||||
|
||||
/* 视图对象:用于验证 MenuView_Init 的接口与渲染端口初始化 */
|
||||
MenuView view;
|
||||
|
||||
/* 自定义最小菜单树:用于单独测试 BuildTree / DecorateDisplayNames */
|
||||
tagMenuItem customItems[4];
|
||||
tagMenuItem customItems2[7];
|
||||
tagMenuModel customModelTab[4] =
|
||||
{
|
||||
{0, "Root", "", 0, 0x0000, 0, NULL},
|
||||
{1, "ChildA", "", 0, 0x0000, 0, NULL},
|
||||
{2, "Leaf", "", 0, 0x0000, 0, NULL},
|
||||
{1, "ChildB", "", 0, 0x0000, 0, NULL}
|
||||
};
|
||||
tagMenuModel customModelTab2[7] =
|
||||
{
|
||||
{0, "RootA", "", 0, 0x0000, 0, NULL},
|
||||
{1, "A1", "", 0, 0x0000, 0, NULL},
|
||||
{2, "A1a", "", 0, 0x0000, 0, NULL},
|
||||
{1, "A2", "", 0, 0x0000, 0, NULL},
|
||||
{0, "RootB", "", 0, 0x0000, 0, NULL},
|
||||
{1, "B1", "", 0, 0x0000, 0, NULL},
|
||||
{0, "RootC", "", 0, 0x0000, 0, NULL}
|
||||
};
|
||||
|
||||
memset(&ctrl, 0, sizeof(ctrl));
|
||||
memset(items, 0, sizeof(items));
|
||||
/* ---------------------------------------------------------------------
|
||||
* Case 1:
|
||||
* 测试 MenuModel_Init / MenuModel_CountTopLevel
|
||||
* - 初始化真实菜单模型
|
||||
* - 验证 0 级菜单数量统计结果有效
|
||||
* --------------------------------------------------------------------- */
|
||||
memset(&menuModel, 0, sizeof(menuModel));
|
||||
MenuModel_Init(&menuModel);
|
||||
|
||||
MenuTree_0LevelNumCal(&ctrl, model, 4);
|
||||
ASSERT_EQ_INT(1, ctrl.by0LevelNum);
|
||||
MenuTree_MainCreate(items, model, 4);
|
||||
/* ---------------------------------------------------------------------
|
||||
* Case 2:
|
||||
* 测试 MenuView_Utf8LenCal
|
||||
* - 验证英文与中文字符串长度计算逻辑
|
||||
* --------------------------------------------------------------------- */
|
||||
ASSERT_EQ_INT(3, MenuModel_Utf8LenCal((uint8_t *)"ABC"));
|
||||
ASSERT_EQ_INT(2, MenuModel_Utf8LenCal((uint8_t *)"你"));
|
||||
|
||||
ASSERT_EQ_INT(3, MenuLayout_Utf8LenCal((uint8_t *)"ABC"));
|
||||
ASSERT_EQ_INT(2, MenuLayout_Utf8LenCal((uint8_t *)"你"));
|
||||
|
||||
first[0] = &items[0];
|
||||
index[0] = &items[0];
|
||||
max_len = MenuLayout_CharLenCal(0, menu_num, first, index);
|
||||
/* ---------------------------------------------------------------------
|
||||
* Case 3:
|
||||
* 测试 MenuModel_IndexMenuItems / MenuModel_GetMenuMaxDisplayLen
|
||||
* - 使用真实菜单模型验证序号初始化、子节点数量统计与子菜单宽度统计
|
||||
* --------------------------------------------------------------------- */
|
||||
first[0] = &menuModel.menuItems[0];
|
||||
index[0] = &menuModel.menuItems[0];
|
||||
first[1] = menuModel.menuItems[0].links.lower;
|
||||
max_len = MenuModel_GetMenuMaxDisplayLen(first[1]);
|
||||
ASSERT_TRUE(max_len > 0);
|
||||
ASSERT_TRUE(menu_num[1] > 0);
|
||||
ASSERT_STREQ("设置", (const char *)items[1].byName);
|
||||
ASSERT_EQ_INT(0, menuModel.menuItems[0].rect.wPos);
|
||||
ASSERT_EQ_INT(0, first[1]->rect.wPos);
|
||||
ASSERT_TRUE(menuModel.menuItems[0].rect.wNum > 0);
|
||||
if (first[1]->links.lower == NULL)
|
||||
{
|
||||
ASSERT_EQ_INT(0, first[1]->rect.wNum);
|
||||
}
|
||||
else
|
||||
{
|
||||
ASSERT_TRUE(first[1]->rect.wNum > 0);
|
||||
}
|
||||
|
||||
/* ---------------------------------------------------------------------
|
||||
* Case 4:
|
||||
* 测试 MenuModel_DecorateDisplayNames 的真实初始化结果
|
||||
* - 验证存在子菜单的项名称非空,并被正确用于显示
|
||||
* --------------------------------------------------------------------- */
|
||||
/* 有子菜单的项会被装饰追加 '\x10'(右箭头标记) */
|
||||
ASSERT_TRUE(((const char *)menuModel.menuItems[1].menuDef.byName)[0] != '\0');
|
||||
|
||||
/* ---------------------------------------------------------------------
|
||||
* Case 5:
|
||||
* 测试 MenuModel_BuildTree
|
||||
* - 使用自定义 4 节点菜单表验证父子、同层、闭环链路关系
|
||||
* --------------------------------------------------------------------- */
|
||||
memset(customItems, 0, sizeof(customItems));
|
||||
|
||||
MenuModel_BuildTree(customItems, customModelTab, 4);
|
||||
MenuModel_IndexMenuItems(customItems, 4);
|
||||
ASSERT_TRUE(customItems[0].links.lower == &customItems[1]);
|
||||
ASSERT_TRUE(customItems[1].links.higher == &customItems[0]);
|
||||
ASSERT_TRUE(customItems[1].links.lower == &customItems[2]);
|
||||
ASSERT_TRUE(customItems[2].links.higher == &customItems[1]);
|
||||
ASSERT_TRUE(customItems[1].links.behind == &customItems[3]);
|
||||
ASSERT_TRUE(customItems[3].links.before == &customItems[1]);
|
||||
ASSERT_TRUE(customItems[3].links.higher == &customItems[0]);
|
||||
ASSERT_TRUE(customItems[3].links.behind == &customItems[1]); /* 同层闭环 */
|
||||
ASSERT_EQ_INT(0, customItems[0].rect.wPos);
|
||||
ASSERT_EQ_INT(2, customItems[0].rect.wNum);
|
||||
ASSERT_EQ_INT(0, customItems[1].rect.wPos);
|
||||
ASSERT_EQ_INT(1, customItems[1].rect.wNum);
|
||||
ASSERT_EQ_INT(1, customItems[3].rect.wPos);
|
||||
ASSERT_EQ_INT(0, customItems[3].rect.wNum);
|
||||
ASSERT_EQ_INT(0, customItems[2].rect.wPos);
|
||||
ASSERT_EQ_INT(0, customItems[2].rect.wNum);
|
||||
|
||||
/* ---------------------------------------------------------------------
|
||||
* Case 5.1:
|
||||
* 强化测试 MenuModel_IndexMenuItems
|
||||
* - 覆盖多顶层、多兄弟、跨层回退后的 wPos / wNum 统计
|
||||
* --------------------------------------------------------------------- */
|
||||
memset(customItems2, 0, sizeof(customItems2));
|
||||
MenuModel_BuildTree(customItems2, customModelTab2, 7);
|
||||
MenuModel_IndexMenuItems(customItems2, 7);
|
||||
ASSERT_EQ_INT(0, customItems2[0].rect.wPos);
|
||||
ASSERT_EQ_INT(2, customItems2[0].rect.wNum);
|
||||
ASSERT_EQ_INT(0, customItems2[1].rect.wPos);
|
||||
ASSERT_EQ_INT(1, customItems2[1].rect.wNum);
|
||||
ASSERT_EQ_INT(0, customItems2[2].rect.wPos);
|
||||
ASSERT_EQ_INT(0, customItems2[2].rect.wNum);
|
||||
ASSERT_EQ_INT(1, customItems2[3].rect.wPos);
|
||||
ASSERT_EQ_INT(0, customItems2[3].rect.wNum);
|
||||
ASSERT_EQ_INT(1, customItems2[4].rect.wPos);
|
||||
ASSERT_EQ_INT(1, customItems2[4].rect.wNum);
|
||||
ASSERT_EQ_INT(0, customItems2[5].rect.wPos);
|
||||
ASSERT_EQ_INT(0, customItems2[5].rect.wNum);
|
||||
ASSERT_EQ_INT(2, customItems2[6].rect.wPos);
|
||||
ASSERT_EQ_INT(0, customItems2[6].rect.wNum);
|
||||
|
||||
/* ---------------------------------------------------------------------
|
||||
* Case 6:
|
||||
* 测试 MenuModel_DecorateDisplayNames
|
||||
* - 验证有子节点的项追加 '\x10'
|
||||
* - 验证叶子节点名称不被追加装饰符
|
||||
* --------------------------------------------------------------------- */
|
||||
MenuModel_DecorateDisplayNames(customItems, 4);
|
||||
ASSERT_TRUE(customItems[0].menuDef.byName[4] == '\x10');
|
||||
ASSERT_TRUE(customItems[0].menuDef.byName[5] == '\0');
|
||||
ASSERT_TRUE(customItems[1].menuDef.byName[6] == '\x10');
|
||||
ASSERT_TRUE(customItems[1].menuDef.byName[7] == '\0');
|
||||
ASSERT_TRUE(customItems[2].menuDef.byName[4] == '\0'); /* 叶子节点不追加 */
|
||||
|
||||
/* ---------------------------------------------------------------------
|
||||
* Case 7:
|
||||
* 测试 MenuView_Init / MenuView_GetPortForTest
|
||||
* - 验证 View 初始化后渲染端口、布局参数、接口函数指针均可用
|
||||
* --------------------------------------------------------------------- */
|
||||
/* View 初始化后,渲染端口派生数据和对外接口必须可用 */
|
||||
memset(&view, 0, sizeof(view));
|
||||
ASSERT_TRUE(MenuView_GetPortForTest() == NULL);
|
||||
MenuView_Init(&view);
|
||||
ASSERT_TRUE(MenuView_GetPortForTest() != NULL);
|
||||
ASSERT_TRUE(view.update_selection_new_level != NULL);
|
||||
ASSERT_TRUE(view.update_selection_same_level != NULL);
|
||||
ASSERT_TRUE(view.full_refresh != NULL);
|
||||
|
||||
/* ---------------------------------------------------------------------
|
||||
* Case 8:
|
||||
* 测试 MenuPage_GetInstance
|
||||
* - 验证页面生命周期入口已经完整绑定
|
||||
* --------------------------------------------------------------------- */
|
||||
/* 页面生命周期入口必须完整绑定,避免注册后生命周期不执行 */
|
||||
menuPage = MenuPage_GetInstance();
|
||||
ASSERT_TRUE(menuPage != NULL);
|
||||
ASSERT_TRUE(menuPage->on_create != NULL);
|
||||
ASSERT_TRUE(menuPage->on_enter != NULL);
|
||||
ASSERT_TRUE(menuPage->on_exit != NULL);
|
||||
ASSERT_TRUE(menuPage->on_destroy != NULL);
|
||||
ASSERT_TRUE(menuPage->on_event != NULL);
|
||||
ASSERT_TRUE(menuPage->on_loop != NULL);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -1,47 +0,0 @@
|
||||
#include "test_common.h"
|
||||
|
||||
#include <string.h>
|
||||
|
||||
#include "../src/Drv/lcd/lcd.h"
|
||||
#include "../src/Drv/menu/app/menu.h"
|
||||
#include "../src/Drv/menu/view/menu_layout.h"
|
||||
|
||||
int main(void)
|
||||
{
|
||||
tagMenuItem root;
|
||||
tagMenuItem child;
|
||||
MenuLayoutConfig config = {
|
||||
LCD_SIZE_X,
|
||||
LCD_SIZE_Y,
|
||||
MENU_YMIN,
|
||||
MENU_YMAX_FROM_LCD(LCD_SIZE_Y),
|
||||
LINE_HEIGHT,
|
||||
MENU_WITDTH,
|
||||
MENU_XADD,
|
||||
MENU_YADD};
|
||||
|
||||
memset(&root, 0, sizeof(root));
|
||||
memset(&child, 0, sizeof(child));
|
||||
|
||||
memcpy(root.byName, "Root", 5);
|
||||
memcpy(child.byName, "Child", 6);
|
||||
root.byClass = 0;
|
||||
child.byClass = 1;
|
||||
|
||||
root.ptLower = &child;
|
||||
root.ptBehind = &root;
|
||||
root.ptBefore = &root;
|
||||
child.ptHigher = &root;
|
||||
child.ptBehind = &child;
|
||||
child.ptBefore = &child;
|
||||
|
||||
ASSERT_EQ_INT(3, MenuLayout_Utf8LenCal((uint8_t *)"ABC"));
|
||||
ASSERT_EQ_INT(2, MenuLayout_Utf8LenCal((uint8_t *)"你"));
|
||||
|
||||
MenuLayout_PositionCal(&root, 1, &config);
|
||||
ASSERT_TRUE(root.wEPosX > root.wSPosX);
|
||||
ASSERT_TRUE(root.wEPosY > root.wSPosY);
|
||||
ASSERT_TRUE(root.wEPosY <= LCD_SIZE_Y);
|
||||
|
||||
return 0;
|
||||
}
|
||||
@@ -1,93 +0,0 @@
|
||||
#include <string.h>
|
||||
|
||||
#include "test_common.h"
|
||||
|
||||
#include "../src/Drv/key.h"
|
||||
#include "../src/Drv/menu/presenter/menu_navigator.h"
|
||||
|
||||
static int g_exec_count = 0;
|
||||
|
||||
static int on_exec(void)
|
||||
{
|
||||
g_exec_count++;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void build_legacy_like_tree(MenuNavState *nav, tagMenuItem *root, tagMenuItem *m1, tagMenuItem *m2, tagMenuItem *m1_sub)
|
||||
{
|
||||
memset(nav, 0, sizeof(*nav));
|
||||
memset(root, 0, sizeof(*root));
|
||||
memset(m1, 0, sizeof(*m1));
|
||||
memset(m2, 0, sizeof(*m2));
|
||||
memset(m1_sub, 0, sizeof(*m1_sub));
|
||||
|
||||
root->byClass = 0;
|
||||
root->ptLower = m1;
|
||||
root->ptBefore = root;
|
||||
root->ptBehind = root;
|
||||
root->wPos = 1;
|
||||
|
||||
m1->byClass = 1;
|
||||
m1->wPos = 1;
|
||||
m1->ptHigher = root;
|
||||
m1->ptBefore = m2;
|
||||
m1->ptBehind = m2;
|
||||
m1->ptLower = m1_sub;
|
||||
m1->pfnWinProc = on_exec;
|
||||
|
||||
m2->byClass = 1;
|
||||
m2->wPos = 2;
|
||||
m2->ptHigher = root;
|
||||
m2->ptBefore = m1;
|
||||
m2->ptBehind = m1;
|
||||
m2->pfnWinProc = on_exec;
|
||||
|
||||
m1_sub->byClass = 2;
|
||||
m1_sub->wPos = 1;
|
||||
m1_sub->ptHigher = m1;
|
||||
m1_sub->ptBefore = m1_sub;
|
||||
m1_sub->ptBehind = m1_sub;
|
||||
m1_sub->pfnWinProc = on_exec;
|
||||
|
||||
nav->ptHead = root;
|
||||
nav->ptCurrent = m1;
|
||||
nav->ptCurBak = m1;
|
||||
nav->ptRoute[0] = root;
|
||||
nav->ptRoute[1] = m1;
|
||||
}
|
||||
|
||||
int main(void)
|
||||
{
|
||||
MenuNavState nav;
|
||||
tagMenuItem root;
|
||||
tagMenuItem m1;
|
||||
tagMenuItem m2;
|
||||
tagMenuItem m1_sub;
|
||||
MenuNavResult result;
|
||||
|
||||
build_legacy_like_tree(&nav, &root, &m1, &m2, &m1_sub);
|
||||
|
||||
result = MenuNavigator_ProcessKey(&nav, KEY_D);
|
||||
ASSERT_EQ_INT(1, result.needRefresh);
|
||||
ASSERT_TRUE(nav.ptCurrent == &m2);
|
||||
|
||||
result = MenuNavigator_ProcessKey(&nav, KEY_U);
|
||||
ASSERT_EQ_INT(1, result.needRefresh);
|
||||
ASSERT_TRUE(nav.ptCurrent == &m1);
|
||||
|
||||
result = MenuNavigator_ProcessKey(&nav, KEY_ENT);
|
||||
ASSERT_EQ_INT(1, result.needRefresh);
|
||||
ASSERT_TRUE(nav.ptCurrent == &m1_sub);
|
||||
|
||||
result = MenuNavigator_ProcessKey(&nav, KEY_ESC);
|
||||
ASSERT_EQ_INT(1, result.needRefresh);
|
||||
ASSERT_TRUE(nav.ptCurrent == &m1);
|
||||
|
||||
g_exec_count = 0;
|
||||
nav.ptCurrent = &m2;
|
||||
result = MenuNavigator_ProcessKey(&nav, KEY_ENT);
|
||||
ASSERT_EQ_INT(0, result.needRefresh);
|
||||
ASSERT_EQ_INT(1, g_exec_count);
|
||||
|
||||
return 0;
|
||||
}
|
||||
@@ -1,79 +0,0 @@
|
||||
#include "test_common.h"
|
||||
|
||||
#include <string.h>
|
||||
|
||||
#include "../src/Drv/key.h"
|
||||
#include "../src/Drv/menu/presenter/menu_navigator.h"
|
||||
|
||||
static int g_exec_count = 0;
|
||||
|
||||
static int on_exec(void)
|
||||
{
|
||||
g_exec_count++;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void build_two_level(MenuNavState *nav, tagMenuItem *root, tagMenuItem *child_a, tagMenuItem *child_b)
|
||||
{
|
||||
memset(nav, 0, sizeof(*nav));
|
||||
memset(root, 0, sizeof(*root));
|
||||
memset(child_a, 0, sizeof(*child_a));
|
||||
memset(child_b, 0, sizeof(*child_b));
|
||||
|
||||
root->byClass = 0;
|
||||
root->wPos = 1;
|
||||
root->ptLower = child_a;
|
||||
root->ptBefore = root;
|
||||
root->ptBehind = root;
|
||||
|
||||
child_a->byClass = 1;
|
||||
child_a->wPos = 1;
|
||||
child_a->ptHigher = root;
|
||||
child_a->ptBefore = child_b;
|
||||
child_a->ptBehind = child_b;
|
||||
child_a->pfnWinProc = on_exec;
|
||||
|
||||
child_b->byClass = 1;
|
||||
child_b->wPos = 2;
|
||||
child_b->ptHigher = root;
|
||||
child_b->ptBefore = child_a;
|
||||
child_b->ptBehind = child_a;
|
||||
child_b->pfnWinProc = on_exec;
|
||||
|
||||
nav->ptHead = root;
|
||||
nav->ptCurrent = child_a;
|
||||
nav->ptCurBak = child_a;
|
||||
nav->ptRoute[0] = root;
|
||||
nav->ptRoute[1] = child_a;
|
||||
}
|
||||
|
||||
int main(void)
|
||||
{
|
||||
MenuNavState nav;
|
||||
tagMenuItem root;
|
||||
tagMenuItem a;
|
||||
tagMenuItem b;
|
||||
MenuNavResult result;
|
||||
|
||||
build_two_level(&nav, &root, &a, &b);
|
||||
|
||||
result = MenuNavigator_ProcessKey(&nav, KEY_D);
|
||||
ASSERT_EQ_INT(1, result.needRefresh);
|
||||
ASSERT_TRUE(nav.ptCurrent == &b);
|
||||
|
||||
result = MenuNavigator_ProcessKey(&nav, KEY_U);
|
||||
ASSERT_EQ_INT(1, result.needRefresh);
|
||||
ASSERT_TRUE(nav.ptCurrent == &a);
|
||||
|
||||
g_exec_count = 0;
|
||||
result = MenuNavigator_ProcessKey(&nav, KEY_ENT);
|
||||
ASSERT_EQ_INT(0, result.needRefresh);
|
||||
ASSERT_EQ_INT(1, g_exec_count);
|
||||
|
||||
result = MenuNavigator_ProcessKey(&nav, KEY_ESC);
|
||||
ASSERT_EQ_INT(1, result.skipRenderThisRound);
|
||||
ASSERT_TRUE(nav.ptCurrent == root.ptLower);
|
||||
ASSERT_TRUE(nav.ptRoute[0] == &root);
|
||||
|
||||
return 0;
|
||||
}
|
||||
@@ -1,37 +0,0 @@
|
||||
#include "test_common.h"
|
||||
|
||||
#include <string.h>
|
||||
|
||||
#include "../src/Drv/menu/model/menu_tree_builder.h"
|
||||
|
||||
static int noop_proc(void)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
int main(void)
|
||||
{
|
||||
tagMenuCtrl ctrl;
|
||||
tagMenuItem items[4];
|
||||
const tagMenuModel model[4] = {
|
||||
{0, "Root", "", 0, 0, 0, (FUNCPTR)noop_proc},
|
||||
{1, "A", "", 0, 0, 0, (FUNCPTR)noop_proc},
|
||||
{1, "B", "", 0, 0, 0, (FUNCPTR)noop_proc},
|
||||
{2, "C", "", 0, 0, 0, (FUNCPTR)noop_proc},
|
||||
};
|
||||
|
||||
memset(&ctrl, 0, sizeof(ctrl));
|
||||
memset(items, 0, sizeof(items));
|
||||
|
||||
MenuTree_0LevelNumCal(&ctrl, model, 4);
|
||||
ASSERT_EQ_INT(1, ctrl.by0LevelNum);
|
||||
|
||||
MenuTree_MainCreate(items, model, 4);
|
||||
ASSERT_TRUE(items[0].ptLower == &items[1]);
|
||||
ASSERT_TRUE(items[1].ptBehind == &items[2]);
|
||||
ASSERT_TRUE(items[2].ptBefore == &items[1]);
|
||||
ASSERT_TRUE(items[2].ptLower == &items[3]);
|
||||
ASSERT_TRUE(items[3].ptHigher == &items[2]);
|
||||
|
||||
return 0;
|
||||
}
|
||||
292
tests/test_p1_page_manager.c
Normal file
292
tests/test_p1_page_manager.c
Normal file
@@ -0,0 +1,292 @@
|
||||
#include <string.h>
|
||||
|
||||
#include "test_common.h"
|
||||
|
||||
#include "../src/Drv/key.h"
|
||||
#include "../src/Drv/pages/page_manager.h"
|
||||
|
||||
/* -------------------------------------------------------------------------
|
||||
* 结构体名: test_page_ctx_t
|
||||
* 功能:
|
||||
* 测试页面私有上下文,用于统计各生命周期回调与事件处理的触发次数。
|
||||
*
|
||||
* 字段说明:
|
||||
* createCount - on_create 被调用次数
|
||||
* enterCount - on_enter 被调用次数
|
||||
* exitCount - on_exit 被调用次数
|
||||
* destroyCount - on_destroy 被调用次数
|
||||
* loopCount - on_loop 被调用次数
|
||||
* eventResult - on_event 预设返回值(用于控制是否触发全局兜底)
|
||||
* ------------------------------------------------------------------------- */
|
||||
typedef struct
|
||||
{
|
||||
int createCount;
|
||||
int enterCount;
|
||||
int exitCount;
|
||||
int destroyCount;
|
||||
int loopCount;
|
||||
event_result_t eventResult;
|
||||
} test_page_ctx_t;
|
||||
|
||||
static test_page_ctx_t g_ctx_a;
|
||||
static test_page_ctx_t g_ctx_b;
|
||||
|
||||
/* A 页生命周期/事件回调桩函数:将调用痕迹写入 page->model 对应的计数器 */
|
||||
static void on_create_a(page_t *page) { ((test_page_ctx_t *)page->model)->createCount++; }
|
||||
static void on_enter_a(page_t *page) { ((test_page_ctx_t *)page->model)->enterCount++; }
|
||||
static void on_exit_a(page_t *page) { ((test_page_ctx_t *)page->model)->exitCount++; }
|
||||
static void on_destroy_a(page_t *page) { ((test_page_ctx_t *)page->model)->destroyCount++; }
|
||||
static event_result_t on_event_a(page_t *page, input_event_t *event)
|
||||
{
|
||||
(void)event;
|
||||
return ((test_page_ctx_t *)page->model)->eventResult;
|
||||
}
|
||||
static void on_loop_a(page_t *page) { ((test_page_ctx_t *)page->model)->loopCount++; }
|
||||
|
||||
/* B 页生命周期/事件回调桩函数:用于验证多页切换与销毁链路 */
|
||||
static void on_create_b(page_t *page) { ((test_page_ctx_t *)page->model)->createCount++; }
|
||||
static void on_enter_b(page_t *page) { ((test_page_ctx_t *)page->model)->enterCount++; }
|
||||
static void on_exit_b(page_t *page) { ((test_page_ctx_t *)page->model)->exitCount++; }
|
||||
static void on_destroy_b(page_t *page) { ((test_page_ctx_t *)page->model)->destroyCount++; }
|
||||
static event_result_t on_event_b(page_t *page, input_event_t *event)
|
||||
{
|
||||
(void)event;
|
||||
return ((test_page_ctx_t *)page->model)->eventResult;
|
||||
}
|
||||
static void on_loop_b(page_t *page) { ((test_page_ctx_t *)page->model)->loopCount++; }
|
||||
|
||||
static void setup_page(page_t *page,
|
||||
page_id_t pageId,
|
||||
uint8_t isCached,
|
||||
test_page_ctx_t *ctx,
|
||||
void (*on_create)(page_t *),
|
||||
void (*on_enter)(page_t *),
|
||||
void (*on_exit)(page_t *),
|
||||
void (*on_destroy)(page_t *),
|
||||
event_result_t (*on_event)(page_t *, input_event_t *),
|
||||
void (*on_loop)(page_t *))
|
||||
{
|
||||
memset(page, 0, sizeof(*page));
|
||||
page->page_id = pageId;
|
||||
page->is_cached = isCached;
|
||||
page->on_create = on_create;
|
||||
page->on_enter = on_enter;
|
||||
page->on_exit = on_exit;
|
||||
page->on_destroy = on_destroy;
|
||||
page->on_event = on_event;
|
||||
page->on_loop = on_loop;
|
||||
page->model = ctx;
|
||||
}
|
||||
|
||||
/* -------------------------------------------------------------------------
|
||||
* 函数名: main
|
||||
* 功能:
|
||||
* 覆盖 PageManager 的核心行为链路,验证注册、导航、事件分发(含 ESC 兜底回退)、
|
||||
* 生命周期回调顺序以及循环驱动是否符合预期。
|
||||
*
|
||||
* 参数:
|
||||
* 无
|
||||
*
|
||||
* 边界处理:
|
||||
* - 通过 memset 将页面对象和上下文清零,避免脏数据影响断言。
|
||||
* - 使用 ASSERT_TRUE 先校验 GetTop 非空,再访问 page_id,防止空指针解引用。
|
||||
*
|
||||
* 说明:
|
||||
* - pageA 设为缓存页(is_cached = 1),用于验证回退后可继续进入而不销毁。
|
||||
* - pageB 设为非缓存页(is_cached = 0),用于验证 ESC 回退时触发 on_destroy。
|
||||
* - 将 pageB.on_event 设为 EVENT_UNHANDLED,确保 DispatchEvent 走全局兜底,
|
||||
* 从而触发 KEY_ESC -> PageManager_Pop() 的系统行为。
|
||||
* - 关键验证点:
|
||||
* 1) Navigate 到 A:A create/enter 各一次;
|
||||
* 2) Navigate 到 B:A exit 一次,B create/enter 各一次;
|
||||
* 3) ESC 事件:B exit + destroy,栈顶回到 A,A enter 再次触发;
|
||||
* 4) Loop:仅驱动当前栈顶 A 的 on_loop。
|
||||
*
|
||||
* 返回值:
|
||||
* - 0:测试通过
|
||||
* ------------------------------------------------------------------------- */
|
||||
int main(void)
|
||||
{
|
||||
page_t pageA;
|
||||
page_t pageB;
|
||||
page_t pageA2;
|
||||
page_t pageB2;
|
||||
page_t pageA3;
|
||||
input_event_t event;
|
||||
page_t invalidNonePage;
|
||||
page_t invalidMaxPage;
|
||||
|
||||
/* ===== Case 1: Init 后的默认行为与空栈边界 ===== */
|
||||
memset(&g_ctx_a, 0, sizeof(g_ctx_a));
|
||||
memset(&g_ctx_b, 0, sizeof(g_ctx_b));
|
||||
PageManager_Init();
|
||||
ASSERT_TRUE(PageManager_GetTop() == NULL);
|
||||
ASSERT_TRUE(PageManager_Find(PAGE_ID_MENU) == NULL);
|
||||
ASSERT_EQ_INT(PAGE_MANAGER_ERR_NOT_FOUND, PageManager_Navigate(PAGE_ID_MENU));
|
||||
ASSERT_EQ_INT(PAGE_MANAGER_ERR_STACK_BOTTOM, PageManager_Pop());
|
||||
ASSERT_EQ_INT(PAGE_MANAGER_ERR_NULL_PARAM, PageManager_Register(NULL));
|
||||
ASSERT_EQ_INT(PAGE_MANAGER_ERR_NULL_PARAM, PageManager_Push(NULL));
|
||||
ASSERT_EQ_INT(PAGE_MANAGER_ERR_NULL_PARAM, PageManager_DispatchEvent(NULL));
|
||||
PageManager_Loop();
|
||||
|
||||
/* ===== Case 2: Register 参数边界与重复注册覆盖 ===== */
|
||||
setup_page(&invalidNonePage,
|
||||
PAGE_ID_NONE,
|
||||
1,
|
||||
&g_ctx_a,
|
||||
on_create_a,
|
||||
on_enter_a,
|
||||
on_exit_a,
|
||||
on_destroy_a,
|
||||
on_event_a,
|
||||
on_loop_a);
|
||||
setup_page(&invalidMaxPage,
|
||||
PAGE_ID_MAX,
|
||||
1,
|
||||
&g_ctx_b,
|
||||
on_create_b,
|
||||
on_enter_b,
|
||||
on_exit_b,
|
||||
on_destroy_b,
|
||||
on_event_b,
|
||||
on_loop_b);
|
||||
ASSERT_EQ_INT(PAGE_MANAGER_ERR_INVALID_ID, PageManager_Register(&invalidNonePage));
|
||||
ASSERT_EQ_INT(PAGE_MANAGER_ERR_INVALID_ID, PageManager_Register(&invalidMaxPage));
|
||||
|
||||
setup_page(&pageA,
|
||||
PAGE_ID_MENU,
|
||||
1,
|
||||
&g_ctx_a,
|
||||
on_create_a,
|
||||
on_enter_a,
|
||||
on_exit_a,
|
||||
on_destroy_a,
|
||||
on_event_a,
|
||||
on_loop_a);
|
||||
setup_page(&pageB,
|
||||
PAGE_ID_APP_INFO,
|
||||
0,
|
||||
&g_ctx_b,
|
||||
on_create_b,
|
||||
on_enter_b,
|
||||
on_exit_b,
|
||||
on_destroy_b,
|
||||
on_event_b,
|
||||
on_loop_b);
|
||||
setup_page(&pageA2,
|
||||
PAGE_ID_MENU,
|
||||
1,
|
||||
&g_ctx_a,
|
||||
on_create_a,
|
||||
on_enter_a,
|
||||
on_exit_a,
|
||||
on_destroy_a,
|
||||
on_event_a,
|
||||
on_loop_a);
|
||||
setup_page(&pageB2,
|
||||
PAGE_ID_APP_INFO,
|
||||
0,
|
||||
&g_ctx_b,
|
||||
on_create_b,
|
||||
on_enter_b,
|
||||
on_exit_b,
|
||||
on_destroy_b,
|
||||
on_event_b,
|
||||
on_loop_b);
|
||||
setup_page(&pageA3,
|
||||
PAGE_ID_MENU,
|
||||
1,
|
||||
&g_ctx_a,
|
||||
on_create_a,
|
||||
on_enter_a,
|
||||
on_exit_a,
|
||||
on_destroy_a,
|
||||
on_event_a,
|
||||
on_loop_a);
|
||||
|
||||
ASSERT_EQ_INT(PAGE_MANAGER_OK, PageManager_Register(&pageA));
|
||||
ASSERT_EQ_INT(PAGE_MANAGER_OK, PageManager_Register(&pageB));
|
||||
ASSERT_TRUE(PageManager_Find(PAGE_ID_MENU) == &pageA);
|
||||
ASSERT_TRUE(PageManager_Find(PAGE_ID_APP_INFO) == &pageB);
|
||||
ASSERT_EQ_INT(PAGE_MANAGER_OK, PageManager_Register(&pageA2)); /* 重复 ID 覆盖 */
|
||||
ASSERT_TRUE(PageManager_Find(PAGE_ID_MENU) == &pageA2);
|
||||
|
||||
/* ===== Case 3: Navigate + 生命周期 + GetTop ===== */
|
||||
ASSERT_EQ_INT(PAGE_MANAGER_OK, PageManager_Navigate(PAGE_ID_MENU));
|
||||
ASSERT_EQ_INT(1, g_ctx_a.createCount);
|
||||
ASSERT_EQ_INT(1, g_ctx_a.enterCount);
|
||||
ASSERT_TRUE(PageManager_GetTop() != NULL);
|
||||
ASSERT_EQ_INT(PAGE_ID_MENU, PageManager_GetTop()->page_id);
|
||||
ASSERT_EQ_INT(PAGE_MANAGER_OK, PageManager_Navigate(PAGE_ID_MENU)); /* 幂等导航到当前页 */
|
||||
ASSERT_EQ_INT(1, g_ctx_a.enterCount); /* 不应重复 enter */
|
||||
|
||||
ASSERT_EQ_INT(PAGE_MANAGER_OK, PageManager_Navigate(PAGE_ID_APP_INFO));
|
||||
ASSERT_EQ_INT(1, g_ctx_a.exitCount);
|
||||
ASSERT_EQ_INT(1, g_ctx_b.createCount);
|
||||
ASSERT_EQ_INT(1, g_ctx_b.enterCount);
|
||||
ASSERT_TRUE(PageManager_GetTop() != NULL);
|
||||
ASSERT_EQ_INT(PAGE_ID_APP_INFO, PageManager_GetTop()->page_id);
|
||||
|
||||
/* ===== Case 4: DispatchEvent handled/unhandled 分支 ===== */
|
||||
g_ctx_b.eventResult = EVENT_HANDLED;
|
||||
event.type = PAGE_EVENT_KEY;
|
||||
event.keyVal = KEY_ESC;
|
||||
ASSERT_EQ_INT(PAGE_MANAGER_OK, PageManager_DispatchEvent(&event));
|
||||
ASSERT_TRUE(PageManager_GetTop() == &pageB); /* handled 时不走全局 Pop */
|
||||
ASSERT_EQ_INT(0, g_ctx_b.exitCount);
|
||||
ASSERT_EQ_INT(0, g_ctx_b.destroyCount);
|
||||
|
||||
g_ctx_b.eventResult = EVENT_UNHANDLED;
|
||||
event.type = PAGE_EVENT_KEY;
|
||||
event.keyVal = KEY_ESC;
|
||||
ASSERT_EQ_INT(PAGE_MANAGER_OK, PageManager_DispatchEvent(&event));
|
||||
ASSERT_TRUE(PageManager_GetTop() == &pageA2);
|
||||
ASSERT_EQ_INT(PAGE_ID_MENU, PageManager_GetTop()->page_id);
|
||||
ASSERT_EQ_INT(1, g_ctx_b.exitCount);
|
||||
ASSERT_EQ_INT(1, g_ctx_b.destroyCount);
|
||||
ASSERT_EQ_INT(2, g_ctx_a.enterCount);
|
||||
|
||||
/* 未处理但非键盘事件,不应触发全局行为 */
|
||||
event.type = 0;
|
||||
event.keyVal = KEY_ESC;
|
||||
ASSERT_EQ_INT(PAGE_MANAGER_OK, PageManager_DispatchEvent(&event));
|
||||
ASSERT_TRUE(PageManager_GetTop() == &pageA2);
|
||||
|
||||
/* ===== Case 5: Loop 仅驱动栈顶页 ===== */
|
||||
PageManager_Loop();
|
||||
ASSERT_EQ_INT(1, g_ctx_a.loopCount);
|
||||
ASSERT_EQ_INT(0, g_ctx_b.loopCount);
|
||||
|
||||
/* ===== Case 6: Push/Pop 边界(含栈满) ===== */
|
||||
ASSERT_EQ_INT(PAGE_MANAGER_OK, PageManager_Push(&pageB2));
|
||||
ASSERT_EQ_INT(2, g_ctx_b.createCount); /* 上次被 destroy,重新 push 应再 create */
|
||||
ASSERT_EQ_INT(2, g_ctx_b.enterCount);
|
||||
ASSERT_TRUE(PageManager_GetTop() == &pageB2);
|
||||
|
||||
ASSERT_EQ_INT(PAGE_MANAGER_OK, PageManager_Push(&pageA3));
|
||||
ASSERT_TRUE(PageManager_GetTop() == &pageA3);
|
||||
|
||||
/* 当前栈深推进到 4(A2 -> B2 -> A3 -> B) */
|
||||
ASSERT_EQ_INT(PAGE_MANAGER_OK, PageManager_Push(&pageB));
|
||||
ASSERT_TRUE(PageManager_GetTop() == &pageB);
|
||||
|
||||
/* 再 Push 一次达到栈深 5(上限) */
|
||||
ASSERT_EQ_INT(PAGE_MANAGER_OK, PageManager_Push(&pageA3));
|
||||
ASSERT_TRUE(PageManager_GetTop() == &pageA3);
|
||||
|
||||
/* 栈满后继续 Push 应失败 */
|
||||
ASSERT_EQ_INT(PAGE_MANAGER_ERR_STACK_FULL, PageManager_Push(&pageA2));
|
||||
|
||||
/* 连续 Pop 到底页,最后一次应失败 */
|
||||
ASSERT_EQ_INT(PAGE_MANAGER_OK, PageManager_Pop());
|
||||
ASSERT_EQ_INT(PAGE_MANAGER_OK, PageManager_Pop());
|
||||
ASSERT_EQ_INT(PAGE_MANAGER_OK, PageManager_Pop());
|
||||
ASSERT_EQ_INT(PAGE_MANAGER_OK, PageManager_Pop());
|
||||
ASSERT_EQ_INT(PAGE_MANAGER_ERR_STACK_BOTTOM, PageManager_Pop());
|
||||
ASSERT_TRUE(PageManager_GetTop() == &pageA2);
|
||||
|
||||
/* ===== Case 7: Navigate 未注册页 ===== */
|
||||
ASSERT_EQ_INT(PAGE_MANAGER_ERR_NOT_FOUND, PageManager_Navigate(PAGE_ID_NONE));
|
||||
|
||||
return 0;
|
||||
}
|
||||
@@ -1,39 +0,0 @@
|
||||
#include "test_common.h"
|
||||
|
||||
#include "../src/Drv/key.h"
|
||||
#include "../src/Drv/menu/app/menu.h"
|
||||
|
||||
int main(void)
|
||||
{
|
||||
int decorated_found = 0;
|
||||
uint16_t itemCount = 0;
|
||||
const tagMenuItem *menuItems;
|
||||
|
||||
MenuApp_Init();
|
||||
Key_Init();
|
||||
menuItems = MenuApp_GetMenuItems(&itemCount);
|
||||
|
||||
for (uint16_t i = 0; i < itemCount; i++)
|
||||
{
|
||||
if (menuItems[i].ptLower != NULL)
|
||||
{
|
||||
uint8_t len = 0;
|
||||
while ((len < 50) && (menuItems[i].byName[len] != '\0'))
|
||||
{
|
||||
len++;
|
||||
}
|
||||
ASSERT_TRUE(len > 0);
|
||||
ASSERT_EQ_INT('\x10', menuItems[i].byName[len - 1]);
|
||||
decorated_found = 1;
|
||||
break;
|
||||
}
|
||||
}
|
||||
ASSERT_TRUE(decorated_found == 1);
|
||||
|
||||
/* 首次路由应仅触发首帧绘制,不应崩溃 */
|
||||
MenuApp_PollInput();
|
||||
|
||||
/* 二次刷新路径也不应崩溃 */
|
||||
MenuApp_Render();
|
||||
return 0;
|
||||
}
|
||||
Reference in New Issue
Block a user