/** ********************************************************************************************************************** * @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; }