整定一版

This commit is contained in:
冯佳
2025-12-19 17:01:27 +08:00
parent 294a49f207
commit 8bddc34c88
43 changed files with 7273 additions and 956 deletions

View File

@ -6,14 +6,70 @@ project(menu VERSION 1.3.0 LANGUAGES C)
set(CMAKE_C_STANDARD 99) set(CMAKE_C_STANDARD 99)
set(CMAKE_C_STANDARD_REQUIRED ON) set(CMAKE_C_STANDARD_REQUIRED ON)
# 源文件 # 核心层源文件
set(MENU_SOURCES set(CORE_SOURCES
menu.c src/core/menu_core.c
src/core/menu_event.c
src/core/menu_hash.c
src/core/menu_stack.c
src/core/menu_permission.c
src/core/menu_persistence.c
) )
# 库头文件 # 功能扩展层源文件
set(FEATURE_SOURCES
src/param/menu_param.c
src/lang/menu_lang.c
)
# API层源文件
set(API_SOURCES
api/menu_api.c
)
# 硬件端口层源文件
set(PORT_SOURCES
port/menu_port.c
)
# 所有源文件
set(MENU_SOURCES
${CORE_SOURCES}
${FEATURE_SOURCES}
${API_SOURCES}
${PORT_SOURCES}
)
# 核心层头文件
set(CORE_HEADERS
src/core/menu_config.h
src/core/menu_types.h
src/core/menu_permission.h
src/core/menu_persistence.h
)
# 功能扩展层头文件
set(FEATURE_HEADERS
src/param/menu_param.h
src/lang/menu_lang.h
)
# API层头文件
set(API_HEADERS
api/menu_api.h
)
# 硬件端口层头文件
set(PORT_HEADERS
port/menu_port.h
)
# 所有头文件
set(MENU_HEADERS set(MENU_HEADERS
menu.h ${CORE_HEADERS}
${FEATURE_HEADERS}
${API_HEADERS}
${PORT_HEADERS}
) )
# 创建静态库 # 创建静态库
@ -24,20 +80,31 @@ set_target_properties(menu PROPERTIES
PUBLIC_HEADER "${MENU_HEADERS}" PUBLIC_HEADER "${MENU_HEADERS}"
) )
# 包含当前目录 # 包含目录
target_include_directories(menu PUBLIC target_include_directories(menu PUBLIC
$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}> $<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}>
$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/src/core>
$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/src/param>
$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/src/lang>
$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/api>
$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/port>
$<INSTALL_INTERFACE:include> $<INSTALL_INTERFACE:include>
) )
# 示例程序 # 示例程序
option(BUILD_EXAMPLES "Build examples" ON) option(BUILD_EXAMPLES "Build examples" OFF)
if(BUILD_EXAMPLES) if(BUILD_EXAMPLES)
# 添加示例目录 # 添加示例目录
add_subdirectory(examples) add_subdirectory(examples)
endif() endif()
# 添加demo目录
add_subdirectory(demo)
# 添加test目录
add_subdirectory(test)
# 安装规则 # 安装规则
install(TARGETS menu install(TARGETS menu
ARCHIVE DESTINATION lib ARCHIVE DESTINATION lib

260
api/menu_api.c Normal file
View File

@ -0,0 +1,260 @@
/**
**********************************************************************************************************************
* @file menu_api.c
* @brief 菜单组件对外API接口实现
* @author menu_component
* @date 2025-12-19
**********************************************************************************************************************
*/
/* Includes ----------------------------------------------------------------------------------------------------------*/
#include "menu_api.h"
#include "../src/core/menu_core.h"
#include "../src/core/menu_event.h"
#include "../src/core/menu_hash.h"
/* 函数实现 ---------------------------------------------------------------------------------------------------------*/
/* 菜单初始化与反初始化 ---------------------------------------------------------------------------------------------*/
MenuErrCode menu_init(void)
{
return menu_core_init();
}
MenuErrCode menu_deinit(void)
{
return menu_core_deinit();
}
/* 菜单节点管理 -----------------------------------------------------------------------------------------------------*/
MenuNodeId menu_register_node(MenuNodeId node_id, MenuNodeId parent_id, const char* name, MenuCallback enter_cb, MenuCallback exit_cb)
{
// 参数验证
if (name == NULL) {
return 0; // 保持原有API兼容0表示注册失败
}
// 调用核心层的节点注册功能
return menu_core_register_node(node_id, parent_id, name, enter_cb, exit_cb);
}
MenuErrCode menu_register_nodes(const struct MenuNode* nodes, uint16_t count)
{
// 暂未实现批量注册功能
(void)nodes;
(void)count;
return MENU_ERR_OPERATION_FAILED;
}
MenuErrCode menu_unregister_node(MenuNodeId node_id)
{
// 调用核心层的节点注销功能
return menu_core_unregister_node(node_id);
}
MenuErrCode menu_update_node(MenuNodeId node_id, const char* name, MenuCallback enter_cb, MenuCallback exit_cb)
{
MenuCoreCtx* core_ctx = menu_get_core_ctx();
MenuNode* node = menu_hash_find(core_ctx, node_id);
if (node == NULL || !node->flags.is_registered) {
return MENU_ERR_NODE_NOT_FOUND;
}
// 更新节点信息
if (name != NULL) {
node->name = name;
}
if (enter_cb != NULL) {
node->enter_cb = enter_cb;
}
if (exit_cb != NULL) {
node->exit_cb = exit_cb;
}
return MENU_ERR_OK;
}
const struct MenuNode* menu_find_node(MenuNodeId node_id)
{
MenuCoreCtx* core_ctx = menu_get_core_ctx();
return menu_hash_find(core_ctx, node_id);
}
/* 菜单事件处理 -----------------------------------------------------------------------------------------------------*/
MenuErrCode menu_post_event(MenuEventType event_type, uint8_t priority)
{
MenuCoreCtx* core_ctx = menu_get_core_ctx();
MenuEvent event;
event.type = event_type;
event.priority = priority;
event.target_node_id = core_ctx->current_node_id;
event.data = NULL;
event.timestamp = core_ctx->last_refresh_tick;
return menu_event_queue_push(&core_ctx->event_queue, &event);
}
MenuErrCode menu_post_event_with_data(MenuEventType event_type, uint8_t priority, void* data)
{
MenuCoreCtx* core_ctx = menu_get_core_ctx();
MenuEvent event;
event.type = event_type;
event.priority = priority;
event.target_node_id = core_ctx->current_node_id;
event.data = data;
event.timestamp = core_ctx->last_refresh_tick;
return menu_event_queue_push(&core_ctx->event_queue, &event);
}
/* 菜单导航操作 -----------------------------------------------------------------------------------------------------*/
MenuErrCode menu_main_loop(uint32_t tick)
{
// 调用核心层的主循环功能
return menu_core_main_loop(tick);
}
MenuErrCode menu_enter(void)
{
return menu_post_event(MENU_EVENT_KEY_ENTER, 1);
}
MenuErrCode menu_exit(void)
{
return menu_post_event(MENU_EVENT_KEY_ESC, 1);
}
MenuErrCode menu_up(void)
{
return menu_post_event(MENU_EVENT_KEY_UP, 1);
}
MenuErrCode menu_down(void)
{
return menu_post_event(MENU_EVENT_KEY_DOWN, 1);
}
MenuErrCode menu_select(MenuNodeId node_id)
{
// 暂未实现直接选择指定节点功能
(void)node_id;
return MENU_ERR_OPERATION_FAILED;
}
MenuErrCode menu_reset(void)
{
// 暂未实现重置功能
return MENU_ERR_OPERATION_FAILED;
}
/* 菜单状态查询 -----------------------------------------------------------------------------------------------------*/
MenuState menu_get_state(void)
{
return menu_get_current_state();
}
MenuNodeId menu_get_current_node(void)
{
return menu_get_current_node_id();
}
uint8_t menu_get_nav_depth(void)
{
MenuCoreCtx* core_ctx = menu_get_core_ctx();
return core_ctx->nav_path.depth;
}
uint8_t menu_get_nav_path(MenuNodeId* path, uint8_t max_depth)
{
MenuCoreCtx* core_ctx = menu_get_core_ctx();
uint8_t actual_depth = core_ctx->nav_path.depth;
if (actual_depth > max_depth) {
actual_depth = max_depth;
}
for (uint8_t i = 0; i < actual_depth; i++) {
path[i] = core_ctx->nav_path.path[i];
}
return actual_depth;
}
/* 状态机扩展接口 ---------------------------------------------------------------------------------------------------*/
#if MENU_CONFIG_ENABLE_STATE_MACHINE_EXT
MenuErrCode menu_state_register_transition(const MenuStateTransition* transition)
{
MenuCoreCtx* core_ctx = menu_get_core_ctx();
if (core_ctx->custom_transition_count >= MENU_CONFIG_MAX_STATE_TRANSITIONS) {
return MENU_ERR_OUT_OF_MEMORY;
}
core_ctx->custom_transitions[core_ctx->custom_transition_count] = *transition;
core_ctx->custom_transition_count++;
return MENU_ERR_OK;
}
MenuErrCode menu_state_unregister_transition(MenuState current_state, MenuEventType event)
{
MenuCoreCtx* core_ctx = menu_get_core_ctx();
bool found = false;
// 查找并删除匹配的状态转换规则
for (uint16_t i = 0; i < core_ctx->custom_transition_count; i++) {
if (core_ctx->custom_transitions[i].current_state == current_state &&
core_ctx->custom_transitions[i].event == event) {
// 找到匹配的规则,将后续规则前移
for (uint16_t j = i; j < core_ctx->custom_transition_count - 1; j++) {
core_ctx->custom_transitions[j] = core_ctx->custom_transitions[j + 1];
}
core_ctx->custom_transition_count--;
found = true;
break;
}
}
return found ? MENU_ERR_OK : MENU_ERR_INVALID_STATE;
}
MenuErrCode menu_state_switch(MenuState state)
{
MenuCoreCtx* core_ctx = menu_get_core_ctx();
core_ctx->current_state = state;
return MENU_ERR_OK;
}
#endif
/* 内存监控接口 -----------------------------------------------------------------------------------------------------*/
#if MENU_CONFIG_ENABLE_MEM_MONITOR
uint16_t menu_mem_get_used_nodes(void)
{
MenuCoreCtx* core_ctx = menu_get_core_ctx();
return core_ctx->node_count;
}
uint16_t menu_mem_get_free_nodes(void)
{
MenuCoreCtx* core_ctx = menu_get_core_ctx();
return core_ctx->free_node_count;
}
uint16_t menu_mem_get_max_nodes(void)
{
return MENU_CONFIG_MAX_NODES;
}
#endif

332
api/menu_api.h Normal file
View File

@ -0,0 +1,332 @@
/**
**********************************************************************************************************************
* @file menu_api.h
* @brief 菜单组件对外API接口定义
* @author menu_component
* @date 2025-12-19
**********************************************************************************************************************
*/
#ifndef MENU_API_H
#define MENU_API_H
/* Includes ----------------------------------------------------------------------------------------------------------*/
#include "../src/core/menu_config.h"
#include "../src/core/menu_types.h"
/* 宏定义 -----------------------------------------------------------------------------------------------------------*/
#define MENU_API_VERSION "1.0.1"
/* 函数声明 ---------------------------------------------------------------------------------------------------------*/
/* 菜单初始化与反初始化 ---------------------------------------------------------------------------------------------*/
/**
* @brief 初始化菜单组件
* @return 错误码
*/
MenuErrCode menu_init(void);
/**
* @brief 反初始化菜单组件
* @return 错误码
*/
MenuErrCode menu_deinit(void);
/* 菜单节点管理 -----------------------------------------------------------------------------------------------------*/
/**
* @brief 注册菜单节点
* @param node_id 节点ID0表示自动分配
* @param parent_id 父节点ID
* @param name 节点名称
* @param enter_cb 进入回调
* @param exit_cb 退出回调
* @return 实际分配的节点ID
*/
MenuNodeId menu_register_node(MenuNodeId node_id, MenuNodeId parent_id, const char* name, MenuCallback enter_cb, MenuCallback exit_cb);
/**
* @brief 批量注册菜单节点
* @param nodes 节点数组
* @param count 节点数量
* @return 错误码
*/
MenuErrCode menu_register_nodes(const struct MenuNode* nodes, uint16_t count);
/**
* @brief 注销菜单节点
* @param node_id 节点ID
* @return 错误码
*/
MenuErrCode menu_unregister_node(MenuNodeId node_id);
/**
* @brief 更新菜单节点
* @param node_id 节点ID
* @param name 节点名称NULL表示不更新
* @param enter_cb 进入回调NULL表示不更新
* @param exit_cb 退出回调NULL表示不更新
* @return 错误码
*/
MenuErrCode menu_update_node(MenuNodeId node_id, const char* name, MenuCallback enter_cb, MenuCallback exit_cb);
/**
* @brief 查找菜单节点
* @param node_id 节点ID
* @return 节点指针未找到返回NULL
*/
const struct MenuNode* menu_find_node(MenuNodeId node_id);
/* 菜单事件处理 -----------------------------------------------------------------------------------------------------*/
/**
* @brief 发送菜单事件
* @param event_type 事件类型
* @param priority 事件优先级0-30最高
* @return 错误码
*/
MenuErrCode menu_post_event(MenuEventType event_type, uint8_t priority);
/**
* @brief 发送带数据的菜单事件
* @param event_type 事件类型
* @param priority 事件优先级0-30最高
* @param data 事件数据
* @return 错误码
*/
MenuErrCode menu_post_event_with_data(MenuEventType event_type, uint8_t priority, void* data);
/* 菜单导航操作 -----------------------------------------------------------------------------------------------------*/
/**
* @brief 菜单主循环,处理事件和刷新显示
* @param tick 当前系统时间ms
* @return 错误码
*/
MenuErrCode menu_main_loop(uint32_t tick);
/**
* @brief 进入菜单
* @return 错误码
*/
MenuErrCode menu_enter(void);
/**
* @brief 退出菜单
* @return 错误码
*/
MenuErrCode menu_exit(void);
/**
* @brief 上移菜单项
* @return 错误码
*/
MenuErrCode menu_up(void);
/**
* @brief 下移菜单项
* @return 错误码
*/
MenuErrCode menu_down(void);
/**
* @brief 选择菜单项
* @param node_id 节点ID
* @return 错误码
*/
MenuErrCode menu_select(MenuNodeId node_id);
/**
* @brief 重置菜单,回到主菜单
* @return 错误码
*/
MenuErrCode menu_reset(void);
/* 菜单状态查询 -----------------------------------------------------------------------------------------------------*/
/**
* @brief 获取当前菜单状态
* @return 当前菜单状态
*/
MenuState menu_get_state(void);
/**
* @brief 获取当前选中的节点ID
* @return 当前节点ID
*/
MenuNodeId menu_get_current_node(void);
/**
* @brief 获取导航深度
* @return 导航深度
*/
uint8_t menu_get_nav_depth(void);
/**
* @brief 获取导航路径
* @param path 用于存储导航路径的数组
* @param max_depth 数组最大深度
* @return 实际获取的深度
*/
uint8_t menu_get_nav_path(MenuNodeId* path, uint8_t max_depth);
/* 状态机扩展接口 ---------------------------------------------------------------------------------------------------*/
#if MENU_CONFIG_ENABLE_STATE_MACHINE_EXT
/**
* @brief 注册自定义状态转换规则
* @param transition 状态转换规则
* @return 错误码
*/
MenuErrCode menu_state_register_transition(const MenuStateTransition* transition);
/**
* @brief 注销自定义状态转换规则
* @param current_state 当前状态
* @param event 触发事件
* @return 错误码
*/
MenuErrCode menu_state_unregister_transition(MenuState current_state, MenuEventType event);
/**
* @brief 切换到指定状态
* @param state 目标状态
* @return 错误码
*/
MenuErrCode menu_state_switch(MenuState state);
#endif
/* 内存监控接口 -----------------------------------------------------------------------------------------------------*/
#if MENU_CONFIG_ENABLE_MEM_MONITOR
/**
* @brief 获取已使用节点数量
* @return 已使用节点数量
*/
uint16_t menu_mem_get_used_nodes(void);
/**
* @brief 获取空闲节点数量
* @return 空闲节点数量
*/
uint16_t menu_mem_get_free_nodes(void);
/**
* @brief 获取最大节点数量
* @return 最大节点数量
*/
uint16_t menu_mem_get_max_nodes(void);
#endif
/* 权限管理接口 -----------------------------------------------------------------------------------------------------*/
#if MENU_CONFIG_ENABLE_PERMISSION
/**
* @brief 注册角色
* @param role 角色结构体指针
* @return 错误码
*/
MenuErrCode menu_permission_register_role(const MenuRole* role);
/**
* @brief 注销角色
* @param role_id 角色ID
* @return 错误码
*/
MenuErrCode menu_permission_unregister_role(MenuRoleId role_id);
/**
* @brief 设置当前角色
* @param role_id 角色ID
* @return 错误码
*/
MenuErrCode menu_permission_set_current_role(MenuRoleId role_id);
/**
* @brief 获取当前角色
* @return 当前角色ID
*/
MenuRoleId menu_permission_get_current_role(void);
/**
* @brief 更新节点权限级别
* @param node_id 节点ID
* @param permission_level 权限级别
* @return 错误码
*/
MenuErrCode menu_permission_update_node_level(MenuNodeId node_id, MenuPermissionLevel permission_level);
/**
* @brief 获取节点权限级别
* @param node_id 节点ID
* @return 节点权限级别
*/
MenuPermissionLevel menu_permission_get_node_level(MenuNodeId node_id);
/**
* @brief 检查节点是否可访问
* @param node_id 节点ID
* @return true表示可访问false表示不可访问
*/
bool menu_permission_check_node_access(MenuNodeId node_id);
/**
* @brief 获取角色信息
* @param role_id 角色ID
* @return 角色结构体指针NULL表示角色不存在
*/
const MenuRole* menu_permission_get_role(MenuRoleId role_id);
#endif
/* 状态持久化接口 ---------------------------------------------------------------------------------------------------*/
#if MENU_CONFIG_ENABLE_PERSISTENCE
/**
* @brief 注册持久化回调函数
* @param save_cb 保存回调函数
* @param restore_cb 恢复回调函数
* @return 错误码
*/
MenuErrCode menu_persistence_register_callback(MenuPersistenceCallback save_cb, MenuRestoreCallback restore_cb);
/**
* @brief 启用或禁用自动保存
* @param enable true表示启用false表示禁用
* @return 错误码
*/
MenuErrCode menu_persistence_set_auto_save(bool enable);
/**
* @brief 获取自动保存状态
* @return true表示启用false表示禁用
*/
bool menu_persistence_get_auto_save(void);
/**
* @brief 设置自动保存间隔
* @param interval 自动保存间隔ms
* @return 错误码
*/
MenuErrCode menu_persistence_set_auto_save_interval(uint32_t interval);
/**
* @brief 获取自动保存间隔
* @return 自动保存间隔ms
*/
uint32_t menu_persistence_get_auto_save_interval(void);
/**
* @brief 手动保存菜单状态
* @return 错误码
*/
MenuErrCode menu_persistence_save(void);
/**
* @brief 恢复菜单状态
* @return 错误码
*/
MenuErrCode menu_persistence_restore(void);
#endif
#endif /* MENU_API_H */

30
demo/CMakeLists.txt Normal file
View File

@ -0,0 +1,30 @@
cmake_minimum_required(VERSION 3.10)
project(menu_demo C)
# 设置C标准
set(CMAKE_C_STANDARD 99)
set(CMAKE_C_STANDARD_REQUIRED ON)
# 添加当前目录和上级目录作为包含目录
include_directories(
${CMAKE_CURRENT_SOURCE_DIR}
${CMAKE_SOURCE_DIR}
)
# 收集示例所需的源文件
set(DEMO_SOURCES
demo.c
language.c
)
# 创建demo可执行程序
add_executable(menu_demo ${DEMO_SOURCES})
# 链接menu库
target_link_libraries(menu_demo PRIVATE menu)
# 安装规则
install(TARGETS menu_demo
RUNTIME DESTINATION bin
)

92
demo/build/CMakeCache.txt Normal file
View File

@ -0,0 +1,92 @@
# This is the CMakeCache file.
# For build in directory: e:/Jfen_work/local_git_code/menu/demo/build
# It was generated by CMake: C:/Users/ZHIZHANKEJI/AppData/Local/Programs/Python/Python311/Lib/site-packages/cmake/data/bin/cmake.exe
# You can edit this file to change values found and used by cmake.
# If you do not want to change any of the values, simply exit the editor.
# If you do want to change a value, simply edit, save, and exit the editor.
# The syntax for the file is as follows:
# KEY:TYPE=VALUE
# KEY is the name of a variable in the cache.
# TYPE is a hint to GUIs for the type of VALUE, DO NOT EDIT TYPE!.
# VALUE is the current value for the KEY.
########################
# EXTERNAL cache entries
########################
//Value Computed by CMake.
CMAKE_FIND_PACKAGE_REDIRECTS_DIR:STATIC=E:/Jfen_work/local_git_code/menu/demo/build/CMakeFiles/pkgRedirects
//Program used to build from makefiles.
CMAKE_MAKE_PROGRAM:STRING=nmake
//Value Computed by CMake
CMAKE_PROJECT_DESCRIPTION:STATIC=
//Value Computed by CMake
CMAKE_PROJECT_HOMEPAGE_URL:STATIC=
//Value Computed by CMake
CMAKE_PROJECT_NAME:STATIC=menu_demo
//Value Computed by CMake
demo_BINARY_DIR:STATIC=E:/Jfen_work/local_git_code/menu/demo/build
//Value Computed by CMake
demo_IS_TOP_LEVEL:STATIC=ON
//Value Computed by CMake
demo_SOURCE_DIR:STATIC=E:/Jfen_work/local_git_code/menu/demo
//Value Computed by CMake
menu_demo_BINARY_DIR:STATIC=E:/Jfen_work/local_git_code/menu/demo/build
//Value Computed by CMake
menu_demo_IS_TOP_LEVEL:STATIC=ON
//Value Computed by CMake
menu_demo_SOURCE_DIR:STATIC=E:/Jfen_work/local_git_code/menu/demo
########################
# INTERNAL cache entries
########################
//This is the directory where this CMakeCache.txt was created
CMAKE_CACHEFILE_DIR:INTERNAL=e:/Jfen_work/local_git_code/menu/demo/build
//Major version of cmake used to create the current loaded cache
CMAKE_CACHE_MAJOR_VERSION:INTERNAL=3
//Minor version of cmake used to create the current loaded cache
CMAKE_CACHE_MINOR_VERSION:INTERNAL=31
//Patch version of cmake used to create the current loaded cache
CMAKE_CACHE_PATCH_VERSION:INTERNAL=2
//Path to CMake executable.
CMAKE_COMMAND:INTERNAL=C:/Users/ZHIZHANKEJI/AppData/Local/Programs/Python/Python311/Lib/site-packages/cmake/data/bin/cmake.exe
//Path to cpack program executable.
CMAKE_CPACK_COMMAND:INTERNAL=C:/Users/ZHIZHANKEJI/AppData/Local/Programs/Python/Python311/Lib/site-packages/cmake/data/bin/cpack.exe
//Path to ctest program executable.
CMAKE_CTEST_COMMAND:INTERNAL=C:/Users/ZHIZHANKEJI/AppData/Local/Programs/Python/Python311/Lib/site-packages/cmake/data/bin/ctest.exe
//Path to cache edit program executable.
CMAKE_EDIT_COMMAND:INTERNAL=C:/Users/ZHIZHANKEJI/AppData/Local/Programs/Python/Python311/Lib/site-packages/cmake/data/bin/cmake-gui.exe
//Name of external makefile project generator.
CMAKE_EXTRA_GENERATOR:INTERNAL=
//Name of generator.
CMAKE_GENERATOR:INTERNAL=NMake Makefiles
//Generator instance identifier.
CMAKE_GENERATOR_INSTANCE:INTERNAL=
//Name of generator platform.
CMAKE_GENERATOR_PLATFORM:INTERNAL=
//Name of generator toolset.
CMAKE_GENERATOR_TOOLSET:INTERNAL=
//Source directory with the top level CMakeLists.txt file for this
// project
CMAKE_HOME_DIRECTORY:INTERNAL=E:/Jfen_work/local_git_code/menu/demo
//ADVANCED property for variable: CMAKE_MAKE_PROGRAM
CMAKE_MAKE_PROGRAM-ADVANCED:INTERNAL=1
//number of local generators
CMAKE_NUMBER_OF_MAKEFILES:INTERNAL=1
//Platform information initialized
CMAKE_PLATFORM_INFO_INITIALIZED:INTERNAL=1
//Path to CMake installation.
CMAKE_ROOT:INTERNAL=C:/Users/ZHIZHANKEJI/AppData/Local/Programs/Python/Python311/Lib/site-packages/cmake/data/share/cmake-3.31

View File

@ -0,0 +1,15 @@
set(CMAKE_HOST_SYSTEM "Windows-10.0.26200")
set(CMAKE_HOST_SYSTEM_NAME "Windows")
set(CMAKE_HOST_SYSTEM_VERSION "10.0.26200")
set(CMAKE_HOST_SYSTEM_PROCESSOR "AMD64")
set(CMAKE_SYSTEM "Windows-10.0.26200")
set(CMAKE_SYSTEM_NAME "Windows")
set(CMAKE_SYSTEM_VERSION "10.0.26200")
set(CMAKE_SYSTEM_PROCESSOR "AMD64")
set(CMAKE_CROSSCOMPILING "FALSE")
set(CMAKE_SYSTEM_LOADED 1)

View File

@ -0,0 +1,11 @@
---
events:
-
kind: "message-v1"
backtrace:
- "C:/Users/ZHIZHANKEJI/AppData/Local/Programs/Python/Python311/Lib/site-packages/cmake/data/share/cmake-3.31/Modules/CMakeDetermineSystem.cmake:205 (message)"
- "CMakeLists.txt:3 (project)"
message: |
The system is: Windows - 10.0.26200 - AMD64
...

View File

@ -0,0 +1 @@
# This file is generated by cmake for dependency checking of the CMakeCache.txt file

681
demo/demo.c Normal file
View File

@ -0,0 +1,681 @@
/**
**********************************************************************************************************************
* @file demo.c
* @brief 菜单组件示例程序 - 多语言版本
* @author menu_component
* @date 2025-12-19
**********************************************************************************************************************
*/
#include "../api/menu_api.h"
#include "../port/menu_port.h"
#include "language.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#if defined(_WIN32) || defined(_WIN64)
#include <conio.h>
#define NON_BLOCKING_GETCHAR() _getch()
#define IS_KEY_AVAILABLE() _kbhit()
#else
#include <sys/time.h>
#include <sys/select.h>
#include <unistd.h>
#include <termios.h>
// 保存原始终端设置
static struct termios sg_original_termios;
/**
* @brief 设置终端为无缓冲模式
*/
static void set_nonblocking_terminal(void)
{
struct termios new_termios;
tcgetattr(STDIN_FILENO, &sg_original_termios);
new_termios = sg_original_termios;
new_termios.c_lflag &= ~(ICANON | ECHO); // 关闭行缓冲和回显
new_termios.c_cc[VMIN] = 1; // 至少读取1个字符
new_termios.c_cc[VTIME] = 0; // 无超时
tcsetattr(STDIN_FILENO, TCSANOW, &new_termios);
}
/**
* @brief 恢复终端为原始模式
*/
static void restore_terminal(void)
{
tcsetattr(STDIN_FILENO, TCSANOW, &sg_original_termios);
}
/**
* @brief 检查是否有按键输入
*/
static bool IS_KEY_AVAILABLE(void)
{
struct timeval tv;
fd_set fds;
tv.tv_sec = 0;
tv.tv_usec = 0;
FD_ZERO(&fds);
FD_SET(STDIN_FILENO, &fds);
return select(STDIN_FILENO + 1, &fds, NULL, NULL, &tv) > 0;
}
/**
* @brief 无缓冲获取按键
*/
static char NON_BLOCKING_GETCHAR(void)
{
return getchar();
}
#endif
/* 使用新的菜单节点ID枚举 */
#define MENU_ID_MAIN MENU_NODE_ID_MAIN
#define MENU_ID_ITEM1 MENU_NODE_ID_MEASUREMENT
#define MENU_ID_ITEM2 MENU_NODE_ID_PROTECTION
#define MENU_ID_ITEM3 MENU_NODE_ID_CONTROL
#define MENU_ID_SUB1 MENU_NODE_ID_SETTING
#define MENU_ID_SUB2 MENU_NODE_ID_RECORD
#define MENU_ID_LANGUAGE MENU_NODE_ID_HELP
#define MENU_ID_GRANDCHILD1 MENU_NODE_ID_ABOUT
#define MENU_ID_GRANDCHILD2 (MENU_NODE_ID_ABOUT + 1)
/* 测量菜单子项 */
#define MENU_ID_MEASUREMENT_VOLTAGE MENU_NODE_ID_MEASUREMENT_VOLTAGE
#define MENU_ID_MEASUREMENT_CURRENT MENU_NODE_ID_MEASUREMENT_CURRENT
#define MENU_ID_MEASUREMENT_POWER MENU_NODE_ID_MEASUREMENT_POWER
#define MENU_ID_MEASUREMENT_ENERGY MENU_NODE_ID_MEASUREMENT_ENERGY
#define MENU_ID_MEASUREMENT_FREQUENCY MENU_NODE_ID_MEASUREMENT_FREQUENCY
#define MENU_ID_MEASUREMENT_TEMPERATURE MENU_NODE_ID_MEASUREMENT_TEMPERATURE
/* 保护菜单子项 */
#define MENU_ID_PROTECTION_OVER_VOLTAGE MENU_NODE_ID_PROTECTION_OVER_VOLTAGE
#define MENU_ID_PROTECTION_UNDER_VOLTAGE MENU_NODE_ID_PROTECTION_UNDER_VOLTAGE
#define MENU_ID_PROTECTION_OVER_CURRENT MENU_NODE_ID_PROTECTION_OVER_CURRENT
#define MENU_ID_PROTECTION_SETTING MENU_NODE_ID_PROTECTION_SETTING
/* 控制菜单子项 */
#define MENU_ID_CONTROL_ON_OFF MENU_NODE_ID_CONTROL_ON_OFF
#define MENU_ID_CONTROL_START_STOP MENU_NODE_ID_CONTROL_START_STOP
#define MENU_ID_CONTROL_AUTO_MANUAL MENU_NODE_ID_CONTROL_AUTO_MANUAL
/* 设置菜单子项 */
#define MENU_ID_SETTING_LANGUAGE MENU_NODE_ID_SETTING_LANGUAGE
/* 持久化函数声明 */
static MenuErrCode demo_persistence_save(const uint8_t* data, uint16_t size);
static MenuErrCode demo_persistence_restore(uint8_t* data, uint16_t* size);
/* 全局变量 */
static uint32_t sg_tick = 0;
static bool sg_is_menu_active = false;
static SystemLang_e sg_current_language = SYSTEM_LANG_CHINESE;
/* 菜单回调函数 */
/**
* @brief 主菜单进入回调
*/
static MenuErrCode menu_enter_main(MenuNodeId node_id, struct MenuCoreCtx* core_ctx)
{
(void)node_id;
(void)core_ctx;
sg_is_menu_active = true;
printf("%s: %s\n", get_text(TEXT_ENTER), get_text(TEXT_MAIN_MENU));
return MENU_ERR_OK;
}
/**
* @brief 主菜单退出回调
*/
static MenuErrCode menu_exit_main(MenuNodeId node_id, struct MenuCoreCtx* core_ctx)
{
(void)node_id;
(void)core_ctx;
sg_is_menu_active = false;
printf("%s: %s\n", get_text(TEXT_EXIT), get_text(TEXT_MAIN_MENU));
return MENU_ERR_OK;
}
/**
* @brief 测量菜单进入回调
*/
static MenuErrCode menu_enter_item1(MenuNodeId node_id, struct MenuCoreCtx* core_ctx)
{
(void)node_id;
(void)core_ctx;
printf("%s: %s\n", get_text(TEXT_ENTER), get_text(TEXT_MAIN_MEASUREMENT));
return MENU_ERR_OK;
}
/**
* @brief 保护菜单进入回调
*/
static MenuErrCode menu_enter_item2(MenuNodeId node_id, struct MenuCoreCtx* core_ctx)
{
(void)node_id;
(void)core_ctx;
printf("%s: %s\n", get_text(TEXT_ENTER), get_text(TEXT_MAIN_PROTECTION));
return MENU_ERR_OK;
}
/**
* @brief 控制菜单进入回调
*/
static MenuErrCode menu_enter_item3(MenuNodeId node_id, struct MenuCoreCtx* core_ctx)
{
(void)node_id;
(void)core_ctx;
printf("%s: %s\n", get_text(TEXT_ENTER), get_text(TEXT_MAIN_CONTROL));
return MENU_ERR_OK;
}
/**
* @brief 语言设置进入回调(声明)
*/
static MenuErrCode menu_enter_language(MenuNodeId node_id, struct MenuCoreCtx* core_ctx);
/**
* @brief 设置菜单进入回调(声明)
*/
static MenuErrCode menu_enter_setting(MenuNodeId node_id, struct MenuCoreCtx* core_ctx);
/**
* @brief 重新注册菜单节点,用于语言切换
*/
static void re_register_menu_nodes(void)
{
// 先注销所有节点
menu_deinit();
menu_init();
// 主菜单
MenuNodeId main_node_id = menu_register_node(0, 0, get_text(TEXT_MAIN_MENU), menu_enter_main, menu_exit_main);
// 菜单项
MenuNodeId item1_node_id = menu_register_node(0, main_node_id, get_text(TEXT_MAIN_MEASUREMENT), menu_enter_item1, NULL);
MenuNodeId item2_node_id = menu_register_node(0, main_node_id, get_text(TEXT_MAIN_PROTECTION), menu_enter_item2, NULL);
MenuNodeId item3_node_id = menu_register_node(0, main_node_id, get_text(TEXT_MAIN_CONTROL), menu_enter_item3, NULL);
MenuNodeId sub1_node_id = menu_register_node(0, main_node_id, get_text(TEXT_MAIN_SETTING), menu_enter_setting, NULL);
// 测量菜单子项
menu_register_node(0, item1_node_id, get_text(TEXT_MEASURE_VOLTAGE), NULL, NULL);
menu_register_node(0, item1_node_id, get_text(TEXT_MEASURE_CURRENT), NULL, NULL);
menu_register_node(0, item1_node_id, get_text(TEXT_MEASURE_POWER), NULL, NULL);
menu_register_node(0, item1_node_id, get_text(TEXT_MEASURE_ENERGY), NULL, NULL);
menu_register_node(0, item1_node_id, get_text(TEXT_MEASURE_FREQUENCY), NULL, NULL);
menu_register_node(0, item1_node_id, get_text(TEXT_MEASURE_TEMPERATURE), NULL, NULL);
// 保护菜单子项
menu_register_node(0, item2_node_id, get_text(TEXT_PROTECT_OVER_VOLTAGE), NULL, NULL);
menu_register_node(0, item2_node_id, get_text(TEXT_PROTECT_UNDER_VOLTAGE), NULL, NULL);
menu_register_node(0, item2_node_id, get_text(TEXT_PROTECT_OVER_CURRENT), NULL, NULL);
menu_register_node(0, item2_node_id, get_text(TEXT_PROTECT_SETTING), NULL, NULL);
// 控制菜单子项
menu_register_node(0, item3_node_id, get_text(TEXT_CONTROL_ON_OFF), NULL, NULL);
menu_register_node(0, item3_node_id, get_text(TEXT_CONTROL_START_STOP), NULL, NULL);
menu_register_node(0, item3_node_id, get_text(TEXT_CONTROL_AUTO_MANUAL), NULL, NULL);
// 设置菜单子项
menu_register_node(0, sub1_node_id, get_text(TEXT_SETTING_LANGUAGE), menu_enter_language, NULL);
// 重新注册持久化回调函数
menu_persistence_register_callback(demo_persistence_save, demo_persistence_restore);
}
/**
* @brief 设置菜单进入回调
*/
static MenuErrCode menu_enter_setting(MenuNodeId node_id, struct MenuCoreCtx* core_ctx)
{
(void)node_id;
(void)core_ctx;
printf("%s: %s\n", get_text(TEXT_ENTER), get_text(TEXT_MAIN_SETTING));
return MENU_ERR_OK;
}
/**
* @brief 语言设置进入回调
*/
static MenuErrCode menu_enter_language(MenuNodeId node_id, struct MenuCoreCtx* core_ctx)
{
(void)node_id;
(void)core_ctx;
printf("%s: %s\n", get_text(TEXT_ENTER), get_text(TEXT_SETTING_LANGUAGE));
// 切换语言
if (sg_current_language == SYSTEM_LANG_CHINESE)
{
sg_current_language = SYSTEM_LANG_ENGLISH;
set_language(SYSTEM_LANG_ENGLISH);
printf("%s: English\n", get_text(TEXT_SETTING_LANGUAGE));
}
else
{
sg_current_language = SYSTEM_LANG_CHINESE;
set_language(SYSTEM_LANG_CHINESE);
printf("%s: 中文\n", get_text(TEXT_SETTING_LANGUAGE));
}
// 重新注册菜单节点,更新菜单名称
re_register_menu_nodes();
return MENU_ERR_OK;
}
/**
* @brief 默认获取系统时间函数
*/
static uint32_t demo_get_tick(void)
{
return sg_tick++;
}
/**
* @brief 默认打印函数
*/
static void demo_printf(const char* fmt, va_list args)
{
vprintf(fmt, args);
}
/**
* @brief 持久化数据保存回调函数
* @param data 要保存的数据
* @param size 数据大小
* @return 错误码
*/
static MenuErrCode demo_persistence_save(const uint8_t* data, uint16_t size)
{
FILE* fp = NULL;
MenuErrCode ret = MENU_ERR_OK;
// 打开文件进行写入
fp = fopen("menu_persistence.dat", "wb");
if (fp == NULL) {
printf("%s: %s\n", get_text(TEXT_ERROR), "打开文件失败");
return MENU_ERR_OPERATION_FAILED;
}
// 写入数据
if (fwrite(data, 1, size, fp) != size) {
printf("%s: %s\n", get_text(TEXT_ERROR), "写入文件失败");
ret = MENU_ERR_OPERATION_FAILED;
}
// 关闭文件
fclose(fp);
return ret;
}
/**
* @brief 持久化数据恢复回调函数
* @param data 恢复数据缓冲区
* @param size 数据大小
* @return 错误码
*/
static MenuErrCode demo_persistence_restore(uint8_t* data, uint16_t* size)
{
FILE* fp = NULL;
MenuErrCode ret = MENU_ERR_OK;
size_t read_size = 0;
// 打开文件进行读取
fp = fopen("menu_persistence.dat", "rb");
if (fp == NULL) {
printf("%s: %s\n", get_text(TEXT_WARNING), "文件不存在,使用默认设置");
return MENU_ERR_OPERATION_FAILED;
}
// 获取文件大小
fseek(fp, 0, SEEK_END);
read_size = ftell(fp);
fseek(fp, 0, SEEK_SET);
// 检查文件大小是否合法
if (read_size > *size) {
printf("%s: %s\n", get_text(TEXT_ERROR), "文件过大");
fclose(fp);
return MENU_ERR_INVALID_PARAM;
}
// 读取数据
if (fread(data, 1, read_size, fp) != read_size) {
printf("%s: %s\n", get_text(TEXT_ERROR), "读取文件失败");
ret = MENU_ERR_OPERATION_FAILED;
} else {
// 更新实际读取的大小
*size = (uint16_t)read_size;
}
// 关闭文件
fclose(fp);
return ret;
}
/**
* @brief 硬件驱动配置
*/
static MenuPortDriver sg_demo_driver = {
.printf = demo_printf,
.get_tick = demo_get_tick,
.delay_ms = NULL,
.display = NULL,
.key_scan = NULL,
.irq_ctrl = NULL,
.error_handler = NULL,
.modbus_send = NULL,
.modbus_receive = NULL,
.persistence_save = demo_persistence_save,
.persistence_restore = demo_persistence_restore,
};
/**
* @brief 演示菜单导航
*/
/**
* @brief 显示当前菜单的子菜单项
*/
static void display_menu_items(void)
{
MenuNodeId curr_node_id = menu_get_current_node();
const MenuNode* curr_node = menu_find_node(curr_node_id);
if (curr_node == NULL) {
printf("\n无效节点\n");
return;
}
// 清屏(可选,根据终端支持情况)
printf("\033[2J\033[H");
// 显示当前菜单路径
printf("%s\n", get_text(TEXT_MAIN_MENU));
printf("==================\n");
// 显示当前菜单状态
printf("%s: %s\n", get_text(TEXT_CURRENT), curr_node->name ? curr_node->name : "Unnamed");
// 显示导航路径
uint8_t path_depth = menu_get_nav_depth();
MenuNodeId path[MENU_CONFIG_STACK_DEPTH];
menu_get_nav_path(path, MENU_CONFIG_STACK_DEPTH);
printf("%s: ", get_text(TEXT_STATUS));
for (uint8_t i = 0; i < path_depth; i++) {
const MenuNode* path_node = menu_find_node(path[i]);
if (path_node != NULL) {
printf("%s", path_node->name ? path_node->name : "Unnamed");
if (i < path_depth - 1) {
printf(" -> ");
}
}
}
printf("\n\n");
// 显示当前菜单的子菜单项
printf("%s:\n", get_text(TEXT_SUMMARY));
if (curr_node->first_child_id != 0) {
// 遍历所有子节点
const MenuNode* child_node = menu_find_node(curr_node->first_child_id);
uint8_t item_index = 1;
while (child_node != NULL) {
// 子菜单仅显示列表,不高亮选中项(高亮在同级节点中处理)
printf(" %d. %s\n", item_index, child_node->name ? child_node->name : "Unnamed");
MenuNodeId next_id = child_node->next_sibling_id;
if (next_id == 0) {
break;
}
child_node = menu_find_node(next_id);
item_index++;
}
} else {
printf(" %s\n", get_text(TEXT_EMPTY));
}
// 显示当前节点在同级节点中的位置
const MenuNode* parent_node = menu_find_node(curr_node->parent_id);
if (parent_node != NULL) {
printf("\n%s:\n", get_text(TEXT_CURRENT_SELECTION));
// 遍历所有兄弟节点
const MenuNode* sibling_node = menu_find_node(parent_node->first_child_id);
uint8_t item_index = 1;
while (sibling_node != NULL) {
// 检查当前显示的节点是否为选中节点
bool is_selected = (sibling_node->id == curr_node_id);
// 高亮显示当前选中的菜单项
if (is_selected) {
printf(" **> %d. %s <**\n",
item_index, sibling_node->name ? sibling_node->name : "Unnamed");
} else {
printf(" %d. %s\n", item_index, sibling_node->name ? sibling_node->name : "Unnamed");
}
MenuNodeId next_id = sibling_node->next_sibling_id;
if (next_id == 0) {
break;
}
sibling_node = menu_find_node(next_id);
item_index++;
}
}
// 显示菜单操作提示
printf("\n%s:\n", get_text(TEXT_OPERATIONS));
printf(" ↑/%s: %s\n", get_text(TEXT_DOWN_ARROW), get_text(TEXT_SELECT_ITEM));
printf(" %s: %s\n", get_text(TEXT_ENTER), get_text(TEXT_ENTER_SUBMENU));
printf(" Esc/%s: %s\n", get_text(TEXT_LEFT_ARROW), get_text(TEXT_RETURN_PARENT));
printf(" q: %s\n", get_text(TEXT_EXIT));
printf("==================\n");
}
/**
* @brief 演示菜单导航
*/
static void demo_menu_navigation(void)
{
char key = 0;
// 设置终端为无缓冲模式仅在非Windows平台
#ifndef _WIN32
set_nonblocking_terminal();
#endif
printf("\n%s\n", get_text(TEXT_MAIN_MENU));
printf("==================\n");
printf("%s: %s\n", get_text(TEXT_SELECT_OPTION), get_text(TEXT_MAIN_MENU));
printf("Enter: %s\n", get_text(TEXT_ENTER));
printf("Esc: %s\n", get_text(TEXT_EXIT));
printf("q: %s\n", get_text(TEXT_EXIT));
printf("%s: %s\n", get_text(TEXT_UP_ARROW), get_text(TEXT_SELECT_PREVIOUS));
printf("%s: %s\n", get_text(TEXT_DOWN_ARROW), get_text(TEXT_SELECT_NEXT));
printf("%s: %s\n", get_text(TEXT_RIGHT_ARROW), get_text(TEXT_ENTER_SUBMENU));
printf("%s: %s\n", get_text(TEXT_LEFT_ARROW), get_text(TEXT_RETURN_PARENT));
printf("==================\n");
// 初始化显示
display_menu_items();
while (1) {
// 持续处理菜单主循环,确保事件得到及时处理
menu_main_loop(demo_get_tick());
// 检查是否有按键输入
if (IS_KEY_AVAILABLE()) {
// 获取按键输入(无缓冲)
key = NON_BLOCKING_GETCHAR();
// 处理退出键
if (key == 'q' || key == 'Q') {
#ifndef _WIN32
restore_terminal();
#endif
return;
}
// 处理回车键
else if (key == '\n' || key == '\r') {
// 不管节点是否有子菜单,都处理回车事件
menu_post_event(MENU_EVENT_KEY_ENTER, 1);
// 处理菜单主循环
menu_main_loop(demo_get_tick());
// 显示当前菜单状态和菜单项
display_menu_items();
}
// 处理Esc键和箭头键
else if (key == '\x1B') {
// 读取完整的Esc序列
char esc_key = NON_BLOCKING_GETCHAR();
if (esc_key == '[') {
key = NON_BLOCKING_GETCHAR();
switch (key) {
case 'A': // 上箭头 - 选择上一项
menu_post_event(MENU_EVENT_KEY_UP, 1);
break;
case 'B': // 下箭头 - 选择下一项
menu_post_event(MENU_EVENT_KEY_DOWN, 1);
break;
case 'C': // 右箭头 - 进入子菜单
menu_post_event(MENU_EVENT_KEY_ENTER, 1);
break;
case 'D': // 左箭头 - 返回上一级菜单
menu_post_event(MENU_EVENT_KEY_ESC, 1);
break;
default:
break;
}
// 处理菜单主循环
menu_main_loop(demo_get_tick());
// 显示当前菜单状态和菜单项
display_menu_items();
} else {
// 单独的Esc键 - 返回上一级菜单
menu_post_event(MENU_EVENT_KEY_ESC, 1);
// 处理菜单主循环
menu_main_loop(demo_get_tick());
// 显示当前菜单状态和菜单项
display_menu_items();
}
}
// 处理普通字符输入
else {
// 可以根据需要添加其他字符处理逻辑
}
}
// 短延迟避免CPU占用过高
#ifdef _WIN32
Sleep(10);
#else
usleep(10000); // 10ms
#endif
}
}
/**
* @brief 主函数
*/
int main(int argc, char* argv[])
{
(void)argc;
(void)argv;
// 初始化多语言系统
set_language(SYSTEM_LANG_CHINESE);
printf("%s\n", get_text(TEXT_MAIN_MENU));
printf("==================\n");
// 初始化硬件端口层
menu_port_init(&sg_demo_driver);
// 初始化菜单组件
MenuErrCode err = menu_init();
if (err != MENU_ERR_OK) {
printf("%s\n", get_text(TEXT_TEST));
return EXIT_FAILURE;
}
// 注册菜单节点
// 主菜单
MenuNodeId main_node_id = menu_register_node(0, 0, get_text(TEXT_MAIN_MENU), menu_enter_main, menu_exit_main);
// 菜单项
MenuNodeId item1_node_id = menu_register_node(0, main_node_id, get_text(TEXT_MAIN_MEASUREMENT), menu_enter_item1, NULL);
MenuNodeId item2_node_id = menu_register_node(0, main_node_id, get_text(TEXT_MAIN_PROTECTION), menu_enter_item2, NULL);
MenuNodeId item3_node_id = menu_register_node(0, main_node_id, get_text(TEXT_MAIN_CONTROL), menu_enter_item3, NULL);
MenuNodeId sub1_node_id = menu_register_node(0, main_node_id, get_text(TEXT_MAIN_SETTING), menu_enter_setting, NULL);
// 测量菜单子项
menu_register_node(0, item1_node_id, get_text(TEXT_MEASURE_VOLTAGE), NULL, NULL);
menu_register_node(0, item1_node_id, get_text(TEXT_MEASURE_CURRENT), NULL, NULL);
menu_register_node(0, item1_node_id, get_text(TEXT_MEASURE_POWER), NULL, NULL);
menu_register_node(0, item1_node_id, get_text(TEXT_MEASURE_ENERGY), NULL, NULL);
menu_register_node(0, item1_node_id, get_text(TEXT_MEASURE_FREQUENCY), NULL, NULL);
menu_register_node(0, item1_node_id, get_text(TEXT_MEASURE_TEMPERATURE), NULL, NULL);
// 保护菜单子项
menu_register_node(0, item2_node_id, get_text(TEXT_PROTECT_OVER_VOLTAGE), NULL, NULL);
menu_register_node(0, item2_node_id, get_text(TEXT_PROTECT_UNDER_VOLTAGE), NULL, NULL);
menu_register_node(0, item2_node_id, get_text(TEXT_PROTECT_OVER_CURRENT), NULL, NULL);
menu_register_node(0, item2_node_id, get_text(TEXT_PROTECT_SETTING), NULL, NULL);
// 控制菜单子项
menu_register_node(0, item3_node_id, get_text(TEXT_CONTROL_ON_OFF), NULL, NULL);
menu_register_node(0, item3_node_id, get_text(TEXT_CONTROL_START_STOP), NULL, NULL);
menu_register_node(0, item3_node_id, get_text(TEXT_CONTROL_AUTO_MANUAL), NULL, NULL);
// 设置菜单子项
menu_register_node(0, sub1_node_id, get_text(TEXT_SETTING_LANGUAGE), menu_enter_language, NULL);
// 注册持久化回调函数
err = menu_persistence_register_callback(demo_persistence_save, demo_persistence_restore);
if (err != MENU_ERR_OK) {
printf("%s: %s\n", get_text(TEXT_ERROR), get_text(TEXT_REGISTER_CALLBACK_FAILED));
}
// 尝试恢复之前的菜单状态
err = menu_persistence_restore();
if (err == MENU_ERR_OK) {
printf("%s: %s\n", get_text(TEXT_SUCCESS), get_text(TEXT_RESTORE_MENU_STATE));
} else {
printf("%s: %s: Error %d\n", get_text(TEXT_ERROR), get_text(TEXT_RESTORE_MENU_STATE_FAILED), err);
printf("%s\n", get_text(TEXT_START_DEFAULT));
}
printf("%s\n", get_text(TEXT_TEST));
// 运行菜单演示
demo_menu_navigation();
// 保存当前菜单状态
err = menu_persistence_save();
if (err == MENU_ERR_OK) {
printf("%s: %s\n", get_text(TEXT_SUCCESS), get_text(TEXT_SAVE_MENU_STATE));
} else {
printf("%s: %s: Error %d\n", get_text(TEXT_ERROR), get_text(TEXT_SAVE_MENU_STATE_FAILED), err);
}
// 清理资源
menu_deinit();
menu_port_deinit();
printf("\n%s\n", get_text(TEXT_EXIT));
return EXIT_SUCCESS;
}

525
demo/language.c Normal file
View File

@ -0,0 +1,525 @@
#include "language.h"
/* 全局变量定义 ---------------------------------------------------------------------------------------------------*/
/**
* @brief 当前系统语言
*/
static SystemLang_e sg_current_language = SYSTEM_LANG_CHINESE;
/**
* @brief 中文文本数组
*/
static const char *sg_chinese_text[] = {
[TEXT_ENTER] = "进入",
[TEXT_EXIT] = "退出",
[TEXT_RETURN] = "返回",
[TEXT_OK] = "确定",
[TEXT_CANCEL] = "取消",
[TEXT_SAVE] = "保存",
[TEXT_LOAD] = "加载",
[TEXT_MAIN_MENU] = "主菜单",
[TEXT_MAIN_MEASUREMENT] = "测量",
[TEXT_MAIN_PROTECTION] = "保护",
[TEXT_MAIN_CONTROL] = "控制",
[TEXT_MAIN_SETTING] = "设置",
[TEXT_MAIN_RECORD] = "记录",
[TEXT_MAIN_HELP] = "帮助",
[TEXT_MAIN_ABOUT] = "关于",
[TEXT_MEASURE_VOLTAGE] = "电压",
[TEXT_MEASURE_CURRENT] = "电流",
[TEXT_MEASURE_POWER] = "功率",
[TEXT_MEASURE_ENERGY] = "能量",
[TEXT_MEASURE_FREQUENCY] = "频率",
[TEXT_MEASURE_TEMPERATURE] = "温度",
[TEXT_PROTECT_OVER_VOLTAGE] = "过电压保护",
[TEXT_PROTECT_UNDER_VOLTAGE] = "欠电压保护",
[TEXT_PROTECT_OVER_CURRENT] = "过电流保护",
[TEXT_PROTECT_SETTING] = "保护设置",
[TEXT_CONTROL_ON_OFF] = "开关控制",
[TEXT_CONTROL_START_STOP] = "启停控制",
[TEXT_CONTROL_AUTO_MANUAL] = "自动/手动",
[TEXT_SETTING_LANGUAGE] = "语言设置",
[TEXT_RECORD_EVENT] = "事件记录",
[TEXT_HELP_GUIDE] = "指南",
[TEXT_ABOUT_VERSION] = "版本信息",
[TEXT_PERSIST_SAVE_STATE] = "保存菜单状态",
[TEXT_PERSIST_RESTORE_STATE] = "恢复菜单状态",
[TEXT_PERSIST_SAVE_SUCCESS] = "保存成功",
[TEXT_PERSIST_SAVE_FAILED] = "保存失败",
[TEXT_PERSIST_RESTORE_SUCCESS] = "恢复成功",
[TEXT_PERSIST_RESTORE_FAILED] = "恢复失败",
[TEXT_ERROR] = "错误",
[TEXT_WARNING] = "警告",
[TEXT_SUCCESS] = "成功",
[TEXT_INFO] = "信息",
[TEXT_TEST] = "测试",
[TEXT_SELECT_OPTION] = "选择操作",
[TEXT_CURRENT] = "当前",
[TEXT_STATUS] = "状态",
[TEXT_SUMMARY] = "摘要",
[TEXT_EMPTY] = "",
[TEXT_CURRENT_SELECTION] = "当前选择",
[TEXT_OPERATIONS] = "操作",
[TEXT_SELECT_ITEM] = "选择项",
[TEXT_ENTER_SUBMENU] = "进入子菜单",
[TEXT_RETURN_PARENT] = "返回父菜单",
[TEXT_SELECT_PREVIOUS] = "选择上一项",
[TEXT_SELECT_NEXT] = "选择下一项",
[TEXT_START_DEFAULT] = "从默认开始",
[TEXT_REGISTER_CALLBACK_FAILED] = "注册回调失败",
[TEXT_RESTORE_MENU_STATE] = "恢复菜单状态",
[TEXT_RESTORE_MENU_STATE_FAILED] = "恢复菜单状态失败",
[TEXT_SAVE_MENU_STATE_FAILED] = "保存菜单状态失败",
[TEXT_UP_ARROW] = "上箭头",
[TEXT_DOWN_ARROW] = "下箭头",
[TEXT_LEFT_ARROW] = "左箭头",
[TEXT_RIGHT_ARROW] = "右箭头",
};
/**
* @brief 英文文本数组
*/
static const char *sg_english_text[] = {
[TEXT_ENTER] = "Enter",
[TEXT_EXIT] = "Exit",
[TEXT_RETURN] = "Return",
[TEXT_OK] = "OK",
[TEXT_CANCEL] = "Cancel",
[TEXT_SAVE] = "Save",
[TEXT_LOAD] = "Load",
[TEXT_MAIN_MENU] = "Main Menu",
[TEXT_MAIN_MEASUREMENT] = "Measurement",
[TEXT_MAIN_PROTECTION] = "Protection",
[TEXT_MAIN_CONTROL] = "Control",
[TEXT_MAIN_SETTING] = "Setting",
[TEXT_MAIN_RECORD] = "Record",
[TEXT_MAIN_HELP] = "Help",
[TEXT_MAIN_ABOUT] = "About",
[TEXT_MEASURE_VOLTAGE] = "Voltage",
[TEXT_MEASURE_CURRENT] = "Current",
[TEXT_MEASURE_POWER] = "Power",
[TEXT_MEASURE_ENERGY] = "Energy",
[TEXT_MEASURE_FREQUENCY] = "Frequency",
[TEXT_MEASURE_TEMPERATURE] = "Temperature",
[TEXT_PROTECT_OVER_VOLTAGE] = "Over Voltage Protection",
[TEXT_PROTECT_UNDER_VOLTAGE] = "Under Voltage Protection",
[TEXT_PROTECT_OVER_CURRENT] = "Over Current Protection",
[TEXT_PROTECT_SETTING] = "Protection Setting",
[TEXT_CONTROL_ON_OFF] = "On/Off",
[TEXT_CONTROL_START_STOP] = "Start/Stop",
[TEXT_CONTROL_AUTO_MANUAL] = "Auto/Manual",
[TEXT_SETTING_LANGUAGE] = "Language Setting",
[TEXT_RECORD_EVENT] = "Event Record",
[TEXT_HELP_GUIDE] = "Guide",
[TEXT_ABOUT_VERSION] = "Version Info",
[TEXT_PERSIST_SAVE_STATE] = "Save Menu State",
[TEXT_PERSIST_RESTORE_STATE] = "Restore Menu State",
[TEXT_PERSIST_SAVE_SUCCESS] = "Save Success",
[TEXT_PERSIST_SAVE_FAILED] = "Save Failed",
[TEXT_PERSIST_RESTORE_SUCCESS] = "Restore Success",
[TEXT_PERSIST_RESTORE_FAILED] = "Restore Failed",
[TEXT_ERROR] = "Error",
[TEXT_WARNING] = "Warning",
[TEXT_SUCCESS] = "Success",
[TEXT_INFO] = "Info",
[TEXT_TEST] = "Test",
[TEXT_SELECT_OPTION] = "Select Option",
[TEXT_CURRENT] = "Current",
[TEXT_STATUS] = "Status",
[TEXT_SUMMARY] = "Summary",
[TEXT_EMPTY] = "Empty",
[TEXT_CURRENT_SELECTION] = "Current Selection",
[TEXT_OPERATIONS] = "Operations",
[TEXT_SELECT_ITEM] = "Select Item",
[TEXT_ENTER_SUBMENU] = "Enter Submenu",
[TEXT_RETURN_PARENT] = "Return Parent",
[TEXT_SELECT_PREVIOUS] = "Select Previous",
[TEXT_SELECT_NEXT] = "Select Next",
[TEXT_START_DEFAULT] = "Start Default",
[TEXT_REGISTER_CALLBACK_FAILED] = "Register Callback Failed",
[TEXT_RESTORE_MENU_STATE] = "Restore Menu State",
[TEXT_RESTORE_MENU_STATE_FAILED] = "Restore Menu State Failed",
[TEXT_SAVE_MENU_STATE_FAILED] = "Save Menu State Failed",
[TEXT_UP_ARROW] = "Up Arrow",
[TEXT_DOWN_ARROW] = "Down Arrow",
[TEXT_LEFT_ARROW] = "Left Arrow",
[TEXT_RIGHT_ARROW] = "Right Arrow",
};
/**
* @brief 日语文本数组
*/
static const char *sg_japanese_text[] = {
[TEXT_ENTER] = "入る",
[TEXT_EXIT] = "退出",
[TEXT_RETURN] = "戻る",
[TEXT_OK] = "OK",
[TEXT_CANCEL] = "キャンセル",
[TEXT_SAVE] = "保存",
[TEXT_LOAD] = "読み込む",
[TEXT_MAIN_MENU] = "メインメニュー",
[TEXT_MAIN_MEASUREMENT] = "計測",
[TEXT_MAIN_PROTECTION] = "保護",
[TEXT_MAIN_CONTROL] = "制御",
[TEXT_MAIN_SETTING] = "設定",
[TEXT_MAIN_RECORD] = "記録",
[TEXT_MAIN_HELP] = "ヘルプ",
[TEXT_MAIN_ABOUT] = "について",
[TEXT_MEASURE_VOLTAGE] = "電圧",
[TEXT_MEASURE_CURRENT] = "電流",
[TEXT_MEASURE_POWER] = "電力",
[TEXT_MEASURE_ENERGY] = "エネルギー",
[TEXT_MEASURE_FREQUENCY] = "周波数",
[TEXT_MEASURE_TEMPERATURE] = "温度",
[TEXT_PROTECT_OVER_VOLTAGE] = "過電圧保護",
[TEXT_PROTECT_UNDER_VOLTAGE] = "不足電圧保護",
[TEXT_PROTECT_OVER_CURRENT] = "過電流保護",
[TEXT_PROTECT_SETTING] = "保護設定",
[TEXT_CONTROL_ON_OFF] = "オン/オフ",
[TEXT_CONTROL_START_STOP] = "スタート/ストップ",
[TEXT_CONTROL_AUTO_MANUAL] = "オート/マニュアル",
[TEXT_SETTING_LANGUAGE] = "言語設定",
[TEXT_RECORD_EVENT] = "イベント記録",
[TEXT_HELP_GUIDE] = "ガイド",
[TEXT_ABOUT_VERSION] = "バージョン情報",
[TEXT_PERSIST_SAVE_STATE] = "メニュー状態を保存",
[TEXT_PERSIST_RESTORE_STATE] = "メニュー状態を復元",
[TEXT_PERSIST_SAVE_SUCCESS] = "保存成功",
[TEXT_PERSIST_SAVE_FAILED] = "保存失敗",
[TEXT_PERSIST_RESTORE_SUCCESS] = "復元成功",
[TEXT_PERSIST_RESTORE_FAILED] = "復元失敗",
[TEXT_ERROR] = "エラー",
[TEXT_WARNING] = "警告",
[TEXT_SUCCESS] = "成功",
[TEXT_INFO] = "情報",
[TEXT_TEST] = "テスト",
[TEXT_SELECT_OPTION] = "オプションを選択",
[TEXT_CURRENT] = "現在",
[TEXT_STATUS] = "ステータス",
[TEXT_SUMMARY] = "サマリー",
[TEXT_EMPTY] = "",
[TEXT_CURRENT_SELECTION] = "現在の選択",
[TEXT_OPERATIONS] = "操作",
[TEXT_SELECT_ITEM] = "項目を選択",
[TEXT_ENTER_SUBMENU] = "サブメニューに入る",
[TEXT_RETURN_PARENT] = "親メニューに戻る",
[TEXT_SELECT_PREVIOUS] = "前の項目を選択",
[TEXT_SELECT_NEXT] = "次の項目を選択",
[TEXT_START_DEFAULT] = "デフォルトから開始",
[TEXT_REGISTER_CALLBACK_FAILED] = "コールバック登録失敗",
[TEXT_RESTORE_MENU_STATE] = "メニュー状態を復元",
[TEXT_RESTORE_MENU_STATE_FAILED] = "メニュー状態復元失敗",
[TEXT_SAVE_MENU_STATE_FAILED] = "メニュー状態保存失敗",
[TEXT_UP_ARROW] = "上矢印",
[TEXT_DOWN_ARROW] = "下矢印",
[TEXT_LEFT_ARROW] = "左矢印",
[TEXT_RIGHT_ARROW] = "右矢印",
};
/**
* @brief 德语文本数组
*/
static const char *sg_german_text[] = {
[TEXT_ENTER] = "Eingeben",
[TEXT_EXIT] = "Verlassen",
[TEXT_RETURN] = "Zurück",
[TEXT_OK] = "OK",
[TEXT_CANCEL] = "Abbrechen",
[TEXT_SAVE] = "Speichern",
[TEXT_LOAD] = "Laden",
[TEXT_MAIN_MENU] = "Hauptmenü",
[TEXT_MAIN_MEASUREMENT] = "Messung",
[TEXT_MAIN_PROTECTION] = "Schutz",
[TEXT_MAIN_CONTROL] = "Steuerung",
[TEXT_MAIN_SETTING] = "Einstellung",
[TEXT_MAIN_RECORD] = "Aufzeichnung",
[TEXT_MAIN_HELP] = "Hilfe",
[TEXT_MAIN_ABOUT] = "Über",
[TEXT_MEASURE_VOLTAGE] = "Spannung",
[TEXT_MEASURE_CURRENT] = "Strom",
[TEXT_MEASURE_POWER] = "Leistung",
[TEXT_MEASURE_ENERGY] = "Energie",
[TEXT_MEASURE_FREQUENCY] = "Frequenz",
[TEXT_MEASURE_TEMPERATURE] = "Temperatur",
[TEXT_PROTECT_OVER_VOLTAGE] = "Überspannungsschutz",
[TEXT_PROTECT_UNDER_VOLTAGE] = "Unterspannungsschutz",
[TEXT_PROTECT_OVER_CURRENT] = "Überstromschutz",
[TEXT_PROTECT_SETTING] = "Schutz-Einstellung",
[TEXT_CONTROL_ON_OFF] = "Ein/Aus",
[TEXT_CONTROL_START_STOP] = "Start/Stopp",
[TEXT_CONTROL_AUTO_MANUAL] = "Auto/Manuell",
[TEXT_SETTING_LANGUAGE] = "Spracheinstellung",
[TEXT_RECORD_EVENT] = "Ereignisaufzeichnung",
[TEXT_HELP_GUIDE] = "Anleitung",
[TEXT_ABOUT_VERSION] = "Versionsinfo",
[TEXT_PERSIST_SAVE_STATE] = "Menüzustand speichern",
[TEXT_PERSIST_RESTORE_STATE] = "Menüzustand wiederherstellen",
[TEXT_PERSIST_SAVE_SUCCESS] = "Speichern erfolgreich",
[TEXT_PERSIST_SAVE_FAILED] = "Speichern fehlgeschlagen",
[TEXT_PERSIST_RESTORE_SUCCESS] = "Wiederherstellen erfolgreich",
[TEXT_PERSIST_RESTORE_FAILED] = "Wiederherstellen fehlgeschlagen",
[TEXT_ERROR] = "Fehler",
[TEXT_WARNING] = "Warnung",
[TEXT_SUCCESS] = "Erfolg",
[TEXT_INFO] = "Info",
[TEXT_TEST] = "Test",
[TEXT_SELECT_OPTION] = "Option wählen",
[TEXT_CURRENT] = "Aktuell",
[TEXT_STATUS] = "Status",
[TEXT_SUMMARY] = "Zusammenfassung",
[TEXT_EMPTY] = "Leer",
[TEXT_CURRENT_SELECTION] = "Aktuelle Auswahl",
[TEXT_OPERATIONS] = "Operationen",
[TEXT_SELECT_ITEM] = "Element auswählen",
[TEXT_ENTER_SUBMENU] = "In Untermenü eintreten",
[TEXT_RETURN_PARENT] = "Zu übergeordnetem Menü zurückkehren",
[TEXT_SELECT_PREVIOUS] = "Vorheriges auswählen",
[TEXT_SELECT_NEXT] = "Nächstes auswählen",
[TEXT_START_DEFAULT] = "Standard starten",
[TEXT_REGISTER_CALLBACK_FAILED] = "Rückrufregistrierung fehlgeschlagen",
[TEXT_RESTORE_MENU_STATE] = "Menüzustand wiederherstellen",
[TEXT_RESTORE_MENU_STATE_FAILED] = "Wiederherstellung des Menüzustands fehlgeschlagen",
[TEXT_SAVE_MENU_STATE_FAILED] = "Speichern des Menüzustands fehlgeschlagen",
[TEXT_UP_ARROW] = "Pfeil nach oben",
[TEXT_DOWN_ARROW] = "Pfeil nach unten",
[TEXT_LEFT_ARROW] = "Pfeil nach links",
[TEXT_RIGHT_ARROW] = "Pfeil nach rechts",
};
/**
* @brief 语言文本映射表
*/
static const char **sg_language_text_map[] = {
[SYSTEM_LANG_CHINESE] = sg_chinese_text,
[SYSTEM_LANG_ENGLISH] = sg_english_text,
[SYSTEM_LANG_JAPANESE] = sg_japanese_text,
[SYSTEM_LANG_GERMAN] = sg_german_text,
};
/**
* @brief 全局菜单节点映射表
*/
const MenuNodeInfo_t menu_node_map[] = {
// 一级菜单
{MENU_NODE_ID_MAIN, MENU_NODE_ID_NONE, TEXT_MAIN_MENU, 1, MENU_NODE_ATTR_HAS_CHILDREN | MENU_NODE_ATTR_VISIBLE},
{MENU_NODE_ID_MEASUREMENT, MENU_NODE_ID_MAIN, TEXT_MAIN_MEASUREMENT, 1, MENU_NODE_ATTR_HAS_CHILDREN | MENU_NODE_ATTR_VISIBLE},
{MENU_NODE_ID_PROTECTION, MENU_NODE_ID_MAIN, TEXT_MAIN_PROTECTION, 1, MENU_NODE_ATTR_HAS_CHILDREN | MENU_NODE_ATTR_VISIBLE},
{MENU_NODE_ID_CONTROL, MENU_NODE_ID_MAIN, TEXT_MAIN_CONTROL, 1, MENU_NODE_ATTR_OPERABLE | MENU_NODE_ATTR_VISIBLE},
{MENU_NODE_ID_SETTING, MENU_NODE_ID_MAIN, TEXT_MAIN_SETTING, 1, MENU_NODE_ATTR_HAS_CHILDREN | MENU_NODE_ATTR_VISIBLE},
{MENU_NODE_ID_RECORD, MENU_NODE_ID_MAIN, TEXT_MAIN_RECORD, 1, MENU_NODE_ATTR_HAS_CHILDREN | MENU_NODE_ATTR_VISIBLE},
{MENU_NODE_ID_HELP, MENU_NODE_ID_MAIN, TEXT_MAIN_HELP, 1, MENU_NODE_ATTR_HAS_CHILDREN | MENU_NODE_ATTR_VISIBLE},
{MENU_NODE_ID_ABOUT, MENU_NODE_ID_MAIN, TEXT_MAIN_ABOUT, 1, MENU_NODE_ATTR_HAS_CHILDREN | MENU_NODE_ATTR_VISIBLE},
// 二级菜单:测量菜单子项
{MENU_NODE_ID_MEASUREMENT_VOLTAGE, MENU_NODE_ID_MEASUREMENT, TEXT_MEASURE_VOLTAGE, 2, MENU_NODE_ATTR_OPERABLE | MENU_NODE_ATTR_VISIBLE},
{MENU_NODE_ID_MEASUREMENT_CURRENT, MENU_NODE_ID_MEASUREMENT, TEXT_MEASURE_CURRENT, 2, MENU_NODE_ATTR_OPERABLE | MENU_NODE_ATTR_VISIBLE},
{MENU_NODE_ID_MEASUREMENT_POWER, MENU_NODE_ID_MEASUREMENT, TEXT_MEASURE_POWER, 2, MENU_NODE_ATTR_OPERABLE | MENU_NODE_ATTR_VISIBLE},
{MENU_NODE_ID_MEASUREMENT_ENERGY, MENU_NODE_ID_MEASUREMENT, TEXT_MEASURE_ENERGY, 2, MENU_NODE_ATTR_OPERABLE | MENU_NODE_ATTR_VISIBLE},
{MENU_NODE_ID_MEASUREMENT_FREQUENCY, MENU_NODE_ID_MEASUREMENT, TEXT_MEASURE_FREQUENCY, 2, MENU_NODE_ATTR_OPERABLE | MENU_NODE_ATTR_VISIBLE},
{MENU_NODE_ID_MEASUREMENT_TEMPERATURE, MENU_NODE_ID_MEASUREMENT, TEXT_MEASURE_TEMPERATURE, 2, MENU_NODE_ATTR_OPERABLE | MENU_NODE_ATTR_VISIBLE},
// 二级菜单:保护菜单子项
{MENU_NODE_ID_PROTECTION_OVER_VOLTAGE, MENU_NODE_ID_PROTECTION, TEXT_PROTECT_OVER_VOLTAGE, 2, MENU_NODE_ATTR_OPERABLE | MENU_NODE_ATTR_VISIBLE},
{MENU_NODE_ID_PROTECTION_UNDER_VOLTAGE, MENU_NODE_ID_PROTECTION, TEXT_PROTECT_UNDER_VOLTAGE, 2, MENU_NODE_ATTR_OPERABLE | MENU_NODE_ATTR_VISIBLE},
{MENU_NODE_ID_PROTECTION_OVER_CURRENT, MENU_NODE_ID_PROTECTION, TEXT_PROTECT_OVER_CURRENT, 2, MENU_NODE_ATTR_OPERABLE | MENU_NODE_ATTR_VISIBLE},
{MENU_NODE_ID_PROTECTION_SETTING, MENU_NODE_ID_PROTECTION, TEXT_PROTECT_SETTING, 2, MENU_NODE_ATTR_OPERABLE | MENU_NODE_ATTR_VISIBLE},
// 二级菜单:控制菜单子项
{MENU_NODE_ID_CONTROL_ON_OFF, MENU_NODE_ID_CONTROL, TEXT_CONTROL_ON_OFF, 2, MENU_NODE_ATTR_OPERABLE | MENU_NODE_ATTR_VISIBLE},
{MENU_NODE_ID_CONTROL_START_STOP, MENU_NODE_ID_CONTROL, TEXT_CONTROL_START_STOP, 2, MENU_NODE_ATTR_OPERABLE | MENU_NODE_ATTR_VISIBLE},
{MENU_NODE_ID_CONTROL_AUTO_MANUAL, MENU_NODE_ID_CONTROL, TEXT_CONTROL_AUTO_MANUAL, 2, MENU_NODE_ATTR_OPERABLE | MENU_NODE_ATTR_VISIBLE},
// 二级菜单:其他子项
{MENU_NODE_ID_SETTING_LANGUAGE, MENU_NODE_ID_SETTING, TEXT_SETTING_LANGUAGE, 2, MENU_NODE_ATTR_OPERABLE | MENU_NODE_ATTR_VISIBLE},
{MENU_NODE_ID_RECORD_EVENT, MENU_NODE_ID_RECORD, TEXT_RECORD_EVENT, 2, MENU_NODE_ATTR_OPERABLE | MENU_NODE_ATTR_VISIBLE},
{MENU_NODE_ID_HELP_GUIDE, MENU_NODE_ID_HELP, TEXT_HELP_GUIDE, 2, MENU_NODE_ATTR_OPERABLE | MENU_NODE_ATTR_VISIBLE},
{MENU_NODE_ID_ABOUT_VERSION, MENU_NODE_ID_ABOUT, TEXT_ABOUT_VERSION, 2, MENU_NODE_ATTR_OPERABLE | MENU_NODE_ATTR_VISIBLE},
};
/**
* @brief 菜单节点映射表大小
*/
const uint32_t menu_node_map_size = sizeof(menu_node_map) / sizeof(menu_node_map[0]);
/* 函数实现 ---------------------------------------------------------------------------------------------------------*/
/**
* @brief 初始化语言模块
*/
MenuErrCode language_init(void)
{
// 初始化核心菜单库语言模块
MenuErrCode ret = menu_lang_init();
if (ret != MENU_ERR_OK)
{
return ret;
}
// 设置默认语言
return set_language(SYSTEM_LANG_CHINESE);
}
/**
* @brief 根据文本ID获取对应语言的文本字符串
*/
const char* get_text(TextId_e text_id)
{
if (text_id >= TEXT_MAX)
{
return NULL;
}
if (sg_current_language >= SYSTEM_LANG_MAX)
{
return NULL;
}
return sg_language_text_map[sg_current_language][text_id];
}
/**
* @brief 设置当前系统语言
*/
MenuErrCode set_language(SystemLang_e lang)
{
if (lang >= SYSTEM_LANG_MAX)
{
return MENU_ERR_INVALID_PARAM;
}
sg_current_language = lang;
// 同步到核心菜单库
return menu_lang_set_current((LangId)lang);
}
/**
* @brief 获取当前系统语言
*/
SystemLang_e get_current_language(void)
{
return sg_current_language;
}
/**
* @brief 根据菜单节点ID获取对应的文本ID
*/
TextId_e get_menu_text_id(MenuNodeId_e node_id)
{
for (uint32_t i = 0; i < menu_node_map_size; i++)
{
if (menu_node_map[i].node_id == node_id)
{
return menu_node_map[i].text_id;
}
}
return TEXT_MAX;
}
/**
* @brief 根据菜单节点ID获取对应的父节点ID
*/
MenuNodeId_e get_menu_parent_id(MenuNodeId_e node_id)
{
for (uint32_t i = 0; i < menu_node_map_size; i++)
{
if (menu_node_map[i].node_id == node_id)
{
return menu_node_map[i].parent_id;
}
}
return MENU_NODE_ID_NONE;
}
/**
* @brief 检查菜单节点是否有子节点
*/
uint8_t menu_node_has_children(MenuNodeId_e node_id)
{
for (uint32_t i = 0; i < menu_node_map_size; i++)
{
if (menu_node_map[i].node_id == node_id)
{
return MENU_NODE_HAS_ATTR(menu_node_map[i].attributes, MENU_NODE_ATTR_HAS_CHILDREN);
}
}
return 0;
}
/**
* @brief 检查菜单节点是否可操作
*/
uint8_t menu_node_is_operable(MenuNodeId_e node_id)
{
for (uint32_t i = 0; i < menu_node_map_size; i++)
{
if (menu_node_map[i].node_id == node_id)
{
return MENU_NODE_HAS_ATTR(menu_node_map[i].attributes, MENU_NODE_ATTR_OPERABLE);
}
}
return 0;
}
/**
* @brief 获取菜单节点的显示文本
*/
const char* get_menu_node_text(MenuNodeId_e node_id)
{
TextId_e text_id = get_menu_text_id(node_id);
if (text_id == TEXT_MAX)
{
return NULL;
}
return get_text(text_id);
}

311
demo/language.h Normal file
View File

@ -0,0 +1,311 @@
#ifndef LANGUAGE_H
#define LANGUAGE_H
/* Includes ----------------------------------------------------------------------------------------------------------*/
#include "../../src/lang/menu_lang.h"
#include "../../src/core/menu_core.h"
/* 类型定义 ---------------------------------------------------------------------------------------------------------*/
/**
* @brief 系统语言枚举
* @note 与核心菜单库语言ID映射便于扩展
*/
typedef enum
{
SYSTEM_LANG_CHINESE = 0, // 中文(默认)
SYSTEM_LANG_ENGLISH, // 英文
SYSTEM_LANG_JAPANESE, // 日语
SYSTEM_LANG_GERMAN, // 德语
SYSTEM_LANG_MAX, // 语言类型总数(仅用于遍历/边界判断)
} SystemLang_e;
/**
* @brief 文本ID枚举按模块分级
* @note 采用模块化命名,便于扩展和维护
* 分级规则:
* - 基础文本(0x0000~0x00FF)
* - 主菜单(0x0100~0x01FF)
* - 测量菜单(0x0200~0x02FF)
* - 保护菜单(0x0300~0x03FF)
* - 控制菜单(0x0400~0x04FF)
* - 设置菜单(0x0500~0x05FF)
* - 记录菜单(0x0600~0x06FF)
* - 帮助菜单(0x0700~0x07FF)
* - 关于菜单(0x0800~0x08FF)
* - 持久化相关(0x0900~0x09FF)
*/
typedef enum
{
// 基础文本0x0000 ~ 0x00FF
TEXT_ENTER = 0x0000, // 进入
TEXT_EXIT, // 退出
TEXT_RETURN, // 返回
TEXT_OK, // 确定
TEXT_CANCEL, // 取消
TEXT_SAVE, // 保存
TEXT_LOAD, // 加载
// 主菜单文本0x0100 ~ 0x01FF
TEXT_MAIN_MENU = 0x0100, // 主菜单
TEXT_MAIN_MEASUREMENT, // 测量
TEXT_MAIN_PROTECTION, // 保护
TEXT_MAIN_CONTROL, // 控制
TEXT_MAIN_SETTING, // 设置
TEXT_MAIN_RECORD, // 记录
TEXT_MAIN_HELP, // 帮助
TEXT_MAIN_ABOUT, // 关于
// 测量菜单文本0x0200 ~ 0x02FF
TEXT_MEASURE_VOLTAGE = 0x0200, // 电压
TEXT_MEASURE_CURRENT, // 电流
TEXT_MEASURE_POWER, // 功率
TEXT_MEASURE_ENERGY, // 能量
TEXT_MEASURE_FREQUENCY, // 频率
TEXT_MEASURE_TEMPERATURE, // 温度
// 保护菜单文本0x0300 ~ 0x03FF
TEXT_PROTECT_OVER_VOLTAGE = 0x0300, // 过电压保护
TEXT_PROTECT_UNDER_VOLTAGE, // 欠电压保护
TEXT_PROTECT_OVER_CURRENT, // 过电流保护
TEXT_PROTECT_SETTING, // 保护设置
// 控制菜单文本0x0400 ~ 0x04FF
TEXT_CONTROL_ON_OFF = 0x0400, // 开关控制
TEXT_CONTROL_START_STOP, // 启停控制
TEXT_CONTROL_AUTO_MANUAL, // 自动/手动切换
// 设置菜单文本0x0500 ~ 0x05FF
TEXT_SETTING_LANGUAGE = 0x0500, // 语言设置
// 记录菜单文本0x0600 ~ 0x06FF
TEXT_RECORD_EVENT = 0x0600, // 事件记录
// 帮助菜单文本0x0700 ~ 0x07FF
TEXT_HELP_GUIDE = 0x0700, // 指南
// 关于菜单文本0x0800 ~ 0x08FF
TEXT_ABOUT_VERSION = 0x0800, // 版本信息
// 持久化相关文本0x0900 ~ 0x09FF
TEXT_PERSIST_SAVE_STATE = 0x0900, // 保存菜单状态
TEXT_PERSIST_RESTORE_STATE, // 恢复菜单状态
TEXT_PERSIST_SAVE_SUCCESS, // 保存成功
TEXT_PERSIST_SAVE_FAILED, // 保存失败
TEXT_PERSIST_RESTORE_SUCCESS, // 恢复成功
TEXT_PERSIST_RESTORE_FAILED, // 恢复失败
// 提示信息0x0A00 ~ 0x0AFF
TEXT_ERROR = 0x0A00, // 错误
TEXT_WARNING, // 警告
TEXT_SUCCESS, // 成功
TEXT_INFO, // 信息
TEXT_TEST, // 测试
TEXT_SELECT_OPTION, // 选择操作
TEXT_CURRENT, // 当前
TEXT_STATUS, // 状态
TEXT_SUMMARY, // 摘要
TEXT_EMPTY, // 空
TEXT_CURRENT_SELECTION, // 当前选择
TEXT_OPERATIONS, // 操作
TEXT_SELECT_ITEM, // 选择项
TEXT_ENTER_SUBMENU, // 进入子菜单
TEXT_RETURN_PARENT, // 返回父菜单
TEXT_SELECT_PREVIOUS, // 选择上一项
TEXT_SELECT_NEXT, // 选择下一项
TEXT_START_DEFAULT, // 从默认开始
TEXT_REGISTER_CALLBACK_FAILED, // 注册回调失败
TEXT_RESTORE_MENU_STATE, // 恢复菜单状态
TEXT_RESTORE_MENU_STATE_FAILED, // 恢复菜单状态失败
TEXT_SAVE_MENU_STATE_FAILED, // 保存菜单状态失败
TEXT_SAVE_MENU_STATE, // 保存菜单状态
// 操作提示0x0B00 ~ 0x0BFF
TEXT_UP_ARROW = 0x0B00, // 上箭头
TEXT_DOWN_ARROW, // 下箭头
TEXT_LEFT_ARROW, // 左箭头
TEXT_RIGHT_ARROW, // 右箭头
TEXT_MAX = 0x0FFF, // 所有文本ID最大值用于边界检查
} TextId_e;
/**
* @brief 菜单节点ID枚举
* @note 采用模块化命名,便于扩展和维护
* 分级规则:
* - 一级菜单(0x1000~0x1FFF)
* - 二级菜单(0x2000~0x2FFF)
* - 三级菜单(0x3000~0x3FFF)
* - 四级菜单(0x4000~0x4FFF)
*/
typedef enum
{
// 无效节点/根节点标记
MENU_NODE_ID_NONE = 0x0000,
// 一级菜单0x1000 ~ 0x1FFF
MENU_NODE_ID_MAIN = 0x1000, // 主菜单
MENU_NODE_ID_MEASUREMENT, // 测量菜单
MENU_NODE_ID_PROTECTION, // 保护菜单
MENU_NODE_ID_CONTROL, // 控制菜单
MENU_NODE_ID_SETTING, // 设置菜单
MENU_NODE_ID_RECORD, // 记录菜单
MENU_NODE_ID_HELP, // 帮助菜单
MENU_NODE_ID_ABOUT, // 关于菜单
// 二级菜单测量菜单子项0x2000 ~ 0x20FF
MENU_NODE_ID_MEASUREMENT_VOLTAGE = 0x2000, // 电压测量
MENU_NODE_ID_MEASUREMENT_CURRENT, // 电流测量
MENU_NODE_ID_MEASUREMENT_POWER, // 功率测量
MENU_NODE_ID_MEASUREMENT_ENERGY, // 能量测量
MENU_NODE_ID_MEASUREMENT_FREQUENCY, // 频率测量
MENU_NODE_ID_MEASUREMENT_TEMPERATURE, // 温度测量
// 二级菜单保护菜单子项0x2100 ~ 0x21FF
MENU_NODE_ID_PROTECTION_OVER_VOLTAGE = 0x2100, // 过电压保护
MENU_NODE_ID_PROTECTION_UNDER_VOLTAGE, // 欠电压保护
MENU_NODE_ID_PROTECTION_OVER_CURRENT, // 过电流保护
MENU_NODE_ID_PROTECTION_SETTING, // 保护设置
// 二级菜单控制菜单子项0x2200 ~ 0x22FF
MENU_NODE_ID_CONTROL_ON_OFF = 0x2200, // 开关控制
MENU_NODE_ID_CONTROL_START_STOP, // 启停控制
MENU_NODE_ID_CONTROL_AUTO_MANUAL, // 自动/手动切换
// 二级菜单设置菜单子项0x2300 ~ 0x23FF
MENU_NODE_ID_SETTING_LANGUAGE = 0x2300, // 语言设置
// 二级菜单记录菜单子项0x2400 ~ 0x24FF
MENU_NODE_ID_RECORD_EVENT = 0x2400, // 事件记录
// 二级菜单帮助菜单子项0x2500 ~ 0x25FF
MENU_NODE_ID_HELP_GUIDE = 0x2500, // 指南
// 二级菜单关于菜单子项0x2600 ~ 0x26FF
MENU_NODE_ID_ABOUT_VERSION = 0x2600, // 版本信息
MENU_NODE_ID_MAX = 0x4FFF, // 菜单节点ID最大值
} MenuNodeId_e;
/**
* @brief 菜单节点属性枚举
* @note 使用位掩码方式,便于组合和扩展
*/
typedef enum
{
MENU_NODE_ATTR_NONE = 0x00, // 无属性
MENU_NODE_ATTR_HAS_CHILDREN = 0x01, // 有子节点
MENU_NODE_ATTR_OPERABLE = 0x02, // 可操作
MENU_NODE_ATTR_VISIBLE = 0x04, // 可见
MENU_NODE_ATTR_EDITABLE = 0x08, // 可编辑
} MenuNodeAttr_e;
/**
* @brief 菜单节点信息结构体
* @note 用于建立菜单节点ID与文本ID、父子关系、属性等映射
* 采用更紧凑的设计,便于扩展
*/
typedef struct
{
MenuNodeId_e node_id; // 当前菜单节点ID唯一标识
MenuNodeId_e parent_id; // 父节点IDMENU_NODE_ID_NONE表示无父节点
TextId_e text_id; // 对应显示的文本ID
uint8_t level; // 菜单层级1=一级2=二级3=三级4=四级)
uint8_t attributes; // 节点属性使用MenuNodeAttr_e位掩码组合
} MenuNodeInfo_t;
/* 宏定义 ---------------------------------------------------------------------------------------------------------*/
/**
* @brief 菜单节点属性检查宏
* @param attr 节点属性
* @param flag 要检查的属性标志
* @return 1表示属性存在0表示属性不存在
*/
#define MENU_NODE_HAS_ATTR(attr, flag) ((attr) & (flag))
/**
* @brief 菜单框架配置常量
*/
#define MENU_CFG_MAX_DEPTH 4 // 最大菜单深度(一级~四级)
#define MENU_CFG_MAX_NODES 50 // 最大菜单节点数
#define MENU_CFG_MAX_TEXT_LEN 50 // 最大文本长度(字符数)
/* 全局变量声明 ---------------------------------------------------------------------------------------------------*/
/**
* @brief 全局菜单节点映射表
* @note 所有菜单节点的关联关系集中在这里维护,修改时仅需调整此表
* 按一级菜单、二级菜单的顺序排列,便于阅读和维护
*/
extern const MenuNodeInfo_t menu_node_map[];
/**
* @brief 菜单节点映射表大小
*/
extern const uint32_t menu_node_map_size;
/* 函数声明 ---------------------------------------------------------------------------------------------------------*/
/**
* @brief 初始化语言模块
* @return MenuErrCode 操作结果
*/
MenuErrCode language_init(void);
/**
* @brief 根据文本ID获取对应语言的文本字符串
* @param text_id 文本ID
* @return const char* 对应语言的文本字符串若ID无效返回NULL
*/
const char* get_text(TextId_e text_id);
/**
* @brief 设置当前系统语言
* @param lang 要设置的语言
* @return MenuErrCode 操作结果
*/
MenuErrCode set_language(SystemLang_e lang);
/**
* @brief 获取当前系统语言
* @return SystemLang_e 当前使用的语言
*/
SystemLang_e get_current_language(void);
/**
* @brief 根据菜单节点ID获取对应的文本ID
* @param node_id 菜单节点ID
* @return TextId_e 对应的文本ID若节点不存在返回TEXT_MAX
*/
TextId_e get_menu_text_id(MenuNodeId_e node_id);
/**
* @brief 根据菜单节点ID获取对应的父节点ID
* @param node_id 菜单节点ID
* @return MenuNodeId_e 对应的父节点ID若节点不存在返回MENU_NODE_ID_NONE
*/
MenuNodeId_e get_menu_parent_id(MenuNodeId_e node_id);
/**
* @brief 检查菜单节点是否有子节点
* @param node_id 菜单节点ID
* @return uint8_t 1=有子节点0=无子节点/节点不存在
*/
uint8_t menu_node_has_children(MenuNodeId_e node_id);
/**
* @brief 检查菜单节点是否可操作
* @param node_id 菜单节点ID
* @return uint8_t 1=可操作0=不可操作/节点不存在
*/
uint8_t menu_node_is_operable(MenuNodeId_e node_id);
/**
* @brief 获取菜单节点的显示文本
* @param node_id 菜单节点ID
* @return const char* 对应语言的文本字符串若节点无效返回NULL
*/
const char* get_menu_node_text(MenuNodeId_e node_id);
#endif /* LANGUAGE_H */

View File

@ -1,33 +0,0 @@
cmake_minimum_required(VERSION 3.10)
# 添加当前目录和上级目录作为包含目录
include_directories(
${CMAKE_CURRENT_SOURCE_DIR}
${CMAKE_CURRENT_SOURCE_DIR}/hmi/inc
${CMAKE_CURRENT_SOURCE_DIR}/language
${CMAKE_SOURCE_DIR}
)
# 收集示例所需的源文件
set(EXAMPLE_SOURCES
demo.c
language/language.c
hmi/src/mainhmi.c
hmi/src/hmi_common.c
hmi/src/hmi_camera.c
hmi/src/hmi_more_set.c
hmi/src/hmi_music.c
hmi/src/hmi_set.c
hmi/src/hmi_video.c
)
# 创建demo可执行程序
add_executable(demo ${EXAMPLE_SOURCES})
# 链接menu库
target_link_libraries(demo PRIVATE menu)
# 安装规则
install(TARGETS demo
RUNTIME DESTINATION bin
)

View File

@ -0,0 +1,83 @@
# This is the CMakeCache file.
# For build in directory: e:/Jfen_work/local_git_code/menu/examples/build
# It was generated by CMake: C:/Users/ZHIZHANKEJI/AppData/Local/Programs/Python/Python311/Lib/site-packages/cmake/data/bin/cmake.exe
# You can edit this file to change values found and used by cmake.
# If you do not want to change any of the values, simply exit the editor.
# If you do want to change a value, simply edit, save, and exit the editor.
# The syntax for the file is as follows:
# KEY:TYPE=VALUE
# KEY is the name of a variable in the cache.
# TYPE is a hint to GUIs for the type of VALUE, DO NOT EDIT TYPE!.
# VALUE is the current value for the KEY.
########################
# EXTERNAL cache entries
########################
//Value Computed by CMake.
CMAKE_FIND_PACKAGE_REDIRECTS_DIR:STATIC=E:/Jfen_work/local_git_code/menu/examples/build/CMakeFiles/pkgRedirects
//Program used to build from makefiles.
CMAKE_MAKE_PROGRAM:STRING=nmake
//Value Computed by CMake
CMAKE_PROJECT_DESCRIPTION:STATIC=
//Value Computed by CMake
CMAKE_PROJECT_HOMEPAGE_URL:STATIC=
//Value Computed by CMake
CMAKE_PROJECT_NAME:STATIC=Project
//Value Computed by CMake
Project_BINARY_DIR:STATIC=E:/Jfen_work/local_git_code/menu/examples/build
//Value Computed by CMake
Project_IS_TOP_LEVEL:STATIC=ON
//Value Computed by CMake
Project_SOURCE_DIR:STATIC=E:/Jfen_work/local_git_code/menu/examples
########################
# INTERNAL cache entries
########################
//This is the directory where this CMakeCache.txt was created
CMAKE_CACHEFILE_DIR:INTERNAL=e:/Jfen_work/local_git_code/menu/examples/build
//Major version of cmake used to create the current loaded cache
CMAKE_CACHE_MAJOR_VERSION:INTERNAL=3
//Minor version of cmake used to create the current loaded cache
CMAKE_CACHE_MINOR_VERSION:INTERNAL=31
//Patch version of cmake used to create the current loaded cache
CMAKE_CACHE_PATCH_VERSION:INTERNAL=2
//Path to CMake executable.
CMAKE_COMMAND:INTERNAL=C:/Users/ZHIZHANKEJI/AppData/Local/Programs/Python/Python311/Lib/site-packages/cmake/data/bin/cmake.exe
//Path to cpack program executable.
CMAKE_CPACK_COMMAND:INTERNAL=C:/Users/ZHIZHANKEJI/AppData/Local/Programs/Python/Python311/Lib/site-packages/cmake/data/bin/cpack.exe
//Path to ctest program executable.
CMAKE_CTEST_COMMAND:INTERNAL=C:/Users/ZHIZHANKEJI/AppData/Local/Programs/Python/Python311/Lib/site-packages/cmake/data/bin/ctest.exe
//Path to cache edit program executable.
CMAKE_EDIT_COMMAND:INTERNAL=C:/Users/ZHIZHANKEJI/AppData/Local/Programs/Python/Python311/Lib/site-packages/cmake/data/bin/cmake-gui.exe
//Name of external makefile project generator.
CMAKE_EXTRA_GENERATOR:INTERNAL=
//Name of generator.
CMAKE_GENERATOR:INTERNAL=NMake Makefiles
//Generator instance identifier.
CMAKE_GENERATOR_INSTANCE:INTERNAL=
//Name of generator platform.
CMAKE_GENERATOR_PLATFORM:INTERNAL=
//Name of generator toolset.
CMAKE_GENERATOR_TOOLSET:INTERNAL=
//Source directory with the top level CMakeLists.txt file for this
// project
CMAKE_HOME_DIRECTORY:INTERNAL=E:/Jfen_work/local_git_code/menu/examples
//ADVANCED property for variable: CMAKE_MAKE_PROGRAM
CMAKE_MAKE_PROGRAM-ADVANCED:INTERNAL=1
//number of local generators
CMAKE_NUMBER_OF_MAKEFILES:INTERNAL=1
//Platform information initialized
CMAKE_PLATFORM_INFO_INITIALIZED:INTERNAL=1
//Path to CMake installation.
CMAKE_ROOT:INTERNAL=C:/Users/ZHIZHANKEJI/AppData/Local/Programs/Python/Python311/Lib/site-packages/cmake/data/share/cmake-3.31

View File

@ -0,0 +1,15 @@
set(CMAKE_HOST_SYSTEM "Windows-10.0.26200")
set(CMAKE_HOST_SYSTEM_NAME "Windows")
set(CMAKE_HOST_SYSTEM_VERSION "10.0.26200")
set(CMAKE_HOST_SYSTEM_PROCESSOR "AMD64")
set(CMAKE_SYSTEM "Windows-10.0.26200")
set(CMAKE_SYSTEM_NAME "Windows")
set(CMAKE_SYSTEM_VERSION "10.0.26200")
set(CMAKE_SYSTEM_PROCESSOR "AMD64")
set(CMAKE_CROSSCOMPILING "FALSE")
set(CMAKE_SYSTEM_LOADED 1)

View File

@ -0,0 +1,11 @@
---
events:
-
kind: "message-v1"
backtrace:
- "C:/Users/ZHIZHANKEJI/AppData/Local/Programs/Python/Python311/Lib/site-packages/cmake/data/share/cmake-3.31/Modules/CMakeDetermineSystem.cmake:205 (message)"
- "CMakeLists.txt"
message: |
The system is: Windows - 10.0.26200 - AMD64
...

View File

@ -0,0 +1 @@
# This file is generated by cmake for dependency checking of the CMakeCache.txt file

View File

@ -1,65 +0,0 @@
/* 菜单显示效果图可看:
https://blog.csdn.net/qq_24130227/article/details/121167276
*/
#include "menu.h"
#include "mainhmi.h"
#include "language.h"
#include <stdio.h>
#include <string.h>
int isEnterMenu = 0;
void EnterMainMenu(const MenuItemInfo_t *pItemInfo)
{
isEnterMenu = 1;
}
void ExitMainMenu(const MenuItemInfo_t *pItemInfo)
{
isEnterMenu = 0;
}
int main(int argc, char **argv)
{
int cmd = 0;
int8_t musicMenuId, languageMenuId, moreSetMenuId;
MainMenuCfg_t tMainMenu = MENU_ITEM_BIND(TEXT_MAIN_MENU, EnterMainMenu, ExitMainMenu, Hmi_LoadMainHmi, Hmi_MainTask, NULL);
Menu_Init(&tMainMenu);
while (1)
{
CLEAR();
MOVETO(0, 0);
if (!isEnterMenu)
{
printf("%s(0-%s%s; 1-%s): ", get_text(TEXT_SELECT_OPTION),
get_text(TEXT_ENTER), get_text(TEXT_MAIN_MENU), get_text(TEXT_EXIT));
scanf(" %d", &cmd); // 空格作用是忽略上次的回车
if (cmd == 0)
{
Menu_MainEnter();
CLEAR();
MOVETO(0, 0);
}
else if (cmd == 1)
{
break;
}
}
Menu_Task();
}
Menu_DeInit();
return 0;
}

666
menu.c
View File

@ -1,666 +0,0 @@
/**
**********************************************************************************************************************
* @file cot_menu.c
* @brief 该文件提供菜单框架功能
* @author const_zpc any question please send mail to const_zpc@163.com
* @version V1.3.0
* @date 2024-06-09
*
* @details 功能详细说明:
* + 菜单初始化函数
* + 返回主菜单函数
* + 菜单控制函数
* + 菜单轮询任务函数
*
**********************************************************************************************************************
* 源码路径https://gitee.com/cot_package/cot_menu.git 具体问题及建议可在该网址填写 Issue
*
* 使用方式:
* 1、使用前初始化函数 cotMenu_Init, 设置主菜单内容
* 2、周期调用函数 cotMenu_Task, 用来处理菜单显示和执行相关回调函数
* 3、使用其他函数对菜单界面控制
*
**********************************************************************************************************************
*/
/* Includes ----------------------------------------------------------------------------------------------------------*/
#include "menu.h"
#ifdef _MENU_USE_MALLOC_
#include <malloc.h>
#endif
#ifdef _MENU_USE_SHORTCUT_
#include <stdarg.h>
#endif
/* Private typedef ---------------------------------------------------------------------------------------------------*/
typedef struct MenuCtrl
{
struct MenuCtrl *pParentMenuCtrl; /*!< 父菜单控制处理 */
MenuDsecStr_u uMenuDesc; /*!< 当前菜单的字符串描述 */
void *pExtendData; /*!< 当前选项注册时的扩展数据 */
ShowMenuCallFun_f pfnShowMenuFun; /*!< 当前菜单显示效果函数 */
MenuList_t *pMenuList; /*!< 当前菜单列表 */
MenuCallFun_f pfnLoadCallFun; /*!< 当前菜单加载函数 */
MenuCallFun_f pfnRunCallFun; /*!< 当前选项的非菜单功能函数 */
menusize_t itemsNum; /*!< 当前菜单选项总数目 */
menusize_t showBaseItem; /*!< 当前菜单首个显示的选项 */
menusize_t selectItem; /*!< 当前菜单选中的选项 */
bool isSelected; /*!< 菜单选项是否已经被选择 */
}MenuCtrl_t;
typedef struct
{
MenuCtrl_t *pMenuCtrl; /*!< 当前菜单控制处理 */
MenuCallFun_f pfnMainEnterCallFun; /*!< 主菜单进入时(进入菜单)需要执行一次的函数 */
MenuCallFun_f pfnMainExitCallFun; /*!< 主菜单进入后退出时(退出菜单)需要执行一次的函数 */
MenuCallFun_f pfnLoadCallFun; /*!< 重加载函数 */
uint8_t isEnterMainMenu : 1; /*!< 是否进入了主菜单 */
}MenuManage_t;
/* Private define ----------------------------------------------------------------------------------------------------*/
/* Private macro -----------------------------------------------------------------------------------------------------*/
/* Private variables -------------------------------------------------------------------------------------------------*/
static MenuManage_t sg_tMenuManage;
#ifndef _MENU_USE_MALLOC_
static MenuCtrl_t sg_arrMenuCtrl[COT_MENU_MAX_DEPTH];
#endif
static uint8_t sg_currMenuDepth = 0;
/* Private function prototypes ---------------------------------------------------------------------------------------*/
static MenuCtrl_t *NewMenu(void);
static void DeleteMenu(MenuCtrl_t *pMenu);
/* Private function --------------------------------------------------------------------------------------------------*/
/**
* @brief 新建菜单层级
*
* @return MenuCtrl_t*
*/
static MenuCtrl_t *NewMenu(void)
{
MenuCtrl_t *pMenuCtrl = NULL;
if (sg_currMenuDepth < COT_MENU_MAX_DEPTH)
{
#ifdef _COT_MENU_USE_MALLOC_
pMenuCtrl = (MenuCtrl_t *)malloc(sizeof(MenuCtrl_t));
#else
pMenuCtrl = &sg_arrMenuCtrl[sg_currMenuDepth];
#endif
sg_currMenuDepth++;
}
return pMenuCtrl;
}
/**
* @brief 销毁菜单层级
*
* @param pMenu
*/
static void DeleteMenu(MenuCtrl_t *pMenu)
{
#ifdef _COT_MENU_USE_MALLOC_
free(pMenu);
#endif
if (sg_currMenuDepth > 0)
{
sg_currMenuDepth--;
}
}
/**
* @brief 菜单初始化
*
* @param[in] pMainMenu 主菜单注册信息
* @return 0,成功; -1,失败
*/
int Menu_Init(MainMenuCfg_t *pMainMenu)
{
MenuCtrl_t *pNewMenuCtrl = NULL;
if (sg_tMenuManage.pMenuCtrl != NULL)
{
return -1;
}
#if COT_MENU_MAX_DEPTH != 0
sg_currMenuDepth = 0;
#endif
if ((pNewMenuCtrl = NewMenu()) == NULL)
{
return -1;
}
pNewMenuCtrl->uMenuDesc = pMainMenu->uMenuDesc;
pNewMenuCtrl->pExtendData = pMainMenu->pExtendData;
pNewMenuCtrl->pParentMenuCtrl = NULL;
pNewMenuCtrl->pfnLoadCallFun = pMainMenu->pfnLoadCallFun;
pNewMenuCtrl->pfnShowMenuFun = NULL;
pNewMenuCtrl->pfnRunCallFun = pMainMenu->pfnRunCallFun;
pNewMenuCtrl->pMenuList = NULL;
pNewMenuCtrl->itemsNum = 0;
pNewMenuCtrl->selectItem = 0;
pNewMenuCtrl->showBaseItem = 0;
sg_tMenuManage.pMenuCtrl = pNewMenuCtrl;
sg_tMenuManage.isEnterMainMenu = 0;
sg_tMenuManage.pfnMainEnterCallFun = pMainMenu->pfnEnterCallFun;
sg_tMenuManage.pfnMainExitCallFun = pMainMenu->pfnExitCallFun;
sg_tMenuManage.pfnLoadCallFun = pNewMenuCtrl->pfnLoadCallFun;
return 0;
}
/**
* @brief 菜单反初始化
*
* @attention 不管处于任何界面都会逐级退出到主菜单后(会调用退出函数),再退出主菜单,最后反初始化
* @return 0,成功; -1,失败
*/
int Menu_DeInit(void)
{
if (sg_tMenuManage.pMenuCtrl == NULL)
{
return -1;
}
Menu_MainExit();
DeleteMenu(sg_tMenuManage.pMenuCtrl);
sg_tMenuManage.pMenuCtrl = NULL;
sg_tMenuManage.isEnterMainMenu = 0;
sg_tMenuManage.pfnMainEnterCallFun = NULL;
sg_tMenuManage.pfnMainExitCallFun = NULL;
sg_tMenuManage.pfnLoadCallFun = NULL;
return 0;
}
/**
* @brief 子菜单绑定当前菜单选项
*
* @param pMenuList 新的菜单列表
* @param menuNum 新的菜单列表数目
* @param pfnShowMenuFun 新的菜单列表显示效果回调函数, 为NULL则延续上级菜单显示效果
* @return 0,成功; -1,失败
*/
int Menu_Bind(MenuList_t *pMenuList, menusize_t menuNum, ShowMenuCallFun_f pfnShowMenuFun)
{
if (sg_tMenuManage.pMenuCtrl == NULL)
{
return -1;
}
if (sg_tMenuManage.pMenuCtrl->pMenuList != NULL)
{
return 0;
}
sg_tMenuManage.pMenuCtrl->pMenuList = pMenuList;
sg_tMenuManage.pMenuCtrl->itemsNum = menuNum;
if (pfnShowMenuFun != NULL)
{
sg_tMenuManage.pMenuCtrl->pfnShowMenuFun = pfnShowMenuFun;
}
return 0;
}
/**
* @brief 复位菜单, 回到主菜单界面
*
* @note 该复位回到主菜单不会执行退出所需要执行的回调函数
* @return 0,成功; -1,失败
*/
int Menu_Reset(void)
{
if (sg_tMenuManage.pMenuCtrl == NULL || sg_tMenuManage.isEnterMainMenu == 0)
{
return -1;
}
while (sg_tMenuManage.pMenuCtrl->pParentMenuCtrl != NULL)
{
MenuCtrl_t *pMenuCtrl = sg_tMenuManage.pMenuCtrl;
sg_tMenuManage.pMenuCtrl = sg_tMenuManage.pMenuCtrl->pParentMenuCtrl;
DeleteMenu(pMenuCtrl);
}
sg_tMenuManage.pMenuCtrl->selectItem = 0;
return 0;
}
/**
* @brief 主菜单进入
*
* @return 0,成功; -1,失败
*/
int Menu_MainEnter(void)
{
if (sg_tMenuManage.pMenuCtrl == NULL || sg_tMenuManage.isEnterMainMenu == 1)
{
return -1;
}
if (sg_tMenuManage.pfnMainEnterCallFun != NULL)
{
MenuItemInfo_t tItemInfo;
tItemInfo.uMenuDesc = sg_tMenuManage.pMenuCtrl->uMenuDesc;
tItemInfo.pExtendData = sg_tMenuManage.pMenuCtrl->pExtendData;
sg_tMenuManage.pfnMainEnterCallFun(&tItemInfo);
}
sg_tMenuManage.isEnterMainMenu = 1;
sg_tMenuManage.pfnLoadCallFun = sg_tMenuManage.pMenuCtrl->pfnLoadCallFun;
return 0;
}
/**
* @brief 主菜单退出
*
* @attention 不管处于任何界面都会逐级退出到主菜单后(会调用退出函数),再退出主菜单
* @return 0,成功; -1,失败
*/
int Menu_MainExit(void)
{
if (sg_tMenuManage.pMenuCtrl == NULL || sg_tMenuManage.isEnterMainMenu == 0)
{
return -1;
}
while (Menu_Exit(1) == 0){}
if (sg_tMenuManage.pfnMainExitCallFun != NULL)
{
sg_tMenuManage.pfnMainExitCallFun(NULL);
}
sg_tMenuManage.isEnterMainMenu = 0;
return 0;
}
/**
* @brief 进入当前菜单选项
*
* @return 0,成功; -1,失败
*/
int Menu_Enter(void)
{
MenuCtrl_t *pNewMenuCtrl = NULL;
MenuCtrl_t *pCurrMenuCtrl = sg_tMenuManage.pMenuCtrl;
if (sg_tMenuManage.pMenuCtrl == NULL || sg_tMenuManage.pMenuCtrl->pMenuList == NULL || sg_tMenuManage.isEnterMainMenu == 0)
{
return -1;
}
if ((pNewMenuCtrl = NewMenu()) == NULL)
{
return -1;
}
pNewMenuCtrl->uMenuDesc = pCurrMenuCtrl->pMenuList[pCurrMenuCtrl->selectItem].uMenuDesc;
pNewMenuCtrl->pExtendData = pCurrMenuCtrl->pMenuList[pCurrMenuCtrl->selectItem].pExtendData;
pNewMenuCtrl->pMenuList = NULL;
pNewMenuCtrl->itemsNum = 0;
pNewMenuCtrl->pfnShowMenuFun = pCurrMenuCtrl->pfnShowMenuFun;
pNewMenuCtrl->pfnLoadCallFun = pCurrMenuCtrl->pMenuList[pCurrMenuCtrl->selectItem].pfnLoadCallFun;
pNewMenuCtrl->pfnRunCallFun = pCurrMenuCtrl->pMenuList[pCurrMenuCtrl->selectItem].pfnRunCallFun;
pNewMenuCtrl->selectItem = 0;
pNewMenuCtrl->isSelected = true;
pNewMenuCtrl->pParentMenuCtrl = pCurrMenuCtrl;
sg_tMenuManage.pMenuCtrl = pNewMenuCtrl;
sg_tMenuManage.pfnLoadCallFun = pNewMenuCtrl->pfnLoadCallFun;
if (pCurrMenuCtrl->pMenuList[pCurrMenuCtrl->selectItem].pfnEnterCallFun != NULL)
{
MenuItemInfo_t tItemInfo;
tItemInfo.uMenuDesc = sg_tMenuManage.pMenuCtrl->uMenuDesc;
tItemInfo.pExtendData = sg_tMenuManage.pMenuCtrl->pExtendData;
pCurrMenuCtrl->pMenuList[pCurrMenuCtrl->selectItem].pfnEnterCallFun(&tItemInfo);
}
return 0;
}
/**
* @brief 退出当前选项并返回上一层菜单
*
* @param[in] isReset 菜单选项是否从头选择
* @return 0,成功; -1,失败, 即目前处于主菜单, 无法返回
*/
int Menu_Exit(bool isReset)
{
MenuCtrl_t *pMenuCtrl = sg_tMenuManage.pMenuCtrl;
if (sg_tMenuManage.pMenuCtrl == NULL || sg_tMenuManage.isEnterMainMenu == 0)
{
return -1;
}
if (sg_tMenuManage.pMenuCtrl->pParentMenuCtrl == NULL)
{
return -1;
}
sg_tMenuManage.pMenuCtrl = sg_tMenuManage.pMenuCtrl->pParentMenuCtrl;
sg_tMenuManage.pfnLoadCallFun = sg_tMenuManage.pMenuCtrl->pfnLoadCallFun;
DeleteMenu(pMenuCtrl);
pMenuCtrl = NULL;
if (sg_tMenuManage.pMenuCtrl->pMenuList[sg_tMenuManage.pMenuCtrl->selectItem].pfnExitCallFun != NULL)
{
MenuItemInfo_t tItemInfo;
tItemInfo.uMenuDesc = sg_tMenuManage.pMenuCtrl->pMenuList[sg_tMenuManage.pMenuCtrl->selectItem].uMenuDesc;
tItemInfo.pExtendData = sg_tMenuManage.pMenuCtrl->pMenuList[sg_tMenuManage.pMenuCtrl->selectItem].pExtendData;
sg_tMenuManage.pMenuCtrl->isSelected = false;
sg_tMenuManage.pMenuCtrl->pMenuList[sg_tMenuManage.pMenuCtrl->selectItem].pfnExitCallFun(&tItemInfo);
}
if (isReset)
{
sg_tMenuManage.pMenuCtrl->selectItem = 0;
}
return 0;
}
/**
* @brief 选择上一个菜单选项
*
* @param[in] isAllowRoll 第一个选项时是否从跳转到最后一个选项
* @return 0,成功; -1,失败
*/
int Menu_SelectPrevious(bool isAllowRoll)
{
if (sg_tMenuManage.pMenuCtrl == NULL || sg_tMenuManage.pMenuCtrl->pMenuList == NULL || sg_tMenuManage.isEnterMainMenu == 0)
{
return -1;
}
if (sg_tMenuManage.pMenuCtrl->selectItem > 0)
{
sg_tMenuManage.pMenuCtrl->selectItem--;
}
else
{
if (isAllowRoll)
{
sg_tMenuManage.pMenuCtrl->selectItem = sg_tMenuManage.pMenuCtrl->itemsNum - 1;
}
else
{
sg_tMenuManage.pMenuCtrl->selectItem = 0;
return -1;
}
}
return 0;
}
/**
* @brief 选择下一个菜单选项
*
* @param[in] isAllowRoll 最后一个选项时是否跳转到第一个选项
* @return 0,成功; -1,失败
*/
int Menu_SelectNext(bool isAllowRoll)
{
if (sg_tMenuManage.pMenuCtrl == NULL || sg_tMenuManage.pMenuCtrl->pMenuList == NULL || sg_tMenuManage.isEnterMainMenu == 0)
{
return -1;
}
if (sg_tMenuManage.pMenuCtrl->selectItem < (sg_tMenuManage.pMenuCtrl->itemsNum - 1))
{
sg_tMenuManage.pMenuCtrl->selectItem++;
}
else
{
if (isAllowRoll)
{
sg_tMenuManage.pMenuCtrl->selectItem = 0;
}
else
{
sg_tMenuManage.pMenuCtrl->selectItem = sg_tMenuManage.pMenuCtrl->itemsNum - 1;
return -1;
}
}
return 0;
}
/**
* @brief 选择指定的菜单选项
*
* @param selectItem 指定的菜单选项
* @return 0,成功; -1,失败
*/
int Menu_Select(menusize_t selectItem)
{
if (sg_tMenuManage.pMenuCtrl == NULL || sg_tMenuManage.pMenuCtrl->pMenuList == NULL || sg_tMenuManage.isEnterMainMenu == 0)
{
return -1;
}
if (selectItem >= sg_tMenuManage.pMenuCtrl->itemsNum)
{
return -1;
}
sg_tMenuManage.pMenuCtrl->selectItem = selectItem;
return 0;
}
#ifdef _MENU_USE_SHORTCUT_
/**
* @brief 相对主菜单或当前菜单通过下级各菜单索引快速进入指定选项
*
* @param[in] isAbsolute 是否采用绝对菜单索引(从主菜单开始)
* @param[in] deep 菜单深度,大于 0
* @param[in] ... 各级菜单索引值(从0开始), 入参个数由 deep 的值决定
* @return 0,成功; -1,失败
*/
int Menu_ShortcutEnter(bool isAbsolute, uint8_t deep, ...)
{
uint8_t selectDeep = 0;
va_list pItemList;
menusize_t selectItem;
if (sg_tMenuManage.pMenuCtrl == NULL || sg_tMenuManage.pMenuCtrl->pMenuList == NULL || sg_tMenuManage.isEnterMainMenu == 0)
{
return -1;
}
if (isAbsolute)
{
Menu_Reset();
}
va_start(pItemList, deep);
while (selectDeep < deep)
{
selectItem = va_arg(pItemList, int);
if (selectItem >= sg_tMenuManage.pMenuCtrl->itemsNum)
{
va_end(pItemList);
return -1;
}
sg_tMenuManage.pMenuCtrl->selectItem = selectItem;
Menu_Enter();
selectDeep++;
}
va_end(pItemList);
return 0;
}
#endif
/**
* @brief 限制当前菜单界面最多显示的菜单数目
*
* @note 在菜单显示效果回调函数中使用, 使用成员变量 showBaseItem 得到显示界面的第一个选项索引
* @param[in,out] tMenuShow 当前菜单显示信息
* @param[in,out] showNum 当前菜单中需要显示的选项数目, 根据当前菜单选项的总数得到最终的显示的选项数目
* @return 0,成功; -1,失败
*/
int Menu_LimitShowListNum(MenuShow_t *ptMenuShow, menusize_t *pShowNum)
{
if (ptMenuShow == NULL || pShowNum == NULL)
{
return -1;
}
if (*pShowNum > ptMenuShow->itemsNum)
{
*pShowNum = ptMenuShow->itemsNum;
}
if (ptMenuShow->selectItem < ptMenuShow->showBaseItem)
{
ptMenuShow->showBaseItem = ptMenuShow->selectItem;
}
else if ((ptMenuShow->selectItem - ptMenuShow->showBaseItem) >= *pShowNum)
{
ptMenuShow->showBaseItem = ptMenuShow->selectItem - *pShowNum + 1;
}
else
{
// 保持
}
return 0;
}
/**
* @brief 获取当前父菜单显示信息
* 如获取当前菜单的二级父菜单信息level 为2
*
* @param[out] ptMenuShow 父 n 级菜单显示信息
* @param[in] level n 级, 大于 0
* @return 0,成功; -1,失败
*/
int Menu_QueryParentMenu(MenuShow_t *ptMenuShow, uint8_t level)
{
int i;
MenuList_t *pMenu;
MenuCtrl_t *pMenuCtrl = NULL;
if (sg_tMenuManage.pMenuCtrl == NULL || sg_tMenuManage.isEnterMainMenu == 0)
{
return -1;
}
pMenuCtrl = sg_tMenuManage.pMenuCtrl->pParentMenuCtrl;
while (level && pMenuCtrl != NULL)
{
pMenu = pMenuCtrl->pMenuList;
ptMenuShow->itemsNum = pMenuCtrl->itemsNum;
ptMenuShow->selectItem = pMenuCtrl->selectItem;
ptMenuShow->showBaseItem = pMenuCtrl->showBaseItem;
ptMenuShow->uMenuDesc = sg_tMenuManage.pMenuCtrl->uMenuDesc;
for (i = 0; i < ptMenuShow->itemsNum && i < COT_MENU_MAX_NUM; i++)
{
ptMenuShow->uItemsListDesc[i] = pMenu[i].uMenuDesc;
ptMenuShow->pItemsListExtendData[i] = pMenu[i].pExtendData;
}
pMenuCtrl = pMenuCtrl->pParentMenuCtrl;
level--;
}
if (level != 0 && pMenuCtrl == NULL)
{
return -1;
}
return 0;
}
/**
* @brief 菜单任务
*
* @return 0,成功, 处于菜单模式下; -1,失败, 未处于菜单模式下
*/
int Menu_Task(void)
{
int i;
MenuList_t *pMenuList;
MenuShow_t tMenuShow;
if (sg_tMenuManage.pMenuCtrl == NULL || sg_tMenuManage.isEnterMainMenu == 0)
{
return -1;
}
if (sg_tMenuManage.pfnLoadCallFun != NULL)
{
MenuItemInfo_t tItemInfo;
tItemInfo.uMenuDesc = sg_tMenuManage.pMenuCtrl->uMenuDesc;
tItemInfo.pExtendData = sg_tMenuManage.pMenuCtrl->pExtendData;
sg_tMenuManage.pfnLoadCallFun(&tItemInfo);
sg_tMenuManage.pfnLoadCallFun = NULL;
}
if (sg_tMenuManage.pMenuCtrl->pMenuList != NULL)
{
pMenuList = sg_tMenuManage.pMenuCtrl->pMenuList;
tMenuShow.itemsNum = sg_tMenuManage.pMenuCtrl->itemsNum;
tMenuShow.selectItem = sg_tMenuManage.pMenuCtrl->selectItem;
tMenuShow.showBaseItem = sg_tMenuManage.pMenuCtrl->showBaseItem;
tMenuShow.uMenuDesc = sg_tMenuManage.pMenuCtrl->uMenuDesc;
tMenuShow.pExtendData = sg_tMenuManage.pMenuCtrl->pExtendData;
for (i = 0; i < tMenuShow.itemsNum && i < COT_MENU_MAX_NUM; i++)
{
tMenuShow.uItemsListDesc[i] = pMenuList[i].uMenuDesc;
tMenuShow.pItemsListExtendData[i] = pMenuList[i].pExtendData;
}
if (sg_tMenuManage.pMenuCtrl->pfnShowMenuFun != NULL)
{
sg_tMenuManage.pMenuCtrl->pfnShowMenuFun(&tMenuShow);
}
sg_tMenuManage.pMenuCtrl->showBaseItem = tMenuShow.showBaseItem;
}
if (sg_tMenuManage.pMenuCtrl->pfnRunCallFun != NULL)
{
MenuItemInfo_t tItemInfo;
tItemInfo.uMenuDesc = sg_tMenuManage.pMenuCtrl->uMenuDesc;
tItemInfo.pExtendData = sg_tMenuManage.pMenuCtrl->pExtendData;
sg_tMenuManage.pMenuCtrl->pfnRunCallFun(&tItemInfo);
}
return 0;
}

181
menu.h
View File

@ -1,181 +0,0 @@
/**
**********************************************************************************************************************
* @file cot_menu.h
* @brief 该文件提供菜单框架所有函数原型
* @author const_zpc any question please send mail to const_zpc@163.com
* @date 2024-06-09
**********************************************************************************************************************
*
**********************************************************************************************************************
*/
/* Define to prevent recursive inclusion -----------------------------------------------------------------------------*/
#ifndef MENU_H
#define MENU_H
/* Includes ----------------------------------------------------------------------------------------------------------*/
#include <stdint.h>
#include <stdbool.h>
#include <stddef.h>
#ifdef __cplusplus
extern "C" {
#endif
/******************************************* 配置项 ********************************************************************/
/* 定义 _MENU_USE_MALLOC_ 则采用 malloc/free 的方式实现多级菜单, 否则通过数组的形式 */
// #define _MENU_USE_MALLOC_
/* 定义 _MENU_USE_SHORTCUT_ 则启用快捷菜单选项进入功能 */
#define _MENU_USE_SHORTCUT_
/* 多级菜单深度 */
#define COT_MENU_MAX_DEPTH 10
/* 菜单支持的最大选项数目 */
#define COT_MENU_MAX_NUM 20
/******************************************* 配置项 ********************************************************************/
/* Exported types ----------------------------------------------------------------------------------------------------*/
#if COT_MENU_MAX_NUM < 255
typedef uint8_t menusize_t;
#else
typedef uint16_t menusize_t;
#endif
typedef union
{
void *pVoid; /*!< 通用指针(目的消除编译警告) */
size_t textId; /*!< 文本ID */
char *pTextString; /*!< 文本字符串 */
} MenuDsecStr_u;
/**
* @brief 单个选项信息
*
*
*/
typedef struct
{
MenuDsecStr_u uMenuDesc; /*!< 该选项的描述 */
void *pExtendData; /*!< 该选项注册时的扩展数据 */
} MenuItemInfo_t;
typedef void (*MenuCallFun_f)(const MenuItemInfo_t *pItemInfo);
/**
* @brief 单个选项带菜单列表等信息
*
*
*/
typedef struct
{
menusize_t itemsNum; /*!< 当前菜单中选项的总数目 */
menusize_t selectItem; /*!< 当前菜单中被选中的选项 */
menusize_t showBaseItem; /*!< 当前菜单首个显示的选项 */
MenuDsecStr_u uMenuDesc; /*!< 当前菜单的描述 */
void *pExtendData; /*!< 当前菜单注册时的扩展数据 */
MenuDsecStr_u uItemsListDesc[COT_MENU_MAX_NUM];/*!< 当前菜单中选项列表的描述 */
void *pItemsListExtendData[COT_MENU_MAX_NUM]; /*!< 当前菜单中选项列表注册时的扩展数据 */
} MenuShow_t;
typedef void (*ShowMenuCallFun_f)(MenuShow_t *ptShowInfo);
/**
* @brief 菜单信息注册结构体
*
*
*/
typedef struct
{
MenuDsecStr_u uMenuDesc; /*!< 当前菜单的描述 */
MenuCallFun_f pfnEnterCallFun; /*!< 当前菜单选项进入时(从父菜单进入)需要执行一次的函数, 为NULL不执行 */
MenuCallFun_f pfnExitCallFun; /*!< 当前菜单选项进入后退出时(退出至父菜单)需要执行一次的函数, 为NULL不执行 */
MenuCallFun_f pfnLoadCallFun; /*!< 当前菜单选项每次加载时(从父菜单进入或子菜单退出)需要执行一次的函数, 为NULL不执行 */
MenuCallFun_f pfnRunCallFun; /*!< 当前菜单选项的周期调度函数 */
void *pExtendData; /*!< 当前选项的菜单显示效果函数扩展数据入参, 可自行设置该内容 */
} MenuList_t, MainMenuCfg_t;
/* Exported constants ------------------------------------------------------------------------------------------------*/
/* Exported macro ----------------------------------------------------------------------------------------------------*/
/**
* @brief 单个菜单选项定义
*
* @param title 菜单选项描述
* @param enterFun 当前菜单选项进入时(从父菜单进入)需要执行一次的函数, 为NULL不执行
* @param exitFun 当前菜单选项进入后退出时(退出至父菜单)需要执行一次的函数, 为NULL不执行
* @param loadFun 当前菜单选项每次加载时(从父菜单进入或子菜单退出)需要执行一次的函数, 为NULL不执行
* @param runFun 当前菜单选项的周期调度函数
* @param extendData 当前选项的菜单显示效果函数扩展数据入参, 可自行设置该内容
*/
#define MENU_ITEM_BIND(title, enterFun, exitFun, loadFun, runFun, extendData) \
{ \
.uMenuDesc = {.pVoid = (void *)title}, \
.pfnEnterCallFun = enterFun, \
.pfnExitCallFun = exitFun, \
.pfnLoadCallFun = loadFun, \
.pfnRunCallFun = runFun, \
.pExtendData = extendData \
}
// 计算得到菜单列表元素数目
#define MENU_GET_NUM(X) (sizeof(X) / sizeof(MenuList_t))
/* Exported functions ------------------------------------------------------------------------------------------------*/
/* 菜单初始化和反初始化 */
extern int Menu_Init(MainMenuCfg_t *pMainMenu);
extern int Menu_DeInit(void);
extern int Menu_Bind(MenuList_t *pMenuList, menusize_t menuNum, ShowMenuCallFun_f pfnShowMenuFun);
/* 菜单选项显示时需要使用的功能扩展函数 */
extern int Menu_LimitShowListNum(MenuShow_t *ptMenuShow, menusize_t *pShowNum);
extern int Menu_QueryParentMenu(MenuShow_t *ptMenuShow, uint8_t level);
/* 菜单操作 */
extern int Menu_MainEnter(void);
extern int Menu_MainExit(void);
extern int Menu_Reset(void);
extern int Menu_Enter(void);
extern int Menu_Exit(bool isReset);
extern int Menu_SelectPrevious(bool isAllowRoll);
extern int Menu_SelectNext(bool isAllowRoll);
extern int Menu_Select(menusize_t selectItem);
extern int Menu_ShortcutEnter(bool isAbsolute, uint8_t deep, ...);
/* 菜单轮询处理任务 */
extern int cotMenu_Task(void);
#ifdef __cplusplus
}
#endif
#endif // MENU_H

BIN
menu_persistence.dat Normal file

Binary file not shown.

319
port/menu_port.c Normal file
View File

@ -0,0 +1,319 @@
/**
**********************************************************************************************************************
* @file menu_port.c
* @brief 菜单组件硬件端口层实现
* @author menu_component
* @date 2025-12-19
**********************************************************************************************************************
*/
/* Includes ----------------------------------------------------------------------------------------------------------*/
#include "menu_port.h"
#include <stdarg.h>
#include <stdio.h>
/* 全局变量 ---------------------------------------------------------------------------------------------------------*/
static const MenuPortDriver* sg_menu_port_driver = NULL;
/* 默认硬件驱动实现 -------------------------------------------------------------------------------------------------*/
/**
* @brief 默认打印函数
* @param fmt 格式化字符串
* @param args 可变参数列表
*/
static void menu_port_default_printf(const char* fmt, va_list args)
{
// 默认不实现打印功能
(void)fmt;
(void)args;
}
/**
* @brief 默认获取系统时间函数
* @return 系统时间ms
*/
static uint32_t menu_port_default_get_tick(void)
{
// 默认返回0用户需要根据实际硬件实现
return 0;
}
/**
* @brief 默认延迟函数
* @param ms 延迟时间ms
*/
static void menu_port_default_delay_ms(uint32_t ms)
{
// 默认实现简单的延迟
for (uint32_t i = 0; i < ms * 1000; i++) {
__asm__ volatile ("nop");
}
}
/**
* @brief 默认显示函数
* @param menu_name 菜单名称
* @param menu_id 菜单ID
*/
static void menu_port_default_display(const char* menu_name, uint16_t menu_id)
{
// 默认不实现显示功能
(void)menu_name;
(void)menu_id;
}
/**
* @brief 默认按键扫描函数
* @return 按键事件类型
*/
static MenuEventType menu_port_default_key_scan(void)
{
// 默认返回无事件
return MENU_EVENT_NONE;
}
/**
* @brief 默认中断管理函数
* @param enable 是否启用中断
*/
static void menu_port_default_irq_ctrl(bool enable)
{
// 默认不实现中断管理
(void)enable;
}
/**
* @brief 默认错误处理函数
* @param err_code 错误码
*/
static void menu_port_default_error_handler(MenuErrCode err_code)
{
// 默认不实现错误处理
(void)err_code;
}
/**
* @brief 默认Modbus发送函数
* @param reg_type 寄存器类型
* @param reg_addr 寄存器地址
* @param reg_buf 寄存器数据
* @param buf_len 数据长度
* @return 错误码
*/
static MenuErrCode menu_port_default_modbus_send(ModbusRegType reg_type, uint16_t reg_addr, const uint8_t* reg_buf, uint8_t buf_len)
{
// 默认不实现Modbus发送
(void)reg_type;
(void)reg_addr;
(void)reg_buf;
(void)buf_len;
return MENU_ERR_OPERATION_FAILED;
}
/**
* @brief 默认Modbus接收函数
* @param reg_type 寄存器类型
* @param reg_addr 寄存器地址
* @param reg_buf 寄存器数据
* @param buf_len 数据长度
* @return 错误码
*/
static MenuErrCode menu_port_default_modbus_receive(ModbusRegType reg_type, uint16_t reg_addr, uint8_t* reg_buf, uint8_t buf_len)
{
// 默认不实现Modbus接收
(void)reg_type;
(void)reg_addr;
(void)reg_buf;
(void)buf_len;
return MENU_ERR_OPERATION_FAILED;
}
/**
* @brief 默认持久化保存函数
* @param data 要保存的数据
* @param size 数据大小
* @return 错误码
*/
static MenuErrCode menu_port_default_persistence_save(const uint8_t* data, uint16_t size)
{
// 默认不实现持久化保存功能
(void)data;
(void)size;
return MENU_ERR_OPERATION_FAILED;
}
/**
* @brief 默认持久化恢复函数
* @param data 恢复数据缓冲区
* @param size 数据大小
* @return 错误码
*/
static MenuErrCode menu_port_default_persistence_restore(uint8_t* data, uint16_t* size)
{
// 默认不实现持久化恢复功能
(void)data;
(void)size;
return MENU_ERR_OPERATION_FAILED;
}
/**
* @brief 默认硬件驱动
*/
static const MenuPortDriver sg_default_driver = {
.printf = menu_port_default_printf,
.get_tick = menu_port_default_get_tick,
.delay_ms = menu_port_default_delay_ms,
.display = menu_port_default_display,
.key_scan = menu_port_default_key_scan,
.irq_ctrl = menu_port_default_irq_ctrl,
.error_handler = menu_port_default_error_handler,
.modbus_send = menu_port_default_modbus_send,
.modbus_receive = menu_port_default_modbus_receive,
.persistence_save = menu_port_default_persistence_save,
.persistence_restore = menu_port_default_persistence_restore,
};
/* 函数实现 ---------------------------------------------------------------------------------------------------------*/
/**
* @brief 初始化硬件端口层
* @param driver 硬件驱动结构体指针
* @return 错误码
*/
MenuErrCode menu_port_init(const MenuPortDriver* driver)
{
if (driver != NULL) {
sg_menu_port_driver = driver;
} else {
sg_menu_port_driver = &sg_default_driver;
}
return MENU_ERR_OK;
}
/**
* @brief 反初始化硬件端口层
* @return 错误码
*/
MenuErrCode menu_port_deinit(void)
{
sg_menu_port_driver = NULL;
return MENU_ERR_OK;
}
/**
* @brief 获取当前系统时间ms
* @return 系统时间ms
*/
uint32_t menu_port_get_tick(void)
{
if (sg_menu_port_driver != NULL && sg_menu_port_driver->get_tick != NULL) {
return sg_menu_port_driver->get_tick();
}
return menu_port_default_get_tick();
}
/**
* @brief 硬件延迟函数
* @param ms 延迟时间ms
*/
void menu_port_delay_ms(uint32_t ms)
{
if (sg_menu_port_driver != NULL && sg_menu_port_driver->delay_ms != NULL) {
sg_menu_port_driver->delay_ms(ms);
} else {
menu_port_default_delay_ms(ms);
}
}
/**
* @brief 打印函数
* @param fmt 格式化字符串
* @param ... 可变参数
*/
void menu_port_printf(const char* fmt, ...)
{
va_list args;
va_start(args, fmt);
if (sg_menu_port_driver != NULL && sg_menu_port_driver->printf != NULL) {
sg_menu_port_driver->printf(fmt, args);
} else {
menu_port_default_printf(fmt, args);
}
va_end(args);
}
/**
* @brief 菜单显示函数
* @param menu_name 菜单名称
* @param menu_id 菜单ID
*/
void menu_port_display(const char* menu_name, uint16_t menu_id)
{
if (sg_menu_port_driver != NULL && sg_menu_port_driver->display != NULL) {
sg_menu_port_driver->display(menu_name, menu_id);
} else {
menu_port_default_display(menu_name, menu_id);
}
}
/**
* @brief 按键扫描函数
* @return 按键事件类型
*/
MenuEventType menu_port_key_scan(void)
{
if (sg_menu_port_driver != NULL && sg_menu_port_driver->key_scan != NULL) {
return sg_menu_port_driver->key_scan();
}
return menu_port_default_key_scan();
}
/**
* @brief 中断管理函数
* @param enable 是否启用中断
*/
void menu_port_irq_ctrl(bool enable)
{
if (sg_menu_port_driver != NULL && sg_menu_port_driver->irq_ctrl != NULL) {
sg_menu_port_driver->irq_ctrl(enable);
} else {
menu_port_default_irq_ctrl(enable);
}
}
/**
* @brief 错误处理函数
* @param err_code 错误码
*/
void menu_port_error_handler(MenuErrCode err_code)
{
if (sg_menu_port_driver != NULL && sg_menu_port_driver->error_handler != NULL) {
sg_menu_port_driver->error_handler(err_code);
} else {
menu_port_default_error_handler(err_code);
}
}
/**
* @brief 断言失败处理函数
* @param expr 断言表达式
* @param file 文件名
* @param line 行号
*/
void menu_assert_failed(const char* expr, const char* file, uint32_t line)
{
// 打印断言失败信息
char buf[128] = {0};
snprintf(buf, sizeof(buf), "Assertion failed: %s, file %s, line %lu\n", expr, file, (unsigned long)line);
// 直接使用printf输出因为menu_port_printf需要va_list参数
printf("%s", buf);
// 进入死循环
while (1) {
// 可以添加其他处理逻辑,如复位系统等
}
}

111
port/menu_port.h Normal file
View File

@ -0,0 +1,111 @@
/**
**********************************************************************************************************************
* @file menu_port.h
* @brief 菜单组件硬件端口层定义
* @author menu_component
* @date 2025-12-19
**********************************************************************************************************************
*/
#ifndef MENU_PORT_H
#define MENU_PORT_H
/* Includes ----------------------------------------------------------------------------------------------------------*/
#include "../src/core/menu_config.h"
#include <stdarg.h>
/* 类型定义 ---------------------------------------------------------------------------------------------------------*/
/**
* @brief Modbus寄存器类型
*/
typedef enum {
MODBUS_REG_TYPE_COIL, /*!< 线圈寄存器 */
MODBUS_REG_TYPE_DISCRETE_INPUT, /*!< 离散输入寄存器 */
MODBUS_REG_TYPE_HOLDING_REG, /*!< 保持寄存器 */
MODBUS_REG_TYPE_INPUT_REG, /*!< 输入寄存器 */
} ModbusRegType;
/**
* @brief 持久化操作回调函数类型定义
*/
typedef MenuErrCode (*MenuPersistenceSaveCallback)(const uint8_t* data, uint16_t size); ///< 保存持久化数据回调
typedef MenuErrCode (*MenuPersistenceRestoreCallback)(uint8_t* data, uint16_t* size); ///< 恢复持久化数据回调
/**
* @brief 硬件驱动结构体
*/
typedef struct {
void (*printf)(const char* fmt, va_list args); ///< 硬件打印接口
uint32_t (*get_tick)(void); ///< 获取系统滴答时间
void (*delay_ms)(uint32_t ms); ///< 硬件延迟函数
void (*display)(const char* menu_name, uint16_t menu_id); ///< 菜单显示接口
MenuEventType (*key_scan)(void); ///< 按键扫描接口(可选)
void (*irq_ctrl)(bool enable); ///< 中断管理接口(可选)
void (*error_handler)(MenuErrCode err_code); ///< 错误处理接口(可选)
MenuErrCode (*modbus_send)(ModbusRegType reg_type, uint16_t reg_addr, const uint8_t* reg_buf, uint8_t buf_len); ///< Modbus发送接口可选
MenuErrCode (*modbus_receive)(ModbusRegType reg_type, uint16_t reg_addr, uint8_t* reg_buf, uint8_t buf_len); ///< Modbus接收接口可选
MenuPersistenceSaveCallback persistence_save; ///< 持久化数据保存接口(可选)
MenuPersistenceRestoreCallback persistence_restore; ///< 持久化数据恢复接口(可选)
} MenuPortDriver;
/* 函数声明 ---------------------------------------------------------------------------------------------------------*/
/**
* @brief 初始化硬件端口层
* @param driver 硬件驱动结构体指针
* @return 错误码
*/
MenuErrCode menu_port_init(const MenuPortDriver* driver);
/**
* @brief 反初始化硬件端口层
* @return 错误码
*/
MenuErrCode menu_port_deinit(void);
/**
* @brief 获取当前系统时间ms
* @return 系统时间ms
*/
uint32_t menu_port_get_tick(void);
/**
* @brief 硬件延迟函数
* @param ms 延迟时间ms
*/
void menu_port_delay_ms(uint32_t ms);
/**
* @brief 打印函数
* @param fmt 格式化字符串
* @param ... 可变参数
*/
void menu_port_printf(const char* fmt, ...);
/**
* @brief 菜单显示函数
* @param menu_name 菜单名称
* @param menu_id 菜单ID
*/
void menu_port_display(const char* menu_name, uint16_t menu_id);
/**
* @brief 按键扫描函数
* @return 按键事件类型
*/
MenuEventType menu_port_key_scan(void);
/**
* @brief 中断管理函数
* @param enable 是否启用中断
*/
void menu_port_irq_ctrl(bool enable);
/**
* @brief 错误处理函数
* @param err_code 错误码
*/
void menu_port_error_handler(MenuErrCode err_code);
#endif /* MENU_PORT_H */

107
src/core/menu_config.h Normal file
View File

@ -0,0 +1,107 @@
/**
**********************************************************************************************************************
* @file menu_config.h
* @brief 菜单组件核心配置文件
* @author menu_component
* @date 2025-12-19
**********************************************************************************************************************
*/
#ifndef MENU_CONFIG_H
#define MENU_CONFIG_H
/* Includes ----------------------------------------------------------------------------------------------------------*/
#include <stdint.h>
#include <stdbool.h>
#include <stddef.h>
/* 配置项 -----------------------------------------------------------------------------------------------------------*/
/* 菜单节点配置 */
#define MENU_CONFIG_MAX_NODES 32U /* 最大菜单节点数 */
#define MENU_CONFIG_HASH_TABLE_SIZE 31U /* 哈希表大小(必须为质数) */
#define MENU_CONFIG_STACK_DEPTH 8U /* 菜单栈深度 */
#define MENU_CONFIG_MAX_STATE_TRANSITIONS 16U /* 最大自定义状态转换规则数 */
/* 事件队列配置 */
#define MENU_CONFIG_EVENT_QUEUE_LEN 16U /* 事件队列长度 */
#define MENU_CONFIG_EVENT_MAX_PRIORITY 4U /* 事件最大优先级0-3 */
/* 功能开关配置 */
#define MENU_CONFIG_ENABLE_ASSERT 1U /* 是否启用断言 */
#define MENU_CONFIG_ENABLE_DEBUG 1U /* 是否启用调试打印 */
#define MENU_CONFIG_ENABLE_MEM_MONITOR 1U /* 是否启用内存监控 */
#define MENU_CONFIG_ENABLE_PARAM 1U /* 是否启用参数管理 */
#define MENU_CONFIG_ENABLE_LANG 1U /* 是否启用多语言支持 */
#define MENU_CONFIG_ENABLE_MODBUS_MAP 1U /* 是否启用Modbus映射 */
#define MENU_CONFIG_ENABLE_STATE_MACHINE_EXT 1U /* 是否启用状态机扩展 */
#define MENU_CONFIG_ENABLE_PERMISSION 1U /* 是否启用权限管理 */
#define MENU_CONFIG_ENABLE_PERSISTENCE 1U /* 是否启用状态持久化 */
/* 权限管理配置 */
#define MENU_CONFIG_PERMISSION_MAX_ROLES 8U /* 最大支持的角色数量 */
#define MENU_CONFIG_PERMISSION_MAX_LEVEL 16U /* 最大权限级别 */
/* 状态持久化配置 */
#define MENU_CONFIG_PERSISTENCE_MAX_SIZE 256U /* 持久化数据的最大大小(字节) */
#define MENU_CONFIG_PERSISTENCE_AUTO_SAVE_INTERVAL 5000U /* 自动保存间隔ms */
#define MENU_CONFIG_PERSISTENCE_ENABLE_ENCRYPT 1U /* 是否启用持久化数据加密 */
/* 类型定义 ---------------------------------------------------------------------------------------------------------*/
typedef uint16_t MenuNodeId; /* 菜单节点ID类型 */
typedef uint8_t MenuState; /* 菜单状态类型 */
typedef uint8_t MenuEventType; /* 事件类型 */
typedef uint16_t MenuErrCode; /* 错误码类型 */
/* 状态定义 ---------------------------------------------------------------------------------------------------------*/
#define MENU_STATE_INIT 0U /* 初始化状态 */
#define MENU_STATE_NORMAL 1U /* 正常导航状态 */
#define MENU_STATE_PARAM_EDIT 2U /* 参数编辑状态 */
#define MENU_STATE_CONFIRM 3U /* 确认状态 */
#define MENU_STATE_ERROR 4U /* 错误状态 */
/* 事件类型定义 -----------------------------------------------------------------------------------------------------*/
#define MENU_EVENT_NONE 0U /* 无事件 */
#define MENU_EVENT_KEY_UP 1U /* 上键事件 */
#define MENU_EVENT_KEY_DOWN 2U /* 下键事件 */
#define MENU_EVENT_KEY_ENTER 3U /* 确认键事件 */
#define MENU_EVENT_KEY_ESC 4U /* 退出键事件 */
#define MENU_EVENT_TIMEOUT 5U /* 超时事件 */
#define MENU_EVENT_CUSTOM_BASE 10U /* 自定义事件基值 */
/* 错误码定义 -------------------------------------------------------------------------------------------------------*/
#define MENU_ERR_OK 0U /* 成功 */
#define MENU_ERR_INVALID_PARAM 1U /* 参数无效 */
#define MENU_ERR_OUT_OF_MEMORY 2U /* 内存不足 */
#define MENU_ERR_NODE_NOT_FOUND 3U /* 节点未找到 */
#define MENU_ERR_QUEUE_FULL 4U /* 队列已满 */
#define MENU_ERR_QUEUE_EMPTY 5U /* 队列为空 */
#define MENU_ERR_INVALID_STATE 6U /* 无效状态 */
#define MENU_ERR_OPERATION_FAILED 7U /* 操作失败 */
#define MENU_ERR_NOT_INITIALIZED 8U /* 未初始化 */
#define MENU_ERR_ALREADY_EXISTS 9U /* 已存在 */
#define MENU_ERR_STACK_OVERFLOW 10U /* 栈溢出 */
#define MENU_ERR_STACK_UNDERFLOW 11U /* 栈下溢 */
#define MENU_ERR_STACK_EMPTY 12U /* 栈为空 */
#define MENU_ERR_NAV_PATH_OVERFLOW 13U /* 导航路径溢出 */
#define MENU_ERR_INVALID_CALLBACK 14U /* 无效回调函数 */
#define MENU_ERR_INVALID_TRANSITION 15U /* 无效状态转换 */
#define MENU_ERR_NOT_SUPPORTED 16U /* 不支持的操作 */
/* 断言定义 ---------------------------------------------------------------------------------------------------------*/
#if MENU_CONFIG_ENABLE_ASSERT
extern void menu_assert_failed(const char* expr, const char* file, uint32_t line);
#define MENU_ASSERT(expr) ((expr) ? (void)0U : menu_assert_failed(#expr, __FILE__, __LINE__))
#else
#define MENU_ASSERT(expr) ((void)0U)
#endif
/* 调试打印定义 -----------------------------------------------------------------------------------------------------*/
#if MENU_CONFIG_ENABLE_DEBUG
extern void menu_debug_print(const char* fmt, ...);
#define MENU_DEBUG(fmt, ...) menu_debug_print(fmt, ##__VA_ARGS__)
#else
#define MENU_DEBUG(fmt, ...) ((void)0U)
#endif
#endif /* MENU_CONFIG_H */

778
src/core/menu_core.c Normal file
View File

@ -0,0 +1,778 @@
/**
**********************************************************************************************************************
* @file menu_core.c
* @brief 菜单组件核心功能实现
* @author menu_component
* @date 2025-12-19
**********************************************************************************************************************
*/
/* Includes ----------------------------------------------------------------------------------------------------------*/
#include "menu_types.h"
#include "menu_core.h"
#include "menu_hash.h"
#include "menu_event.h"
#include "menu_stack.h"
#if MENU_CONFIG_ENABLE_PERMISSION
#include "menu_permission.h"
#endif
#if MENU_CONFIG_ENABLE_PERSISTENCE
#include "menu_persistence.h"
#endif
/* 私有函数原型 -----------------------------------------------------------------------------------------------------*/
static MenuErrCode menu_core_init_nodes(MenuCoreCtx* core_ctx);
static MenuNode* menu_core_get_free_node(MenuCoreCtx* core_ctx);
static MenuErrCode menu_core_state_transition(MenuCoreCtx* core_ctx, MenuEventType event);
static MenuErrCode menu_core_navigate_enter(MenuCoreCtx* core_ctx, MenuNodeId node_id);
static MenuErrCode menu_core_navigate_back(MenuCoreCtx* core_ctx);
static MenuErrCode menu_core_navigate_up(MenuCoreCtx* core_ctx);
static MenuErrCode menu_core_navigate_down(MenuCoreCtx* core_ctx);
/* 全局变量 ---------------------------------------------------------------------------------------------------------*/
static MenuCoreCtx sg_menu_core_ctx;
/* 状态转换规则定义 -------------------------------------------------------------------------------------------------*/
#if MENU_CONFIG_ENABLE_STATE_MACHINE_EXT
static MenuStateTransition sg_default_transitions[] = {
// 初始状态转换
{MENU_STATE_INIT, MENU_EVENT_KEY_ENTER, MENU_STATE_NORMAL, NULL, "Init to Normal"},
{MENU_STATE_INIT, MENU_EVENT_TIMEOUT, MENU_STATE_ERROR, NULL, "Init timeout to Error"},
// 正常状态转换
{MENU_STATE_NORMAL, MENU_EVENT_KEY_ENTER, MENU_STATE_PARAM_EDIT, NULL, "Normal to Param Edit"},
{MENU_STATE_NORMAL, MENU_EVENT_KEY_ESC, MENU_STATE_NORMAL, NULL, "Normal to Normal"},
{MENU_STATE_NORMAL, MENU_EVENT_KEY_UP, MENU_STATE_NORMAL, NULL, "Normal to Normal (Up)"},
{MENU_STATE_NORMAL, MENU_EVENT_KEY_DOWN, MENU_STATE_NORMAL, NULL, "Normal to Normal (Down)"},
// 参数编辑状态转换
{MENU_STATE_PARAM_EDIT, MENU_EVENT_KEY_ENTER, MENU_STATE_CONFIRM, NULL, "Param Edit to Confirm"},
{MENU_STATE_PARAM_EDIT, MENU_EVENT_KEY_ESC, MENU_STATE_NORMAL, NULL, "Param Edit to Normal"},
// 确认状态转换
{MENU_STATE_CONFIRM, MENU_EVENT_KEY_ENTER, MENU_STATE_NORMAL, NULL, "Confirm to Normal"},
{MENU_STATE_CONFIRM, MENU_EVENT_KEY_ESC, MENU_STATE_PARAM_EDIT, NULL, "Confirm to Param Edit"},
// 错误状态转换
{MENU_STATE_ERROR, MENU_EVENT_KEY_ESC, MENU_STATE_INIT, NULL, "Error to Init"},
};
#define MENU_DEFAULT_TRANSITION_COUNT (sizeof(sg_default_transitions) / sizeof(MenuStateTransition))
#endif
/* 函数实现 ---------------------------------------------------------------------------------------------------------*/
/**
* @brief 初始化菜单核心
* @return 错误码
*/
MenuErrCode menu_core_init(void)
{
MenuCoreCtx* core_ctx = &sg_menu_core_ctx;
// 初始化核心上下文
core_ctx->is_initialized = false;
core_ctx->current_node_id = 0;
core_ctx->current_state = MENU_STATE_INIT;
core_ctx->last_refresh_tick = 0;
core_ctx->error_code = MENU_ERR_OK;
core_ctx->error_msg = NULL;
core_ctx->node_count = 0;
core_ctx->free_node_count = MENU_CONFIG_MAX_NODES;
#if MENU_CONFIG_ENABLE_STATE_MACHINE_EXT
core_ctx->custom_transition_count = 0;
#endif
// 初始化各个组件
menu_core_init_nodes(core_ctx);
menu_hash_init(core_ctx);
menu_stack_init(&core_ctx->stack, MENU_CONFIG_STACK_DEPTH);
menu_nav_path_init(&core_ctx->nav_path, MENU_CONFIG_STACK_DEPTH);
menu_event_queue_init(&core_ctx->event_queue);
#if MENU_CONFIG_ENABLE_STATE_MACHINE_EXT
// 注册默认状态转换规则
for (uint16_t i = 0; i < MENU_DEFAULT_TRANSITION_COUNT; i++) {
core_ctx->custom_transitions[i] = sg_default_transitions[i];
}
core_ctx->custom_transition_count = MENU_DEFAULT_TRANSITION_COUNT;
#endif
#if MENU_CONFIG_ENABLE_PERMISSION
// 初始化权限管理模块
menu_permission_init();
#endif
#if MENU_CONFIG_ENABLE_PERSISTENCE
// 初始化状态持久化模块
menu_persistence_init();
#endif
core_ctx->is_initialized = true;
return MENU_ERR_OK;
}
/**
* @brief 反初始化菜单核心
* @return 错误码
*/
MenuErrCode menu_core_deinit(void)
{
MenuCoreCtx* core_ctx = &sg_menu_core_ctx;
if (!core_ctx->is_initialized) {
return MENU_ERR_INVALID_STATE;
}
// 清空事件队列
menu_event_queue_clear(&core_ctx->event_queue);
// 清空栈和导航路径
menu_stack_clear(&core_ctx->stack);
menu_nav_path_clear(&core_ctx->nav_path);
// 重置哈希表
menu_hash_init(core_ctx);
// 重置所有节点
menu_core_init_nodes(core_ctx);
// 重置核心上下文
core_ctx->is_initialized = false;
core_ctx->current_node_id = 0;
core_ctx->current_state = MENU_STATE_INIT;
core_ctx->last_refresh_tick = 0;
core_ctx->error_code = MENU_ERR_OK;
core_ctx->error_msg = NULL;
core_ctx->node_count = 0;
core_ctx->free_node_count = MENU_CONFIG_MAX_NODES;
#if MENU_CONFIG_ENABLE_STATE_MACHINE_EXT
core_ctx->custom_transition_count = 0;
#endif
#if MENU_CONFIG_ENABLE_PERMISSION
// 反初始化权限管理模块
menu_permission_deinit();
#endif
#if MENU_CONFIG_ENABLE_PERSISTENCE
// 反初始化状态持久化模块
menu_persistence_deinit();
#endif
return MENU_ERR_OK;
}
/**
* @brief 初始化菜单节点池
* @param core_ctx 菜单核心上下文
* @return 错误码
*/
static MenuErrCode menu_core_init_nodes(MenuCoreCtx* core_ctx)
{
for (uint16_t i = 0; i < MENU_CONFIG_MAX_NODES; i++) {
MenuNode* node = &core_ctx->nodes[i];
node->id = i + 1; // 节点ID从1开始
node->parent_id = 0;
node->name = NULL;
node->enter_cb = NULL;
node->exit_cb = NULL;
node->first_child_id = 0;
node->next_sibling_id = 0;
node->prev_sibling_id = 0;
node->hash_next_id = 0;
node->flags.is_registered = false;
node->flags.is_selected = false;
node->flags.is_visible = true; // 默认可见
node->flags.is_enabled = true; // 默认启用
#if MENU_CONFIG_ENABLE_PARAM
node->param_id = 0;
#endif
#if MENU_CONFIG_ENABLE_PERMISSION
node->permission_level = 0; // 默认权限级别为0最低权限
#endif
}
return MENU_ERR_OK;
}
/**
* @brief 获取一个空闲节点
* @param core_ctx 菜单核心上下文
* @return 空闲节点指针没有空闲节点返回NULL
*/
static MenuNode* menu_core_get_free_node(MenuCoreCtx* core_ctx)
{
static uint16_t last_allocated_index = 0; // 记录上次分配的索引,实现循环分配
// 先从上次分配的位置开始查找,实现循环分配
for (uint16_t i = 0; i < MENU_CONFIG_MAX_NODES; i++) {
uint16_t index = (last_allocated_index + i) % MENU_CONFIG_MAX_NODES;
MenuNode* node = &core_ctx->nodes[index];
if (!node->flags.is_registered) {
last_allocated_index = index;
return node;
}
}
return NULL;
}
/**
* @brief 注册菜单节点
* @param node_id 节点ID0表示自动分配
* @param parent_id 父节点ID
* @param name 节点名称
* @param enter_cb 进入回调
* @param exit_cb 退出回调
* @return 实际分配的节点ID
*/
MenuNodeId menu_core_register_node(MenuNodeId node_id, MenuNodeId parent_id, const char* name, MenuCallback enter_cb, MenuCallback exit_cb)
{
MenuCoreCtx* core_ctx = &sg_menu_core_ctx;
// 参数验证
if (!core_ctx->is_initialized) {
return 0;
}
// 检查父节点是否有效(如果不是根节点)
if (parent_id != 0) {
MenuNode* parent_node = menu_hash_find(core_ctx, parent_id);
if (parent_node == NULL || !parent_node->flags.is_registered) {
return 0;
}
}
// 检查节点名称是否有效
if (name == NULL) {
return 0;
}
MenuNode* node = NULL;
if (node_id == 0) {
// 自动分配节点ID
node = menu_core_get_free_node(core_ctx);
if (node == NULL) {
return 0;
}
} else {
// 使用指定节点ID
if (node_id > MENU_CONFIG_MAX_NODES || node_id < 1) {
return 0;
}
node = &core_ctx->nodes[node_id - 1];
if (node->flags.is_registered) {
return 0;
}
}
// 初始化节点
node->parent_id = parent_id;
node->name = name;
node->enter_cb = enter_cb;
node->exit_cb = exit_cb;
// 添加到父节点的子节点列表
if (parent_id != 0) {
MenuNode* parent_node = menu_hash_find(core_ctx, parent_id);
if (parent_node != NULL) {
if (parent_node->first_child_id == 0) {
// 父节点没有子节点,直接添加
parent_node->first_child_id = node->id;
} else {
// 查找父节点的最后一个子节点
MenuNode* last_child = menu_hash_find(core_ctx, parent_node->first_child_id);
while (last_child->next_sibling_id != 0) {
last_child = menu_hash_find(core_ctx, last_child->next_sibling_id);
}
// 添加到最后一个子节点的后面
last_child->next_sibling_id = node->id;
node->prev_sibling_id = last_child->id;
}
}
}
// 添加到哈希表
menu_hash_add(core_ctx, node);
// 更新节点计数
node->flags.is_registered = true;
core_ctx->node_count++;
core_ctx->free_node_count--;
// 如果是第一个节点,设置为当前节点
if (core_ctx->current_node_id == 0) {
core_ctx->current_node_id = node->id;
menu_nav_path_add(&core_ctx->nav_path, node->id);
// 初始化栈,将根节点压入栈中,确保栈和导航路径深度一致
menu_stack_push(&core_ctx->stack, node->id);
}
return node->id;
}
/**
* @brief 注销菜单节点
* @param node_id 节点ID
* @return 错误码
*/
static MenuErrCode menu_core_unregister_node_recursive(MenuCoreCtx* core_ctx, MenuNodeId node_id);
/**
* @brief 注销菜单节点
* @param node_id 节点ID
* @return 错误码
*/
MenuErrCode menu_core_unregister_node(MenuNodeId node_id)
{
MenuCoreCtx* core_ctx = &sg_menu_core_ctx;
// 参数验证
if (!core_ctx->is_initialized) {
return MENU_ERR_INVALID_STATE;
}
if (node_id == 0 || node_id > MENU_CONFIG_MAX_NODES) {
return MENU_ERR_INVALID_PARAM;
}
// 递归注销所有子节点
return menu_core_unregister_node_recursive(core_ctx, node_id);
}
/**
* @brief 递归注销菜单节点及其所有子节点
* @param core_ctx 菜单核心上下文
* @param node_id 节点ID
* @return 错误码
*/
static MenuErrCode menu_core_unregister_node_recursive(MenuCoreCtx* core_ctx, MenuNodeId node_id)
{
MenuNode* node = menu_hash_find(core_ctx, node_id);
if (node == NULL || !node->flags.is_registered) {
return MENU_ERR_NODE_NOT_FOUND;
}
// 递归注销所有子节点
if (node->first_child_id != 0) {
MenuNode* child_node = menu_hash_find(core_ctx, node->first_child_id);
while (child_node != NULL) {
MenuNodeId next_child_id = child_node->next_sibling_id;
// 递归注销子节点
menu_core_unregister_node_recursive(core_ctx, child_node->id);
child_node = menu_hash_find(core_ctx, next_child_id);
}
}
// 从哈希表中移除
menu_hash_remove(core_ctx, node);
// 从父节点的子节点列表中移除
if (node->parent_id != 0) {
MenuNode* parent_node = menu_hash_find(core_ctx, node->parent_id);
if (parent_node != NULL) {
if (parent_node->first_child_id == node_id) {
// 移除的是第一个子节点
parent_node->first_child_id = node->next_sibling_id;
} else {
// 移除的是中间或最后一个子节点
if (node->prev_sibling_id != 0) {
MenuNode* prev_sibling = menu_hash_find(core_ctx, node->prev_sibling_id);
if (prev_sibling != NULL) {
prev_sibling->next_sibling_id = node->next_sibling_id;
}
}
}
if (node->next_sibling_id != 0) {
MenuNode* next_sibling = menu_hash_find(core_ctx, node->next_sibling_id);
if (next_sibling != NULL) {
next_sibling->prev_sibling_id = node->prev_sibling_id;
}
}
}
}
// 重置节点状态
node->flags.is_registered = false;
node->flags.is_selected = false;
node->parent_id = 0;
node->next_sibling_id = 0;
node->prev_sibling_id = 0;
node->first_child_id = 0;
// 更新节点计数
core_ctx->node_count--;
core_ctx->free_node_count++;
return MENU_ERR_OK;
}
/**
* @brief 状态转换处理
* @param core_ctx 菜单核心上下文
* @param event 事件类型
* @return 错误码
*/
static MenuErrCode menu_core_state_transition(MenuCoreCtx* core_ctx, MenuEventType event)
{
MenuStateTransition default_transition = {0};
MenuStateTransition* matched_transition = NULL;
// 1. 查找匹配的自定义状态转换规则
#if MENU_CONFIG_ENABLE_STATE_MACHINE_EXT
for (uint16_t i = 0; i < core_ctx->custom_transition_count; i++) {
MenuStateTransition* transition = &core_ctx->custom_transitions[i];
if (transition->current_state == core_ctx->current_state && transition->event == event) {
matched_transition = transition;
break;
}
}
#endif
// 2. 如果没有找到匹配的自定义规则,使用默认状态转换
if (matched_transition == NULL) {
// 默认状态转换规则:保持当前状态不变
default_transition.current_state = core_ctx->current_state;
default_transition.event = event;
default_transition.next_state = core_ctx->current_state; // 默认不改变状态
default_transition.action = NULL;
default_transition.desc = "默认状态转换";
matched_transition = &default_transition;
}
// 3. 先更新状态,再执行转换动作
MenuState old_state = core_ctx->current_state;
core_ctx->current_state = matched_transition->next_state;
// 4. 执行状态转换动作(如果有)
if (matched_transition->action != NULL) {
MenuErrCode err = matched_transition->action(core_ctx);
if (err != MENU_ERR_OK) {
// 动作执行失败,恢复原状态
core_ctx->current_state = old_state;
return err;
}
}
return MENU_ERR_OK;
}
/**
* @brief 导航进入子菜单
* @param core_ctx 菜单核心上下文
* @param node_id 节点ID
* @return 错误码
*/
static MenuErrCode menu_core_navigate_enter(MenuCoreCtx* core_ctx, MenuNodeId node_id)
{
MenuNode* node = menu_hash_find(core_ctx, node_id);
if (node == NULL) {
return MENU_ERR_NODE_NOT_FOUND;
}
// 如果当前节点没有子节点,直接返回
if (node->first_child_id == 0) {
return MENU_ERR_OK;
}
// 进入第一个子节点
MenuNodeId target_node_id = node->first_child_id;
MenuNode* target_node = menu_hash_find(core_ctx, target_node_id);
if (target_node == NULL) {
return MENU_ERR_NODE_NOT_FOUND;
}
// 执行当前节点的退出回调
MenuNode* curr_node = menu_hash_find(core_ctx, core_ctx->current_node_id);
if (curr_node != NULL && curr_node->exit_cb != NULL) {
curr_node->exit_cb(core_ctx->current_node_id, core_ctx);
}
// 压入当前节点到栈中
MenuErrCode err = menu_stack_push(&core_ctx->stack, core_ctx->current_node_id);
if (err != MENU_ERR_OK) {
return err;
}
// 更新当前节点
core_ctx->current_node_id = target_node_id;
// 添加到导航路径
err = menu_nav_path_add(&core_ctx->nav_path, target_node_id);
if (err != MENU_ERR_OK) {
menu_stack_pop(&core_ctx->stack, &node_id);
core_ctx->current_node_id = node_id;
return err;
}
// 执行新节点的进入回调
if (target_node->enter_cb != NULL) {
target_node->enter_cb(target_node_id, core_ctx);
}
return MENU_ERR_OK;
}
/**
* @brief 导航返回上一级菜单
* @param core_ctx 菜单核心上下文
* @return 错误码
*/
static MenuErrCode menu_core_navigate_back(MenuCoreCtx* core_ctx)
{
// 执行当前节点的退出回调
MenuNode* curr_node = menu_hash_find(core_ctx, core_ctx->current_node_id);
if (curr_node != NULL && curr_node->exit_cb != NULL) {
curr_node->exit_cb(core_ctx->current_node_id, core_ctx);
}
MenuNodeId prev_node_id;
MenuErrCode err;
// 如果栈不为空弹出上一级节点ID
if (!menu_stack_is_empty(&core_ctx->stack)) {
err = menu_stack_pop(&core_ctx->stack, &prev_node_id);
if (err != MENU_ERR_OK) {
return err;
}
} else {
// 栈为空,返回到根节点
// 查找根节点
prev_node_id = 0;
for (uint16_t i = 0; i < MENU_CONFIG_MAX_NODES; i++) {
MenuNode* node = &core_ctx->nodes[i];
if (node->flags.is_registered && node->parent_id == 0) {
prev_node_id = node->id;
break;
}
}
// 如果没有找到根节点,返回错误
if (prev_node_id == 0) {
return MENU_ERR_NODE_NOT_FOUND;
}
}
// 从导航路径移除当前节点
err = menu_nav_path_remove_last(&core_ctx->nav_path);
if (err != MENU_ERR_OK) {
// 如果导航路径移除失败,检查是否还有深度
if (core_ctx->nav_path.depth > 0) {
// 导航路径还有深度,直接更新当前节点
core_ctx->current_node_id = prev_node_id;
} else {
// 导航路径已为空,添加根节点
menu_nav_path_add(&core_ctx->nav_path, prev_node_id);
}
} else {
// 导航路径移除成功,更新当前节点
core_ctx->current_node_id = prev_node_id;
}
// 执行上一级节点的进入回调
MenuNode* prev_node = menu_hash_find(core_ctx, prev_node_id);
if (prev_node != NULL && prev_node->enter_cb != NULL) {
prev_node->enter_cb(prev_node_id, core_ctx);
}
return MENU_ERR_OK;
}
/**
* @brief 导航向上选择
* @param core_ctx 菜单核心上下文
* @return 错误码
*/
static MenuErrCode menu_core_navigate_up(MenuCoreCtx* core_ctx)
{
MenuNode* curr_node = menu_hash_find(core_ctx, core_ctx->current_node_id);
if (curr_node == NULL) {
return MENU_ERR_NODE_NOT_FOUND;
}
// 查找当前节点的父节点
MenuNode* parent_node = menu_hash_find(core_ctx, curr_node->parent_id);
// 如果当前节点有父节点,在同级节点中向上切换
if (parent_node != NULL) {
// 查找同级的上一个兄弟节点
if (curr_node->prev_sibling_id != 0) {
core_ctx->current_node_id = curr_node->prev_sibling_id;
} else if (parent_node->first_child_id != 0) {
// 没有上一个兄弟节点,查找父节点的最后一个子节点(环形导航)
MenuNode* last_child = menu_hash_find(core_ctx, parent_node->first_child_id);
if (last_child != NULL) {
// 查找最后一个子节点
while (last_child->next_sibling_id != 0) {
MenuNode* next_child = menu_hash_find(core_ctx, last_child->next_sibling_id);
if (next_child == NULL) {
break; // 防止无限循环,安全退出
}
last_child = next_child;
}
core_ctx->current_node_id = last_child->id;
}
}
} else {
// 如果当前节点是根节点,在其子节点中向上切换
if (curr_node->first_child_id != 0) {
// 查找最后一个子节点
MenuNode* last_child = menu_hash_find(core_ctx, curr_node->first_child_id);
if (last_child != NULL) {
while (last_child->next_sibling_id != 0) {
MenuNode* next_child = menu_hash_find(core_ctx, last_child->next_sibling_id);
if (next_child == NULL) {
break; // 防止无限循环,安全退出
}
last_child = next_child;
}
core_ctx->current_node_id = last_child->id;
}
}
}
return MENU_ERR_OK;
}
/**
* @brief 导航向下选择
* @param core_ctx 菜单核心上下文
* @return 错误码
*/
static MenuErrCode menu_core_navigate_down(MenuCoreCtx* core_ctx)
{
MenuNode* curr_node = menu_hash_find(core_ctx, core_ctx->current_node_id);
if (curr_node == NULL) {
return MENU_ERR_NODE_NOT_FOUND;
}
// 查找当前节点的父节点
MenuNode* parent_node = menu_hash_find(core_ctx, curr_node->parent_id);
// 如果当前节点有父节点,在同级节点中向下切换
if (parent_node != NULL) {
// 查找同级的下一个兄弟节点
if (curr_node->next_sibling_id != 0) {
core_ctx->current_node_id = curr_node->next_sibling_id;
} else if (parent_node->first_child_id != 0) {
// 没有下一个兄弟节点,选择父节点的第一个子节点(环形导航)
core_ctx->current_node_id = parent_node->first_child_id;
}
} else {
// 如果当前节点是根节点,在其子节点中向下切换
if (curr_node->first_child_id != 0) {
// 导航到第一个子节点
MenuNode* first_child = menu_hash_find(core_ctx, curr_node->first_child_id);
if (first_child != NULL) {
core_ctx->current_node_id = first_child->id;
}
}
// 如果根节点没有子节点,保持当前节点不变
}
return MENU_ERR_OK;
}
/**
* @brief 菜单主循环
* @param tick 当前系统时间ms
* @return 错误码
*/
MenuErrCode menu_core_main_loop(uint32_t tick)
{
MenuCoreCtx* core_ctx = &sg_menu_core_ctx;
if (!core_ctx->is_initialized) {
return MENU_ERR_INVALID_STATE;
}
// 更新最后刷新时间
core_ctx->last_refresh_tick = tick;
// 处理事件队列
while (!menu_event_queue_is_empty(&core_ctx->event_queue)) {
MenuEvent event;
MenuErrCode err = menu_event_queue_pop(&core_ctx->event_queue, &event);
if (err != MENU_ERR_OK) {
continue;
}
// 处理事件
switch (event.type) {
case MENU_EVENT_KEY_UP:
menu_core_navigate_up(core_ctx);
break;
case MENU_EVENT_KEY_DOWN:
menu_core_navigate_down(core_ctx);
break;
case MENU_EVENT_KEY_ENTER:
menu_core_navigate_enter(core_ctx, core_ctx->current_node_id);
break;
case MENU_EVENT_KEY_ESC:
menu_core_navigate_back(core_ctx);
break;
default:
// 其他事件通过状态机处理
menu_core_state_transition(core_ctx, event.type);
break;
}
}
// 处理自动保存
#if MENU_CONFIG_ENABLE_PERSISTENCE
menu_persistence_process_auto_save(tick);
#endif
return MENU_ERR_OK;
}
/**
* @brief 获取菜单核心上下文
* @return 菜单核心上下文指针
*/
MenuCoreCtx* menu_get_core_ctx(void)
{
return &sg_menu_core_ctx;
}
/**
* @brief 获取当前选中的节点ID
* @return 当前节点ID
*/
MenuNodeId menu_get_current_node_id(void)
{
return sg_menu_core_ctx.current_node_id;
}
/**
* @brief 获取当前菜单状态
* @return 当前菜单状态
*/
MenuState menu_get_current_state(void)
{
return sg_menu_core_ctx.current_state;
}
/**
* @brief 获取导航深度
* @return 导航深度
*/
uint8_t menu_core_get_nav_depth(void)
{
return sg_menu_core_ctx.nav_path.depth;
}

246
src/core/menu_core.h Normal file
View File

@ -0,0 +1,246 @@
/**
**********************************************************************************************************************
* @file menu_core.h
* @brief 菜单组件核心功能声明
* @author menu_component
* @date 2025-12-19
**********************************************************************************************************************
*/
#ifndef MENU_CORE_H
#define MENU_CORE_H
/* Includes ----------------------------------------------------------------------------------------------------------*/
#include "menu_types.h"
/* 函数声明 ---------------------------------------------------------------------------------------------------------*/
/**
* @brief 初始化菜单核心
* @return 错误码
*/
MenuErrCode menu_core_init(void);
/**
* @brief 反初始化菜单核心
* @return 错误码
*/
MenuErrCode menu_core_deinit(void);
/**
* @brief 注册菜单节点
* @param node_id 节点ID0表示自动分配
* @param parent_id 父节点ID
* @param name 节点名称
* @param enter_cb 进入回调
* @param exit_cb 退出回调
* @return 实际分配的节点ID
*/
MenuNodeId menu_core_register_node(MenuNodeId node_id, MenuNodeId parent_id, const char* name, MenuCallback enter_cb, MenuCallback exit_cb);
/**
* @brief 注销菜单节点
* @param node_id 节点ID
* @return 错误码
*/
MenuErrCode menu_core_unregister_node(MenuNodeId node_id);
/**
* @brief 获取菜单核心上下文
* @return 菜单核心上下文指针
*/
struct MenuCoreCtx* menu_get_core_ctx(void);
/**
* @brief 获取当前选中的节点ID
* @return 当前节点ID
*/
MenuNodeId menu_get_current_node_id(void);
/**
* @brief 获取当前菜单状态
* @return 当前菜单状态
*/
MenuState menu_get_current_state(void);
/**
* @brief 获取导航深度
* @return 导航深度
*/
uint8_t menu_core_get_nav_depth(void);
/**
* @brief 菜单主循环
* @param tick 当前系统时间ms
* @return 错误码
*/
MenuErrCode menu_core_main_loop(uint32_t tick);
/* 权限管理函数声明 -------------------------------------------------------------------------------------------------*/
#if MENU_CONFIG_ENABLE_PERMISSION
/**
* @brief 初始化权限管理模块
* @return 错误码
*/
MenuErrCode menu_permission_init(void);
/**
* @brief 反初始化权限管理模块
* @return 错误码
*/
MenuErrCode menu_permission_deinit(void);
/**
* @brief 注册角色
* @param role 角色结构体指针
* @return 错误码
*/
MenuErrCode menu_permission_register_role(const MenuRole* role);
/**
* @brief 注销角色
* @param role_id 角色ID
* @return 错误码
*/
MenuErrCode menu_permission_unregister_role(MenuRoleId role_id);
/**
* @brief 设置当前角色
* @param role_id 角色ID
* @return 错误码
*/
MenuErrCode menu_permission_set_current_role(MenuRoleId role_id);
/**
* @brief 获取当前角色
* @return 当前角色ID
*/
MenuRoleId menu_permission_get_current_role(void);
/**
* @brief 检查节点是否可访问
* @param node_id 节点ID
* @return true表示可访问false表示不可访问
*/
bool menu_permission_check_node_access(MenuNodeId node_id);
/**
* @brief 更新节点权限级别
* @param node_id 节点ID
* @param permission_level 权限级别
* @return 错误码
*/
MenuErrCode menu_permission_update_node_level(MenuNodeId node_id, MenuPermissionLevel permission_level);
/**
* @brief 获取节点权限级别
* @param node_id 节点ID
* @return 节点权限级别
*/
MenuPermissionLevel menu_permission_get_node_level(MenuNodeId node_id);
/**
* @brief 更新所有节点的可见性和可用性
* @return 错误码
*/
MenuErrCode menu_permission_update_all_nodes(void);
/**
* @brief 获取角色信息
* @param role_id 角色ID
* @return 角色结构体指针NULL表示角色不存在
*/
const MenuRole* menu_permission_get_role(MenuRoleId role_id);
#endif /* MENU_CONFIG_ENABLE_PERMISSION */
/* 状态持久化函数声明 -------------------------------------------------------------------------------------------------*/
#if MENU_CONFIG_ENABLE_PERSISTENCE
/**
* @brief 初始化状态持久化模块
* @return 错误码
*/
MenuErrCode menu_persistence_init(void);
/**
* @brief 反初始化状态持久化模块
* @return 错误码
*/
MenuErrCode menu_persistence_deinit(void);
/**
* @brief 注册持久化回调函数
* @param save_cb 保存回调函数
* @param restore_cb 恢复回调函数
* @return 错误码
*/
MenuErrCode menu_persistence_register_callback(MenuPersistenceCallback save_cb, MenuRestoreCallback restore_cb);
/**
* @brief 启用或禁用自动保存
* @param enable true表示启用false表示禁用
* @return 错误码
*/
MenuErrCode menu_persistence_set_auto_save(bool enable);
/**
* @brief 获取自动保存状态
* @return true表示启用false表示禁用
*/
bool menu_persistence_get_auto_save(void);
/**
* @brief 设置自动保存间隔
* @param interval 自动保存间隔ms
* @return 错误码
*/
MenuErrCode menu_persistence_set_auto_save_interval(uint32_t interval);
/**
* @brief 获取自动保存间隔
* @return 自动保存间隔ms
*/
uint32_t menu_persistence_get_auto_save_interval(void);
/**
* @brief 手动保存菜单状态
* @return 错误码
*/
MenuErrCode menu_persistence_save(void);
/**
* @brief 恢复菜单状态
* @return 错误码
*/
MenuErrCode menu_persistence_restore(void);
/**
* @brief 检查持久化数据是否有效
* @param data 持久化数据
* @param size 数据大小
* @return true表示有效false表示无效
*/
bool menu_persistence_check_data_valid(const uint8_t* data, uint16_t size);
/**
* @brief 处理自动保存(由菜单主循环调用)
* @param tick 当前系统时间ms
* @return 错误码
*/
MenuErrCode menu_persistence_process_auto_save(uint32_t tick);
/**
* @brief 计算数据校验和
* @param data 数据指针
* @param size 数据大小
* @return 校验和
*/
uint8_t menu_persistence_calculate_checksum(const uint8_t* data, uint16_t size);
#endif /* MENU_CONFIG_ENABLE_PERSISTENCE */
#endif /* MENU_CORE_H */

186
src/core/menu_event.c Normal file
View File

@ -0,0 +1,186 @@
/**
**********************************************************************************************************************
* @file menu_event.c
* @brief 菜单组件事件队列实现
* @author menu_component
* @date 2025-12-19
**********************************************************************************************************************
*/
/* Includes ----------------------------------------------------------------------------------------------------------*/
#include "menu_types.h"
/* 函数实现 ---------------------------------------------------------------------------------------------------------*/
/**
* @brief 初始化事件队列
* @param queue 事件队列指针
*/
void menu_event_queue_init(MenuEventQueue* queue)
{
MENU_ASSERT(queue != NULL);
queue->head = 0;
queue->tail = 0;
queue->count = 0;
queue->reserved = 0;
}
/**
* @brief 检查事件队列是否为空
* @param queue 事件队列指针
* @return true为空false不为空
*/
bool menu_event_queue_is_empty(const MenuEventQueue* queue)
{
MENU_ASSERT(queue != NULL);
return (queue->count == 0);
}
/**
* @brief 检查事件队列是否已满
* @param queue 事件队列指针
* @return true已满false未满
*/
bool menu_event_queue_is_full(const MenuEventQueue* queue)
{
MENU_ASSERT(queue != NULL);
return (queue->count >= MENU_CONFIG_EVENT_QUEUE_LEN);
}
/**
* @brief 获取事件队列中的元素数量
* @param queue 事件队列指针
* @return 元素数量
*/
uint8_t menu_event_queue_get_count(const MenuEventQueue* queue)
{
MENU_ASSERT(queue != NULL);
return queue->count;
}
/**
* @brief 向事件队列添加事件
* @param queue 事件队列指针
* @param event 要添加的事件
* @return 错误码
*/
MenuErrCode menu_event_queue_push(MenuEventQueue* queue, const MenuEvent* event)
{
MENU_ASSERT(queue != NULL);
MENU_ASSERT(event != NULL);
// 检查事件优先级是否合法
if (event->priority >= MENU_CONFIG_EVENT_MAX_PRIORITY) {
return MENU_ERR_INVALID_PARAM;
}
if (menu_event_queue_is_full(queue)) {
// 队列已满,替换低优先级事件
uint8_t lowest_prio = 0;
uint8_t lowest_idx = queue->head;
// 找到最低优先级的事件
for (uint8_t i = 0; i < MENU_CONFIG_EVENT_QUEUE_LEN; i++) {
uint8_t idx = (queue->head + i) % MENU_CONFIG_EVENT_QUEUE_LEN;
if (queue->buffer[idx].priority > lowest_prio) {
lowest_prio = queue->buffer[idx].priority;
lowest_idx = idx;
}
}
// 替换最低优先级事件
queue->buffer[lowest_idx] = *event;
return MENU_ERR_OK;
}
// 队列未满,添加到队尾
queue->buffer[queue->tail] = *event;
queue->tail = (queue->tail + 1) % MENU_CONFIG_EVENT_QUEUE_LEN;
queue->count++;
return MENU_ERR_OK;
}
/**
* @brief 从事件队列获取下一个事件
* @param queue 事件队列指针
* @param event 用于存储获取的事件
* @return 错误码
*/
MenuErrCode menu_event_queue_pop(MenuEventQueue* queue, MenuEvent* event)
{
MENU_ASSERT(queue != NULL);
MENU_ASSERT(event != NULL);
if (menu_event_queue_is_empty(queue)) {
return MENU_ERR_QUEUE_EMPTY;
}
// 寻找最高优先级的事件
uint8_t highest_prio = MENU_CONFIG_EVENT_MAX_PRIORITY;
uint8_t highest_idx = queue->head;
for (uint8_t i = 0; i < queue->count; i++) {
uint8_t idx = (queue->head + i) % MENU_CONFIG_EVENT_QUEUE_LEN;
if (queue->buffer[idx].priority < highest_prio) {
highest_prio = queue->buffer[idx].priority;
highest_idx = idx;
}
}
// 取出最高优先级事件
*event = queue->buffer[highest_idx];
// 移除该事件,将后续事件前移
for (uint8_t i = highest_idx; i != queue->tail; i = (i + 1) % MENU_CONFIG_EVENT_QUEUE_LEN) {
uint8_t next_idx = (i + 1) % MENU_CONFIG_EVENT_QUEUE_LEN;
if (next_idx == queue->tail) {
break;
}
queue->buffer[i] = queue->buffer[next_idx];
}
// 更新队列状态
if (queue->tail > 0) {
queue->tail--;
} else {
queue->tail = MENU_CONFIG_EVENT_QUEUE_LEN - 1;
}
queue->count--;
return MENU_ERR_OK;
}
/**
* @brief 清空事件队列
* @param queue 事件队列指针
*/
void menu_event_queue_clear(MenuEventQueue* queue)
{
MENU_ASSERT(queue != NULL);
queue->head = 0;
queue->tail = 0;
queue->count = 0;
}
/**
* @brief 向事件队列推送一个简单事件
* @param core_ctx 菜单核心上下文
* @param type 事件类型
* @param priority 事件优先级
* @return 错误码
*/
MenuErrCode menu_core_post_event(struct MenuCoreCtx* core_ctx, MenuEventType type, uint8_t priority)
{
MenuEvent event;
event.type = type;
event.priority = priority;
event.target_node_id = core_ctx->current_node_id;
event.data = NULL;
event.timestamp = core_ctx->last_refresh_tick;
return menu_event_queue_push(&core_ctx->event_queue, &event);
}

76
src/core/menu_event.h Normal file
View File

@ -0,0 +1,76 @@
/**
**********************************************************************************************************************
* @file menu_event.h
* @brief 菜单组件事件队列功能声明
* @author menu_component
* @date 2025-12-19
**********************************************************************************************************************
*/
#ifndef MENU_EVENT_H
#define MENU_EVENT_H
/* Includes ----------------------------------------------------------------------------------------------------------*/
#include "menu_types.h"
/* 函数声明 ---------------------------------------------------------------------------------------------------------*/
/**
* @brief 初始化事件队列
* @param queue 事件队列指针
*/
void menu_event_queue_init(MenuEventQueue* queue);
/**
* @brief 检查事件队列是否为空
* @param queue 事件队列指针
* @return true为空false不为空
*/
bool menu_event_queue_is_empty(const MenuEventQueue* queue);
/**
* @brief 检查事件队列是否已满
* @param queue 事件队列指针
* @return true已满false未满
*/
bool menu_event_queue_is_full(const MenuEventQueue* queue);
/**
* @brief 获取事件队列中的元素数量
* @param queue 事件队列指针
* @return 元素数量
*/
uint8_t menu_event_queue_get_count(const MenuEventQueue* queue);
/**
* @brief 向事件队列添加事件
* @param queue 事件队列指针
* @param event 要添加的事件
* @return 错误码
*/
MenuErrCode menu_event_queue_push(MenuEventQueue* queue, const MenuEvent* event);
/**
* @brief 从事件队列获取下一个事件
* @param queue 事件队列指针
* @param event 用于存储获取的事件
* @return 错误码
*/
MenuErrCode menu_event_queue_pop(MenuEventQueue* queue, MenuEvent* event);
/**
* @brief 清空事件队列
* @param queue 事件队列指针
*/
void menu_event_queue_clear(MenuEventQueue* queue);
/**
* @brief 向事件队列推送一个简单事件
* @param core_ctx 菜单核心上下文
* @param type 事件类型
* @param priority 事件优先级
* @return 错误码
*/
MenuErrCode menu_core_post_event(struct MenuCoreCtx* core_ctx, MenuEventType type, uint8_t priority);
#endif /* MENU_EVENT_H */

141
src/core/menu_hash.c Normal file
View File

@ -0,0 +1,141 @@
/**
**********************************************************************************************************************
* @file menu_hash.c
* @brief 菜单组件哈希表实现
* @author menu_component
* @date 2025-12-19
**********************************************************************************************************************
*/
/* Includes ----------------------------------------------------------------------------------------------------------*/
#include "menu_types.h"
/* 私有函数原型 -----------------------------------------------------------------------------------------------------*/
static uint16_t menu_hash_func(MenuNodeId node_id);
/* 函数实现 ---------------------------------------------------------------------------------------------------------*/
/**
* @brief 优化的哈希函数用于菜单节点ID
* @param node_id 节点ID
* @return 哈希值
*/
static uint16_t menu_hash_func(MenuNodeId node_id)
{
// 高性能哈希函数针对连续节点ID优化减少哈希冲突
uint32_t hash = node_id;
// 使用FNV-1a哈希算法具有良好的分布性和低冲突率
hash = (hash ^ 0x811C9DC5) * 0x01000193;
hash = (hash ^ (hash >> 16)) * 0x01000193;
hash = hash ^ (hash >> 16);
return (uint16_t)(hash % MENU_CONFIG_HASH_TABLE_SIZE);
}
/**
* @brief 初始化哈希表
* @param core_ctx 菜单核心上下文
*/
void menu_hash_init(struct MenuCoreCtx* core_ctx)
{
MENU_ASSERT(core_ctx != NULL);
// 初始化哈希表所有条目为0
for (uint16_t i = 0; i < MENU_CONFIG_HASH_TABLE_SIZE; i++) {
core_ctx->hash_table[i] = 0;
}
}
/**
* @brief 将节点添加到哈希表
* @param core_ctx 菜单核心上下文
* @param node 要添加的节点
*/
void menu_hash_add(struct MenuCoreCtx* core_ctx, MenuNode* node)
{
MENU_ASSERT(core_ctx != NULL);
MENU_ASSERT(node != NULL);
// 计算哈希值
uint16_t hash = menu_hash_func(node->id);
// 将节点插入到哈希表对应位置的链表头部
node->hash_next_id = core_ctx->hash_table[hash];
core_ctx->hash_table[hash] = node->id;
}
/**
* @brief 从哈希表中移除节点
* @param core_ctx 菜单核心上下文
* @param node 要移除的节点
*/
void menu_hash_remove(struct MenuCoreCtx* core_ctx, MenuNode* node)
{
MENU_ASSERT(core_ctx != NULL);
MENU_ASSERT(node != NULL);
// 计算哈希值
uint16_t hash = menu_hash_func(node->id);
// 在链表中查找并移除节点
MenuNodeId prev_id = 0;
MenuNodeId curr_id = core_ctx->hash_table[hash];
MenuNode* curr_node = NULL;
while (curr_id != 0) {
curr_node = &core_ctx->nodes[curr_id - 1];
if (curr_node->id == node->id) {
// 找到了节点,进行移除操作
if (prev_id == 0) {
// 移除的是链表头
core_ctx->hash_table[hash] = node->hash_next_id;
} else {
// 移除的是链表中间节点
MenuNode* prev_node = &core_ctx->nodes[prev_id - 1];
prev_node->hash_next_id = node->hash_next_id;
}
// 清空节点的哈希表链接
node->hash_next_id = 0;
break;
}
// 继续遍历
prev_id = curr_id;
curr_id = curr_node->hash_next_id;
}
}
/**
* @brief 根据ID查找节点
* @param core_ctx 菜单核心上下文
* @param node_id 节点ID
* @return 找到的节点指针未找到返回NULL
*/
MenuNode* menu_hash_find(struct MenuCoreCtx* core_ctx, MenuNodeId node_id)
{
MENU_ASSERT(core_ctx != NULL);
// 计算哈希值
uint16_t hash = menu_hash_func(node_id);
// 在链表中查找节点
MenuNodeId curr_id = core_ctx->hash_table[hash];
while (curr_id != 0) {
MenuNode* curr_node = &core_ctx->nodes[curr_id - 1];
if (curr_node->id == node_id) {
// 找到了节点
return curr_node;
}
// 继续遍历
curr_id = curr_node->hash_next_id;
}
// 未找到节点
return NULL;
}

46
src/core/menu_hash.h Normal file
View File

@ -0,0 +1,46 @@
/**
**********************************************************************************************************************
* @file menu_hash.h
* @brief 菜单组件哈希表功能声明
* @author menu_component
* @date 2025-12-19
**********************************************************************************************************************
*/
#ifndef MENU_HASH_H
#define MENU_HASH_H
/* Includes ----------------------------------------------------------------------------------------------------------*/
#include "menu_types.h"
/* 函数声明 ---------------------------------------------------------------------------------------------------------*/
/**
* @brief 初始化哈希表
* @param core_ctx 菜单核心上下文
*/
void menu_hash_init(struct MenuCoreCtx* core_ctx);
/**
* @brief 将节点添加到哈希表
* @param core_ctx 菜单核心上下文
* @param node 要添加的节点
*/
void menu_hash_add(struct MenuCoreCtx* core_ctx, MenuNode* node);
/**
* @brief 从哈希表中移除节点
* @param core_ctx 菜单核心上下文
* @param node 要移除的节点
*/
void menu_hash_remove(struct MenuCoreCtx* core_ctx, MenuNode* node);
/**
* @brief 根据ID查找节点
* @param core_ctx 菜单核心上下文
* @param node_id 节点ID
* @return 找到的节点指针未找到返回NULL
*/
MenuNode* menu_hash_find(struct MenuCoreCtx* core_ctx, MenuNodeId node_id);
#endif /* MENU_HASH_H */

350
src/core/menu_permission.c Normal file
View File

@ -0,0 +1,350 @@
/**
**********************************************************************************************************************
* @file menu_permission.c
* @brief 菜单组件权限管理功能实现
* @author menu_component
* @date 2025-12-19
**********************************************************************************************************************
*/
/* Includes ----------------------------------------------------------------------------------------------------------*/
#include "menu_permission.h"
#include "menu_hash.h"
/* 私有函数原型 -----------------------------------------------------------------------------------------------------*/
#if MENU_CONFIG_ENABLE_PERMISSION
/**
* @brief 获取角色在角色列表中的索引
* @param core_ctx 菜单核心上下文
* @param role_id 角色ID
* @return 角色索引,-1表示未找到
*/
static int menu_permission_get_role_index(MenuCoreCtx* core_ctx, MenuRoleId role_id);
/**
* @brief 更新单个节点的可见性和可用性
* @param core_ctx 菜单核心上下文
* @param node 节点指针
* @return 错误码
*/
static MenuErrCode menu_permission_update_node(MenuCoreCtx* core_ctx, MenuNode* node);
/* 函数实现 ---------------------------------------------------------------------------------------------------------*/
/**
* @brief 初始化权限管理模块
* @return 错误码
*/
MenuErrCode menu_permission_init(void)
{
MenuCoreCtx* core_ctx = menu_get_core_ctx();
if (core_ctx == NULL) {
return MENU_ERR_NOT_INITIALIZED;
}
// 初始化权限配置
core_ctx->permission_config.current_role = 0;
core_ctx->permission_config.role_count = 0;
// 注册默认角色(管理员角色,权限级别最高)
MenuRole default_role = {
.id = 0,
.name = "Admin",
.max_level = MENU_CONFIG_PERMISSION_MAX_LEVEL - 1
};
return menu_permission_register_role(&default_role);
}
/**
* @brief 反初始化权限管理模块
* @return 错误码
*/
MenuErrCode menu_permission_deinit(void)
{
MenuCoreCtx* core_ctx = menu_get_core_ctx();
if (core_ctx == NULL) {
return MENU_ERR_NOT_INITIALIZED;
}
// 清空角色列表
core_ctx->permission_config.role_count = 0;
core_ctx->permission_config.current_role = 0;
return MENU_ERR_OK;
}
/**
* @brief 注册角色
* @param role 角色结构体指针
* @return 错误码
*/
MenuErrCode menu_permission_register_role(const MenuRole* role)
{
MenuCoreCtx* core_ctx = menu_get_core_ctx();
if (core_ctx == NULL || role == NULL) {
return MENU_ERR_INVALID_PARAM;
}
if (core_ctx->permission_config.role_count >= MENU_CONFIG_PERMISSION_MAX_ROLES) {
return MENU_ERR_OUT_OF_MEMORY;
}
// 检查角色ID是否已存在
if (menu_permission_get_role_index(core_ctx, role->id) >= 0) {
return MENU_ERR_ALREADY_EXISTS;
}
// 添加角色到列表
core_ctx->permission_config.roles[core_ctx->permission_config.role_count] = *role;
core_ctx->permission_config.role_count++;
// 更新所有节点的可见性和可用性
return menu_permission_update_all_nodes();
}
/**
* @brief 注销角色
* @param role_id 角色ID
* @return 错误码
*/
MenuErrCode menu_permission_unregister_role(MenuRoleId role_id)
{
MenuCoreCtx* core_ctx = menu_get_core_ctx();
int index = menu_permission_get_role_index(core_ctx, role_id);
if (core_ctx == NULL) {
return MENU_ERR_NOT_INITIALIZED;
}
if (index < 0) {
return MENU_ERR_NODE_NOT_FOUND;
}
// 如果当前角色被注销,切换到默认角色
if (core_ctx->permission_config.current_role == role_id) {
core_ctx->permission_config.current_role = 0;
}
// 移除角色(将后续角色前移)
for (uint8_t i = index; i < core_ctx->permission_config.role_count - 1; i++) {
core_ctx->permission_config.roles[i] = core_ctx->permission_config.roles[i + 1];
}
core_ctx->permission_config.role_count--;
// 更新所有节点的可见性和可用性
return menu_permission_update_all_nodes();
}
/**
* @brief 设置当前角色
* @param role_id 角色ID
* @return 错误码
*/
MenuErrCode menu_permission_set_current_role(MenuRoleId role_id)
{
MenuCoreCtx* core_ctx = menu_get_core_ctx();
if (core_ctx == NULL) {
return MENU_ERR_NOT_INITIALIZED;
}
// 检查角色是否存在
if (menu_permission_get_role_index(core_ctx, role_id) < 0) {
return MENU_ERR_NODE_NOT_FOUND;
}
// 设置当前角色
core_ctx->permission_config.current_role = role_id;
// 更新所有节点的可见性和可用性
return menu_permission_update_all_nodes();
}
/**
* @brief 获取当前角色
* @return 当前角色ID
*/
MenuRoleId menu_permission_get_current_role(void)
{
MenuCoreCtx* core_ctx = menu_get_core_ctx();
if (core_ctx == NULL) {
return 0;
}
return core_ctx->permission_config.current_role;
}
/**
* @brief 检查节点是否可访问
* @param node_id 节点ID
* @return true表示可访问false表示不可访问
*/
bool menu_permission_check_node_access(MenuNodeId node_id)
{
MenuCoreCtx* core_ctx = menu_get_core_ctx();
MenuNode* node = menu_hash_find(core_ctx, node_id);
int role_index;
if (core_ctx == NULL || node == NULL || !node->flags.is_registered) {
return false;
}
// 获取当前角色索引
role_index = menu_permission_get_role_index(core_ctx, core_ctx->permission_config.current_role);
if (role_index < 0) {
return false;
}
// 检查权限级别
return (node->permission_level <= core_ctx->permission_config.roles[role_index].max_level);
}
/**
* @brief 更新节点权限级别
* @param node_id 节点ID
* @param permission_level 权限级别
* @return 错误码
*/
MenuErrCode menu_permission_update_node_level(MenuNodeId node_id, MenuPermissionLevel permission_level)
{
MenuCoreCtx* core_ctx = menu_get_core_ctx();
MenuNode* node = menu_hash_find(core_ctx, node_id);
if (core_ctx == NULL || node == NULL || !node->flags.is_registered) {
return MENU_ERR_NODE_NOT_FOUND;
}
// 更新权限级别
node->permission_level = permission_level;
// 更新节点的可见性和可用性
return menu_permission_update_node(core_ctx, node);
}
/**
* @brief 获取节点权限级别
* @param node_id 节点ID
* @return 节点权限级别
*/
MenuPermissionLevel menu_permission_get_node_level(MenuNodeId node_id)
{
MenuCoreCtx* core_ctx = menu_get_core_ctx();
MenuNode* node = menu_hash_find(core_ctx, node_id);
if (core_ctx == NULL || node == NULL || !node->flags.is_registered) {
return MENU_CONFIG_PERMISSION_MAX_LEVEL;
}
return node->permission_level;
}
/**
* @brief 更新所有节点的可见性和可用性
* @return 错误码
*/
MenuErrCode menu_permission_update_all_nodes(void)
{
MenuCoreCtx* core_ctx = menu_get_core_ctx();
if (core_ctx == NULL) {
return MENU_ERR_NOT_INITIALIZED;
}
// 遍历所有节点,更新可见性和可用性
for (uint16_t i = 0; i < MENU_CONFIG_MAX_NODES; i++) {
if (core_ctx->nodes[i].flags.is_registered) {
menu_permission_update_node(core_ctx, &core_ctx->nodes[i]);
}
}
return MENU_ERR_OK;
}
/**
* @brief 获取角色信息
* @param role_id 角色ID
* @return 角色结构体指针NULL表示角色不存在
*/
const MenuRole* menu_permission_get_role(MenuRoleId role_id)
{
MenuCoreCtx* core_ctx = menu_get_core_ctx();
int index;
if (core_ctx == NULL) {
return NULL;
}
index = menu_permission_get_role_index(core_ctx, role_id);
if (index < 0) {
return NULL;
}
return &core_ctx->permission_config.roles[index];
}
/* 私有函数实现 -----------------------------------------------------------------------------------------------------*/
/**
* @brief 获取角色在角色列表中的索引
* @param core_ctx 菜单核心上下文
* @param role_id 角色ID
* @return 角色索引,-1表示未找到
*/
static int menu_permission_get_role_index(MenuCoreCtx* core_ctx, MenuRoleId role_id)
{
if (core_ctx == NULL) {
return -1;
}
// 遍历角色列表查找匹配的角色ID
for (uint8_t i = 0; i < core_ctx->permission_config.role_count; i++) {
if (core_ctx->permission_config.roles[i].id == role_id) {
return i;
}
}
return -1;
}
/**
* @brief 更新单个节点的可见性和可用性
* @param core_ctx 菜单核心上下文
* @param node 节点指针
* @return 错误码
*/
static MenuErrCode menu_permission_update_node(MenuCoreCtx* core_ctx, MenuNode* node)
{
int role_index;
bool is_accessible;
if (core_ctx == NULL || node == NULL) {
return MENU_ERR_INVALID_PARAM;
}
// 获取当前角色索引
role_index = menu_permission_get_role_index(core_ctx, core_ctx->permission_config.current_role);
if (role_index < 0) {
// 角色不存在,禁用节点
node->flags.is_visible = false;
node->flags.is_enabled = false;
return MENU_ERR_OK;
}
// 检查节点是否可访问
is_accessible = (node->permission_level <= core_ctx->permission_config.roles[role_index].max_level);
// 更新节点的可见性和可用性
node->flags.is_visible = is_accessible;
node->flags.is_enabled = is_accessible;
return MENU_ERR_OK;
}
#endif /* MENU_CONFIG_ENABLE_PERMISSION */

View File

@ -0,0 +1,96 @@
/**
**********************************************************************************************************************
* @file menu_permission.h
* @brief 菜单组件权限管理功能声明
* @author menu_component
* @date 2025-12-19
**********************************************************************************************************************
*/
#ifndef MENU_PERMISSION_H
#define MENU_PERMISSION_H
/* Includes ----------------------------------------------------------------------------------------------------------*/
#include "menu_types.h"
/* 函数声明 ---------------------------------------------------------------------------------------------------------*/
#if MENU_CONFIG_ENABLE_PERMISSION
/**
* @brief 初始化权限管理模块
* @return 错误码
*/
MenuErrCode menu_permission_init(void);
/**
* @brief 反初始化权限管理模块
* @return 错误码
*/
MenuErrCode menu_permission_deinit(void);
/**
* @brief 注册角色
* @param role 角色结构体指针
* @return 错误码
*/
MenuErrCode menu_permission_register_role(const MenuRole* role);
/**
* @brief 注销角色
* @param role_id 角色ID
* @return 错误码
*/
MenuErrCode menu_permission_unregister_role(MenuRoleId role_id);
/**
* @brief 设置当前角色
* @param role_id 角色ID
* @return 错误码
*/
MenuErrCode menu_permission_set_current_role(MenuRoleId role_id);
/**
* @brief 获取当前角色
* @return 当前角色ID
*/
MenuRoleId menu_permission_get_current_role(void);
/**
* @brief 检查节点是否可访问
* @param node_id 节点ID
* @return true表示可访问false表示不可访问
*/
bool menu_permission_check_node_access(MenuNodeId node_id);
/**
* @brief 更新节点权限级别
* @param node_id 节点ID
* @param permission_level 权限级别
* @return 错误码
*/
MenuErrCode menu_permission_update_node_level(MenuNodeId node_id, MenuPermissionLevel permission_level);
/**
* @brief 获取节点权限级别
* @param node_id 节点ID
* @return 节点权限级别
*/
MenuPermissionLevel menu_permission_get_node_level(MenuNodeId node_id);
/**
* @brief 更新所有节点的可见性和可用性
* @return 错误码
*/
MenuErrCode menu_permission_update_all_nodes(void);
/**
* @brief 获取角色信息
* @param role_id 角色ID
* @return 角色结构体指针NULL表示角色不存在
*/
const MenuRole* menu_permission_get_role(MenuRoleId role_id);
#endif /* MENU_CONFIG_ENABLE_PERMISSION */
#endif /* MENU_PERMISSION_H */

441
src/core/menu_persistence.c Normal file
View File

@ -0,0 +1,441 @@
/**
**********************************************************************************************************************
* @file menu_persistence.c
* @brief 菜单组件状态持久化功能实现
* @author menu_component
* @date 2025-12-19
**********************************************************************************************************************
*/
/* Includes ----------------------------------------------------------------------------------------------------------*/
#include "menu_persistence.h"
/* 私有函数原型 -----------------------------------------------------------------------------------------------------*/
#if MENU_CONFIG_ENABLE_PERSISTENCE
/**
* @brief 构建持久化数据
* @param core_ctx 菜单核心上下文
* @param data 持久化数据指针
* @return 错误码
*/
static MenuErrCode menu_persistence_build_data(MenuCoreCtx* core_ctx, MenuPersistenceData* data);
/**
* @brief 应用持久化数据
* @param core_ctx 菜单核心上下文
* @param data 持久化数据指针
* @return 错误码
*/
static MenuErrCode menu_persistence_apply_data(MenuCoreCtx* core_ctx, const MenuPersistenceData* data);
/**
* @brief 加密持久化数据
* @param data 要加密的数据
* @param size 数据大小
* @return 错误码
*/
static MenuErrCode menu_persistence_encrypt(uint8_t* data, uint16_t size);
/**
* @brief 解密持久化数据
* @param data 要解密的数据
* @param size 数据大小
* @return 错误码
*/
static MenuErrCode menu_persistence_decrypt(uint8_t* data, uint16_t size);
/* 函数实现 ---------------------------------------------------------------------------------------------------------*/
/**
* @brief 初始化状态持久化模块
* @return 错误码
*/
MenuErrCode menu_persistence_init(void)
{
MenuCoreCtx* core_ctx = menu_get_core_ctx();
if (core_ctx == NULL) {
return MENU_ERR_NOT_INITIALIZED;
}
// 初始化持久化配置
core_ctx->persistence_config.auto_save_enabled = true;
core_ctx->persistence_config.auto_save_interval = MENU_CONFIG_PERSISTENCE_AUTO_SAVE_INTERVAL;
core_ctx->persistence_config.last_save_tick = 0;
core_ctx->persistence_config.encrypt_enabled = (MENU_CONFIG_PERSISTENCE_ENABLE_ENCRYPT == 1U);
core_ctx->persistence_config.save_cb = NULL;
core_ctx->persistence_config.restore_cb = NULL;
return MENU_ERR_OK;
}
/**
* @brief 反初始化状态持久化模块
* @return 错误码
*/
MenuErrCode menu_persistence_deinit(void)
{
MenuCoreCtx* core_ctx = menu_get_core_ctx();
if (core_ctx == NULL) {
return MENU_ERR_NOT_INITIALIZED;
}
// 清空持久化配置
core_ctx->persistence_config.save_cb = NULL;
core_ctx->persistence_config.restore_cb = NULL;
return MENU_ERR_OK;
}
/**
* @brief 注册持久化回调函数
* @param save_cb 保存回调函数
* @param restore_cb 恢复回调函数
* @return 错误码
*/
MenuErrCode menu_persistence_register_callback(MenuPersistenceCallback save_cb, MenuRestoreCallback restore_cb)
{
MenuCoreCtx* core_ctx = menu_get_core_ctx();
if (core_ctx == NULL) {
return MENU_ERR_NOT_INITIALIZED;
}
// 注册回调函数
core_ctx->persistence_config.save_cb = save_cb;
core_ctx->persistence_config.restore_cb = restore_cb;
return MENU_ERR_OK;
}
/**
* @brief 启用或禁用自动保存
* @param enable true表示启用false表示禁用
* @return 错误码
*/
MenuErrCode menu_persistence_set_auto_save(bool enable)
{
MenuCoreCtx* core_ctx = menu_get_core_ctx();
if (core_ctx == NULL) {
return MENU_ERR_NOT_INITIALIZED;
}
core_ctx->persistence_config.auto_save_enabled = enable;
return MENU_ERR_OK;
}
/**
* @brief 获取自动保存状态
* @return true表示启用false表示禁用
*/
bool menu_persistence_get_auto_save(void)
{
MenuCoreCtx* core_ctx = menu_get_core_ctx();
if (core_ctx == NULL) {
return false;
}
return core_ctx->persistence_config.auto_save_enabled;
}
/**
* @brief 设置自动保存间隔
* @param interval 自动保存间隔ms
* @return 错误码
*/
MenuErrCode menu_persistence_set_auto_save_interval(uint32_t interval)
{
MenuCoreCtx* core_ctx = menu_get_core_ctx();
if (core_ctx == NULL) {
return MENU_ERR_NOT_INITIALIZED;
}
core_ctx->persistence_config.auto_save_interval = interval;
return MENU_ERR_OK;
}
/**
* @brief 获取自动保存间隔
* @return 自动保存间隔ms
*/
uint32_t menu_persistence_get_auto_save_interval(void)
{
MenuCoreCtx* core_ctx = menu_get_core_ctx();
if (core_ctx == NULL) {
return 0;
}
return core_ctx->persistence_config.auto_save_interval;
}
/**
* @brief 手动保存菜单状态
* @return 错误码
*/
MenuErrCode menu_persistence_save(void)
{
MenuCoreCtx* core_ctx = menu_get_core_ctx();
MenuPersistenceData data;
uint8_t* data_buf;
uint16_t data_size = sizeof(MenuPersistenceData);
MenuErrCode ret;
if (core_ctx == NULL) {
return MENU_ERR_NOT_INITIALIZED;
}
if (core_ctx->persistence_config.save_cb == NULL) {
return MENU_ERR_INVALID_CALLBACK;
}
// 构建持久化数据
ret = menu_persistence_build_data(core_ctx, &data);
if (ret != MENU_ERR_OK) {
return ret;
}
// 转换为字节数组
data_buf = (uint8_t*)&data;
// 如果启用加密,加密数据
if (core_ctx->persistence_config.encrypt_enabled) {
ret = menu_persistence_encrypt(data_buf, data_size);
if (ret != MENU_ERR_OK) {
return ret;
}
}
// 调用保存回调函数
ret = core_ctx->persistence_config.save_cb(data_buf, data_size);
if (ret == MENU_ERR_OK) {
// 更新上次保存时间
core_ctx->persistence_config.last_save_tick = core_ctx->last_refresh_tick;
}
return ret;
}
/**
* @brief 恢复菜单状态
* @return 错误码
*/
MenuErrCode menu_persistence_restore(void)
{
MenuCoreCtx* core_ctx = menu_get_core_ctx();
uint8_t data_buf[MENU_CONFIG_PERSISTENCE_MAX_SIZE];
uint16_t data_size = sizeof(MenuPersistenceData);
MenuPersistenceData* data = (MenuPersistenceData*)data_buf;
MenuErrCode ret;
if (core_ctx == NULL) {
return MENU_ERR_NOT_INITIALIZED;
}
if (core_ctx->persistence_config.restore_cb == NULL) {
return MENU_ERR_INVALID_CALLBACK;
}
// 调用恢复回调函数
ret = core_ctx->persistence_config.restore_cb(data_buf, &data_size);
if (ret != MENU_ERR_OK) {
return ret;
}
// 检查数据大小是否正确
if (data_size != sizeof(MenuPersistenceData)) {
return MENU_ERR_INVALID_PARAM;
}
// 如果启用加密,解密数据
if (core_ctx->persistence_config.encrypt_enabled) {
ret = menu_persistence_decrypt(data_buf, data_size);
if (ret != MENU_ERR_OK) {
return ret;
}
}
// 检查数据是否有效
if (!menu_persistence_check_data_valid(data_buf, data_size)) {
return MENU_ERR_INVALID_PARAM;
}
// 应用持久化数据
return menu_persistence_apply_data(core_ctx, data);
}
/**
* @brief 检查持久化数据是否有效
* @param data 持久化数据
* @param size 数据大小
* @return true表示有效false表示无效
*/
bool menu_persistence_check_data_valid(const uint8_t* data, uint16_t size)
{
const MenuPersistenceData* persistence_data;
uint8_t calculated_checksum;
if (data == NULL || size != sizeof(MenuPersistenceData)) {
return false;
}
persistence_data = (const MenuPersistenceData*)data;
// 计算校验和
calculated_checksum = menu_persistence_calculate_checksum(data, size - sizeof(uint8_t));
// 检查校验和
return (calculated_checksum == persistence_data->checksum);
}
/**
* @brief 处理自动保存(由菜单主循环调用)
* @param tick 当前系统时间ms
* @return 错误码
*/
MenuErrCode menu_persistence_process_auto_save(uint32_t tick)
{
MenuCoreCtx* core_ctx = menu_get_core_ctx();
if (core_ctx == NULL) {
return MENU_ERR_NOT_INITIALIZED;
}
// 如果启用自动保存,检查是否需要保存
if (core_ctx->persistence_config.auto_save_enabled) {
if ((tick - core_ctx->persistence_config.last_save_tick) >= core_ctx->persistence_config.auto_save_interval) {
return menu_persistence_save();
}
}
return MENU_ERR_OK;
}
/**
* @brief 计算数据校验和
* @param data 数据指针
* @param size 数据大小
* @return 校验和
*/
uint8_t menu_persistence_calculate_checksum(const uint8_t* data, uint16_t size)
{
uint8_t checksum = 0;
if (data == NULL || size == 0) {
return checksum;
}
// 计算简单的异或校验和
for (uint16_t i = 0; i < size; i++) {
checksum ^= data[i];
}
return checksum;
}
/* 私有函数实现 -----------------------------------------------------------------------------------------------------*/
/**
* @brief 构建持久化数据
* @param core_ctx 菜单核心上下文
* @param data 持久化数据指针
* @return 错误码
*/
static MenuErrCode menu_persistence_build_data(MenuCoreCtx* core_ctx, MenuPersistenceData* data)
{
if (core_ctx == NULL || data == NULL) {
return MENU_ERR_INVALID_PARAM;
}
// 填充持久化数据
data->current_node_id = core_ctx->current_node_id;
data->current_state = core_ctx->current_state;
data->nav_path = core_ctx->nav_path;
data->stack = core_ctx->stack;
data->timestamp = core_ctx->last_refresh_tick;
// 计算校验和(不包括校验和字段本身)
data->checksum = menu_persistence_calculate_checksum((uint8_t*)data, sizeof(MenuPersistenceData) - sizeof(uint8_t));
return MENU_ERR_OK;
}
/**
* @brief 应用持久化数据
* @param core_ctx 菜单核心上下文
* @param data 持久化数据指针
* @return 错误码
*/
static MenuErrCode menu_persistence_apply_data(MenuCoreCtx* core_ctx, const MenuPersistenceData* data)
{
if (core_ctx == NULL || data == NULL) {
return MENU_ERR_INVALID_PARAM;
}
// 恢复菜单状态
core_ctx->current_node_id = data->current_node_id;
core_ctx->current_state = data->current_state;
core_ctx->nav_path = data->nav_path;
core_ctx->stack = data->stack;
return MENU_ERR_OK;
}
/**
* @brief 加密持久化数据
* @param data 要加密的数据
* @param size 数据大小
* @return 错误码
*/
static MenuErrCode menu_persistence_encrypt(uint8_t* data, uint16_t size)
{
// 简单的XOR加密实现
// 实际应用中应使用更安全的加密算法
uint8_t key = 0xA5; // 加密密钥
if (data == NULL || size == 0) {
return MENU_ERR_INVALID_PARAM;
}
for (uint16_t i = 0; i < size; i++) {
data[i] ^= key;
key = (key + data[i]) % 256; // 动态更新密钥
}
return MENU_ERR_OK;
}
/**
* @brief 解密持久化数据
* @param data 要解密的数据
* @param size 数据大小
* @return 错误码
*/
static MenuErrCode menu_persistence_decrypt(uint8_t* data, uint16_t size)
{
// 简单的XOR解密实现与加密算法对称
uint8_t key = 0xA5; // 解密密钥
uint8_t temp;
if (data == NULL || size == 0) {
return MENU_ERR_INVALID_PARAM;
}
for (uint16_t i = 0; i < size; i++) {
temp = data[i];
data[i] ^= key;
key = (key + temp) % 256; // 动态更新密钥
}
return MENU_ERR_OK;
}
#endif /* MENU_CONFIG_ENABLE_PERSISTENCE */

103
src/core/menu_persistence.h Normal file
View File

@ -0,0 +1,103 @@
/**
**********************************************************************************************************************
* @file menu_persistence.h
* @brief 菜单组件状态持久化功能声明
* @author menu_component
* @date 2025-12-19
**********************************************************************************************************************
*/
#ifndef MENU_PERSISTENCE_H
#define MENU_PERSISTENCE_H
/* Includes ----------------------------------------------------------------------------------------------------------*/
#include "menu_types.h"
/* 函数声明 ---------------------------------------------------------------------------------------------------------*/
#if MENU_CONFIG_ENABLE_PERSISTENCE
/**
* @brief 初始化状态持久化模块
* @return 错误码
*/
MenuErrCode menu_persistence_init(void);
/**
* @brief 反初始化状态持久化模块
* @return 错误码
*/
MenuErrCode menu_persistence_deinit(void);
/**
* @brief 注册持久化回调函数
* @param save_cb 保存回调函数
* @param restore_cb 恢复回调函数
* @return 错误码
*/
MenuErrCode menu_persistence_register_callback(MenuPersistenceCallback save_cb, MenuRestoreCallback restore_cb);
/**
* @brief 启用或禁用自动保存
* @param enable true表示启用false表示禁用
* @return 错误码
*/
MenuErrCode menu_persistence_set_auto_save(bool enable);
/**
* @brief 获取自动保存状态
* @return true表示启用false表示禁用
*/
bool menu_persistence_get_auto_save(void);
/**
* @brief 设置自动保存间隔
* @param interval 自动保存间隔ms
* @return 错误码
*/
MenuErrCode menu_persistence_set_auto_save_interval(uint32_t interval);
/**
* @brief 获取自动保存间隔
* @return 自动保存间隔ms
*/
uint32_t menu_persistence_get_auto_save_interval(void);
/**
* @brief 手动保存菜单状态
* @return 错误码
*/
MenuErrCode menu_persistence_save(void);
/**
* @brief 恢复菜单状态
* @return 错误码
*/
MenuErrCode menu_persistence_restore(void);
/**
* @brief 检查持久化数据是否有效
* @param data 持久化数据
* @param size 数据大小
* @return true表示有效false表示无效
*/
bool menu_persistence_check_data_valid(const uint8_t* data, uint16_t size);
/**
* @brief 处理自动保存(由菜单主循环调用)
* @param tick 当前系统时间ms
* @return 错误码
*/
MenuErrCode menu_persistence_process_auto_save(uint32_t tick);
/**
* @brief 计算数据校验和
* @param data 数据指针
* @param size 数据大小
* @return 校验和
*/
uint8_t menu_persistence_calculate_checksum(const uint8_t* data, uint16_t size);
#endif /* MENU_CONFIG_ENABLE_PERSISTENCE */
#endif /* MENU_PERSISTENCE_H */

215
src/core/menu_stack.c Normal file
View File

@ -0,0 +1,215 @@
/**
**********************************************************************************************************************
* @file menu_stack.c
* @brief 菜单组件栈管理实现
* @author menu_component
* @date 2025-12-19
**********************************************************************************************************************
*/
/* Includes ----------------------------------------------------------------------------------------------------------*/
#include "menu_types.h"
/* 函数实现 ---------------------------------------------------------------------------------------------------------*/
/**
* @brief 初始化菜单栈
* @param stack 栈指针
* @param max_depth 最大深度
*/
void menu_stack_init(MenuStack* stack, uint8_t max_depth)
{
MENU_ASSERT(stack != NULL);
stack->top = 0;
stack->max_depth = max_depth;
// 初始化栈元素为0
for (uint8_t i = 0; i < max_depth; i++) {
stack->items[i] = 0;
}
}
/**
* @brief 检查栈是否为空
* @param stack 栈指针
* @return true为空false不为空
*/
bool menu_stack_is_empty(const MenuStack* stack)
{
MENU_ASSERT(stack != NULL);
return (stack->top == 0);
}
/**
* @brief 检查栈是否已满
* @param stack 栈指针
* @return true已满false未满
*/
bool menu_stack_is_full(const MenuStack* stack)
{
MENU_ASSERT(stack != NULL);
return (stack->top >= stack->max_depth);
}
/**
* @brief 获取栈的当前深度
* @param stack 栈指针
* @return 当前深度
*/
uint8_t menu_stack_get_depth(const MenuStack* stack)
{
MENU_ASSERT(stack != NULL);
return stack->top;
}
/**
* @brief 向栈中压入元素
* @param stack 栈指针
* @param node_id 要压入的节点ID
* @return 错误码
*/
MenuErrCode menu_stack_push(MenuStack* stack, MenuNodeId node_id)
{
MENU_ASSERT(stack != NULL);
if (menu_stack_is_full(stack)) {
return MENU_ERR_OUT_OF_MEMORY;
}
stack->items[stack->top] = node_id;
stack->top++;
return MENU_ERR_OK;
}
/**
* @brief 从栈中弹出元素
* @param stack 栈指针
* @param node_id 用于存储弹出的节点ID
* @return 错误码
*/
MenuErrCode menu_stack_pop(MenuStack* stack, MenuNodeId* node_id)
{
MENU_ASSERT(stack != NULL);
MENU_ASSERT(node_id != NULL);
if (menu_stack_is_empty(stack)) {
return MENU_ERR_QUEUE_EMPTY;
}
stack->top--;
*node_id = stack->items[stack->top];
stack->items[stack->top] = 0;
return MENU_ERR_OK;
}
/**
* @brief 获取栈顶元素
* @param stack 栈指针
* @param node_id 用于存储栈顶节点ID
* @return 错误码
*/
MenuErrCode menu_stack_peek(const MenuStack* stack, MenuNodeId* node_id)
{
MENU_ASSERT(stack != NULL);
MENU_ASSERT(node_id != NULL);
if (menu_stack_is_empty(stack)) {
return MENU_ERR_QUEUE_EMPTY;
}
*node_id = stack->items[stack->top - 1];
return MENU_ERR_OK;
}
/**
* @brief 清空栈
* @param stack 栈指针
*/
void menu_stack_clear(MenuStack* stack)
{
MENU_ASSERT(stack != NULL);
stack->top = 0;
// 清空栈元素
for (uint8_t i = 0; i < stack->max_depth; i++) {
stack->items[i] = 0;
}
}
/**
* @brief 初始化菜单导航路径
* @param nav_path 导航路径指针
* @param max_depth 最大深度
*/
void menu_nav_path_init(MenuNavPath* nav_path, uint8_t max_depth)
{
MENU_ASSERT(nav_path != NULL);
nav_path->depth = 0;
nav_path->max_depth = max_depth;
// 初始化路径为0
for (uint8_t i = 0; i < max_depth; i++) {
nav_path->path[i] = 0;
}
}
/**
* @brief 添加导航路径节点
* @param nav_path 导航路径指针
* @param node_id 要添加的节点ID
* @return 错误码
*/
MenuErrCode menu_nav_path_add(MenuNavPath* nav_path, MenuNodeId node_id)
{
MENU_ASSERT(nav_path != NULL);
if (nav_path->depth >= nav_path->max_depth) {
return MENU_ERR_OUT_OF_MEMORY;
}
nav_path->path[nav_path->depth] = node_id;
nav_path->depth++;
return MENU_ERR_OK;
}
/**
* @brief 移除导航路径最后一个节点
* @param nav_path 导航路径指针
* @return 错误码
*/
MenuErrCode menu_nav_path_remove_last(MenuNavPath* nav_path)
{
MENU_ASSERT(nav_path != NULL);
if (nav_path->depth == 0) {
return MENU_ERR_QUEUE_EMPTY;
}
nav_path->depth--;
nav_path->path[nav_path->depth] = 0;
return MENU_ERR_OK;
}
/**
* @brief 清空导航路径
* @param nav_path 导航路径指针
*/
void menu_nav_path_clear(MenuNavPath* nav_path)
{
MENU_ASSERT(nav_path != NULL);
nav_path->depth = 0;
// 清空路径
for (uint8_t i = 0; i < nav_path->max_depth; i++) {
nav_path->path[i] = 0;
}
}

104
src/core/menu_stack.h Normal file
View File

@ -0,0 +1,104 @@
/**
**********************************************************************************************************************
* @file menu_stack.h
* @brief 菜单组件栈管理功能声明
* @author menu_component
* @date 2025-12-19
**********************************************************************************************************************
*/
#ifndef MENU_STACK_H
#define MENU_STACK_H
/* Includes ----------------------------------------------------------------------------------------------------------*/
#include "menu_types.h"
/* 函数声明 ---------------------------------------------------------------------------------------------------------*/
/**
* @brief 初始化菜单栈
* @param stack 栈指针
* @param max_depth 最大深度
*/
void menu_stack_init(MenuStack* stack, uint8_t max_depth);
/**
* @brief 检查栈是否为空
* @param stack 栈指针
* @return true为空false不为空
*/
bool menu_stack_is_empty(const MenuStack* stack);
/**
* @brief 检查栈是否已满
* @param stack 栈指针
* @return true已满false未满
*/
bool menu_stack_is_full(const MenuStack* stack);
/**
* @brief 获取栈的当前深度
* @param stack 栈指针
* @return 当前深度
*/
uint8_t menu_stack_get_depth(const MenuStack* stack);
/**
* @brief 向栈中压入元素
* @param stack 栈指针
* @param node_id 要压入的节点ID
* @return 错误码
*/
MenuErrCode menu_stack_push(MenuStack* stack, MenuNodeId node_id);
/**
* @brief 从栈中弹出元素
* @param stack 栈指针
* @param node_id 用于存储弹出的节点ID
* @return 错误码
*/
MenuErrCode menu_stack_pop(MenuStack* stack, MenuNodeId* node_id);
/**
* @brief 获取栈顶元素
* @param stack 栈指针
* @param node_id 用于存储栈顶节点ID
* @return 错误码
*/
MenuErrCode menu_stack_peek(const MenuStack* stack, MenuNodeId* node_id);
/**
* @brief 清空栈
* @param stack 栈指针
*/
void menu_stack_clear(MenuStack* stack);
/**
* @brief 初始化菜单导航路径
* @param nav_path 导航路径指针
* @param max_depth 最大深度
*/
void menu_nav_path_init(MenuNavPath* nav_path, uint8_t max_depth);
/**
* @brief 添加导航路径节点
* @param nav_path 导航路径指针
* @param node_id 要添加的节点ID
* @return 错误码
*/
MenuErrCode menu_nav_path_add(MenuNavPath* nav_path, MenuNodeId node_id);
/**
* @brief 移除导航路径最后一个节点
* @param nav_path 导航路径指针
* @return 错误码
*/
MenuErrCode menu_nav_path_remove_last(MenuNavPath* nav_path);
/**
* @brief 清空导航路径
* @param nav_path 导航路径指针
*/
void menu_nav_path_clear(MenuNavPath* nav_path);
#endif /* MENU_STACK_H */

233
src/core/menu_types.h Normal file
View File

@ -0,0 +1,233 @@
/**
**********************************************************************************************************************
* @file menu_types.h
* @brief 菜单组件核心数据结构定义
* @author menu_component
* @date 2025-12-19
**********************************************************************************************************************
*/
#ifndef MENU_TYPES_H
#define MENU_TYPES_H
/* Includes ----------------------------------------------------------------------------------------------------------*/
#include "menu_config.h"
/* 前置声明 ---------------------------------------------------------------------------------------------------------*/
struct MenuNode;
struct MenuCoreCtx;
struct MenuEvent;
struct MenuStateTransition;
/* 函数前向声明 -----------------------------------------------------------------------------------------------------*/
/**
* @brief 获取菜单核心上下文
* @return 菜单核心上下文指针
*/
struct MenuCoreCtx* menu_get_core_ctx(void);
/* 回调函数类型定义 -------------------------------------------------------------------------------------------------*/
/**
* @brief 菜单进入回调函数类型
* @param node_id 当前节点ID
* @param core_ctx 菜单核心上下文
* @return 错误码
*/
typedef MenuErrCode (*MenuCallback)(MenuNodeId node_id, struct MenuCoreCtx* core_ctx);
/**
* @brief 状态转换动作函数类型
* @param core_ctx 菜单核心上下文
* @return 错误码
*/
typedef MenuErrCode (*MenuStateAction)(struct MenuCoreCtx* core_ctx);
/**
* @brief 持久化回调函数类型
* @param data 要持久化的数据
* @param size 数据大小
* @return 错误码
*/
typedef MenuErrCode (*MenuPersistenceCallback)(const uint8_t* data, uint16_t size);
/**
* @brief 恢复回调函数类型
* @param data 要恢复的数据
* @param size 数据大小
* @return 错误码
*/
typedef MenuErrCode (*MenuRestoreCallback)(uint8_t* data, uint16_t* size);
/* 权限管理相关定义 -------------------------------------------------------------------------------------------------*/
/**
* @brief 权限级别类型
*/
typedef uint8_t MenuPermissionLevel;
/**
* @brief 角色ID类型
*/
typedef uint8_t MenuRoleId;
/**
* @brief 角色结构体
*/
typedef struct {
MenuRoleId id; ///< 角色ID
const char* name; ///< 角色名称
MenuPermissionLevel max_level; ///< 该角色的最大权限级别
} MenuRole;
/**
* @brief 权限配置结构体
*/
typedef struct {
MenuRoleId current_role; ///< 当前角色ID
MenuRole roles[MENU_CONFIG_PERMISSION_MAX_ROLES]; ///< 角色列表
uint8_t role_count; ///< 角色数量
} MenuPermissionConfig;
/* 核心数据结构定义 -------------------------------------------------------------------------------------------------*/
/**
* @brief 菜单节点结构体
*/
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;
#if MENU_CONFIG_ENABLE_PARAM
uint16_t param_id; ///< 绑定的参数ID启用参数时有效
#endif
#if MENU_CONFIG_ENABLE_PERMISSION
MenuPermissionLevel permission_level; ///< 访问该菜单所需的权限级别
#endif
} MenuNode;
/**
* @brief 菜单事件结构体
*/
typedef struct MenuEvent {
MenuEventType type; ///< 事件类型
uint8_t priority; ///< 事件优先级0-30最高
MenuNodeId target_node_id; ///< 目标节点ID
void* data; ///< 事件数据
uint32_t timestamp; ///< 事件时间戳
} MenuEvent;
/**
* @brief 菜单事件队列结构体
*/
typedef struct MenuEventQueue {
MenuEvent buffer[MENU_CONFIG_EVENT_QUEUE_LEN]; ///< 队列缓冲区
uint8_t head; ///< 入队指针
uint8_t tail; ///< 出队指针
uint8_t count; ///< 队列元素数量
uint8_t reserved; ///< 保留字节,用于对齐
} MenuEventQueue;
/**
* @brief 菜单导航路径结构体
*/
typedef struct MenuNavPath {
MenuNodeId path[MENU_CONFIG_STACK_DEPTH]; ///< 导航路径
uint8_t depth; ///< 当前深度
uint8_t max_depth; ///< 最大深度
} MenuNavPath;
/**
* @brief 菜单栈结构体
*/
typedef struct MenuStack {
MenuNodeId items[MENU_CONFIG_STACK_DEPTH]; ///< 栈元素
uint8_t top; ///< 栈顶指针
uint8_t max_depth; ///< 最大深度
} MenuStack;
/* 状态持久化相关定义 -------------------------------------------------------------------------------------------------*/
/**
* @brief 持久化数据结构体
*/
typedef struct {
MenuNodeId current_node_id; ///< 当前节点ID
MenuState current_state; ///< 当前菜单状态
MenuNavPath nav_path; ///< 当前导航路径
MenuStack stack; ///< 当前菜单栈
uint32_t timestamp; ///< 持久化时间戳
uint8_t checksum; ///< 数据校验和
} MenuPersistenceData;
/**
* @brief 持久化配置结构体
*/
typedef struct {
bool auto_save_enabled; ///< 是否启用自动保存
uint32_t auto_save_interval; ///< 自动保存间隔ms
uint32_t last_save_tick; ///< 上次保存时间
bool encrypt_enabled; ///< 是否启用加密
MenuPersistenceCallback save_cb; ///< 保存回调函数
MenuRestoreCallback restore_cb; ///< 恢复回调函数
} MenuPersistenceConfig;
/**
* @brief 状态转换规则结构体
*/
typedef struct MenuStateTransition {
MenuState current_state; ///< 当前状态
MenuEventType event; ///< 触发事件
MenuState next_state; ///< 下一个状态
MenuStateAction action; ///< 状态转换动作
const char* desc; ///< 状态转换描述(用于调试)
} MenuStateTransition;
/**
* @brief 菜单核心上下文结构体
*/
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; ///< 上次刷新时间ms
bool is_initialized; ///< 是否已初始化
MenuErrCode error_code; ///< 当前错误码
const char* error_msg; ///< 当前错误信息
uint16_t node_count; ///< 已注册节点数量
uint16_t free_node_count; ///< 空闲节点数量
// 状态机扩展
#if MENU_CONFIG_ENABLE_STATE_MACHINE_EXT
MenuStateTransition custom_transitions[MENU_CONFIG_MAX_STATE_TRANSITIONS]; ///< 自定义状态转换规则
uint16_t custom_transition_count; ///< 自定义状态转换规则数量
#endif
// 权限管理
#if MENU_CONFIG_ENABLE_PERMISSION
MenuPermissionConfig permission_config; ///< 权限配置
#endif
// 状态持久化
#if MENU_CONFIG_ENABLE_PERSISTENCE
MenuPersistenceConfig persistence_config; ///< 持久化配置
#endif
} MenuCoreCtx;
#endif /* MENU_TYPES_H */

228
src/lang/menu_lang.c Normal file
View File

@ -0,0 +1,228 @@
/**
**********************************************************************************************************************
* @file menu_lang.c
* @brief 菜单组件多语言支持模块实现
* @author menu_component
* @date 2025-12-19
**********************************************************************************************************************
*/
/* Includes ----------------------------------------------------------------------------------------------------------*/
#include "menu_lang.h"
/* 配置参数 ---------------------------------------------------------------------------------------------------------*/
#define MENU_LANG_MAX_PACKS 8U /* 最大支持的语言包数量 */
/* 全局变量 ---------------------------------------------------------------------------------------------------------*/
static const LangPack* sg_lang_packs[MENU_LANG_MAX_PACKS];
static uint8_t sg_lang_pack_count = 0;
static LangId sg_current_lang_id = 0;
/* 函数实现 ---------------------------------------------------------------------------------------------------------*/
#if MENU_CONFIG_ENABLE_LANG
/**
* @brief 初始化多语言支持模块
* @return 错误码
*/
MenuErrCode menu_lang_init(void)
{
// 初始化语言包数组
for (uint8_t i = 0; i < MENU_LANG_MAX_PACKS; i++) {
sg_lang_packs[i] = NULL;
}
sg_lang_pack_count = 0;
sg_current_lang_id = 0;
return MENU_ERR_OK;
}
/**
* @brief 反初始化多语言支持模块
* @return 错误码
*/
MenuErrCode menu_lang_deinit(void)
{
sg_lang_pack_count = 0;
return MENU_ERR_OK;
}
/**
* @brief 设置当前语言
* @param lang_id 语言ID
* @return 错误码
*/
MenuErrCode menu_lang_set_current(LangId lang_id)
{
// 检查语言包是否已注册
bool found = false;
for (uint8_t i = 0; i < sg_lang_pack_count; i++) {
if (sg_lang_packs[i]->id == lang_id) {
found = true;
break;
}
}
if (!found) {
return MENU_ERR_INVALID_PARAM;
}
sg_current_lang_id = lang_id;
return MENU_ERR_OK;
}
/**
* @brief 获取当前语言ID
* @return 当前语言ID
*/
LangId menu_lang_get_current(void)
{
return sg_current_lang_id;
}
/**
* @brief 查找语言包
* @param lang_id 语言ID
* @return 语言包指针未找到返回NULL
*/
static const LangPack* menu_lang_find_pack_internal(LangId lang_id)
{
for (uint8_t i = 0; i < sg_lang_pack_count; i++) {
if (sg_lang_packs[i]->id == lang_id) {
return sg_lang_packs[i];
}
}
return NULL;
}
/**
* @brief 注册语言包
* @param pack 语言包指针
* @return 错误码
*/
MenuErrCode menu_lang_register_pack(const LangPack* pack)
{
if (pack == NULL) {
return MENU_ERR_INVALID_PARAM;
}
// 检查语言包是否已存在
if (menu_lang_find_pack_internal(pack->id) != NULL) {
return MENU_ERR_OPERATION_FAILED;
}
// 检查语言包数量是否超过最大值
if (sg_lang_pack_count >= MENU_LANG_MAX_PACKS) {
return MENU_ERR_OUT_OF_MEMORY;
}
// 注册语言包
sg_lang_packs[sg_lang_pack_count] = pack;
sg_lang_pack_count++;
return MENU_ERR_OK;
}
/**
* @brief 注销语言包
* @param lang_id 语言ID
* @return 错误码
*/
MenuErrCode menu_lang_unregister_pack(LangId lang_id)
{
// 查找语言包在数组中的位置
uint8_t index = 0;
bool found = false;
for (index = 0; index < sg_lang_pack_count; index++) {
if (sg_lang_packs[index]->id == lang_id) {
found = true;
break;
}
}
if (!found) {
return MENU_ERR_NODE_NOT_FOUND;
}
// 将后续语言包前移
for (uint8_t i = index; i < sg_lang_pack_count - 1; i++) {
sg_lang_packs[i] = sg_lang_packs[i + 1];
}
sg_lang_pack_count--;
// 如果当前语言被注销,切换到第一个语言包
if (sg_current_lang_id == lang_id && sg_lang_pack_count > 0) {
sg_current_lang_id = sg_lang_packs[0]->id;
}
return MENU_ERR_OK;
}
/**
* @brief 根据字符串ID获取当前语言的字符串
* @param str_id 字符串ID
* @return 字符串指针未找到返回NULL
*/
const char* menu_lang_get_str(StrId str_id)
{
// 获取当前语言包
const LangPack* pack = menu_lang_find_pack_internal(sg_current_lang_id);
if (pack == NULL) {
return NULL;
}
// 在当前语言包中查找字符串
for (uint16_t i = 0; i < pack->string_count; i++) {
if (pack->strings[i].id == str_id) {
return pack->strings[i].str;
}
}
// 如果在当前语言包中未找到,尝试在第一个语言包中查找
if (sg_lang_pack_count > 0 && pack != sg_lang_packs[0]) {
pack = sg_lang_packs[0];
for (uint16_t i = 0; i < pack->string_count; i++) {
if (pack->strings[i].id == str_id) {
return pack->strings[i].str;
}
}
}
return NULL;
}
/**
* @brief 加载语言包
* @param pack 语言包指针
* @return 错误码
*/
MenuErrCode menu_lang_load_pack(const LangPack* pack)
{
if (pack == NULL) {
return MENU_ERR_INVALID_PARAM;
}
// 注册语言包
MenuErrCode err = menu_lang_register_pack(pack);
if (err != MENU_ERR_OK) {
return err;
}
// 设置为当前语言
return menu_lang_set_current(pack->id);
}
/**
* @brief 查找语言包
* @param lang_id 语言ID
* @return 语言包指针未找到返回NULL
*/
const LangPack* menu_lang_find_pack(LangId lang_id)
{
return menu_lang_find_pack_internal(lang_id);
}
#endif /* MENU_CONFIG_ENABLE_LANG */

125
src/lang/menu_lang.h Normal file
View File

@ -0,0 +1,125 @@
/**
**********************************************************************************************************************
* @file menu_lang.h
* @brief 菜单组件多语言支持模块
* @author menu_component
* @date 2025-12-19
**********************************************************************************************************************
*/
#ifndef MENU_LANG_H
#define MENU_LANG_H
/* Includes ----------------------------------------------------------------------------------------------------------*/
#include "../core/menu_config.h"
/* 类型定义 ---------------------------------------------------------------------------------------------------------*/
/**
* @brief 语言ID类型
*/
typedef uint8_t LangId;
/**
* @brief 字符串ID类型
*/
typedef uint16_t StrId;
/**
* @brief 字符串项结构体
*/
typedef struct {
StrId id; ///< 字符串ID
const char* str; ///< 字符串内容
} LangStrItem;
/**
* @brief 语言包结构体
*/
typedef struct {
LangId id; ///< 语言ID
const char* name; ///< 语言名称(如"English"、"中文"
const LangStrItem* strings; ///< 字符串数组
uint16_t string_count; ///< 字符串数量
} LangPack;
/* 函数声明 ---------------------------------------------------------------------------------------------------------*/
#if MENU_CONFIG_ENABLE_LANG
/**
* @brief 初始化多语言支持模块
* @return 错误码
*/
MenuErrCode menu_lang_init(void);
/**
* @brief 反初始化多语言支持模块
* @return 错误码
*/
MenuErrCode menu_lang_deinit(void);
/**
* @brief 设置当前语言
* @param lang_id 语言ID
* @return 错误码
*/
MenuErrCode menu_lang_set_current(LangId lang_id);
/**
* @brief 获取当前语言ID
* @return 当前语言ID
*/
LangId menu_lang_get_current(void);
/**
* @brief 注册语言包
* @param pack 语言包指针
* @return 错误码
*/
MenuErrCode menu_lang_register_pack(const LangPack* pack);
/**
* @brief 注销语言包
* @param lang_id 语言ID
* @return 错误码
*/
MenuErrCode menu_lang_unregister_pack(LangId lang_id);
/**
* @brief 根据字符串ID获取当前语言的字符串
* @param str_id 字符串ID
* @return 字符串指针未找到返回NULL
*/
const char* menu_lang_get_str(StrId str_id);
/**
* @brief 加载语言包
* @param pack 语言包指针
* @return 错误码
*/
MenuErrCode menu_lang_load_pack(const LangPack* pack);
/**
* @brief 查找语言包
* @param lang_id 语言ID
* @return 语言包指针未找到返回NULL
*/
const LangPack* menu_lang_find_pack(LangId lang_id);
#else /* MENU_CONFIG_ENABLE_LANG */
/* 多语言功能未启用时的空实现 */
#define menu_lang_init() MENU_ERR_OK
#define menu_lang_deinit() MENU_ERR_OK
#define menu_lang_set_current(...) MENU_ERR_OK
#define menu_lang_get_current() 0
#define menu_lang_register_pack(...) MENU_ERR_OK
#define menu_lang_unregister_pack(...) MENU_ERR_OK
#define menu_lang_get_str(id) ""
#define menu_lang_load_pack(...) MENU_ERR_OK
#define menu_lang_find_pack(...) NULL
#endif /* MENU_CONFIG_ENABLE_LANG */
#endif /* MENU_LANG_H */

397
src/param/menu_param.c Normal file
View File

@ -0,0 +1,397 @@
/**
**********************************************************************************************************************
* @file menu_param.c
* @brief 菜单组件参数管理模块实现
* @author menu_component
* @date 2025-12-19
**********************************************************************************************************************
*/
/* Includes ----------------------------------------------------------------------------------------------------------*/
#include "menu_param.h"
#include "../core/menu_hash.h"
/* 配置参数 ---------------------------------------------------------------------------------------------------------*/
#define MENU_PARAM_MAX_NUM 32U /* 最大参数数量 */
/* 全局变量 ---------------------------------------------------------------------------------------------------------*/
static MenuParam sg_menu_params[MENU_PARAM_MAX_NUM];
static uint16_t sg_menu_param_count = 0;
/* 函数实现 ---------------------------------------------------------------------------------------------------------*/
#if MENU_CONFIG_ENABLE_PARAM
/**
* @brief 初始化参数管理模块
* @return 错误码
*/
MenuErrCode menu_param_init(void)
{
// 初始化参数数组
for (uint16_t i = 0; i < MENU_PARAM_MAX_NUM; i++) {
sg_menu_params[i].id = 0;
sg_menu_params[i].name = NULL;
sg_menu_params[i].type = PARAM_TYPE_UINT8;
sg_menu_params[i].value = NULL;
sg_menu_params[i].default_value = NULL;
sg_menu_params[i].min_value = NULL;
sg_menu_params[i].max_value = NULL;
sg_menu_params[i].is_read_only = false;
sg_menu_params[i].is_modified = false;
}
sg_menu_param_count = 0;
return MENU_ERR_OK;
}
/**
* @brief 反初始化参数管理模块
* @return 错误码
*/
MenuErrCode menu_param_deinit(void)
{
sg_menu_param_count = 0;
return MENU_ERR_OK;
}
/**
* @brief 查找参数
* @param id 参数ID
* @return 参数指针未找到返回NULL
*/
MenuParam* menu_param_find_internal(uint16_t id)
{
for (uint16_t i = 0; i < sg_menu_param_count; i++) {
if (sg_menu_params[i].id == id) {
return &sg_menu_params[i];
}
}
return NULL;
}
/**
* @brief 注册参数
* @param id 参数ID
* @param name 参数名称
* @param type 参数类型
* @param value 参数值指针
* @param default_value 默认值指针
* @param min_value 最小值指针
* @param max_value 最大值指针
* @param is_read_only 是否只读
* @return 错误码
*/
MenuErrCode menu_param_register(uint16_t id, const char* name, ParamType type, void* value, void* default_value, void* min_value, void* max_value, bool is_read_only)
{
// 检查参数ID是否已存在
if (menu_param_find_internal(id) != NULL) {
return MENU_ERR_OPERATION_FAILED;
}
// 检查参数数量是否超过最大值
if (sg_menu_param_count >= MENU_PARAM_MAX_NUM) {
return MENU_ERR_OUT_OF_MEMORY;
}
// 检查参数值指针是否有效
if (value == NULL) {
return MENU_ERR_INVALID_PARAM;
}
// 注册参数
MenuParam* param = &sg_menu_params[sg_menu_param_count];
param->id = id;
param->name = name;
param->type = type;
param->value = value;
param->default_value = default_value;
param->min_value = min_value;
param->max_value = max_value;
param->is_read_only = is_read_only;
param->is_modified = false;
sg_menu_param_count++;
return MENU_ERR_OK;
}
/**
* @brief 注销参数
* @param id 参数ID
* @return 错误码
*/
MenuErrCode menu_param_unregister(uint16_t id)
{
MenuParam* param = menu_param_find_internal(id);
if (param == NULL) {
return MENU_ERR_NODE_NOT_FOUND;
}
// 查找参数在数组中的位置
uint16_t index = 0;
for (index = 0; index < sg_menu_param_count; index++) {
if (sg_menu_params[index].id == id) {
break;
}
}
// 将后续参数前移
for (uint16_t i = index; i < sg_menu_param_count - 1; i++) {
sg_menu_params[i] = sg_menu_params[i + 1];
}
sg_menu_param_count--;
return MENU_ERR_OK;
}
/**
* @brief 获取参数值
* @param id 参数ID
* @param value 用于存储参数值的指针
* @return 错误码
*/
MenuErrCode menu_param_get_value(uint16_t id, void* value)
{
MenuParam* param = menu_param_find_internal(id);
if (param == NULL) {
return MENU_ERR_NODE_NOT_FOUND;
}
if (value == NULL) {
return MENU_ERR_INVALID_PARAM;
}
// 根据参数类型获取值
switch (param->type) {
case PARAM_TYPE_INT8:
*(int8_t*)value = *(int8_t*)param->value;
break;
case PARAM_TYPE_UINT8:
*(uint8_t*)value = *(uint8_t*)param->value;
break;
case PARAM_TYPE_INT16:
*(int16_t*)value = *(int16_t*)param->value;
break;
case PARAM_TYPE_UINT16:
*(uint16_t*)value = *(uint16_t*)param->value;
break;
case PARAM_TYPE_INT32:
*(int32_t*)value = *(int32_t*)param->value;
break;
case PARAM_TYPE_UINT32:
*(uint32_t*)value = *(uint32_t*)param->value;
break;
case PARAM_TYPE_FLOAT:
*(float*)value = *(float*)param->value;
break;
default:
return MENU_ERR_INVALID_STATE;
}
return MENU_ERR_OK;
}
/**
* @brief 检查参数值是否在允许范围内
* @param param 参数指针
* @param value 要检查的值
* @return true为在范围内false为超出范围
*/
static bool menu_param_check_range(MenuParam* param, const void* value)
{
if (param->min_value == NULL || param->max_value == NULL) {
return true; // 没有设置范围限制
}
switch (param->type) {
case PARAM_TYPE_INT8:
return (*(int8_t*)value >= *(int8_t*)param->min_value) && (*(int8_t*)value <= *(int8_t*)param->max_value);
case PARAM_TYPE_UINT8:
return (*(uint8_t*)value >= *(uint8_t*)param->min_value) && (*(uint8_t*)value <= *(uint8_t*)param->max_value);
case PARAM_TYPE_INT16:
return (*(int16_t*)value >= *(int16_t*)param->min_value) && (*(int16_t*)value <= *(int16_t*)param->max_value);
case PARAM_TYPE_UINT16:
return (*(uint16_t*)value >= *(uint16_t*)param->min_value) && (*(uint16_t*)value <= *(uint16_t*)param->max_value);
case PARAM_TYPE_INT32:
return (*(int32_t*)value >= *(int32_t*)param->min_value) && (*(int32_t*)value <= *(int32_t*)param->max_value);
case PARAM_TYPE_UINT32:
return (*(uint32_t*)value >= *(uint32_t*)param->min_value) && (*(uint32_t*)value <= *(uint32_t*)param->max_value);
case PARAM_TYPE_FLOAT:
return (*(float*)value >= *(float*)param->min_value) && (*(float*)value <= *(float*)param->max_value);
default:
return false;
}
}
/**
* @brief 设置参数值
* @param id 参数ID
* @param value 新的参数值
* @return 错误码
*/
MenuErrCode menu_param_set_value(uint16_t id, const void* value)
{
MenuParam* param = menu_param_find_internal(id);
if (param == NULL) {
return MENU_ERR_NODE_NOT_FOUND;
}
if (value == NULL) {
return MENU_ERR_INVALID_PARAM;
}
// 检查参数是否只读
if (param->is_read_only) {
return MENU_ERR_OPERATION_FAILED;
}
// 检查参数值是否在允许范围内
if (!menu_param_check_range(param, value)) {
return MENU_ERR_INVALID_PARAM;
}
// 根据参数类型设置值
switch (param->type) {
case PARAM_TYPE_INT8:
if (*(int8_t*)value != *(int8_t*)param->value) {
*(int8_t*)param->value = *(int8_t*)value;
param->is_modified = true;
}
break;
case PARAM_TYPE_UINT8:
if (*(uint8_t*)value != *(uint8_t*)param->value) {
*(uint8_t*)param->value = *(uint8_t*)value;
param->is_modified = true;
}
break;
case PARAM_TYPE_INT16:
if (*(int16_t*)value != *(int16_t*)param->value) {
*(int16_t*)param->value = *(int16_t*)value;
param->is_modified = true;
}
break;
case PARAM_TYPE_UINT16:
if (*(uint16_t*)value != *(uint16_t*)param->value) {
*(uint16_t*)param->value = *(uint16_t*)value;
param->is_modified = true;
}
break;
case PARAM_TYPE_INT32:
if (*(int32_t*)value != *(int32_t*)param->value) {
*(int32_t*)param->value = *(int32_t*)value;
param->is_modified = true;
}
break;
case PARAM_TYPE_UINT32:
if (*(uint32_t*)value != *(uint32_t*)param->value) {
*(uint32_t*)param->value = *(uint32_t*)value;
param->is_modified = true;
}
break;
case PARAM_TYPE_FLOAT:
if (*(float*)value != *(float*)param->value) {
*(float*)param->value = *(float*)value;
param->is_modified = true;
}
break;
default:
return MENU_ERR_INVALID_STATE;
}
return MENU_ERR_OK;
}
/**
* @brief 恢复参数默认值
* @param id 参数ID
* @return 错误码
*/
MenuErrCode menu_param_restore_default(uint16_t id)
{
MenuParam* param = menu_param_find_internal(id);
if (param == NULL) {
return MENU_ERR_NODE_NOT_FOUND;
}
if (param->default_value == NULL) {
return MENU_ERR_INVALID_PARAM;
}
// 检查参数是否只读
if (param->is_read_only) {
return MENU_ERR_OPERATION_FAILED;
}
// 恢复默认值
return menu_param_set_value(id, param->default_value);
}
/**
* @brief 获取参数类型
* @param id 参数ID
* @return 参数类型
*/
ParamType menu_param_get_type(uint16_t id)
{
MenuParam* param = menu_param_find_internal(id);
if (param != NULL) {
return param->type;
}
return PARAM_TYPE_UINT8;
}
/**
* @brief 检查参数是否只读
* @param id 参数ID
* @return true为只读false为可写
*/
bool menu_param_is_read_only(uint16_t id)
{
MenuParam* param = menu_param_find_internal(id);
if (param != NULL) {
return param->is_read_only;
}
return false;
}
/**
* @brief 检查参数是否已修改
* @param id 参数ID
* @return true为已修改false为未修改
*/
bool menu_param_is_modified(uint16_t id)
{
MenuParam* param = menu_param_find_internal(id);
if (param != NULL) {
return param->is_modified;
}
return false;
}
/**
* @brief 清除参数修改标记
* @param id 参数ID
* @return 错误码
*/
MenuErrCode menu_param_clear_modified_flag(uint16_t id)
{
MenuParam* param = menu_param_find_internal(id);
if (param == NULL) {
return MENU_ERR_NODE_NOT_FOUND;
}
param->is_modified = false;
return MENU_ERR_OK;
}
/**
* @brief 查找参数
* @param id 参数ID
* @return 参数指针未找到返回NULL
*/
const MenuParam* menu_param_find(uint16_t id)
{
return menu_param_find_internal(id);
}
#endif /* MENU_CONFIG_ENABLE_PARAM */

159
src/param/menu_param.h Normal file
View File

@ -0,0 +1,159 @@
/**
**********************************************************************************************************************
* @file menu_param.h
* @brief 菜单组件参数管理模块
* @author menu_component
* @date 2025-12-19
**********************************************************************************************************************
*/
#ifndef MENU_PARAM_H
#define MENU_PARAM_H
/* Includes ----------------------------------------------------------------------------------------------------------*/
#include "../core/menu_config.h"
/* 类型定义 ---------------------------------------------------------------------------------------------------------*/
/**
* @brief 参数类型
*/
typedef enum {
PARAM_TYPE_INT8, /*!< 8位有符号整数 */
PARAM_TYPE_UINT8, /*!< 8位无符号整数 */
PARAM_TYPE_INT16, /*!< 16位有符号整数 */
PARAM_TYPE_UINT16, /*!< 16位无符号整数 */
PARAM_TYPE_INT32, /*!< 32位有符号整数 */
PARAM_TYPE_UINT32, /*!< 32位无符号整数 */
PARAM_TYPE_FLOAT, /*!< 浮点数 */
} ParamType;
/**
* @brief 参数结构体
*/
typedef struct {
uint16_t id; ///< 参数ID
const char* name; ///< 参数名称
ParamType type; ///< 参数类型
void* value; ///< 参数值指针
void* default_value; ///< 默认值指针
void* min_value; ///< 最小值指针
void* max_value; ///< 最大值指针
bool is_read_only; ///< 是否只读
bool is_modified; ///< 是否已修改
} MenuParam;
/* 函数声明 ---------------------------------------------------------------------------------------------------------*/
#if MENU_CONFIG_ENABLE_PARAM
/**
* @brief 初始化参数管理模块
* @return 错误码
*/
MenuErrCode menu_param_init(void);
/**
* @brief 反初始化参数管理模块
* @return 错误码
*/
MenuErrCode menu_param_deinit(void);
/**
* @brief 注册参数
* @param id 参数ID
* @param name 参数名称
* @param type 参数类型
* @param value 参数值指针
* @param default_value 默认值指针
* @param min_value 最小值指针
* @param max_value 最大值指针
* @param is_read_only 是否只读
* @return 错误码
*/
MenuErrCode menu_param_register(uint16_t id, const char* name, ParamType type, void* value, void* default_value, void* min_value, void* max_value, bool is_read_only);
/**
* @brief 注销参数
* @param id 参数ID
* @return 错误码
*/
MenuErrCode menu_param_unregister(uint16_t id);
/**
* @brief 获取参数值
* @param id 参数ID
* @param value 用于存储参数值的指针
* @return 错误码
*/
MenuErrCode menu_param_get_value(uint16_t id, void* value);
/**
* @brief 设置参数值
* @param id 参数ID
* @param value 新的参数值
* @return 错误码
*/
MenuErrCode menu_param_set_value(uint16_t id, const void* value);
/**
* @brief 恢复参数默认值
* @param id 参数ID
* @return 错误码
*/
MenuErrCode menu_param_restore_default(uint16_t id);
/**
* @brief 获取参数类型
* @param id 参数ID
* @return 参数类型
*/
ParamType menu_param_get_type(uint16_t id);
/**
* @brief 检查参数是否只读
* @param id 参数ID
* @return true为只读false为可写
*/
bool menu_param_is_read_only(uint16_t id);
/**
* @brief 检查参数是否已修改
* @param id 参数ID
* @return true为已修改false为未修改
*/
bool menu_param_is_modified(uint16_t id);
/**
* @brief 清除参数修改标记
* @param id 参数ID
* @return 错误码
*/
MenuErrCode menu_param_clear_modified_flag(uint16_t id);
/**
* @brief 查找参数
* @param id 参数ID
* @return 参数指针未找到返回NULL
*/
const MenuParam* menu_param_find(uint16_t id);
#else /* MENU_CONFIG_ENABLE_PARAM */
/* 参数管理功能未启用时的空实现 */
#define menu_param_init() MENU_ERR_OK
#define menu_param_deinit() MENU_ERR_OK
#define menu_param_register(...) MENU_ERR_OK
#define menu_param_unregister(...) MENU_ERR_OK
#define menu_param_get_value(...) MENU_ERR_OK
#define menu_param_set_value(...) MENU_ERR_OK
#define menu_param_restore_default(...) MENU_ERR_OK
#define menu_param_get_type(id) PARAM_TYPE_UINT8
#define menu_param_is_read_only(id) false
#define menu_param_is_modified(id) false
#define menu_param_clear_modified_flag(...) MENU_ERR_OK
#define menu_param_find(id) NULL
#endif /* MENU_CONFIG_ENABLE_PARAM */
#endif /* MENU_PARAM_H */

23
test/CMakeLists.txt Normal file
View File

@ -0,0 +1,23 @@
# CMakeLists.txt for test directory
# 收集测试所需的源文件
set(TEST_SOURCES
menu_test.c
)
# 创建测试可执行程序
add_executable(menu_test ${TEST_SOURCES})
# 链接menu库
target_link_libraries(menu_test PRIVATE menu)
# 设置包含目录
target_include_directories(menu_test PRIVATE
${CMAKE_CURRENT_SOURCE_DIR}/..
${CMAKE_CURRENT_SOURCE_DIR}/../src/core
)
# 安装规则
install(TARGETS menu_test
RUNTIME DESTINATION bin
)

304
test/menu_test.c Normal file
View File

@ -0,0 +1,304 @@
/**
**********************************************************************************************************************
* @file menu_test.c
* @brief 菜单组件测试用例
* @author menu_component
* @date 2025-12-19
**********************************************************************************************************************
*/
#include "../api/menu_api.h"
#include "../port/menu_port.h"
#include <stdio.h>
#include <stdlib.h>
/* 测试菜单节点ID定义 */
#define TEST_MENU_ID_ROOT 1U
#define TEST_MENU_ID_ITEM1 2U
#define TEST_MENU_ID_ITEM2 3U
#define TEST_MENU_ID_ITEM3 4U
#define TEST_MENU_ID_SUB1 5U
#define TEST_MENU_ID_SUB2 6U
/* 测试全局变量 */
static uint32_t sg_test_tick = 0;
static bool sg_test_callback_called = false;
static MenuNodeId sg_test_last_node_id = 0;
/* 测试回调函数 */
static MenuErrCode test_enter_callback(MenuNodeId node_id, struct MenuCoreCtx* core_ctx)
{
(void)core_ctx;
sg_test_callback_called = true;
sg_test_last_node_id = node_id;
printf("Callback called for node %d\n", node_id);
return MENU_ERR_OK;
}
static MenuErrCode test_exit_callback(MenuNodeId node_id, struct MenuCoreCtx* core_ctx)
{
(void)core_ctx;
sg_test_callback_called = true;
sg_test_last_node_id = node_id;
printf("Exit callback called for node %d\n", node_id);
return MENU_ERR_OK;
}
/* 测试硬件驱动 */
static uint32_t test_get_tick(void)
{
return sg_test_tick++;
}
static void test_printf(const char* fmt, va_list args)
{
vprintf(fmt, args);
}
static MenuPortDriver sg_test_driver = {
.printf = test_printf,
.get_tick = test_get_tick,
.delay_ms = NULL,
.display = NULL,
.key_scan = NULL,
.irq_ctrl = NULL,
.error_handler = NULL,
.modbus_send = NULL,
.modbus_receive = NULL,
};
/* 测试函数 */
/**
* @brief 测试菜单初始化和反初始化
*/
static bool test_menu_init_deinit(void)
{
printf("\n=== Test: Menu Init/Deinit ===\n");
// 初始化硬件端口
MenuErrCode err = menu_port_init(&sg_test_driver);
if (err != MENU_ERR_OK) {
printf("FAIL: Failed to initialize port\n");
return false;
}
// 初始化菜单
err = menu_init();
if (err != MENU_ERR_OK) {
printf("FAIL: Failed to initialize menu\n");
return false;
}
// 反初始化菜单
err = menu_deinit();
if (err != MENU_ERR_OK) {
printf("FAIL: Failed to deinitialize menu\n");
return false;
}
// 反初始化硬件端口
err = menu_port_deinit();
if (err != MENU_ERR_OK) {
printf("FAIL: Failed to deinitialize port\n");
return false;
}
printf("PASS: Menu init/deinit test passed\n");
return true;
}
/**
* @brief 测试菜单节点注册
*/
static bool test_menu_node_register(void)
{
printf("\n=== Test: Menu Node Register ===\n");
// 初始化
menu_port_init(&sg_test_driver);
menu_init();
// 注册根节点
MenuNodeId root_id = menu_register_node(TEST_MENU_ID_ROOT, 0, "Root Menu", test_enter_callback, test_exit_callback);
if (root_id != TEST_MENU_ID_ROOT) {
printf("FAIL: Failed to register root node\n");
menu_deinit();
menu_port_deinit();
return false;
}
printf("PASS: Root node registered\n");
// 注册子节点
MenuNodeId item1_id = menu_register_node(TEST_MENU_ID_ITEM1, TEST_MENU_ID_ROOT, "Item 1", test_enter_callback, NULL);
if (item1_id != TEST_MENU_ID_ITEM1) {
printf("FAIL: Failed to register item 1\n");
menu_deinit();
menu_port_deinit();
return false;
}
printf("PASS: Item 1 registered\n");
MenuNodeId item2_id = menu_register_node(TEST_MENU_ID_ITEM2, TEST_MENU_ID_ROOT, "Item 2", test_enter_callback, NULL);
if (item2_id != TEST_MENU_ID_ITEM2) {
printf("FAIL: Failed to register item 2\n");
menu_deinit();
menu_port_deinit();
return false;
}
printf("PASS: Item 2 registered\n");
// 注册孙子节点
MenuNodeId sub1_id = menu_register_node(TEST_MENU_ID_SUB1, TEST_MENU_ID_ITEM1, "Sub Item 1", test_enter_callback, NULL);
if (sub1_id != TEST_MENU_ID_SUB1) {
printf("FAIL: Failed to register sub item 1\n");
menu_deinit();
menu_port_deinit();
return false;
}
printf("PASS: Sub item 1 registered\n");
// 清理
menu_deinit();
menu_port_deinit();
printf("PASS: Menu node register test passed\n");
return true;
}
/**
* @brief 测试菜单事件处理
*/
static bool test_menu_event_processing(void)
{
printf("\n=== Test: Menu Event Processing ===\n");
// 初始化
menu_port_init(&sg_test_driver);
menu_init();
// 注册测试节点
menu_register_node(TEST_MENU_ID_ROOT, 0, "Root Menu", test_enter_callback, test_exit_callback);
menu_register_node(TEST_MENU_ID_ITEM1, TEST_MENU_ID_ROOT, "Item 1", test_enter_callback, NULL);
menu_register_node(TEST_MENU_ID_ITEM2, TEST_MENU_ID_ROOT, "Item 2", test_enter_callback, NULL);
// 重置测试状态
sg_test_callback_called = false;
sg_test_last_node_id = 0;
// 发送上键事件
MenuErrCode err = menu_post_event(MENU_EVENT_KEY_UP, 1);
if (err != MENU_ERR_OK) {
printf("FAIL: Failed to post UP event\n");
menu_deinit();
menu_port_deinit();
return false;
}
printf("PASS: UP event posted\n");
// 发送下键事件
err = menu_post_event(MENU_EVENT_KEY_DOWN, 1);
if (err != MENU_ERR_OK) {
printf("FAIL: Failed to post DOWN event\n");
menu_deinit();
menu_port_deinit();
return false;
}
printf("PASS: DOWN event posted\n");
// 处理菜单主循环
menu_main_loop(test_get_tick());
printf("PASS: Menu main loop processed\n");
// 清理
menu_deinit();
menu_port_deinit();
printf("PASS: Menu event processing test passed\n");
return true;
}
/**
* @brief 测试菜单状态查询
*/
static bool test_menu_state_query(void)
{
printf("\n=== Test: Menu State Query ===\n");
// 初始化
menu_port_init(&sg_test_driver);
menu_init();
// 注册测试节点
menu_register_node(TEST_MENU_ID_ROOT, 0, "Root Menu", NULL, NULL);
// 查询当前状态
MenuState state = menu_get_state();
printf("Current state: %d\n", state);
// 查询当前节点
MenuNodeId curr_node = menu_get_current_node();
printf("Current node: %d\n", curr_node);
// 查询导航深度
uint8_t depth = menu_get_nav_depth();
printf("Navigation depth: %d\n", depth);
// 清理
menu_deinit();
menu_port_deinit();
printf("PASS: Menu state query test passed\n");
return true;
}
/**
* @brief 主测试函数
*/
int main(int argc, char* argv[])
{
(void)argc;
(void)argv;
printf("Menu Component Test Suite\n");
printf("========================\n");
int test_count = 0;
int pass_count = 0;
// 运行所有测试
test_count++;
if (test_menu_init_deinit()) {
pass_count++;
}
test_count++;
if (test_menu_node_register()) {
pass_count++;
}
test_count++;
if (test_menu_event_processing()) {
pass_count++;
}
test_count++;
if (test_menu_state_query()) {
pass_count++;
}
// 打印测试结果
printf("\n========================\n");
printf("Test Results: %d/%d tests passed\n", pass_count, test_count);
printf("========================\n");
if (pass_count == test_count) {
printf("All tests passed!\n");
return EXIT_SUCCESS;
} else {
printf("Some tests failed!\n");
return EXIT_FAILURE;
}
}

View File

@ -12,10 +12,10 @@
| 层级 | 主要职责 | 包含模块 | 文件位置 | 用户可定制性 | | 层级 | 主要职责 | 包含模块 | 文件位置 | 用户可定制性 |
|------|----------|----------|----------|--------------| |------|----------|----------|----------|--------------|
| **核心层** | 提供菜单组件的核心功能,包括菜单节点管理、事件处理、状态机等 | 菜单核心、事件队列、哈希表索引、栈管理 | src/core/ | 不可定制 | | **核心层** | 提供菜单组件的核心功能,包括菜单节点管理、事件处理、状态机等 | 菜单核心menu_core.c、事件队列menu_event.c、哈希表索引menu_hash.c、栈管理menu_stack.c | src/core/ | 不可定制 |
| **功能扩展层** | 基于核心层扩展的功能模块,用户可根据需求裁剪 | 参数管理、多语言支持、Modbus映射、状态机扩展 | src/features/、src/param/、src/lang/ | 部分可定制 | | **功能扩展层** | 基于核心层扩展的功能模块,用户可根据需求裁剪 | 参数管理menu_param.c、多语言支持menu_lang.c | src/param/、src/lang/ | 部分可定制 |
| **硬件端口层** | 提供硬件适配接口,对接具体硬件平台 | 硬件驱动抽象、打印接口、系统时间、中断管理 | port/ | 完全可定制 | | **硬件端口层** | 提供硬件适配接口,对接具体硬件平台 | 硬件驱动抽象menu_port.c | port/ | 完全可定制 |
| **API层** | 对外提供统一的API接口屏蔽内部实现细节 | 上下文管理、菜单管理、事件处理、功能扩展接口 | api/ | 不可定制 | | **API层** | 对外提供统一的API接口屏蔽内部实现细节 | 统一API接口menu_api.c | api/ | 不可定制 |
### 2.2 模块间依赖关系 ### 2.2 模块间依赖关系
@ -187,6 +187,45 @@ typedef struct {
- 状态转换动作可定制,便于扩展菜单行为 - 状态转换动作可定制,便于扩展菜单行为
- 支持状态转换描述,便于调试 - 支持状态转换描述,便于调试
### 4.5 参数结构体
```c
typedef struct {
uint16_t id; ///< 参数ID
const char* name; ///< 参数名称
ParamType type; ///< 参数类型
void* value; ///< 参数值指针
void* default_value; ///< 默认值指针
void* min_value; ///< 最小值指针
void* max_value; ///< 最大值指针
bool is_read_only; ///< 是否只读
bool is_modified; ///< 是否已修改
} MenuParam;
```
**设计亮点**
- 支持多种参数类型int8/uint8/int16/uint16/int32/uint32/float
- 自动范围检查和边界处理
- 参数与菜单节点绑定,支持菜单直接调整参数
- 支持默认值恢复功能
### 4.6 语言包结构体
```c
typedef struct {
LangId id; ///< 语言ID
const char* name; ///< 语言名称(如"English"、"中文"
const LangStrItem* strings; ///< 字符串数组
uint16_t string_count; ///< 字符串数量
} LangPack;
```
**设计亮点**
- 支持多种语言切换
- 字符串ID映射机制便于管理和维护
- 自动回退到默认语言,提高系统容错性
- 优化的语言字符串查找算法
## 5. 核心算法 ## 5. 核心算法
### 5.1 哈希表算法 ### 5.1 哈希表算法