diff --git a/README.en.md b/README.en.md index 982b71b098a2e041d0aac28e2bca1d1eefa5f1d4..34ba8e0a6eebca7d9cce856bba855536f9ddb845 100644 --- a/README.en.md +++ b/README.en.md @@ -1,38 +1,21 @@ # 轻量级菜单框架 #### Description -1、采用链表或数组的方式实现多级菜单(通过配置选择); -2、菜单框架更为独立,不耦合按键模块和显示模块,及菜单显示效果更自由; +1、采用链表或数组的方式实现多级菜单(通过配置选择); +2、菜单框架更为独立,不耦合按键模块和显示模块,及菜单显示效果更自由; 3、使用方便 #### Software Architecture Software architecture description -#### Installation - -1. xxxx -2. xxxx -3. xxxx - #### Instructions -1. xxxx -2. xxxx -3. xxxx - -#### Contribution - -1. Fork the repository -2. Create Feat_xxx branch -3. Commit your code -4. Create Pull Request - +1. 使用前初始化函数 Menu_Init, 设置主菜单内容 +2. 周期调用函数 Menu_Task, 用来处理菜单显示和执行相关回调函数 +3. 使用其他函数对菜单界面控制 -#### Gitee Feature +#### Author +1. CSDN 博客 blog.csdn.com +2. 联系邮箱 const_zpc@163.com +3. 欢迎大家评论和维护 -1. You can use Readme\_XXX.md to support different languages, such as Readme\_en.md, Readme\_zh.md -2. Gitee blog [blog.gitee.com](https://blog.gitee.com) -3. Explore open source project [https://gitee.com/explore](https://gitee.com/explore) -4. The most valuable open source project [GVP](https://gitee.com/gvp) -5. The manual of Gitee [https://gitee.com/help](https://gitee.com/help) -6. The most popular members [https://gitee.com/gitee-stars/](https://gitee.com/gitee-stars/) diff --git a/README.md b/README.md index 0fbffd41070186870c266a3317dccddd47fb9d65..b20e0fa4b0722e466bfee1a745fb67a4ffda6df1 100644 --- a/README.md +++ b/README.md @@ -1,39 +1,21 @@ # 轻量级菜单框架 #### 介绍 -1、采用链表或数组的方式实现多级菜单(通过配置选择); -2、菜单框架更为独立,不耦合按键模块和显示模块,及菜单显示效果更自由; +1、采用链表或数组的方式实现多级菜单(通过配置选择); +2、菜单框架更为独立,不耦合按键模块和显示模块,及菜单显示效果更自由; 3、使用方便 #### 软件架构 软件架构说明 - -#### 安装教程 - -1. xxxx -2. xxxx -3. xxxx - #### 使用说明 - -1. xxxx -2. xxxx -3. xxxx - -#### 参与贡献 - -1. Fork 本仓库 -2. 新建 Feat_xxx 分支 -3. 提交代码 -4. 新建 Pull Request +1. 使用前初始化函数 Menu_Init, 设置主菜单内容 +2. 周期调用函数 Menu_Task, 用来处理菜单显示和执行相关回调函数 +3. 使用其他函数对菜单界面控制 -#### 特技 +#### 关于作者 +1. CSDN 博客 blog.csdn.com +2. 联系邮箱 const_zpc@163.com +3. 欢迎大家评论和维护 -1. 使用 Readme\_XXX.md 来支持不同的语言,例如 Readme\_en.md, Readme\_zh.md -2. Gitee 官方博客 [blog.gitee.com](https://blog.gitee.com) -3. 你可以 [https://gitee.com/explore](https://gitee.com/explore) 这个地址来了解 Gitee 上的优秀开源项目 -4. [GVP](https://gitee.com/gvp) 全称是 Gitee 最有价值开源项目,是综合评定出的优秀开源项目 -5. Gitee 官方提供的使用手册 [https://gitee.com/help](https://gitee.com/help) -6. Gitee 封面人物是一档用来展示 Gitee 会员风采的栏目 [https://gitee.com/gitee-stars/](https://gitee.com/gitee-stars/) diff --git a/demo.c b/demo.c new file mode 100644 index 0000000000000000000000000000000000000000..a5f0dd464910ffb9b35f18a96acd5c7eebe56b09 --- /dev/null +++ b/demo.c @@ -0,0 +1,215 @@ + +#include "menu.h" +#include +#include + +// 清除屏幕 +#define CLEAR() printf("\033[2J") +// 定位光标 +#define MOVETO(x,y) printf("\033[%d;%dH", (x), (y)) + + +void OnMusicFunction(void); +void OnVideoFunction(void); +void OnPhotoFunction(void); +void OnCameraFunction(void); +void OnUpgradeFunction(void); +void OnLanguageFunction(void); +void OnAboutMenuFunction(void); +void OnBluetoothFunction(void); +void OnBatteryFunction(void); +void OnStorageFunction(void); +void ShowMainMenu(menusize_t total, menusize_t select, const char *pszDesc[]); +void ShowSetMenu(menusize_t total, menusize_t select, const char *pszDesc[]); + + +/**************************** 三级菜单 *****************************************/ + +/* 更多设置 */ +MenuRegister_t sg_MoreSetMenuTable[] = +{ + {"升级", 0, NULL, NULL, OnUpgradeFunction}, + {"语言", 0, NULL, NULL, OnLanguageFunction}, + {"关于", 0, NULL, NULL, OnAboutMenuFunction}, +}; + +/**************************** 二级菜单 *****************************************/ + +/* 摄像机菜单 */ +MenuRegister_t sg_CameraMenuTable[] = +{ + {"拍照", 0, NULL, NULL, OnPhotoFunction}, + {"摄影", 0, NULL, NULL, OnCameraFunction}, +}; + +/* 设置菜单 */ +MenuRegister_t sg_SetMenuTable[] = +{ + {"蓝牙", 0, NULL, NULL, OnBluetoothFunction}, + {"电池", 0, NULL, NULL, OnBatteryFunction}, + {"储存", 0, NULL, NULL, OnStorageFunction}, + {"更多", GET_MENU_NUM(sg_MoreSetMenuTable), sg_MoreSetMenuTable, ShowSetMenu, NULL}, +}; + +/**************************** 一级菜单 *****************************************/ +/* 主菜单 */ +MenuRegister_t sg_MainMenuTable[] = +{ + {"音乐", 0, NULL, NULL, OnMusicFunction}, + {"视频", 0, NULL, NULL, OnVideoFunction}, + {"摄像机", GET_MENU_NUM(sg_CameraMenuTable), sg_CameraMenuTable, NULL, NULL}, + {"设置", GET_MENU_NUM(sg_SetMenuTable), sg_SetMenuTable, ShowSetMenu, NULL}, +}; + +void OnMusicFunction(void) +{ + printf("--------------------------\n"); + printf(" 音乐功能测试界面\n"); + printf("--------------------------\n"); +} + +void OnVideoFunction(void) +{ + printf("--------------------------\n"); + printf(" 视频功能测试界面\n"); + printf("--------------------------\n"); +} + +void OnPhotoFunction(void) +{ + printf("--------------------------\n"); + printf(" 拍照功能测试界面\n"); + printf("--------------------------\n"); +} + +void OnCameraFunction(void) +{ + printf("--------------------------\n"); + printf(" 摄像功能测试界面\n"); + printf("--------------------------\n"); +} + +void OnUpgradeFunction(void) +{ + printf("--------------------------\n"); + printf(" 升级功能测试界面\n"); + printf("--------------------------\n"); +} + +void OnLanguageFunction(void) +{ + printf("--------------------------\n"); + printf(" 语言功能测试界面\n"); + printf("--------------------------\n"); +} + +void OnAboutMenuFunction(void) +{ + printf("--------------------------\n"); + printf(" 关于菜单框架界面\n"); + printf("--------------------------\n"); +} + +void OnBluetoothFunction(void) +{ + printf("--------------------------\n"); + printf(" 蓝牙功能测试界面\n"); + printf("--------------------------\n"); +} + +void OnBatteryFunction(void) +{ + printf("--------------------------\n"); + printf(" 电池功能测试界面\n"); + printf("--------------------------\n"); +} + +void OnStorageFunction(void) +{ + printf("--------------------------\n"); + printf(" 储存功能测试界面\n"); + printf("--------------------------\n"); +} + +/* 主菜单显示效果 */ +void ShowMainMenu(menusize_t total, menusize_t select, const char *pszDesc[]) +{ + for (int i = 0; i < total; i++) + { + if (i == select) + { + printf("\e[0;30;47m %-10s \e[0m", pszDesc[i]); + } + else + { + printf("\e[7;30;47m %-10s \e[0m", pszDesc[i]); + } + } + + printf("\n"); +} + +/* 设置菜单显示效果 */ +void ShowSetMenu(menusize_t total, menusize_t select, const char *pszDesc[]) +{ + for (int i = 0; i < total; i++) + { + if (i == select) + { + printf("> %-10s\n", pszDesc[i]); + } + else + { + printf(" %-10s\n", pszDesc[i]); + } + } +} + + + +int main(int argc, char **argv) +{ + int ret, cmd = 0; + + Menu_Init(sg_MainMenuTable, GET_MENU_NUM(sg_MainMenuTable), ShowMainMenu); + + while (1) + { + CLEAR(); + MOVETO(0, 0); + Menu_Task(); + + printf("选择操作(0-返回; 1-进入; 2-下一个; 3-上一个; 4-主菜单): "); + scanf(" %d", &cmd); // 空格作用是忽略上次的回车 + + switch (cmd) + { + case 0: + if (Menu_Exit(1) < 0) + { + return 0; + } + break; + + case 1: + Menu_Enter(); + break; + + case 2: + Menu_SelectNext(1); + break; + + case 3: + Menu_SelectPrevious(1); + break; + + case 4: + Menu_ResetMainMenu(); + break; + default: + break; + } + } + + return 0; +} \ No newline at end of file diff --git a/menu.c b/menu.c new file mode 100644 index 0000000000000000000000000000000000000000..4a3765ebed98eb9d55413ab5f658a3aa7aed7ba5 --- /dev/null +++ b/menu.c @@ -0,0 +1,324 @@ +/** + ********************************************************************************************************************** + * @file menu.c + * @brief 该文件提供菜单框架功能 + * @author const_zpc any question please send mail to const_zpc@163.com + * @version V1.0.0 + * @date 2021-11-05 + * + * @details 功能详细说明: + * + 菜单初始化函数 + * + 返回主菜单函数 + * + 菜单控制函数 + * + 菜单轮询任务函数 + * + ********************************************************************************************************************** + * 源码路径:https://gitee.com/const-zpc/menu.git 具体问题及建议可在该网址填写 Issue + * + * 使用方式: + * 1、使用前初始化函数 Menu_Init, 设置主菜单内容 + * 2、周期调用函数 Menu_Task, 用来处理菜单显示和执行相关回调函数 + * 3、使用其他函数对菜单界面控制 + * + ********************************************************************************************************************** + */ + +/* Includes ----------------------------------------------------------------------------------------------------------*/ +#include "menu.h" + +#if MENU_MAX_DEPTH == 0 +#include +#endif + +/* Private typedef ---------------------------------------------------------------------------------------------------*/ +typedef struct MenuCtrl +{ + struct MenuCtrl *pLastMenuCtrl; /*!< 父菜单控制处理 */ + ShowMenuCallFun_f pfnShowMenuFun; /*!< 菜单显示效果函数 */ + MenuRegister_t *pMenuInfo; /*!< 菜单选项内容 */ + menusize_t menuNum; /*!< 菜单选项数目 */ + menusize_t currPos; /*!< 当前选择 */ + uint8_t isRunCallback; /*!< 是否执行回调功能函数 */ +}MenuCtrl_t; + +typedef struct +{ + MenuCtrl_t *pCurrMenuCtrl; /*!< 当前菜单控制处理 */ +}MenuManage_t; + +/* Private define ----------------------------------------------------------------------------------------------------*/ +/* Private macro -----------------------------------------------------------------------------------------------------*/ +/* Private variables -------------------------------------------------------------------------------------------------*/ +static MenuManage_t sg_tMenuManage; + +#if MENU_MAX_DEPTH != 0 +static MenuCtrl_t sg_arrMenuCtrl[MENU_MAX_DEPTH]; +static uint8_t sg_currMenuDepth = 0; +#endif + +/* Private function prototypes ---------------------------------------------------------------------------------------*/ +static MenuCtrl_t *NewMenu(); +static void DeleteMenu(MenuCtrl_t *pMenu); + +/* Private function --------------------------------------------------------------------------------------------------*/ +/** + * @brief 新建菜单层级 + * + * @return MenuCtrl_t* + */ +static MenuCtrl_t *NewMenu() +{ + MenuCtrl_t *pMenuCtrl = NULL; +#if MENU_MAX_DEPTH == 0 + pMenuCtrl = (MenuCtrl_t *)malloc(sizeof(MenuCtrl_t)); +#else + if (sg_currMenuDepth < MENU_MAX_DEPTH) + { + pMenuCtrl = &sg_arrMenuCtrl[sg_currMenuDepth++]; + } +#endif + + return pMenuCtrl; +} + +/** + * @brief 销毁菜单层级 + * + * @param pMenu + */ +static void DeleteMenu(MenuCtrl_t *pMenu) +{ +#if MENU_MAX_DEPTH == 0 + free(pMenu); +#else + if (sg_currMenuDepth > 0) + { + sg_currMenuDepth--; + } +#endif +} + +/** + * @brief 菜单初始化 + * + * @param[in] pMainMenu 主菜单注册信息 + * @param[in] num 主菜单数目 + * @param[in] fpnShowMenu 主菜单显示效果函数 + * @return 0,成功; -1,失败 + */ +int Menu_Init(MenuRegister_t *pMainMenu, uint8_t num, ShowMenuCallFun_f fpnShowMenu) +{ + MenuCtrl_t *pMenuCtrl = NULL; + +#if MENU_MAX_DEPTH != 0 + sg_currMenuDepth = 0; +#endif + + if ((pMenuCtrl = NewMenu()) != NULL) + { + pMenuCtrl->pLastMenuCtrl = NULL; + pMenuCtrl->pfnShowMenuFun = fpnShowMenu; + pMenuCtrl->pMenuInfo = pMainMenu; + pMenuCtrl->menuNum = num; + pMenuCtrl->currPos = 0; + pMenuCtrl->isRunCallback = 0; + + sg_tMenuManage.pCurrMenuCtrl = pMenuCtrl; + + return 0; + } + + return -1; +} + +/** + * @brief 返回主菜单界面 + * + * @return 0,成功; -1,失败 + */ +int Menu_ResetMainMenu(void) +{ + while (Menu_Exit(1) == 0); + + return 0; +} + +/** + * @brief 进入当前菜单选项 + * + * @return 0,成功; -1,失败 + */ +int Menu_Enter(void) +{ + MenuCtrl_t *pMenuCtrl = NULL; + + if (sg_tMenuManage.pCurrMenuCtrl->pMenuInfo[sg_tMenuManage.pCurrMenuCtrl->currPos].subMenuNum != 0) + { + sg_tMenuManage.pCurrMenuCtrl->isRunCallback = 0; + + if ((pMenuCtrl = NewMenu()) != NULL) + { + pMenuCtrl->pLastMenuCtrl = sg_tMenuManage.pCurrMenuCtrl; + pMenuCtrl->pMenuInfo = sg_tMenuManage.pCurrMenuCtrl->pMenuInfo[sg_tMenuManage.pCurrMenuCtrl->currPos].pSubMenu; + pMenuCtrl->menuNum = sg_tMenuManage.pCurrMenuCtrl->pMenuInfo[sg_tMenuManage.pCurrMenuCtrl->currPos].subMenuNum; + + /* 若子菜单没有设置显示风格,则延续上个菜单界面的 */ + if (sg_tMenuManage.pCurrMenuCtrl->pMenuInfo[sg_tMenuManage.pCurrMenuCtrl->currPos].pfnShowMenuFun != NULL) + { + pMenuCtrl->pfnShowMenuFun = sg_tMenuManage.pCurrMenuCtrl->pMenuInfo[sg_tMenuManage.pCurrMenuCtrl->currPos].pfnShowMenuFun; + } + else + { + pMenuCtrl->pfnShowMenuFun = sg_tMenuManage.pCurrMenuCtrl->pfnShowMenuFun; + } + + pMenuCtrl->currPos = 0; + pMenuCtrl->isRunCallback = 0; + + sg_tMenuManage.pCurrMenuCtrl = pMenuCtrl; + return 0; + } + } + else + { + sg_tMenuManage.pCurrMenuCtrl->isRunCallback = 1; + return 0; + } + + return -1; +} + +/** + * @brief 退出当前选项并返回上一层菜单 + * + * @param[in] isReset 菜单选项是否从头选择 + * @return 0,成功; -1,失败, 即主菜单 + */ +int Menu_Exit(uint8_t isReset) +{ + if (sg_tMenuManage.pCurrMenuCtrl->isRunCallback == 1) + { + sg_tMenuManage.pCurrMenuCtrl->isRunCallback = 0; + } + else + { + if (sg_tMenuManage.pCurrMenuCtrl->pLastMenuCtrl != NULL) + { + MenuCtrl_t *pMenuCtrl = sg_tMenuManage.pCurrMenuCtrl; + + sg_tMenuManage.pCurrMenuCtrl = sg_tMenuManage.pCurrMenuCtrl->pLastMenuCtrl; + + if (isReset) + { + sg_tMenuManage.pCurrMenuCtrl->currPos = 0; + } + + DeleteMenu(pMenuCtrl); + } + else + { + return -1; + } + } + + return 0; +} + +/** + * @brief 选择上一个菜单选项 + * + * @param[in] isAllowRoll 第一个选项时是否从跳转到最后一个选项 + * @return 0,成功; -1,失败 + */ +int Menu_SelectPrevious(uint8_t isAllowRoll) +{ + if (sg_tMenuManage.pCurrMenuCtrl->isRunCallback != 0) + { + return -1; + } + + if (sg_tMenuManage.pCurrMenuCtrl->currPos > 0) + { + sg_tMenuManage.pCurrMenuCtrl->currPos--; + } + else + { + if (isAllowRoll) + { + sg_tMenuManage.pCurrMenuCtrl->currPos = sg_tMenuManage.pCurrMenuCtrl->menuNum - 1; + } + else + { + sg_tMenuManage.pCurrMenuCtrl->currPos = 0; + return -1; + } + } + + return 0; +} + +/** + * @brief 选择下一个菜单选项 + * + * @param[in] isAllowRoll 最后一个选项时是否跳转到第一个选项 + * @return 0,成功; -1,失败 + */ +int Menu_SelectNext(uint8_t isAllowRoll) +{ + if (sg_tMenuManage.pCurrMenuCtrl->isRunCallback != 0) + { + return -1; + } + + if (sg_tMenuManage.pCurrMenuCtrl->currPos < (sg_tMenuManage.pCurrMenuCtrl->menuNum - 1)) + { + sg_tMenuManage.pCurrMenuCtrl->currPos++; + } + else + { + if (isAllowRoll) + { + sg_tMenuManage.pCurrMenuCtrl->currPos = 0; + } + else + { + sg_tMenuManage.pCurrMenuCtrl->currPos = sg_tMenuManage.pCurrMenuCtrl->menuNum - 1; + return -1; + } + } + + return 0; +} + +/** + * @brief 菜单任务 + * + */ +int Menu_Task(void) +{ + MenuRegister_t *pMenu = sg_tMenuManage.pCurrMenuCtrl->pMenuInfo; + char *parrszDesc[MENU_MAX_NUM]; + + if (sg_tMenuManage.pCurrMenuCtrl->isRunCallback == 0) + { + for (int i = 0; i < sg_tMenuManage.pCurrMenuCtrl->menuNum && i < MENU_MAX_NUM; i++) + { + parrszDesc[i] = (char *)pMenu[i].pszDesc; + } + + if (sg_tMenuManage.pCurrMenuCtrl->pfnShowMenuFun != NULL) + { + sg_tMenuManage.pCurrMenuCtrl->pfnShowMenuFun(sg_tMenuManage.pCurrMenuCtrl->menuNum, + sg_tMenuManage.pCurrMenuCtrl->currPos, (const char **)parrszDesc); + } + } + else + { + if (pMenu[sg_tMenuManage.pCurrMenuCtrl->currPos].pfnMenuCallFun != NULL) + { + pMenu[sg_tMenuManage.pCurrMenuCtrl->currPos].pfnMenuCallFun(); + } + } + + return 0; +} \ No newline at end of file diff --git a/menu.h b/menu.h new file mode 100644 index 0000000000000000000000000000000000000000..398632a469e5e89cddbee021eaebffde9f39f885 --- /dev/null +++ b/menu.h @@ -0,0 +1,88 @@ +/** + ********************************************************************************************************************** + * @file menu.h + * @brief 该文件提供菜单框架所有函数原型 + * @author const_zpc any question please send mail to const_zpc@163.com + * @version V1.0.0 + * @date 2021-11-05 + ********************************************************************************************************************** + * + ********************************************************************************************************************** + */ + +/* Define to prevent recursive inclusion -----------------------------------------------------------------------------*/ +#ifndef MENU_H +#define MENU_H + + +/* Includes ----------------------------------------------------------------------------------------------------------*/ +#include + +#ifndef NULL +#define NULL 0 +#endif + + +/******************************************* 配置项 ********************************************************************/ + +/* 多级菜单深度, 为0则表示采用 malloc/free 的方式实现多级菜单, 可无限延申, + 否则通过数组的形式实现多级菜单,最多为 MENU_MAX_DEPTH */ +#define MENU_MAX_DEPTH 0 + +/* 菜单支持的最大选项数目 */ +#define MENU_MAX_NUM 20 + +/******************************************* 配置项 ********************************************************************/ + + +/* Exported types ----------------------------------------------------------------------------------------------------*/ + +#if MENU_MAX_NUM < 255 +typedef uint8_t menusize_t; +#else +typedef uint16_t menusize_t; +#endif + + +typedef void (*MenuCallFun_f)(void); + +// total: 菜单项总数目; select: 所选项; pszDesc: 菜单项的字符串描述 +typedef void (*ShowMenuCallFun_f)(menusize_t total, menusize_t select, const char *pszDesc[]); + +/** + * @brief 菜单信息注册结构体 + * + */ +typedef struct MenuRegister +{ + const char *pszDesc; /*!< 当前选项的字符串描述 */ + + menusize_t subMenuNum; /*!< 当前选项的子菜单数目, 子菜单数目为0则表示下一级非菜单界面, 会执行非菜单功能函数 */ + + struct MenuRegister *pSubMenu; /*!< 当前选项的子菜单内容 */ + + ShowMenuCallFun_f pfnShowMenuFun; /*!< 当前选项的子菜单显示效果函数, 为NULL则延续上级菜单显示效果 */ + + MenuCallFun_f pfnMenuCallFun; /*!< 当前选项的非菜单功能函数, 只有当菜单数目为0有效 */ +}MenuRegister_t; + +/* Exported constants ------------------------------------------------------------------------------------------------*/ +/* Exported macro ----------------------------------------------------------------------------------------------------*/ + +#define GET_MENU_NUM(X) (sizeof(X) / sizeof(MenuRegister_t)) + +/* Exported functions ------------------------------------------------------------------------------------------------*/ + +extern int Menu_Init(MenuRegister_t *pMainMenu, uint8_t num, ShowMenuCallFun_f fpnShowMenu); + +extern int Menu_ResetMainMenu(void); + +extern int Menu_Enter(void); +extern int Menu_Exit(uint8_t isReset); + +extern int Menu_SelectPrevious(uint8_t isAllowRoll); +extern int Menu_SelectNext(uint8_t isAllowRoll); + +extern int Menu_Task(void); + +#endif // MENU_H \ No newline at end of file