diff --git a/CMakeLists.txt b/CMakeLists.txt index e8c3ed8..513686a 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -6,14 +6,70 @@ project(menu VERSION 1.3.0 LANGUAGES C) set(CMAKE_C_STANDARD 99) set(CMAKE_C_STANDARD_REQUIRED ON) -# 库源文件 -set(MENU_SOURCES - menu.c +# 核心层源文件 +set(CORE_SOURCES + 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 - menu.h + ${CORE_HEADERS} + ${FEATURE_HEADERS} + ${API_HEADERS} + ${PORT_HEADERS} ) # 创建静态库 @@ -24,20 +80,31 @@ set_target_properties(menu PROPERTIES PUBLIC_HEADER "${MENU_HEADERS}" ) -# 包含当前目录 +# 包含目录 target_include_directories(menu PUBLIC $ + $ + $ + $ + $ + $ $ ) # 示例程序 -option(BUILD_EXAMPLES "Build examples" ON) +option(BUILD_EXAMPLES "Build examples" OFF) if(BUILD_EXAMPLES) # 添加示例目录 add_subdirectory(examples) endif() +# 添加demo目录 +add_subdirectory(demo) + +# 添加test目录 +add_subdirectory(test) + # 安装规则 install(TARGETS menu ARCHIVE DESTINATION lib diff --git a/api/menu_api.c b/api/menu_api.c new file mode 100644 index 0000000..160e528 --- /dev/null +++ b/api/menu_api.c @@ -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 + diff --git a/api/menu_api.h b/api/menu_api.h new file mode 100644 index 0000000..801b5e1 --- /dev/null +++ b/api/menu_api.h @@ -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 节点ID(0表示自动分配) + * @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-3,0最高) + * @return 错误码 + */ +MenuErrCode menu_post_event(MenuEventType event_type, uint8_t priority); + +/** + * @brief 发送带数据的菜单事件 + * @param event_type 事件类型 + * @param priority 事件优先级(0-3,0最高) + * @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 */ diff --git a/demo/CMakeLists.txt b/demo/CMakeLists.txt new file mode 100644 index 0000000..33b0fd7 --- /dev/null +++ b/demo/CMakeLists.txt @@ -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 +) diff --git a/demo/build/CMakeCache.txt b/demo/build/CMakeCache.txt new file mode 100644 index 0000000..3916575 --- /dev/null +++ b/demo/build/CMakeCache.txt @@ -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 + diff --git a/demo/build/CMakeFiles/3.31.2/CMakeSystem.cmake b/demo/build/CMakeFiles/3.31.2/CMakeSystem.cmake new file mode 100644 index 0000000..88ce365 --- /dev/null +++ b/demo/build/CMakeFiles/3.31.2/CMakeSystem.cmake @@ -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) diff --git a/demo/build/CMakeFiles/CMakeConfigureLog.yaml b/demo/build/CMakeFiles/CMakeConfigureLog.yaml new file mode 100644 index 0000000..e6a6fbe --- /dev/null +++ b/demo/build/CMakeFiles/CMakeConfigureLog.yaml @@ -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 +... diff --git a/demo/build/CMakeFiles/cmake.check_cache b/demo/build/CMakeFiles/cmake.check_cache new file mode 100644 index 0000000..3dccd73 --- /dev/null +++ b/demo/build/CMakeFiles/cmake.check_cache @@ -0,0 +1 @@ +# This file is generated by cmake for dependency checking of the CMakeCache.txt file diff --git a/demo/demo.c b/demo/demo.c new file mode 100644 index 0000000..ab8e104 --- /dev/null +++ b/demo/demo.c @@ -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 +#include +#include +#include + +#if defined(_WIN32) || defined(_WIN64) + #include + #define NON_BLOCKING_GETCHAR() _getch() + #define IS_KEY_AVAILABLE() _kbhit() +#else + #include + #include + #include + #include + + // 保存原始终端设置 + 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; +} \ No newline at end of file diff --git a/demo/language.c b/demo/language.c new file mode 100644 index 0000000..480172a --- /dev/null +++ b/demo/language.c @@ -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); +} diff --git a/demo/language.h b/demo/language.h new file mode 100644 index 0000000..14f7546 --- /dev/null +++ b/demo/language.h @@ -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; // 父节点ID(MENU_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 */ \ No newline at end of file diff --git a/examples/CMakeLists.txt b/examples/CMakeLists.txt deleted file mode 100644 index 687ceb8..0000000 --- a/examples/CMakeLists.txt +++ /dev/null @@ -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 -) diff --git a/examples/build/CMakeCache.txt b/examples/build/CMakeCache.txt new file mode 100644 index 0000000..c022bf1 --- /dev/null +++ b/examples/build/CMakeCache.txt @@ -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 + diff --git a/examples/build/CMakeFiles/3.31.2/CMakeSystem.cmake b/examples/build/CMakeFiles/3.31.2/CMakeSystem.cmake new file mode 100644 index 0000000..88ce365 --- /dev/null +++ b/examples/build/CMakeFiles/3.31.2/CMakeSystem.cmake @@ -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) diff --git a/examples/build/CMakeFiles/CMakeConfigureLog.yaml b/examples/build/CMakeFiles/CMakeConfigureLog.yaml new file mode 100644 index 0000000..67e6858 --- /dev/null +++ b/examples/build/CMakeFiles/CMakeConfigureLog.yaml @@ -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 +... diff --git a/examples/build/CMakeFiles/cmake.check_cache b/examples/build/CMakeFiles/cmake.check_cache new file mode 100644 index 0000000..3dccd73 --- /dev/null +++ b/examples/build/CMakeFiles/cmake.check_cache @@ -0,0 +1 @@ +# This file is generated by cmake for dependency checking of the CMakeCache.txt file diff --git a/examples/demo.c b/examples/demo.c deleted file mode 100644 index ebedbdd..0000000 --- a/examples/demo.c +++ /dev/null @@ -1,65 +0,0 @@ - -/* 菜单显示效果图可看: - -https://blog.csdn.net/qq_24130227/article/details/121167276 - -*/ - -#include "menu.h" -#include "mainhmi.h" -#include "language.h" -#include -#include - -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; -} \ No newline at end of file diff --git a/menu.c b/menu.c deleted file mode 100644 index 436430e..0000000 --- a/menu.c +++ /dev/null @@ -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 -#endif - -#ifdef _MENU_USE_SHORTCUT_ -#include -#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; -} diff --git a/menu.h b/menu.h deleted file mode 100644 index af27e25..0000000 --- a/menu.h +++ /dev/null @@ -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 -#include -#include - -#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 diff --git a/menu_persistence.dat b/menu_persistence.dat new file mode 100644 index 0000000..b15631b Binary files /dev/null and b/menu_persistence.dat differ diff --git a/port/menu_port.c b/port/menu_port.c new file mode 100644 index 0000000..961239b --- /dev/null +++ b/port/menu_port.c @@ -0,0 +1,319 @@ +/** + ********************************************************************************************************************** + * @file menu_port.c + * @brief 菜单组件硬件端口层实现 + * @author menu_component + * @date 2025-12-19 + ********************************************************************************************************************** + */ + +/* Includes ----------------------------------------------------------------------------------------------------------*/ +#include "menu_port.h" +#include +#include + +/* 全局变量 ---------------------------------------------------------------------------------------------------------*/ +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) { + // 可以添加其他处理逻辑,如复位系统等 + } +} diff --git a/port/menu_port.h b/port/menu_port.h new file mode 100644 index 0000000..233af6a --- /dev/null +++ b/port/menu_port.h @@ -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 + +/* 类型定义 ---------------------------------------------------------------------------------------------------------*/ + +/** + * @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 */ diff --git a/src/core/menu_config.h b/src/core/menu_config.h new file mode 100644 index 0000000..5a2003a --- /dev/null +++ b/src/core/menu_config.h @@ -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 +#include +#include + +/* 配置项 -----------------------------------------------------------------------------------------------------------*/ + +/* 菜单节点配置 */ +#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 */ diff --git a/src/core/menu_core.c b/src/core/menu_core.c new file mode 100644 index 0000000..d305501 --- /dev/null +++ b/src/core/menu_core.c @@ -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 节点ID(0表示自动分配) + * @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; +} diff --git a/src/core/menu_core.h b/src/core/menu_core.h new file mode 100644 index 0000000..b777c1b --- /dev/null +++ b/src/core/menu_core.h @@ -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 节点ID(0表示自动分配) + * @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 */ diff --git a/src/core/menu_event.c b/src/core/menu_event.c new file mode 100644 index 0000000..8fee3d4 --- /dev/null +++ b/src/core/menu_event.c @@ -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); +} diff --git a/src/core/menu_event.h b/src/core/menu_event.h new file mode 100644 index 0000000..10bd958 --- /dev/null +++ b/src/core/menu_event.h @@ -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 */ diff --git a/src/core/menu_hash.c b/src/core/menu_hash.c new file mode 100644 index 0000000..9b29b09 --- /dev/null +++ b/src/core/menu_hash.c @@ -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; +} diff --git a/src/core/menu_hash.h b/src/core/menu_hash.h new file mode 100644 index 0000000..5f014b3 --- /dev/null +++ b/src/core/menu_hash.h @@ -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 */ diff --git a/src/core/menu_permission.c b/src/core/menu_permission.c new file mode 100644 index 0000000..e3b44af --- /dev/null +++ b/src/core/menu_permission.c @@ -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 */ diff --git a/src/core/menu_permission.h b/src/core/menu_permission.h new file mode 100644 index 0000000..0fa7329 --- /dev/null +++ b/src/core/menu_permission.h @@ -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 */ diff --git a/src/core/menu_persistence.c b/src/core/menu_persistence.c new file mode 100644 index 0000000..f38fd8d --- /dev/null +++ b/src/core/menu_persistence.c @@ -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 */ diff --git a/src/core/menu_persistence.h b/src/core/menu_persistence.h new file mode 100644 index 0000000..92d44d4 --- /dev/null +++ b/src/core/menu_persistence.h @@ -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 */ diff --git a/src/core/menu_stack.c b/src/core/menu_stack.c new file mode 100644 index 0000000..01044a1 --- /dev/null +++ b/src/core/menu_stack.c @@ -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; + } +} diff --git a/src/core/menu_stack.h b/src/core/menu_stack.h new file mode 100644 index 0000000..1c08f10 --- /dev/null +++ b/src/core/menu_stack.h @@ -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 */ diff --git a/src/core/menu_types.h b/src/core/menu_types.h new file mode 100644 index 0000000..738432d --- /dev/null +++ b/src/core/menu_types.h @@ -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-3,0最高) + 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 */ diff --git a/src/lang/menu_lang.c b/src/lang/menu_lang.c new file mode 100644 index 0000000..09af110 --- /dev/null +++ b/src/lang/menu_lang.c @@ -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 */ diff --git a/src/lang/menu_lang.h b/src/lang/menu_lang.h new file mode 100644 index 0000000..606c8a2 --- /dev/null +++ b/src/lang/menu_lang.h @@ -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 */ diff --git a/src/param/menu_param.c b/src/param/menu_param.c new file mode 100644 index 0000000..a483514 --- /dev/null +++ b/src/param/menu_param.c @@ -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 */ diff --git a/src/param/menu_param.h b/src/param/menu_param.h new file mode 100644 index 0000000..4c0ed46 --- /dev/null +++ b/src/param/menu_param.h @@ -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 */ diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt new file mode 100644 index 0000000..e450415 --- /dev/null +++ b/test/CMakeLists.txt @@ -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 +) \ No newline at end of file diff --git a/test/menu_test.c b/test/menu_test.c new file mode 100644 index 0000000..cc233b3 --- /dev/null +++ b/test/menu_test.c @@ -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 +#include + +/* 测试菜单节点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; + } +} diff --git a/开发文档.md b/开发文档.md index 4fb2c31..fae44c3 100644 --- a/开发文档.md +++ b/开发文档.md @@ -12,10 +12,10 @@ | 层级 | 主要职责 | 包含模块 | 文件位置 | 用户可定制性 | |------|----------|----------|----------|--------------| -| **核心层** | 提供菜单组件的核心功能,包括菜单节点管理、事件处理、状态机等 | 菜单核心、事件队列、哈希表索引、栈管理 | src/core/ | 不可定制 | -| **功能扩展层** | 基于核心层扩展的功能模块,用户可根据需求裁剪 | 参数管理、多语言支持、Modbus映射、状态机扩展 | src/features/、src/param/、src/lang/ | 部分可定制 | -| **硬件端口层** | 提供硬件适配接口,对接具体硬件平台 | 硬件驱动抽象、打印接口、系统时间、中断管理 | port/ | 完全可定制 | -| **API层** | 对外提供统一的API接口,屏蔽内部实现细节 | 上下文管理、菜单管理、事件处理、功能扩展接口 | api/ | 不可定制 | +| **核心层** | 提供菜单组件的核心功能,包括菜单节点管理、事件处理、状态机等 | 菜单核心(menu_core.c)、事件队列(menu_event.c)、哈希表索引(menu_hash.c)、栈管理(menu_stack.c) | src/core/ | 不可定制 | +| **功能扩展层** | 基于核心层扩展的功能模块,用户可根据需求裁剪 | 参数管理(menu_param.c)、多语言支持(menu_lang.c) | src/param/、src/lang/ | 部分可定制 | +| **硬件端口层** | 提供硬件适配接口,对接具体硬件平台 | 硬件驱动抽象(menu_port.c) | port/ | 完全可定制 | +| **API层** | 对外提供统一的API接口,屏蔽内部实现细节 | 统一API接口(menu_api.c) | api/ | 不可定制 | ### 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.1 哈希表算法