优化回退
This commit is contained in:
669
README.md
669
README.md
@ -1,214 +1,525 @@
|
||||
# 轻量级菜单框架(C语言)
|
||||
# 菜单组件设计文档
|
||||
|
||||
## 介绍
|
||||
## 1. 概述
|
||||
|
||||
### 多级菜单
|
||||
### 1.1 产品简介
|
||||
菜单组件是一个基于状态机的轻量级菜单系统,设计用于嵌入式设备的人机交互界面。它提供了灵活的菜单节点管理、事件驱动的导航机制、权限控制和状态持久化等功能,支持多语言和模块化扩展。
|
||||
|
||||
同级菜单以数组的方式体现,父菜单和子菜单的关联则使用链表实现。
|
||||
### 1.2 设计目标
|
||||
- **轻量级**:静态内存分配,无动态内存依赖,适合资源受限的嵌入式设备
|
||||
- **模块化**:清晰的分层设计,各功能模块可独立开关
|
||||
- **可扩展**:支持自定义状态转换、事件处理和功能扩展
|
||||
- **易使用**:简洁的API接口,便于集成到各种应用中
|
||||
- **高可靠**:完善的错误处理和状态管理
|
||||
|
||||
> 数组元素内容有:
|
||||
>
|
||||
> * 菜单选项描述方式可选字符串或文本ID(多国语言时,可以统一管理翻译文件,而文本ID可以适用于数组管理的翻译文件)
|
||||
> * 菜单选项进入回调函数:当前菜单选项进入时(从父菜单进入)需要执行一次的函数
|
||||
> * 菜单选项退出回调函数:当前菜单选项进入后退出时(退出至父菜单)需要执行一次的函数
|
||||
> * 菜单选项重加载回调函数:当前菜单选项每次加载时(从父菜单进入或子菜单退出)需要执行一次的函数
|
||||
> * 菜单选项周期调度回调函数:当前菜单选项的周期调度函数
|
||||
> * 菜单选项的扩展数据
|
||||
>
|
||||
> 链表内存可以选择采用动态内存分配或者数组实现
|
||||
### 1.3 主要特性
|
||||
- 基于状态机的菜单导航
|
||||
- 静态菜单节点池管理
|
||||
- 事件驱动的交互机制
|
||||
- 权限管理系统
|
||||
- 状态持久化
|
||||
- 多语言支持
|
||||
- 内存监控
|
||||
- 模块化设计,功能可配置
|
||||
|
||||
方便对不同菜单界面功能解耦
|
||||
## 2. 系统架构
|
||||
|
||||
> 大部分菜单采用的都是数组中包含了所有不同级别的菜单选项内容实现,无法做到很好的解耦方式;
|
||||
>
|
||||
> 该模块通过动态绑定子菜单和链表的方式可以达到较好的解耦状态
|
||||
|
||||
### 显示效果
|
||||
|
||||
该框架只负责菜单选项控制操作,不负责在图像界面显示效果,需要在对应的回调函数中实现菜单显示效果。
|
||||
|
||||
> 设置对应的效果显示函数,即可为不同的菜单设置不同的菜单显示效果,比如图标式、列表式或右侧弹窗式等。
|
||||
>
|
||||
> 可以在不同显示平台体现,比如LCD、OLED或终端界面等。
|
||||
|
||||
### 多语种
|
||||
|
||||
没有直接的语种设置,而是通过联合体提供选择多种选择。
|
||||
|
||||
> 文本ID: 适用于多语种采用数组定义的翻译内容
|
||||
> 字符串: 适用于单语种或者多语种,采用映射的方式实现的翻译内容
|
||||
|
||||
### 可扩展
|
||||
|
||||
每级菜单选项都可以设置自定义数据,用来实现更多的菜单操作或者显示效果等。
|
||||
|
||||
> 不同级别的菜单可以设置自定义数据(比如菜单选项隐藏/图标数据等)
|
||||
|
||||
### 可配置
|
||||
|
||||
| 配置选项 | 描述 |
|
||||
| --------------------------- | ------------------------------------------------------------- |
|
||||
| `_COT_MENU_USE_MALLOC_` | 定义则采用 malloc/free 的方式实现多级菜单, 否则通过数组的形式 |
|
||||
| `_COT_MENU_USE_SHORTCUT_` | 定义则启用快捷菜单选项进入功能 |
|
||||
| `COT_MENU_MAX_DEPTH` | 多级菜单深度 |
|
||||
| `COT_MENU_MAX_NUM` | 菜单支持的最大选项数目 |
|
||||
|
||||
### 功能多样化
|
||||
|
||||
* [X] 支持快速进入指定菜单界面。
|
||||
|
||||
> - 可以通过相对选项索引或者绝对选项索引路径实现
|
||||
>
|
||||
* [X] 可以实现有限界面内显示少量的菜单选项内容。
|
||||
|
||||
> - 有现成的函数可用,无需担心使用不同尺寸重新实现菜单选项部分可见
|
||||
>
|
||||
|
||||
## 软件设计
|
||||
|
||||
略
|
||||
|
||||
## 使用说明
|
||||
|
||||
### 菜单初始化和使用
|
||||
|
||||
```c
|
||||
|
||||
// 定义菜单信息,函数由主菜单模块定义并提供
|
||||
static cotMainMenuCfg_t sg_tMainMenu = {"主菜单", Hmi_EnterMainHmi, NULL, NULL, NULL};
|
||||
|
||||
int main(void)
|
||||
{
|
||||
cotMenu_Init(&sg_tMainMenu);
|
||||
|
||||
while (1)
|
||||
{
|
||||
...
|
||||
|
||||
if (timeFlag)
|
||||
{
|
||||
timeFlag = 0;
|
||||
cotMenu_Task(); // 周期调度
|
||||
}
|
||||
}
|
||||
}
|
||||
### 2.1 分层设计
|
||||
菜单组件采用分层架构设计,从下到上分为:
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────────────┐
|
||||
│ 应用层 (Application) │
|
||||
├─────────────────────────────────────────────────────────────────┤
|
||||
│ API 层 (menu_api) │
|
||||
├─────────────────────────────────────────────────────────────────┤
|
||||
│ 核心层 (Core) │ 权限管理 │ 持久化 │ 多语言 │ 参数管理 │
|
||||
│ ┌─────────────┐ │ ┌─────────┐ │ ┌───────┐ │ ┌───────┐ │ ┌───────┐ │
|
||||
│ │ menu_core │ │ │ menu_per│ │ │ menu_per│ │ │ menu_lang │ │ │ menu_param│ │
|
||||
│ │ menu_event │ │ │ mission │ │ │ sistence│ │ └───────┘ │ │ └───────┘ │
|
||||
│ │ menu_stack │ │ └─────────┘ │ └───────┘ │ │ │
|
||||
│ │ menu_hash │ │ │ │ │
|
||||
│ └─────────────┘ │ │ │ │
|
||||
├─────────────────────────────────────────────────────────────────┤
|
||||
│ 端口层 (menu_port) │
|
||||
├─────────────────────────────────────────────────────────────────┤
|
||||
│ 硬件抽象层 (HAL) │
|
||||
└─────────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
### 主菜单定义和绑定
|
||||
### 2.2 模块关系
|
||||
|
||||
定义一个主菜单选项内容、主菜单显示效果函数和主菜单进入函数等
|
||||
各模块之间的依赖关系如下:
|
||||
|
||||
- **API层**:对外提供统一的菜单操作接口,依赖核心层和各功能模块
|
||||
- **核心层**:包含菜单的核心逻辑,如节点管理、事件处理、状态机等
|
||||
- **功能模块**:基于核心层扩展,提供特定功能,如权限管理、持久化等
|
||||
- **端口层**:提供平台相关的接口实现,如调试打印、内存操作等
|
||||
|
||||
## 3. 核心组件设计
|
||||
|
||||
### 3.1 菜单核心 (menu_core)
|
||||
|
||||
#### 3.1.1 功能描述
|
||||
菜单核心是整个组件的心脏,负责:
|
||||
- 菜单节点的注册、注销和管理
|
||||
- 状态机的运行和状态转换
|
||||
- 事件的处理和分发
|
||||
- 导航路径的管理
|
||||
|
||||
#### 3.1.2 核心流程
|
||||
|
||||
```mermaid
|
||||
stateDiagram-v2
|
||||
[*] --> INIT: 菜单初始化
|
||||
INIT --> NORMAL: 菜单进入
|
||||
NORMAL --> PARAM_EDIT: 进入参数编辑
|
||||
PARAM_EDIT --> NORMAL: 保存参数
|
||||
PARAM_EDIT --> CONFIRM: 请求确认
|
||||
CONFIRM --> NORMAL: 确认操作
|
||||
CONFIRM --> PARAM_EDIT: 取消操作
|
||||
NORMAL --> ERROR: 发生错误
|
||||
ERROR --> NORMAL: 恢复正常
|
||||
NORMAL --> [*]: 菜单退出
|
||||
```
|
||||
|
||||
### 3.2 事件管理 (menu_event)
|
||||
|
||||
#### 3.2.1 功能描述
|
||||
事件管理模块负责:
|
||||
- 事件的发布和订阅
|
||||
- 事件队列的管理
|
||||
- 事件优先级的处理
|
||||
|
||||
#### 3.2.2 事件处理流程
|
||||
|
||||
```mermaid
|
||||
flowchart TD
|
||||
A[事件发布] --> B{事件队列满?}
|
||||
B -->|是| C[返回错误]
|
||||
B -->|否| D[入队事件]
|
||||
D --> E[唤醒事件处理]
|
||||
E --> F{有事件?}
|
||||
F -->|是| G[出队最高优先级事件]
|
||||
G --> H[处理事件]
|
||||
H --> I{状态转换?}
|
||||
I -->|是| J[执行状态转换]
|
||||
I -->|否| K[执行事件回调]
|
||||
J --> F
|
||||
K --> F
|
||||
F -->|否| L[等待下一个事件]
|
||||
```
|
||||
|
||||
### 3.3 节点管理 (menu_core + menu_hash)
|
||||
|
||||
#### 3.3.1 功能描述
|
||||
节点管理模块负责:
|
||||
- 菜单节点的注册和注销
|
||||
- 节点的查找和遍历
|
||||
- 节点关系的维护(父子、兄弟关系)
|
||||
- 节点的哈希索引
|
||||
|
||||
#### 3.3.2 节点数据结构
|
||||
|
||||
菜单节点采用静态数组管理,每个节点包含:
|
||||
- 节点ID、父节点ID
|
||||
- 节点名称和回调函数
|
||||
- 子节点和兄弟节点的引用
|
||||
- 节点状态标志(选中、可见、启用等)
|
||||
- 权限级别(可选)
|
||||
- 参数ID(可选)
|
||||
|
||||
### 3.4 导航管理 (menu_stack + menu_core)
|
||||
|
||||
#### 3.4.1 功能描述
|
||||
导航管理模块负责:
|
||||
- 菜单的上下导航
|
||||
- 菜单的进入和退出
|
||||
- 导航栈的管理
|
||||
- 导航路径的记录
|
||||
|
||||
#### 3.4.2 导航流程
|
||||
|
||||
```mermaid
|
||||
flowchart TD
|
||||
A[当前节点] --> B{按下上键?}
|
||||
B -->|是| C[切换到前一个兄弟节点]
|
||||
B -->|否| D{按下下键?}
|
||||
D -->|是| E[切换到后一个兄弟节点]
|
||||
D -->|否| F{按下确认键?}
|
||||
F -->|是| G[进入子节点/执行操作]
|
||||
F -->|否| H{按下退出键?}
|
||||
H -->|是| I[返回父节点]
|
||||
H -->|否| J[处理其他事件]
|
||||
C --> K[更新当前节点]
|
||||
E --> K
|
||||
G --> K
|
||||
I --> K
|
||||
J --> K
|
||||
K --> A
|
||||
```
|
||||
|
||||
## 4. 数据结构设计
|
||||
|
||||
### 4.1 核心数据结构
|
||||
|
||||
#### 4.1.1 菜单节点 (MenuNode)
|
||||
```c
|
||||
typedef struct MenuNode {
|
||||
MenuNodeId id; ///< 节点ID(唯一)
|
||||
MenuNodeId parent_id; ///< 父节点ID(根节点为0)
|
||||
const char* name; ///< 菜单名称(或多语言索引)
|
||||
MenuCallback enter_cb; ///< 进入回调
|
||||
MenuCallback exit_cb; ///< 退出回调
|
||||
MenuNodeId first_child_id; ///< 第一个子节点ID
|
||||
MenuNodeId next_sibling_id; ///< 下一个兄弟节点ID
|
||||
MenuNodeId prev_sibling_id; ///< 上一个兄弟节点ID
|
||||
MenuNodeId hash_next_id; ///< 哈希表下一个节点ID
|
||||
struct {
|
||||
bool is_registered : 1; ///< 是否已注册
|
||||
bool is_selected : 1; ///< 是否被选中
|
||||
bool is_visible : 1; ///< 是否可见
|
||||
bool is_enabled : 1; ///< 是否启用
|
||||
unsigned int reserved : 4; ///< 保留位
|
||||
} flags;
|
||||
MenuPermissionLevel permission_level; ///< 权限级别
|
||||
uint16_t param_id; ///< 绑定的参数ID
|
||||
} MenuNode;
|
||||
```
|
||||
|
||||
// 扩展数据为图标文件名字,每个菜单选项描述为文本ID
|
||||
cotMenuList_t sg_MainMenuTable[] =
|
||||
{
|
||||
COT_MENU_ITEM_BIND(TEXT_MUSIC, Hmi_MusicEnter, Hmi_MusicExit, Hmi_MusicLoad, Hmi_MusicTask, (MenuImage_t *)&sgc_MusicImage),
|
||||
|
||||
COT_MENU_ITEM_BIND(TEXT_VIDEO, NULL, Hmi_VideoExit, Hmi_VideoLoad, Hmi_VideoTask, (MenuImage_t *)&sgc_VideoImage),
|
||||
|
||||
COT_MENU_ITEM_BIND(TEXT_CAMERA, Hmi_CameraEnter, Hmi_CameraExit, Hmi_CameraLoad, Hmi_CameraTask, (MenuImage_t *)&sgc_CameraImage),
|
||||
|
||||
COT_MENU_ITEM_BIND(TEXT_SETTING, Hmi_SetEnter, Hmi_SetExit, Hmi_SetLoad, Hmi_SetTask, (MenuImage_t *)&sgc_SettingImage),
|
||||
#### 4.1.2 菜单核心上下文 (MenuCoreCtx)
|
||||
```c
|
||||
typedef struct MenuCoreCtx {
|
||||
MenuNode nodes[MENU_CONFIG_MAX_NODES]; ///< 静态菜单节点池
|
||||
MenuNodeId hash_table[MENU_CONFIG_HASH_TABLE_SIZE]; ///< 哈希表
|
||||
MenuStack stack; ///< 菜单导航栈
|
||||
MenuNavPath nav_path; ///< 菜单导航路径
|
||||
MenuEventQueue event_queue; ///< 事件队列
|
||||
MenuNodeId current_node_id; ///< 当前选中的节点ID
|
||||
MenuState current_state; ///< 当前菜单状态
|
||||
uint32_t last_refresh_tick; ///< 上次刷新时间
|
||||
bool is_initialized; ///< 是否已初始化
|
||||
MenuErrCode error_code; ///< 当前错误码
|
||||
const char* error_msg; ///< 当前错误信息
|
||||
uint16_t node_count; ///< 已注册节点数量
|
||||
uint16_t free_node_count; ///< 空闲节点数量
|
||||
// 功能模块扩展字段...
|
||||
} MenuCoreCtx;
|
||||
```
|
||||
|
||||
### 4.2 状态和事件定义
|
||||
|
||||
| 状态名称 | 状态值 | 描述 |
|
||||
|---------|-------|------|
|
||||
| MENU_STATE_INIT | 0 | 初始化状态 |
|
||||
| MENU_STATE_NORMAL | 1 | 正常导航状态 |
|
||||
| MENU_STATE_PARAM_EDIT | 2 | 参数编辑状态 |
|
||||
| MENU_STATE_CONFIRM | 3 | 确认状态 |
|
||||
| MENU_STATE_ERROR | 4 | 错误状态 |
|
||||
|
||||
| 事件名称 | 事件值 | 描述 |
|
||||
|---------|-------|------|
|
||||
| MENU_EVENT_NONE | 0 | 无事件 |
|
||||
| MENU_EVENT_KEY_UP | 1 | 上键事件 |
|
||||
| MENU_EVENT_KEY_DOWN | 2 | 下键事件 |
|
||||
| MENU_EVENT_KEY_ENTER | 3 | 确认键事件 |
|
||||
| MENU_EVENT_KEY_ESC | 4 | 退出键事件 |
|
||||
| MENU_EVENT_TIMEOUT | 5 | 超时事件 |
|
||||
| MENU_EVENT_CUSTOM_BASE | 10 | 自定义事件基值 |
|
||||
|
||||
## 5. 接口设计
|
||||
|
||||
### 5.1 API 概述
|
||||
|
||||
菜单组件提供了简洁的API接口,主要分为以下几类:
|
||||
|
||||
| API类别 | 主要功能 | 文件位置 |
|
||||
|---------|---------|---------|
|
||||
| 初始化与反初始化 | 组件的初始化和反初始化 | menu_api.h |
|
||||
| 节点管理 | 节点的注册、注销和更新 | menu_api.h |
|
||||
| 事件处理 | 事件的发布和处理 | menu_api.h |
|
||||
| 导航操作 | 菜单的上下导航、进入退出 | menu_api.h |
|
||||
| 状态查询 | 获取当前状态、节点和路径 | menu_api.h |
|
||||
| 权限管理 | 角色注册、权限检查 | menu_api.h |
|
||||
| 持久化 | 状态的保存和恢复 | menu_api.h |
|
||||
|
||||
### 5.2 核心API示例
|
||||
|
||||
#### 5.2.1 初始化菜单
|
||||
```c
|
||||
MenuErrCode ret = menu_init();
|
||||
if (ret != MENU_ERR_OK) {
|
||||
// 处理初始化错误
|
||||
}
|
||||
```
|
||||
|
||||
#### 5.2.2 注册菜单节点
|
||||
```c
|
||||
// 注册单个节点
|
||||
MenuNodeId node_id = menu_register_node(
|
||||
0, // 自动分配节点ID
|
||||
parent_node_id, // 父节点ID
|
||||
"Menu Name", // 节点名称
|
||||
enter_callback, // 进入回调
|
||||
exit_callback // 退出回调
|
||||
);
|
||||
|
||||
// 批量注册节点
|
||||
const struct MenuNode nodes[] = {
|
||||
{1, 0, "Main Menu", main_enter, main_exit},
|
||||
{2, 1, "Sub Menu 1", sub1_enter, sub1_exit},
|
||||
{3, 1, "Sub Menu 2", sub2_enter, sub2_exit},
|
||||
};
|
||||
|
||||
/* 主菜单显示效果 */
|
||||
static void ShowMainMenu(cotMenuShow_t *ptShowInfo)
|
||||
{
|
||||
char *pszSelectDesc = get_text(ptShowInfo->pszItemsDesc[ptShowInfo->selectItem].textId); // 根据文本ID找到对应的字符串(多语言)
|
||||
oledsize_t idx = (128 - 6 * strlen(pszSelectDesc)) / 2;
|
||||
|
||||
cotOled_DrawGraphic(40, 0, (const char *)ptShowInfo->pItemsListExtendData[ptShowInfo->selectItem], 1);
|
||||
|
||||
cotOled_SetText(0, 50, " ", 0, FONT_12X12);
|
||||
cotOled_SetText(idx, 50, pszSelectDesc, 0, FONT_12X12);
|
||||
}
|
||||
|
||||
void Hmi_EnterMainHmi(const cotMenuItemInfo_t *pItemInfo)
|
||||
{
|
||||
cotMenu_Bind(sg_MainMenuTable, COT_GET_MENU_NUM(sg_MainMenuTable), ShowMainMenu);
|
||||
}
|
||||
|
||||
menu_register_nodes(nodes, sizeof(nodes)/sizeof(nodes[0]));
|
||||
```
|
||||
|
||||
### 子菜单定义和绑定
|
||||
#### 5.2.3 事件处理
|
||||
```c
|
||||
// 发布事件
|
||||
menu_post_event(MENU_EVENT_KEY_UP, 0);
|
||||
|
||||
如果菜单选项有子菜单,则该菜单选项调用 `cotMenu_Enter`,进入回调函数**不能为NULL**,且该回调函数需调用 `cotMenu_Bind`进行绑定
|
||||
// 主循环
|
||||
while (1) {
|
||||
uint32_t tick = get_system_tick();
|
||||
menu_main_loop(tick);
|
||||
// 其他任务...
|
||||
}
|
||||
```
|
||||
|
||||
## 6. 功能特性
|
||||
|
||||
### 6.1 权限管理
|
||||
|
||||
#### 6.1.1 功能描述
|
||||
权限管理模块允许:
|
||||
- 注册不同的角色和权限级别
|
||||
- 为菜单节点设置访问权限
|
||||
- 检查当前角色是否有权访问特定节点
|
||||
- 动态切换角色
|
||||
|
||||
#### 6.1.2 权限检查流程
|
||||
|
||||
```mermaid
|
||||
flowchart TD
|
||||
A[访问节点请求] --> B{权限功能启用?}
|
||||
B -->|否| C[允许访问]
|
||||
B -->|是| D[获取当前角色]
|
||||
D --> E[获取节点权限级别]
|
||||
E --> F{角色权限 >= 节点权限?}
|
||||
F -->|是| C
|
||||
F -->|否| G[拒绝访问]
|
||||
```
|
||||
|
||||
### 6.2 状态持久化
|
||||
|
||||
#### 6.2.1 功能描述
|
||||
状态持久化模块允许:
|
||||
- 保存当前菜单状态到非易失性存储
|
||||
- 恢复上次保存的菜单状态
|
||||
- 支持自动保存和手动保存
|
||||
- 可配置的保存间隔
|
||||
|
||||
#### 6.2.2 持久化数据结构
|
||||
|
||||
```c
|
||||
typedef struct {
|
||||
MenuNodeId current_node_id; ///< 当前节点ID
|
||||
MenuState current_state; ///< 当前菜单状态
|
||||
MenuNavPath nav_path; ///< 当前导航路径
|
||||
MenuStack stack; ///< 当前菜单栈
|
||||
uint32_t timestamp; ///< 持久化时间戳
|
||||
uint8_t checksum; ///< 数据校验和
|
||||
} MenuPersistenceData;
|
||||
```
|
||||
|
||||
/* 设置的子菜单内容,每个菜单选项描述为字符串 */
|
||||
cotMenuList_t sg_SetMenuTable[] =
|
||||
{
|
||||
COT_MENU_ITEM_BIND("语言", NULL, NULL, NULL, OnLanguageFunction, NULL),
|
||||
COT_MENU_ITEM_BIND("蓝牙", NULL, NULL, NULL, OnBluetoothFunction, NULL),
|
||||
COT_MENU_ITEM_BIND("电池", NULL, NULL, NULL, OnBatteryFunction, NULL),
|
||||
COT_MENU_ITEM_BIND("储存", NULL, NULL, NULL, OnStorageFunction, NULL),
|
||||
COT_MENU_ITEM_BIND("更多", Hmi_MoreSetEnter, Hmi_MoreSetExit, Hmi_MoreSetLoad, Hmi_MoreSetTask, NULL),
|
||||
};
|
||||
### 6.3 多语言支持
|
||||
|
||||
/* 设置菜单显示效果 */
|
||||
static void ShowSetMenu(cotMenuShow_t *ptShowInfo)
|
||||
{
|
||||
uint8_t showNum = 3;
|
||||
menusize_t tmpselect;
|
||||
#### 6.3.1 功能描述
|
||||
多语言支持模块允许:
|
||||
- 注册不同语言的字符串
|
||||
- 切换当前语言
|
||||
- 根据当前语言获取对应的字符串
|
||||
- 支持动态加载语言包
|
||||
|
||||
cotMenu_LimitShowListNum(ptShowInfo, &showNum);
|
||||
### 6.4 内存监控
|
||||
|
||||
// 这里直接使用字符串
|
||||
printf("\e[0;30;46m ------------- %s ------------- \e[0m\n", tParentMenuShowInfo.uMenuDesc.pTextString);
|
||||
#### 6.4.1 功能描述
|
||||
内存监控模块允许:
|
||||
- 获取已使用和空闲的节点数量
|
||||
- 获取最大节点数量
|
||||
- 监控内存使用情况
|
||||
|
||||
for (int i = 0; i < showNum; i++)
|
||||
{
|
||||
tmpselect = i + ptShowInfo->showBaseItem;
|
||||
## 7. 配置与定制
|
||||
|
||||
if (tmpselect == ptShowInfo->selectItem)
|
||||
{
|
||||
printf("\e[0;30;47m %d. %-16s\e[0m |\n", tmpselect + 1, ptShowInfo->pszItemsDesc[tmpselect].pTextString);
|
||||
}
|
||||
else
|
||||
{
|
||||
printf("\e[7;30;47m %d. %-16s\e[0m |\n", tmpselect + 1, ptShowInfo->pszItemsDesc[tmpselect].pTextString);
|
||||
}
|
||||
### 7.1 配置选项
|
||||
|
||||
菜单组件提供了丰富的配置选项,通过修改 `menu_config.h` 文件可以定制:
|
||||
|
||||
| 配置项 | 描述 | 默认值 |
|
||||
|-------|------|-------|
|
||||
| MENU_CONFIG_MAX_NODES | 最大菜单节点数 | 32 |
|
||||
| MENU_CONFIG_STACK_DEPTH | 菜单栈深度 | 8 |
|
||||
| MENU_CONFIG_EVENT_QUEUE_LEN | 事件队列长度 | 16 |
|
||||
| MENU_CONFIG_ENABLE_PERMISSION | 启用权限管理 | 1 |
|
||||
| MENU_CONFIG_ENABLE_PERSISTENCE | 启用状态持久化 | 1 |
|
||||
| MENU_CONFIG_ENABLE_LANG | 启用多语言支持 | 1 |
|
||||
| MENU_CONFIG_ENABLE_DEBUG | 启用调试打印 | 1 |
|
||||
|
||||
### 7.2 功能模块开关
|
||||
|
||||
通过配置宏可以启用或禁用特定功能模块:
|
||||
|
||||
```c
|
||||
// 启用权限管理
|
||||
#define MENU_CONFIG_ENABLE_PERMISSION 1U
|
||||
|
||||
// 禁用多语言支持
|
||||
#define MENU_CONFIG_ENABLE_LANG 0U
|
||||
```
|
||||
|
||||
## 8. 示例用法
|
||||
|
||||
### 8.1 基础菜单示例
|
||||
|
||||
```c
|
||||
#include "menu_api.h"
|
||||
|
||||
// 回调函数
|
||||
MenuErrCode main_enter(MenuNodeId node_id, struct MenuCoreCtx* core_ctx) {
|
||||
printf("Enter Main Menu\n");
|
||||
return MENU_ERR_OK;
|
||||
}
|
||||
|
||||
MenuErrCode main_exit(MenuNodeId node_id, struct MenuCoreCtx* core_ctx) {
|
||||
printf("Exit Main Menu\n");
|
||||
return MENU_ERR_OK;
|
||||
}
|
||||
|
||||
// 初始化菜单
|
||||
void menu_example_init(void) {
|
||||
// 初始化菜单组件
|
||||
menu_init();
|
||||
|
||||
// 注册菜单节点
|
||||
MenuNodeId main_node = menu_register_node(0, 0, "Main Menu", main_enter, main_exit);
|
||||
MenuNodeId sub1_node = menu_register_node(0, main_node, "Sub Menu 1", NULL, NULL);
|
||||
MenuNodeId sub2_node = menu_register_node(0, main_node, "Sub Menu 2", NULL, NULL);
|
||||
|
||||
// 进入菜单
|
||||
menu_enter();
|
||||
}
|
||||
|
||||
// 主循环
|
||||
void menu_example_loop(uint32_t tick) {
|
||||
menu_main_loop(tick);
|
||||
}
|
||||
```
|
||||
|
||||
### 8.2 带权限管理的示例
|
||||
|
||||
```c
|
||||
#include "menu_api.h"
|
||||
|
||||
void permission_example(void) {
|
||||
// 初始化菜单
|
||||
menu_init();
|
||||
|
||||
// 注册角色
|
||||
const MenuRole roles[] = {
|
||||
{0, "Guest", 1}, // 访客角色,权限级别1
|
||||
{1, "User", 5}, // 用户角色,权限级别5
|
||||
{2, "Admin", 10}, // 管理员角色,权限级别10
|
||||
};
|
||||
|
||||
for (int i = 0; i < sizeof(roles)/sizeof(roles[0]); i++) {
|
||||
menu_permission_register_role(&roles[i]);
|
||||
}
|
||||
|
||||
// 设置当前角色
|
||||
menu_permission_set_current_role(0); // 初始为访客角色
|
||||
|
||||
// 注册菜单节点,设置不同权限级别
|
||||
MenuNodeId main_node = menu_register_node(0, 0, "Main", NULL, NULL);
|
||||
MenuNodeId public_node = menu_register_node(0, main_node, "Public", NULL, NULL);
|
||||
MenuNodeId user_node = menu_register_node(0, main_node, "User", NULL, NULL);
|
||||
MenuNodeId admin_node = menu_register_node(0, main_node, "Admin", NULL, NULL);
|
||||
|
||||
// 设置节点权限级别
|
||||
menu_permission_update_node_level(public_node, 1); // 所有人可访问
|
||||
menu_permission_update_node_level(user_node, 5); // 用户及以上可访问
|
||||
menu_permission_update_node_level(admin_node, 10); // 仅管理员可访问
|
||||
|
||||
// 检查权限
|
||||
bool can_access = menu_permission_check_node_access(admin_node); // 返回false(当前是访客角色)
|
||||
|
||||
// 切换角色
|
||||
menu_permission_set_current_role(2); // 切换到管理员角色
|
||||
can_access = menu_permission_check_node_access(admin_node); // 返回true
|
||||
}
|
||||
|
||||
void Hmi_SetEnter(const cotMenuItemInfo_t *pItemInfo)
|
||||
{
|
||||
// 进入设置选项后绑定子菜单,同时为当前绑定的菜单设置显示效果函数
|
||||
cotMenu_Bind(sg_SetMenuTable, COT_GET_MENU_NUM(sg_SetMenuTable), ShowSetMenu);
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
### 菜单控制
|
||||
|
||||
通过调用相关函数实现菜单选项选择、进入、退出等
|
||||
|
||||
```c
|
||||
|
||||
// 需要先进入主菜单
|
||||
cotMenu_MainEnter();
|
||||
|
||||
// 选择上一个,支持循环选择(即第一个可跳转到最后一个)
|
||||
cotMenu_SelectPrevious(true);
|
||||
|
||||
// 选择下一个,不支持循环选择(即最后一个不可跳转到第一个)
|
||||
cotMenu_SelectNext(false);
|
||||
|
||||
// 进入,会执行菜单选项的 pfnEnterCallFun 回调函数
|
||||
cotMenu_Enter();
|
||||
|
||||
// 退出,会执行父菜单该选项的 pfnExitCallFun 回调函数,并在退出后父菜单选项列表复位从头选择
|
||||
cotMenu_Exit(true);
|
||||
## 9. 目录结构
|
||||
|
||||
```
|
||||
menu/
|
||||
├── api/ # API接口层
|
||||
│ ├── menu_api.h # 对外API头文件
|
||||
│ └── menu_api.c # API实现
|
||||
├── src/ # 源代码层
|
||||
│ ├── core/ # 核心模块
|
||||
│ │ ├── menu_core.h/c # 核心逻辑
|
||||
│ │ ├── menu_event.h/c # 事件处理
|
||||
│ │ ├── menu_stack.h/c # 栈管理
|
||||
│ │ ├── menu_hash.h/c # 哈希表
|
||||
│ │ ├── menu_types.h # 类型定义
|
||||
│ │ └── menu_config.h # 配置文件
|
||||
│ ├── param/ # 参数管理
|
||||
│ │ ├── menu_param.h/c
|
||||
│ └── lang/ # 多语言支持
|
||||
│ ├── menu_lang.h/c
|
||||
├── port/ # 端口层
|
||||
│ ├── menu_port.h/c
|
||||
├── examples/ # 示例代码
|
||||
│ ├── language/ # 多语言示例
|
||||
│ └── hmi/ # HMI示例
|
||||
├── demo/ # 演示程序
|
||||
│ ├── demo.c
|
||||
│ └── CMakeLists.txt
|
||||
├── test/ # 测试代码
|
||||
│ ├── menu_test.c
|
||||
│ └── CMakeLists.txt
|
||||
├── CMakeLists.txt # CMake构建文件
|
||||
└── README.md # 设计文档
|
||||
```
|
||||
|
||||
定义了多个菜单选项表后,用来区分上下级,在某个菜单选项进入时绑定
|
||||
## 10. 构建与集成
|
||||
|
||||
1. 使用前初始化函数 cotMenu_Init, 设置主菜单内容
|
||||
2. 周期调用函数 cotMenu_Task, 用来处理菜单显示和执行相关回调函数
|
||||
3. 使用其他函数对菜单界面控制
|
||||
### 10.1 构建方式
|
||||
菜单组件使用CMake进行构建,支持多种编译器和平台:
|
||||
|
||||
```bash
|
||||
mkdir build
|
||||
cd build
|
||||
cmake ..
|
||||
make
|
||||
```
|
||||
|
||||
### 10.2 集成到应用
|
||||
|
||||
1. 将菜单组件的源文件添加到应用项目中
|
||||
2. 包含必要的头文件(主要是 `menu_api.h`)
|
||||
3. 根据需要配置 `menu_config.h`
|
||||
4. 调用API初始化和使用菜单组件
|
||||
|
||||
## 11. 版本历史
|
||||
|
||||
| 版本号 | 发布日期 | 主要变更 |
|
||||
|-------|---------|---------|
|
||||
| 1.0.0 | 2025-12-19 | 初始版本 |
|
||||
| 1.0.1 | 2025-12-20 | 修复了节点查找的bug,优化了事件处理流程 |
|
||||
|
||||
## 12. 总结
|
||||
|
||||
菜单组件是一个功能丰富、易于扩展的轻量级菜单系统,适合各种嵌入式设备的人机交互需求。它的模块化设计允许根据项目需求定制功能,静态内存分配使其适合资源受限的环境,完善的API设计使其易于集成到各种应用中。
|
||||
|
||||
通过合理使用菜单组件,可以快速构建出高效、可靠的嵌入式菜单界面,提高设备的用户体验和可维护性。
|
||||
Reference in New Issue
Block a user