Files
menu/menu.c
2025-12-19 10:22:28 +08:00

667 lines
19 KiB
C
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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