初始化版本
This commit is contained in:
515
src/core/menu_core.c
Normal file
515
src/core/menu_core.c
Normal file
@ -0,0 +1,515 @@
|
||||
/**
|
||||
* @file menu_core.c
|
||||
* @brief 菜单核心逻辑:导航、栈管理、主循环(工业级:状态机驱动)
|
||||
*/
|
||||
#include "menu_core.h"
|
||||
#include "menu_data.h"
|
||||
#include <stddef.h>
|
||||
|
||||
/************************** 全局变量定义 **************************/
|
||||
MenuCoreCtx s_menu_core_ctx;
|
||||
|
||||
/**
|
||||
* @brief 查找菜单节点(通过ID)
|
||||
* @param node_id 节点ID
|
||||
* @return 节点指针(NULL表示未找到)
|
||||
*/
|
||||
MenuNode* menu_core_find_node(MenuNodeId node_id)
|
||||
{
|
||||
MenuCoreCtx* ctx = menu_core_get_ctx();
|
||||
for (uint16_t i = 0; i < MENU_CONFIG_MAX_NODES; i++)
|
||||
{
|
||||
if (ctx->nodes[i].flags.is_registered && ctx->nodes[i].id == node_id)
|
||||
{
|
||||
return &ctx->nodes[i];
|
||||
}
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 菜单栈压入节点
|
||||
* @param node_id 节点ID
|
||||
* @return 错误码
|
||||
*/
|
||||
static MenuErrCode menu_core_stack_push(MenuNodeId node_id)
|
||||
{
|
||||
MenuCoreCtx* ctx = menu_core_get_ctx();
|
||||
MENU_ASSERT(ctx != NULL);
|
||||
|
||||
if (ctx->stack.top >= MENU_CONFIG_STACK_DEPTH)
|
||||
{
|
||||
MENU_DEBUG("Stack overflow: depth %d", MENU_CONFIG_STACK_DEPTH);
|
||||
return MENU_ERR_STACK_OVERFLOW;
|
||||
}
|
||||
|
||||
ctx->stack.nodes[ctx->stack.top++] = node_id;
|
||||
MENU_DEBUG("Stack push: node %d, top %d", node_id, ctx->stack.top);
|
||||
return MENU_OK;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 菜单栈弹出节点
|
||||
* @param node_id 输出参数,弹出的节点ID
|
||||
* @return 错误码
|
||||
*/
|
||||
static MenuErrCode menu_core_stack_pop(MenuNodeId* node_id)
|
||||
{
|
||||
MenuCoreCtx* ctx = menu_core_get_ctx();
|
||||
MENU_ASSERT(ctx != NULL && node_id != NULL);
|
||||
|
||||
if (ctx->stack.top == 0)
|
||||
{
|
||||
MENU_DEBUG("Stack underflow");
|
||||
return MENU_ERR_STACK_UNDERFLOW;
|
||||
}
|
||||
|
||||
*node_id = ctx->stack.nodes[--ctx->stack.top];
|
||||
MENU_DEBUG("Stack pop: node %d, top %d", *node_id, ctx->stack.top);
|
||||
return MENU_OK;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 菜单导航到子节点(确认键)
|
||||
* @param node_id 当前节点ID
|
||||
* @return 错误码
|
||||
*/
|
||||
static MenuErrCode menu_core_navigate_enter(MenuNodeId node_id)
|
||||
{
|
||||
MenuNode* node = menu_core_find_node(node_id);
|
||||
if (node == NULL)
|
||||
{
|
||||
return MENU_ERR_NODE_NOT_FOUND;
|
||||
}
|
||||
|
||||
// 执行进入回调
|
||||
if (node->enter_cb != NULL)
|
||||
{
|
||||
MenuErrCode err = node->enter_cb(node_id);
|
||||
if (err != MENU_OK)
|
||||
{
|
||||
return err;
|
||||
}
|
||||
}
|
||||
|
||||
// 如果有子节点,压入栈并选中第一个子节点
|
||||
if (node->first_child != NULL)
|
||||
{
|
||||
MenuCoreCtx* ctx = menu_core_get_ctx();
|
||||
ctx->current_node_id = node->first_child->id;
|
||||
return menu_core_stack_push(node->first_child->id);
|
||||
}
|
||||
|
||||
return MENU_OK;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 菜单导航返回父节点(返回键)
|
||||
* @return 错误码
|
||||
*/
|
||||
static MenuErrCode menu_core_navigate_back(void)
|
||||
{
|
||||
MenuCoreCtx* ctx = menu_core_get_ctx();
|
||||
MenuNodeId current_id = ctx->current_node_id;
|
||||
MenuNode* current_node = menu_core_find_node(current_id);
|
||||
if (current_node == NULL)
|
||||
{
|
||||
return MENU_ERR_NODE_NOT_FOUND;
|
||||
}
|
||||
|
||||
// 执行退出回调
|
||||
if (current_node->exit_cb != NULL)
|
||||
{
|
||||
MenuErrCode err = current_node->exit_cb(current_id);
|
||||
if (err != MENU_OK)
|
||||
{
|
||||
return err;
|
||||
}
|
||||
}
|
||||
|
||||
// 弹出栈,回到父节点
|
||||
MenuNodeId parent_id = current_node->parent_id;
|
||||
if (parent_id == 0) // 根节点
|
||||
{
|
||||
ctx->current_node_id = current_id;
|
||||
return MENU_OK;
|
||||
}
|
||||
|
||||
MenuNodeId pop_id;
|
||||
MenuErrCode err = menu_core_stack_pop(&pop_id);
|
||||
if (err != MENU_OK)
|
||||
{
|
||||
return err;
|
||||
}
|
||||
|
||||
ctx->current_node_id = parent_id;
|
||||
return MENU_OK;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 菜单导航上选(上键)
|
||||
* @return 错误码
|
||||
*/
|
||||
static MenuErrCode menu_core_navigate_up(void)
|
||||
{
|
||||
MenuCoreCtx* ctx = menu_core_get_ctx();
|
||||
MenuNode* current_node = menu_core_find_node(ctx->current_node_id);
|
||||
if (current_node == NULL)
|
||||
{
|
||||
return MENU_ERR_NODE_NOT_FOUND;
|
||||
}
|
||||
|
||||
// 切换到上一个兄弟节点
|
||||
if (current_node->prev_sibling != NULL)
|
||||
{
|
||||
ctx->current_node_id = current_node->prev_sibling->id;
|
||||
}
|
||||
else
|
||||
{
|
||||
// 循环到最后一个兄弟节点(工业级:友好的交互设计)
|
||||
MenuNode* last_node = current_node;
|
||||
while (last_node->next_sibling != NULL)
|
||||
{
|
||||
last_node = last_node->next_sibling;
|
||||
}
|
||||
ctx->current_node_id = last_node->id;
|
||||
}
|
||||
|
||||
MENU_DEBUG("Navigate up: current node %d", ctx->current_node_id);
|
||||
return MENU_OK;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 菜单导航下选(下键)
|
||||
* @return 错误码
|
||||
*/
|
||||
static MenuErrCode menu_core_navigate_down(void)
|
||||
{
|
||||
MenuCoreCtx* ctx = menu_core_get_ctx();
|
||||
MenuNode* current_node = menu_core_find_node(ctx->current_node_id);
|
||||
if (current_node == NULL)
|
||||
{
|
||||
return MENU_ERR_NODE_NOT_FOUND;
|
||||
}
|
||||
|
||||
// 切换到下一个兄弟节点
|
||||
if (current_node->next_sibling != NULL)
|
||||
{
|
||||
ctx->current_node_id = current_node->next_sibling->id;
|
||||
}
|
||||
else
|
||||
{
|
||||
// 循环到第一个兄弟节点
|
||||
MenuNode* parent_node = menu_core_find_node(current_node->parent_id);
|
||||
if (parent_node != NULL && parent_node->first_child != NULL)
|
||||
{
|
||||
ctx->current_node_id = parent_node->first_child->id;
|
||||
}
|
||||
}
|
||||
|
||||
MENU_DEBUG("Navigate down: current node %d", ctx->current_node_id);
|
||||
return MENU_OK;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 初始化菜单核心上下文
|
||||
* @return 错误码
|
||||
*/
|
||||
MenuErrCode menu_core_ctx_init(void)
|
||||
{
|
||||
MenuCoreCtx* ctx = menu_core_get_ctx();
|
||||
MENU_MEM_SET_ZERO(ctx, sizeof(MenuCoreCtx));
|
||||
ctx->is_initialized = true;
|
||||
return MENU_OK;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 获取菜单核心上下文(内部唯一访问入口)
|
||||
* @return 菜单核心上下文指针
|
||||
*/
|
||||
MenuCoreCtx* menu_core_get_ctx(void)
|
||||
{
|
||||
return &s_menu_core_ctx;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 处理单个菜单事件
|
||||
* @param event 事件指针
|
||||
* @return 错误码
|
||||
*/
|
||||
MenuErrCode menu_core_handle_event(const MenuEvent* event)
|
||||
{
|
||||
MENU_ASSERT(event != NULL);
|
||||
|
||||
MenuErrCode err = MENU_OK;
|
||||
switch (event->type)
|
||||
{
|
||||
case MENU_EVENT_KEY_UP:
|
||||
err = menu_core_navigate_up();
|
||||
break;
|
||||
case MENU_EVENT_KEY_DOWN:
|
||||
err = menu_core_navigate_down();
|
||||
break;
|
||||
case MENU_EVENT_KEY_ENTER:
|
||||
err = menu_core_navigate_enter(menu_core_get_ctx()->current_node_id);
|
||||
break;
|
||||
case MENU_EVENT_KEY_BACK:
|
||||
err = menu_core_navigate_back();
|
||||
break;
|
||||
case MENU_EVENT_NONE:
|
||||
// 无事件,不处理
|
||||
break;
|
||||
default:
|
||||
// 自定义事件(用户可扩展)
|
||||
MENU_DEBUG("Custom event received: type %d, param %lu", event->type, event->param);
|
||||
break;
|
||||
}
|
||||
|
||||
return err;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 刷新菜单显示(内部调用,对接port层显示接口)
|
||||
*/
|
||||
void menu_core_refresh_display(void)
|
||||
{
|
||||
MenuCoreCtx* ctx = menu_core_get_ctx();
|
||||
MenuNode* current_node = menu_core_find_node(ctx->current_node_id);
|
||||
if (current_node == NULL)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// 这里应该调用port层的显示接口
|
||||
// 例如:menu_port_display(current_node->name, current_node->id);
|
||||
MENU_DEBUG("Refresh display: current menu %s (ID: %d)", current_node->name, current_node->id);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 从事件队列获取事件(非阻塞)
|
||||
* @param event 输出参数,获取到的事件
|
||||
* @return 是否获取到事件
|
||||
*/
|
||||
static bool menu_core_get_event(MenuEvent* event)
|
||||
{
|
||||
MenuCoreCtx* ctx = menu_core_get_ctx();
|
||||
MENU_ASSERT(ctx != NULL && event != NULL);
|
||||
|
||||
if (ctx->event_queue.count == 0)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// 从队列头取出事件
|
||||
*event = ctx->event_queue.buffer[ctx->event_queue.head];
|
||||
ctx->event_queue.head = (ctx->event_queue.head + 1) % MENU_CONFIG_EVENT_QUEUE_LEN;
|
||||
ctx->event_queue.count--;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 菜单主循环(需在用户主循环中调用,处理事件和刷新显示)
|
||||
*/
|
||||
void menu_main_loop(void)
|
||||
{
|
||||
MenuCoreCtx* ctx = menu_core_get_ctx();
|
||||
if (!ctx->is_initialized)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// 处理事件队列
|
||||
MenuEvent event;
|
||||
while (menu_core_get_event(&event))
|
||||
{
|
||||
// 检查事件是否超时
|
||||
if (menu_utils_get_tick() - event.timestamp > MENU_CONFIG_EVENT_TIMEOUT)
|
||||
{
|
||||
MENU_DEBUG("Event timeout: type %d", event.type);
|
||||
continue;
|
||||
}
|
||||
|
||||
// 处理事件
|
||||
MenuErrCode err = menu_core_handle_event(&event);
|
||||
if (err != MENU_OK)
|
||||
{
|
||||
MENU_DEBUG("Event handle error: %d", err);
|
||||
}
|
||||
}
|
||||
|
||||
// 定时刷新显示
|
||||
if (menu_utils_get_tick() - ctx->last_refresh_tick >= MENU_CONFIG_REFRESH_INTERVAL)
|
||||
{
|
||||
menu_core_refresh_display();
|
||||
ctx->last_refresh_tick = menu_utils_get_tick();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 菜单组件初始化(必须首先调用)
|
||||
* @return 错误码
|
||||
*/
|
||||
MenuErrCode menu_init(void)
|
||||
{
|
||||
MenuErrCode err = menu_core_ctx_init();
|
||||
if (err != MENU_OK)
|
||||
{
|
||||
return err;
|
||||
}
|
||||
|
||||
// 初始化事件队列
|
||||
MenuCoreCtx* ctx = menu_core_get_ctx();
|
||||
ctx->last_refresh_tick = menu_utils_get_tick();
|
||||
|
||||
return MENU_OK;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 发送事件到菜单事件队列(如按键事件、自定义事件)
|
||||
* @param type 事件类型
|
||||
* @param param 事件附加参数(可选,如按键长按时间)
|
||||
* @return 错误码
|
||||
*/
|
||||
MenuErrCode menu_post_event(MenuEventType type, uint32_t param)
|
||||
{
|
||||
MenuCoreCtx* ctx = menu_core_get_ctx();
|
||||
if (!ctx->is_initialized)
|
||||
{
|
||||
return MENU_ERR_INVALID_PARAM;
|
||||
}
|
||||
|
||||
// 检查事件队列是否已满
|
||||
if (ctx->event_queue.count >= MENU_CONFIG_EVENT_QUEUE_LEN)
|
||||
{
|
||||
return MENU_ERR_EVENT_QUEUE_FULL;
|
||||
}
|
||||
|
||||
// 入队事件
|
||||
ctx->event_queue.buffer[ctx->event_queue.tail].type = type;
|
||||
ctx->event_queue.buffer[ctx->event_queue.tail].param = param;
|
||||
ctx->event_queue.buffer[ctx->event_queue.tail].timestamp = menu_utils_get_tick();
|
||||
ctx->event_queue.tail = (ctx->event_queue.tail + 1) % MENU_CONFIG_EVENT_QUEUE_LEN;
|
||||
ctx->event_queue.count++;
|
||||
|
||||
return MENU_OK;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 注册菜单节点(构建菜单树)
|
||||
* @param parent_id 父节点ID(根节点填0)
|
||||
* @param node_id 当前节点ID(唯一,不可重复)
|
||||
* @param name_str 菜单名称字符串(或多语言索引)
|
||||
* @param enter_cb 进入该菜单的回调函数(NULL表示无)
|
||||
* @param exit_cb 退出该菜单的回调函数(NULL表示无)
|
||||
* @return 错误码
|
||||
*/
|
||||
MenuErrCode menu_register_node(MenuNodeId parent_id, MenuNodeId node_id, const char* name_str, MenuCallback enter_cb, MenuCallback exit_cb)
|
||||
{
|
||||
MenuCoreCtx* ctx = menu_core_get_ctx();
|
||||
if (!ctx->is_initialized || node_id == 0)
|
||||
{
|
||||
return MENU_ERR_INVALID_PARAM;
|
||||
}
|
||||
|
||||
// 查找空闲节点
|
||||
MenuNode* new_node = NULL;
|
||||
for (uint16_t i = 0; i < MENU_CONFIG_MAX_NODES; i++)
|
||||
{
|
||||
if (!ctx->nodes[i].flags.is_registered)
|
||||
{
|
||||
new_node = &ctx->nodes[i];
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (new_node == NULL)
|
||||
{
|
||||
return MENU_ERR_OUT_OF_MEMORY;
|
||||
}
|
||||
|
||||
// 检查节点ID是否已存在
|
||||
if (menu_core_find_node(node_id) != NULL)
|
||||
{
|
||||
return MENU_ERR_INVALID_PARAM;
|
||||
}
|
||||
|
||||
// 初始化新节点
|
||||
MENU_MEM_SET_ZERO(new_node, sizeof(MenuNode));
|
||||
new_node->id = node_id;
|
||||
new_node->parent_id = parent_id;
|
||||
new_node->name = name_str;
|
||||
new_node->enter_cb = enter_cb;
|
||||
new_node->exit_cb = exit_cb;
|
||||
new_node->flags.is_registered = true;
|
||||
new_node->flags.is_selected = false;
|
||||
|
||||
// 如果是根节点(parent_id为0)
|
||||
if (parent_id == 0)
|
||||
{
|
||||
// 检查是否已有根节点
|
||||
if (ctx->current_node_id != 0)
|
||||
{
|
||||
// 已有根节点,将新节点作为兄弟节点添加到根节点之后
|
||||
MenuNode* root_node = menu_core_find_node(ctx->current_node_id);
|
||||
while (root_node->next_sibling != NULL)
|
||||
{
|
||||
root_node = root_node->next_sibling;
|
||||
}
|
||||
root_node->next_sibling = new_node;
|
||||
new_node->prev_sibling = root_node;
|
||||
}
|
||||
else
|
||||
{
|
||||
// 第一个根节点,设置为当前节点
|
||||
ctx->current_node_id = node_id;
|
||||
menu_core_stack_push(node_id);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// 查找父节点
|
||||
MenuNode* parent_node = menu_core_find_node(parent_id);
|
||||
if (parent_node == NULL)
|
||||
{
|
||||
return MENU_ERR_NODE_NOT_FOUND;
|
||||
}
|
||||
|
||||
// 将新节点添加到父节点的子节点列表中
|
||||
if (parent_node->first_child == NULL)
|
||||
{
|
||||
// 父节点还没有子节点,作为第一个子节点
|
||||
parent_node->first_child = new_node;
|
||||
}
|
||||
else
|
||||
{
|
||||
// 已有子节点,添加到最后一个子节点之后
|
||||
MenuNode* last_child = parent_node->first_child;
|
||||
while (last_child->next_sibling != NULL)
|
||||
{
|
||||
last_child = last_child->next_sibling;
|
||||
}
|
||||
last_child->next_sibling = new_node;
|
||||
new_node->prev_sibling = last_child;
|
||||
}
|
||||
}
|
||||
|
||||
return MENU_OK;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 获取当前选中的菜单节点ID
|
||||
* @param node_id 输出参数,当前节点ID
|
||||
* @return 错误码
|
||||
*/
|
||||
MenuErrCode menu_get_current_node(MenuNodeId* node_id)
|
||||
{
|
||||
MenuCoreCtx* ctx = menu_core_get_ctx();
|
||||
if (!ctx->is_initialized || node_id == NULL)
|
||||
{
|
||||
return MENU_ERR_INVALID_PARAM;
|
||||
}
|
||||
|
||||
*node_id = ctx->current_node_id;
|
||||
return MENU_OK;
|
||||
}
|
||||
123
src/lang/menu_lang.c
Normal file
123
src/lang/menu_lang.c
Normal file
@ -0,0 +1,123 @@
|
||||
/**
|
||||
* @file menu_lang.c
|
||||
* @brief 菜单多语言支持模块(可裁剪功能)
|
||||
*/
|
||||
#include "menu_core.h"
|
||||
#include "menu_data.h"
|
||||
|
||||
#if MENU_CONFIG_ENABLE_LANG
|
||||
|
||||
/************************** 全局变量定义 **************************/
|
||||
MenuLangStr s_menu_lang_strs[MENU_CONFIG_MAX_NODES * MENU_CONFIG_MAX_LANGS];
|
||||
uint8_t s_current_lang_id = 0;
|
||||
|
||||
/**
|
||||
* @brief 查找语言字符串(通过字符串ID和语言ID)
|
||||
* @param str_id 字符串ID
|
||||
* @param lang_id 语言ID
|
||||
* @return 语言字符串(NULL表示未找到)
|
||||
*/
|
||||
static const char* menu_lang_find_str(uint16_t str_id, uint8_t lang_id)
|
||||
{
|
||||
for (uint16_t i = 0; i < MENU_CONFIG_MAX_NODES * MENU_CONFIG_MAX_LANGS; i++)
|
||||
{
|
||||
if (s_menu_lang_strs[i].str_id == str_id && s_menu_lang_strs[i].lang_id == lang_id)
|
||||
{
|
||||
return s_menu_lang_strs[i].str;
|
||||
}
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 注册语言字符串
|
||||
* @param str_id 字符串ID
|
||||
* @param lang_id 语言ID
|
||||
* @param str 字符串内容
|
||||
* @return 错误码
|
||||
*/
|
||||
MenuErrCode menu_lang_register_str(uint16_t str_id, uint8_t lang_id, const char* str)
|
||||
{
|
||||
if (lang_id >= MENU_CONFIG_MAX_LANGS || str == NULL)
|
||||
{
|
||||
return MENU_ERR_INVALID_PARAM;
|
||||
}
|
||||
|
||||
// 查找空闲位置或已存在的字符串条目
|
||||
for (uint16_t i = 0; i < MENU_CONFIG_MAX_NODES * MENU_CONFIG_MAX_LANGS; i++)
|
||||
{
|
||||
if (!s_menu_lang_strs[i].str || (s_menu_lang_strs[i].str_id == str_id && s_menu_lang_strs[i].lang_id == lang_id))
|
||||
{
|
||||
s_menu_lang_strs[i].str_id = str_id;
|
||||
s_menu_lang_strs[i].lang_id = lang_id;
|
||||
s_menu_lang_strs[i].str = str;
|
||||
return MENU_OK;
|
||||
}
|
||||
}
|
||||
|
||||
return MENU_ERR_OUT_OF_MEMORY;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 获取当前语言的字符串
|
||||
* @param str_id 字符串ID
|
||||
* @return 字符串内容(NULL表示未找到)
|
||||
*/
|
||||
const char* menu_lang_get_str(uint16_t str_id)
|
||||
{
|
||||
const char* str = menu_lang_find_str(str_id, s_current_lang_id);
|
||||
if (str == NULL)
|
||||
{
|
||||
// 如果当前语言未找到,尝试使用默认语言(0)
|
||||
str = menu_lang_find_str(str_id, 0);
|
||||
}
|
||||
return str;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 设置当前语言
|
||||
* @param lang_id 语言ID(如0-中文,1-英文)
|
||||
* @return 错误码
|
||||
*/
|
||||
MenuErrCode menu_lang_set_current(uint8_t lang_id)
|
||||
{
|
||||
if (lang_id >= MENU_CONFIG_MAX_LANGS)
|
||||
{
|
||||
return MENU_ERR_INVALID_PARAM;
|
||||
}
|
||||
|
||||
s_current_lang_id = lang_id;
|
||||
MENU_DEBUG("Language changed to: %d", lang_id);
|
||||
|
||||
// 语言切换后,刷新菜单显示
|
||||
menu_core_refresh_display();
|
||||
|
||||
return MENU_OK;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 获取当前语言
|
||||
* @param lang_id 输出参数,当前语言ID
|
||||
* @return 错误码
|
||||
*/
|
||||
MenuErrCode menu_lang_get_current(uint8_t* lang_id)
|
||||
{
|
||||
if (lang_id == NULL)
|
||||
{
|
||||
return MENU_ERR_INVALID_PARAM;
|
||||
}
|
||||
|
||||
*lang_id = s_current_lang_id;
|
||||
return MENU_OK;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 获取支持的最大语言数量
|
||||
* @return 最大语言数量
|
||||
*/
|
||||
uint8_t menu_lang_get_max_langs(void)
|
||||
{
|
||||
return MENU_CONFIG_MAX_LANGS;
|
||||
}
|
||||
|
||||
#endif // MENU_CONFIG_ENABLE_LANG
|
||||
302
src/param/menu_param.c
Normal file
302
src/param/menu_param.c
Normal file
@ -0,0 +1,302 @@
|
||||
/**
|
||||
* @file menu_param.c
|
||||
* @brief 菜单参数管理模块(可裁剪功能)
|
||||
*/
|
||||
#include "menu_core.h"
|
||||
#include "menu_data.h"
|
||||
|
||||
#if MENU_CONFIG_ENABLE_PARAM
|
||||
|
||||
/************************** 全局变量定义 **************************/
|
||||
MenuParam s_menu_params[MENU_CONFIG_MAX_PARAMS];
|
||||
|
||||
/**
|
||||
* @brief 查找参数(通过参数ID)
|
||||
* @param param_id 参数ID
|
||||
* @return 参数指针(NULL表示未找到)
|
||||
*/
|
||||
static MenuParam* menu_param_find(uint16_t param_id)
|
||||
{
|
||||
for (uint16_t i = 0; i < MENU_CONFIG_MAX_PARAMS; i++)
|
||||
{
|
||||
if (s_menu_params[i].is_registered && s_menu_params[i].id == param_id)
|
||||
{
|
||||
return &s_menu_params[i];
|
||||
}
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 将浮点值转换为参数内部值(根据类型)
|
||||
* @param param 参数指针
|
||||
* @param value 浮点值
|
||||
* @return 转换后的内部值(通过指针返回)
|
||||
*/
|
||||
static void menu_param_float_to_internal(MenuParam* param, float value)
|
||||
{
|
||||
MENU_ASSERT(param != NULL);
|
||||
|
||||
// 确保值在范围内
|
||||
if (value < param->min_val)
|
||||
{
|
||||
value = param->min_val;
|
||||
}
|
||||
else if (value > param->max_val)
|
||||
{
|
||||
value = param->max_val;
|
||||
}
|
||||
|
||||
// 应用缩放因子
|
||||
value /= param->scale;
|
||||
|
||||
// 根据参数类型转换
|
||||
switch (param->type)
|
||||
{
|
||||
case MENU_PARAM_TYPE_INT8:
|
||||
param->value.i8 = (int8_t)value;
|
||||
break;
|
||||
case MENU_PARAM_TYPE_UINT8:
|
||||
param->value.u8 = (uint8_t)value;
|
||||
break;
|
||||
case MENU_PARAM_TYPE_INT16:
|
||||
param->value.i16 = (int16_t)value;
|
||||
break;
|
||||
case MENU_PARAM_TYPE_UINT16:
|
||||
param->value.u16 = (uint16_t)value;
|
||||
break;
|
||||
case MENU_PARAM_TYPE_INT32:
|
||||
param->value.i32 = (int32_t)value;
|
||||
break;
|
||||
case MENU_PARAM_TYPE_UINT32:
|
||||
param->value.u32 = (uint32_t)value;
|
||||
break;
|
||||
case MENU_PARAM_TYPE_FLOAT:
|
||||
param->value.f = value;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 将参数内部值转换为浮点值(根据类型)
|
||||
* @param param 参数指针
|
||||
* @return 浮点值
|
||||
*/
|
||||
static float menu_param_internal_to_float(const MenuParam* param)
|
||||
{
|
||||
MENU_ASSERT(param != NULL);
|
||||
|
||||
float value = 0.0f;
|
||||
|
||||
// 根据参数类型转换
|
||||
switch (param->type)
|
||||
{
|
||||
case MENU_PARAM_TYPE_INT8:
|
||||
value = (float)param->value.i8;
|
||||
break;
|
||||
case MENU_PARAM_TYPE_UINT8:
|
||||
value = (float)param->value.u8;
|
||||
break;
|
||||
case MENU_PARAM_TYPE_INT16:
|
||||
value = (float)param->value.i16;
|
||||
break;
|
||||
case MENU_PARAM_TYPE_UINT16:
|
||||
value = (float)param->value.u16;
|
||||
break;
|
||||
case MENU_PARAM_TYPE_INT32:
|
||||
value = (float)param->value.i32;
|
||||
break;
|
||||
case MENU_PARAM_TYPE_UINT32:
|
||||
value = (float)param->value.u32;
|
||||
break;
|
||||
case MENU_PARAM_TYPE_FLOAT:
|
||||
value = param->value.f;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
// 应用缩放因子
|
||||
return value * param->scale;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 注册参数到菜单节点(参数管理功能)
|
||||
* @param node_id 菜单节点ID(参数与菜单绑定)
|
||||
* @param param_id 参数ID(唯一)
|
||||
* @param type 参数类型
|
||||
* @param min_val 最小值(浮点型,内部自动转换)
|
||||
* @param max_val 最大值(浮点型,内部自动转换)
|
||||
* @param default_val 默认值(浮点型,内部自动转换)
|
||||
* @param scale 缩放因子(如0.1表示保留1位小数)
|
||||
* @return 错误码
|
||||
*/
|
||||
MenuErrCode menu_param_register(MenuNodeId node_id, uint16_t param_id, MenuParamType type, float min_val, float max_val, float default_val, float scale)
|
||||
{
|
||||
if (scale <= 0.0f)
|
||||
{
|
||||
return MENU_ERR_INVALID_PARAM;
|
||||
}
|
||||
|
||||
// 查找菜单节点
|
||||
MenuNode* node = menu_core_find_node(node_id);
|
||||
if (node == NULL)
|
||||
{
|
||||
return MENU_ERR_NODE_NOT_FOUND;
|
||||
}
|
||||
|
||||
// 检查参数ID是否已存在
|
||||
if (menu_param_find(param_id) != NULL)
|
||||
{
|
||||
return MENU_ERR_INVALID_PARAM;
|
||||
}
|
||||
|
||||
// 查找空闲参数位置
|
||||
MenuParam* param = NULL;
|
||||
for (uint16_t i = 0; i < MENU_CONFIG_MAX_PARAMS; i++)
|
||||
{
|
||||
if (!s_menu_params[i].is_registered)
|
||||
{
|
||||
param = &s_menu_params[i];
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (param == NULL)
|
||||
{
|
||||
return MENU_ERR_OUT_OF_MEMORY;
|
||||
}
|
||||
|
||||
// 初始化参数
|
||||
MENU_MEM_SET_ZERO(param, sizeof(MenuParam));
|
||||
param->id = param_id;
|
||||
param->type = type;
|
||||
param->min_val = min_val;
|
||||
param->max_val = max_val;
|
||||
param->scale = scale;
|
||||
param->is_registered = true;
|
||||
|
||||
// 设置默认值
|
||||
menu_param_float_to_internal(param, default_val);
|
||||
// 保存默认值
|
||||
menu_param_float_to_internal((MenuParam*)¶m->default_val, default_val);
|
||||
|
||||
// 将参数与菜单节点绑定
|
||||
node->param_id = param_id;
|
||||
|
||||
MENU_DEBUG("Param registered: ID=%d, Type=%d, Range=[%0.2f, %0.2f], Default=%0.2f, Scale=%0.2f",
|
||||
param_id, type, min_val, max_val, default_val, scale);
|
||||
|
||||
return MENU_OK;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 设置参数值
|
||||
* @param param_id 参数ID
|
||||
* @param value 新值(浮点型,内部自动转换)
|
||||
* @return 错误码
|
||||
*/
|
||||
MenuErrCode menu_param_set_value(uint16_t param_id, float value)
|
||||
{
|
||||
// 查找参数
|
||||
MenuParam* param = menu_param_find(param_id);
|
||||
if (param == NULL)
|
||||
{
|
||||
return MENU_ERR_NODE_NOT_FOUND;
|
||||
}
|
||||
|
||||
// 设置新值
|
||||
menu_param_float_to_internal(param, value);
|
||||
|
||||
MENU_DEBUG("Param set: ID=%d, Value=%0.2f (Internal: %0.2f)",
|
||||
param_id, value, menu_param_internal_to_float(param));
|
||||
|
||||
return MENU_OK;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 获取参数值
|
||||
* @param param_id 参数ID
|
||||
* @param value 输出参数,当前值(浮点型)
|
||||
* @return 错误码
|
||||
*/
|
||||
MenuErrCode menu_param_get_value(uint16_t param_id, float* value)
|
||||
{
|
||||
if (value == NULL)
|
||||
{
|
||||
return MENU_ERR_INVALID_PARAM;
|
||||
}
|
||||
|
||||
// 查找参数
|
||||
MenuParam* param = menu_param_find(param_id);
|
||||
if (param == NULL)
|
||||
{
|
||||
return MENU_ERR_NODE_NOT_FOUND;
|
||||
}
|
||||
|
||||
// 获取当前值
|
||||
*value = menu_param_internal_to_float(param);
|
||||
|
||||
MENU_DEBUG("Param get: ID=%d, Value=%0.2f", param_id, *value);
|
||||
|
||||
return MENU_OK;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 将参数恢复为默认值
|
||||
* @param param_id 参数ID
|
||||
* @return 错误码
|
||||
*/
|
||||
MenuErrCode menu_param_restore_default(uint16_t param_id)
|
||||
{
|
||||
// 查找参数
|
||||
MenuParam* param = menu_param_find(param_id);
|
||||
if (param == NULL)
|
||||
{
|
||||
return MENU_ERR_NODE_NOT_FOUND;
|
||||
}
|
||||
|
||||
// 恢复默认值
|
||||
float default_val = menu_param_internal_to_float((const MenuParam*)¶m->default_val);
|
||||
return menu_param_set_value(param_id, default_val);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 增加参数值(用于菜单上下键调整)
|
||||
* @param param_id 参数ID
|
||||
* @param step 步长(浮点型)
|
||||
* @return 错误码
|
||||
*/
|
||||
MenuErrCode menu_param_increase(uint16_t param_id, float step)
|
||||
{
|
||||
float current_val = 0.0f;
|
||||
MenuErrCode err = menu_param_get_value(param_id, ¤t_val);
|
||||
if (err != MENU_OK)
|
||||
{
|
||||
return err;
|
||||
}
|
||||
|
||||
return menu_param_set_value(param_id, current_val + step);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 减少参数值(用于菜单上下键调整)
|
||||
* @param param_id 参数ID
|
||||
* @param step 步长(浮点型)
|
||||
* @return 错误码
|
||||
*/
|
||||
MenuErrCode menu_param_decrease(uint16_t param_id, float step)
|
||||
{
|
||||
float current_val = 0.0f;
|
||||
MenuErrCode err = menu_param_get_value(param_id, ¤t_val);
|
||||
if (err != MENU_OK)
|
||||
{
|
||||
return err;
|
||||
}
|
||||
|
||||
return menu_param_set_value(param_id, current_val - step);
|
||||
}
|
||||
|
||||
#endif // MENU_CONFIG_ENABLE_PARAM
|
||||
77
src/utils/menu_utils.c
Normal file
77
src/utils/menu_utils.c
Normal file
@ -0,0 +1,77 @@
|
||||
/**
|
||||
* @file menu_utils.c
|
||||
* @brief 菜单工具模块:断言、打印、系统滴答等
|
||||
*/
|
||||
#include "menu_def.h"
|
||||
#include "menu_port.h"
|
||||
#include <stdarg.h>
|
||||
|
||||
/**
|
||||
* @brief 断言失败处理函数
|
||||
* @param file 文件名
|
||||
* @param line 行号
|
||||
*/
|
||||
void menu_utils_assert_failed(const char* file, uint32_t line)
|
||||
{
|
||||
// 调用port层的打印接口输出断言信息
|
||||
menu_utils_printf("[MENU ASSERT] %s:%lu\r\n", file, line);
|
||||
|
||||
// 可以在这里添加其他处理,如触发硬件异常、保存上下文等
|
||||
// 例如:menu_port_trigger_exception();
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 调试打印函数(对接port层的硬件打印接口)
|
||||
* @param fmt 格式化字符串
|
||||
* @param ... 可变参数
|
||||
*/
|
||||
void menu_utils_printf(const char* fmt, ...)
|
||||
{
|
||||
va_list args;
|
||||
va_start(args, fmt);
|
||||
|
||||
// 调用port层的可变参数打印接口
|
||||
// 注意:这里需要用户在port层实现menu_port_printf函数
|
||||
menu_port_printf(fmt, args);
|
||||
|
||||
va_end(args);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 获取系统滴答时间(ms,对接port层)
|
||||
* @return 当前滴答时间
|
||||
*/
|
||||
uint32_t menu_utils_get_tick(void)
|
||||
{
|
||||
// 调用port层的系统滴答获取接口
|
||||
return menu_port_get_tick();
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 计算两个32位无符号整数的差值(处理溢出情况)
|
||||
* @param now 当前时间
|
||||
* @param last 上次时间
|
||||
* @return 差值(ms)
|
||||
*/
|
||||
uint32_t menu_utils_diff_tick(uint32_t now, uint32_t last)
|
||||
{
|
||||
if (now >= last)
|
||||
{
|
||||
return now - last;
|
||||
}
|
||||
else
|
||||
{
|
||||
// 处理溢出情况
|
||||
return (UINT32_MAX - last) + now + 1;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 延迟函数(毫秒级,对接port层)
|
||||
* @param ms 延迟时间(ms)
|
||||
*/
|
||||
void menu_utils_delay_ms(uint32_t ms)
|
||||
{
|
||||
// 调用port层的延迟接口
|
||||
menu_port_delay_ms(ms);
|
||||
}
|
||||
Reference in New Issue
Block a user