初始化demo版本

This commit is contained in:
冯佳
2025-12-19 10:22:28 +08:00
parent 1cd79af915
commit 294a49f207
22 changed files with 2101 additions and 0 deletions

45
CMakeLists.txt Normal file
View File

@ -0,0 +1,45 @@
cmake_minimum_required(VERSION 3.10)
project(menu VERSION 1.3.0 LANGUAGES C)
# 设置C标准
set(CMAKE_C_STANDARD 99)
set(CMAKE_C_STANDARD_REQUIRED ON)
# 库源文件
set(MENU_SOURCES
menu.c
)
# 库头文件
set(MENU_HEADERS
menu.h
)
# 创建静态库
add_library(menu STATIC ${MENU_SOURCES})
# 设置库的公共头文件
set_target_properties(menu PROPERTIES
PUBLIC_HEADER "${MENU_HEADERS}"
)
# 包含当前目录
target_include_directories(menu PUBLIC
$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}>
$<INSTALL_INTERFACE:include>
)
# 示例程序
option(BUILD_EXAMPLES "Build examples" ON)
if(BUILD_EXAMPLES)
# 添加示例目录
add_subdirectory(examples)
endif()
# 安装规则
install(TARGETS menu
ARCHIVE DESTINATION lib
PUBLIC_HEADER DESTINATION include
)

214
README.md Normal file
View File

@ -0,0 +1,214 @@
# 轻量级菜单框架(C语言)
## 介绍
### 多级菜单
同级菜单以数组的方式体现,父菜单和子菜单的关联则使用链表实现。
> 数组元素内容有:
>
> * 菜单选项描述方式可选字符串或文本ID多国语言时可以统一管理翻译文件而文本ID可以适用于数组管理的翻译文件
> * 菜单选项进入回调函数:当前菜单选项进入时(从父菜单进入)需要执行一次的函数
> * 菜单选项退出回调函数:当前菜单选项进入后退出时(退出至父菜单)需要执行一次的函数
> * 菜单选项重加载回调函数:当前菜单选项每次加载时(从父菜单进入或子菜单退出)需要执行一次的函数
> * 菜单选项周期调度回调函数:当前菜单选项的周期调度函数
> * 菜单选项的扩展数据
>
> 链表内存可以选择采用动态内存分配或者数组实现
方便对不同菜单界面功能解耦
> 大部分菜单采用的都是数组中包含了所有不同级别的菜单选项内容实现,无法做到很好的解耦方式;
>
> 该模块通过动态绑定子菜单和链表的方式可以达到较好的解耦状态
### 显示效果
该框架只负责菜单选项控制操作,不负责在图像界面显示效果,需要在对应的回调函数中实现菜单显示效果。
> 设置对应的效果显示函数,即可为不同的菜单设置不同的菜单显示效果,比如图标式、列表式或右侧弹窗式等。
>
> 可以在不同显示平台体现比如LCD、OLED或终端界面等。
### 多语种
没有直接的语种设置,而是通过联合体提供选择多种选择。
> 文本ID: 适用于多语种采用数组定义的翻译内容
> 字符串: 适用于单语种或者多语种,采用映射的方式实现的翻译内容
### 可扩展
每级菜单选项都可以设置自定义数据,用来实现更多的菜单操作或者显示效果等。
> 不同级别的菜单可以设置自定义数据(比如菜单选项隐藏/图标数据等)
### 可配置
| 配置选项 | 描述 |
| --------------------------- | ------------------------------------------------------------- |
| `_COT_MENU_USE_MALLOC_` | 定义则采用 malloc/free 的方式实现多级菜单, 否则通过数组的形式 |
| `_COT_MENU_USE_SHORTCUT_` | 定义则启用快捷菜单选项进入功能 |
| `COT_MENU_MAX_DEPTH` | 多级菜单深度 |
| `COT_MENU_MAX_NUM` | 菜单支持的最大选项数目 |
### 功能多样化
* [X] 支持快速进入指定菜单界面。
> - 可以通过相对选项索引或者绝对选项索引路径实现
>
* [X] 可以实现有限界面内显示少量的菜单选项内容。
> - 有现成的函数可用,无需担心使用不同尺寸重新实现菜单选项部分可见
>
## 软件设计
## 使用说明
### 菜单初始化和使用
```c
// 定义菜单信息,函数由主菜单模块定义并提供
static cotMainMenuCfg_t sg_tMainMenu = {"主菜单", Hmi_EnterMainHmi, NULL, NULL, NULL};
int main(void)
{
cotMenu_Init(&sg_tMainMenu);
while (1)
{
...
if (timeFlag)
{
timeFlag = 0;
cotMenu_Task(); // 周期调度
}
}
}
```
### 主菜单定义和绑定
定义一个主菜单选项内容、主菜单显示效果函数和主菜单进入函数等
```c
// 扩展数据为图标文件名字每个菜单选项描述为文本ID
cotMenuList_t sg_MainMenuTable[] =
{
COT_MENU_ITEM_BIND(TEXT_MUSIC, Hmi_MusicEnter, Hmi_MusicExit, Hmi_MusicLoad, Hmi_MusicTask, (MenuImage_t *)&sgc_MusicImage),
COT_MENU_ITEM_BIND(TEXT_VIDEO, NULL, Hmi_VideoExit, Hmi_VideoLoad, Hmi_VideoTask, (MenuImage_t *)&sgc_VideoImage),
COT_MENU_ITEM_BIND(TEXT_CAMERA, Hmi_CameraEnter, Hmi_CameraExit, Hmi_CameraLoad, Hmi_CameraTask, (MenuImage_t *)&sgc_CameraImage),
COT_MENU_ITEM_BIND(TEXT_SETTING, Hmi_SetEnter, Hmi_SetExit, Hmi_SetLoad, Hmi_SetTask, (MenuImage_t *)&sgc_SettingImage),
};
/* 主菜单显示效果 */
static void ShowMainMenu(cotMenuShow_t *ptShowInfo)
{
char *pszSelectDesc = get_text(ptShowInfo->pszItemsDesc[ptShowInfo->selectItem].textId); // 根据文本ID找到对应的字符串多语言
oledsize_t idx = (128 - 6 * strlen(pszSelectDesc)) / 2;
cotOled_DrawGraphic(40, 0, (const char *)ptShowInfo->pItemsListExtendData[ptShowInfo->selectItem], 1);
cotOled_SetText(0, 50, " ", 0, FONT_12X12);
cotOled_SetText(idx, 50, pszSelectDesc, 0, FONT_12X12);
}
void Hmi_EnterMainHmi(const cotMenuItemInfo_t *pItemInfo)
{
cotMenu_Bind(sg_MainMenuTable, COT_GET_MENU_NUM(sg_MainMenuTable), ShowMainMenu);
}
```
### 子菜单定义和绑定
如果菜单选项有子菜单,则该菜单选项调用 `cotMenu_Enter`,进入回调函数**不能为NULL**,且该回调函数需调用 `cotMenu_Bind`进行绑定
```c
/* 设置的子菜单内容,每个菜单选项描述为字符串 */
cotMenuList_t sg_SetMenuTable[] =
{
COT_MENU_ITEM_BIND("语言", NULL, NULL, NULL, OnLanguageFunction, NULL),
COT_MENU_ITEM_BIND("蓝牙", NULL, NULL, NULL, OnBluetoothFunction, NULL),
COT_MENU_ITEM_BIND("电池", NULL, NULL, NULL, OnBatteryFunction, NULL),
COT_MENU_ITEM_BIND("储存", NULL, NULL, NULL, OnStorageFunction, NULL),
COT_MENU_ITEM_BIND("更多", Hmi_MoreSetEnter, Hmi_MoreSetExit, Hmi_MoreSetLoad, Hmi_MoreSetTask, NULL),
};
/* 设置菜单显示效果 */
static void ShowSetMenu(cotMenuShow_t *ptShowInfo)
{
uint8_t showNum = 3;
menusize_t tmpselect;
cotMenu_LimitShowListNum(ptShowInfo, &showNum);
// 这里直接使用字符串
printf("\e[0;30;46m ------------- %s ------------- \e[0m\n", tParentMenuShowInfo.uMenuDesc.pTextString);
for (int i = 0; i < showNum; i++)
{
tmpselect = i + ptShowInfo->showBaseItem;
if (tmpselect == ptShowInfo->selectItem)
{
printf("\e[0;30;47m %d. %-16s\e[0m |\n", tmpselect + 1, ptShowInfo->pszItemsDesc[tmpselect].pTextString);
}
else
{
printf("\e[7;30;47m %d. %-16s\e[0m |\n", tmpselect + 1, ptShowInfo->pszItemsDesc[tmpselect].pTextString);
}
}
}
void Hmi_SetEnter(const cotMenuItemInfo_t *pItemInfo)
{
// 进入设置选项后绑定子菜单,同时为当前绑定的菜单设置显示效果函数
cotMenu_Bind(sg_SetMenuTable, COT_GET_MENU_NUM(sg_SetMenuTable), ShowSetMenu);
}
```
### 菜单控制
通过调用相关函数实现菜单选项选择、进入、退出等
```c
// 需要先进入主菜单
cotMenu_MainEnter();
// 选择上一个,支持循环选择(即第一个可跳转到最后一个)
cotMenu_SelectPrevious(true);
// 选择下一个,不支持循环选择(即最后一个不可跳转到第一个)
cotMenu_SelectNext(false);
// 进入,会执行菜单选项的 pfnEnterCallFun 回调函数
cotMenu_Enter();
// 退出,会执行父菜单该选项的 pfnExitCallFun 回调函数,并在退出后父菜单选项列表复位从头选择
cotMenu_Exit(true);
```
定义了多个菜单选项表后,用来区分上下级,在某个菜单选项进入时绑定
1. 使用前初始化函数 cotMenu_Init, 设置主菜单内容
2. 周期调用函数 cotMenu_Task, 用来处理菜单显示和执行相关回调函数
3. 使用其他函数对菜单界面控制

33
examples/CMakeLists.txt Normal file
View File

@ -0,0 +1,33 @@
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
)

65
examples/demo.c Normal file
View File

@ -0,0 +1,65 @@
/* 菜单显示效果图可看:
https://blog.csdn.net/qq_24130227/article/details/121167276
*/
#include "menu.h"
#include "mainhmi.h"
#include "language.h"
#include <stdio.h>
#include <string.h>
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;
}

View File

@ -0,0 +1,13 @@
#ifndef HMI_CAMERA_H
#define HMI_CAMERA_H
#include "hmi_common.h"
extern const MenuImage_t sgc_CameraImage;
void Hmi_CameraEnter(const MenuItemInfo_t *pItemInfo);
void Hmi_CameraExit(const MenuItemInfo_t *pItemInfo);
void Hmi_CameraLoad(const MenuItemInfo_t *pItemInfo);
void Hmi_CameraTask(const MenuItemInfo_t *pItemInfo);
#endif

View File

@ -0,0 +1,20 @@
#ifndef HMI_COMMON_H
#define HMI_COMMON_H
#include "menu.h"
/* 自定义图标数据 */
typedef struct
{
const char *pImageFrame;
const char *pImage;
} MenuImage_t;
// 清除屏幕
#define CLEAR() printf("\033[2J")
// 定位光标
#define MOVETO(x,y) printf("\033[%d;%dH", (x), (y))
void Hmi_OnCommonFunction(const MenuItemInfo_t *pItemInfo);
#endif

View File

@ -0,0 +1,12 @@
#ifndef HMI_MORE_SET_H
#define HMI_MORE_SET_H
#include "hmi_common.h"
void Hmi_MoreSetEnter(const MenuItemInfo_t *pItemInfo);
void Hmi_MoreSetExit(const MenuItemInfo_t *pItemInfo);
void Hmi_MoreSetLoad(const MenuItemInfo_t *pItemInfo);
void Hmi_MoreSetTask(const MenuItemInfo_t *pItemInfo);
#endif

View File

@ -0,0 +1,14 @@
#ifndef HMI_MUSIC_H
#define HMI_MUSIC_H
#include "hmi_common.h"
extern const MenuImage_t sgc_MusicImage;
void Hmi_MusicEnter(const MenuItemInfo_t *pItemInfo);
void Hmi_MusicExit(const MenuItemInfo_t *pItemInfo);
void Hmi_MusicLoad(const MenuItemInfo_t *pItemInfo);
void Hmi_MusicTask(const MenuItemInfo_t *pItemInfo);
#endif

View File

@ -0,0 +1,13 @@
#ifndef HMI_SET_H
#define HMI_SET_H
#include "hmi_common.h"
extern const MenuImage_t sgc_SettingImage;
void Hmi_SetEnter(const MenuItemInfo_t *pItemInfo);
void Hmi_SetExit(const MenuItemInfo_t *pItemInfo);
void Hmi_SetLoad(const MenuItemInfo_t *pItemInfo);
void Hmi_SetTask(const MenuItemInfo_t *pItemInfo);
#endif

View File

@ -0,0 +1,12 @@
#ifndef HMI_VIDEO_H
#define HMI_VIDEO_H
#include "hmi_common.h"
extern const MenuImage_t sgc_VideoImage;
void Hmi_VideoLoad(const MenuItemInfo_t *pItemInfo);
void Hmi_VideoExit(const MenuItemInfo_t *pItemInfo);
void Hmi_VideoTask(const MenuItemInfo_t *pItemInfo);
#endif

View File

@ -0,0 +1,9 @@
#ifndef MAIN_HMI_H
#define MAIN_HMI_H
#include "hmi_common.h"
void Hmi_LoadMainHmi(const MenuItemInfo_t *pItemInfo);
void Hmi_MainTask(const MenuItemInfo_t *pItemInfo);
#endif

View File

@ -0,0 +1,103 @@
#include "hmi_camera.h"
#include "menu.h"
#include "language.h"
#include <stdio.h>
#include <string.h>
#ifdef _WIN32
#include <windows.h>
#else
#include <unistd.h>
#endif
const MenuImage_t sgc_CameraImage = {
"**********",
"&"
};
MenuList_t sg_CameraMenuTable[] =
{
MENU_ITEM_BIND("TEXT_PHOTO", NULL, NULL, NULL, Hmi_OnCommonFunction, NULL),
MENU_ITEM_BIND(TEXT_PHOTOGRAPHY, NULL, NULL, NULL, Hmi_OnCommonFunction, NULL),
};
static void ShowCameraMenu(MenuShow_t *ptShowInfo)
{
uint8_t showNum = 3;
menusize_t tmpselect;
Menu_LimitShowListNum(ptShowInfo, &showNum);
printf("\e[0;30;46m ------------- %s ------------- \e[0m\n", get_text(ptShowInfo->uMenuDesc.textId));
for (int i = 0; i < showNum; i++)
{
tmpselect = i + ptShowInfo->showBaseItem;
if (tmpselect == ptShowInfo->selectItem)
{
printf("\e[0;30;47m %d. %-34s\e[0m\n", tmpselect + 1, get_text(ptShowInfo->uItemsListDesc[tmpselect].textId));
}
else
{
printf("\e[7;30;47m %d. %-34s\e[0m\n", tmpselect + 1, get_text(ptShowInfo->uItemsListDesc[tmpselect].textId));
}
}
}
void Hmi_CameraEnter(const MenuItemInfo_t *pItemInfo)
{
Menu_Bind(sg_CameraMenuTable, MENU_GET_NUM(sg_CameraMenuTable), ShowCameraMenu);
}
void Hmi_CameraLoad(const MenuItemInfo_t *pItemInfo)
{
CLEAR();
MOVETO(0, 0);
printf("---%s-----\n", get_text(TEXT_LOADING));
}
void Hmi_CameraExit(const MenuItemInfo_t *pItemInfo)
{
printf("--------------------------\n");
printf(" %s%s...\n", get_text(TEXT_VIDEO), get_text(TEXT_STOPPING_PLAYBACK));
printf("--------------------------\n");
#ifdef _WIN32
Sleep(1000);
#else
sleep(1);
#endif
}
void Hmi_CameraTask(const MenuItemInfo_t *pItemInfo)
{
int cmd;
printf("%s(0-%s; 1-%s%s; 2-%s; 3-%s; 4-%s): ",
get_text(TEXT_SELECT_OPTION), get_text(TEXT_RETURN), get_text(TEXT_RETURN), get_text(TEXT_MAIN_MENU),
get_text(TEXT_ENTER), get_text(TEXT_NEXT), get_text(TEXT_PREVIOUS));
scanf(" %d", &cmd);
switch (cmd)
{
case 0:
Menu_Exit(true);
break;
case 1:
Menu_Reset();
break;
case 2:
Menu_Enter();
break;
case 3:
Menu_SelectNext(true);
break;
case 4:
Menu_SelectPrevious(true);
break;
default:
break;
}
}

View File

@ -0,0 +1,28 @@
#include "hmi_common.h"
#include "language.h"
#include <stdio.h>
#include <string.h>
#ifdef _WIN32
#include <windows.h>
#else
#include <unistd.h>
#endif
void Hmi_OnCommonFunction(const MenuItemInfo_t *pItemInfo)
{
int cmd;
printf("--------------------------\n");
printf(" %s%s\n", get_text(pItemInfo->uMenuDesc.textId), get_text(TEXT_FUNCTION_TEST));
printf("--------------------------\n");
printf("%s(0-%s): ",
get_text(TEXT_SELECT_OPTION), get_text(TEXT_EXIT));
scanf(" %d", &cmd); // 空格作用是忽略上次的回车
if (cmd == 0)
{
Menu_Exit(false);
}
}

View File

@ -0,0 +1,110 @@
#include "hmi_more_set.h"
#include "menu.h"
#include "language.h"
#include <stdio.h>
#include <string.h>
/* <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> */
MenuList_t sg_MoreSetMenuTable[] =
{
MENU_ITEM_BIND(TEXT_UPGRADE, NULL, NULL, NULL, Hmi_OnCommonFunction, NULL),
MENU_ITEM_BIND(TEXT_ABOUT, NULL, NULL, NULL, Hmi_OnCommonFunction, NULL),
};
static void ShowMoreSetMenu(MenuShow_t *ptShowInfo)
{
uint8_t showNum = 3;
uint8_t showsubNum = 3;
menusize_t tmpselect;
MenuShow_t tParentMenuShowInfo;
if (Menu_QueryParentMenu(&tParentMenuShowInfo, 1) != 0)
{
return;
}
Menu_LimitShowListNum(&tParentMenuShowInfo, &showNum);
Menu_LimitShowListNum(ptShowInfo, &showsubNum);
printf("\e[0;30;46m ------------- %s ------------- \e[0m\n", get_text(tParentMenuShowInfo.uMenuDesc.textId));
for (int i = 0; i < showNum; i++)
{
tmpselect = i + tParentMenuShowInfo.showBaseItem;
if (tmpselect == tParentMenuShowInfo.selectItem)
{
printf("\e[0;30;47m %d. %-16s\e[0m |", tmpselect + 1, get_text(tParentMenuShowInfo.uItemsListDesc[tmpselect].textId));
}
else
{
printf("\e[7;30;47m %d. %-16s\e[0m |", tmpselect + 1, get_text(tParentMenuShowInfo.uItemsListDesc[tmpselect].textId));
}
if (i < showsubNum)
{
tmpselect = i + ptShowInfo->showBaseItem;
if (tmpselect == ptShowInfo->selectItem)
{
printf(" \e[0;30;47m %-14s\e[0m", get_text(ptShowInfo->uItemsListDesc[tmpselect].textId));
}
else
{
printf(" \e[7;30;47m %-14s\e[0m", get_text(ptShowInfo->uItemsListDesc[tmpselect].textId));
}
}
printf("\n");
}
}
void Hmi_MoreSetEnter(const MenuItemInfo_t *pItemInfo)
{
Menu_Bind(sg_MoreSetMenuTable, MENU_GET_NUM(sg_MoreSetMenuTable), ShowMoreSetMenu);
}
void Hmi_MoreSetLoad(const MenuItemInfo_t *pItemInfo)
{
}
void Hmi_MoreSetExit(const MenuItemInfo_t *pItemInfo)
{
}
void Hmi_MoreSetTask(const MenuItemInfo_t *pItemInfo)
{
int cmd;
printf("%s(0-%s; 1-%s%s; 2-%s; 3-%s; 4-%s): ",
get_text(TEXT_SELECT_OPTION), get_text(TEXT_RETURN), get_text(TEXT_RETURN), get_text(TEXT_MAIN_MENU),
get_text(TEXT_ENTER), get_text(TEXT_NEXT), get_text(TEXT_PREVIOUS));
scanf(" %d", &cmd);
switch (cmd)
{
case 0:
Menu_Exit(false);
break;
case 1:
Menu_Reset();
break;
case 2:
Menu_Enter();
break;
case 3:
Menu_SelectNext(true);
break;
case 4:
Menu_SelectPrevious(true);
break;
default:
break;
}
}

View File

@ -0,0 +1,58 @@
#include "hmi_music.h"
#include "menu.h"
#include "language.h"
#include <stdio.h>
#include <string.h>
#ifdef _WIN32
#include <windows.h>
#else
#include <unistd.h>
#endif
static bool sg_isInit = false;
const MenuImage_t sgc_MusicImage = {
"mmmmmmmmmm",
"@"
};
void Hmi_MusicEnter(const MenuItemInfo_t *pItemInfo)
{
}
void Hmi_MusicExit(const MenuItemInfo_t *pItemInfo)
{
}
void Hmi_MusicLoad(const MenuItemInfo_t *pItemInfo)
{
sg_isInit = true;
}
void Hmi_MusicTask(const MenuItemInfo_t *pItemInfo)
{
int cmd = 0;
if (sg_isInit)
{
sg_isInit = false;
printf("--------------------------\n");
printf(" %s%s\n", get_text(pItemInfo->uMenuDesc.textId), get_text(TEXT_FUNCTION_TEST));
printf("--------------------------\n");
#ifdef _WIN32
Sleep(1000);
#else
sleep(1);
#endif
}
printf("%s(0-%s): ",
get_text(TEXT_SELECT_OPTION), get_text(TEXT_EXIT));
scanf(" %d", &cmd);
if (cmd == 0)
{
Menu_Exit(false);
}
}

176
examples/hmi/src/hmi_set.c Normal file
View File

@ -0,0 +1,176 @@
#include "hmi_set.h"
#include "hmi_more_set.h"
#include "menu.h"
#include "language.h"
#include <stdio.h>
#include <string.h>
const MenuImage_t sgc_SettingImage = {
"$$$$$$$$$$",
"%"
};
static void OnLanguageFunction(const MenuItemInfo_t *pItemInfo);
static void OnBluetoothFunction(const MenuItemInfo_t *pItemInfo);
static void OnBatteryFunction(const MenuItemInfo_t *pItemInfo);
static void OnStorageFunction(const MenuItemInfo_t *pItemInfo);
MenuList_t sg_SetMenuTable[] =
{
MENU_ITEM_BIND(TEXT_LANGUAGE, NULL, NULL, NULL, OnLanguageFunction, NULL),
MENU_ITEM_BIND(TEXT_BLUETOOTH, NULL, NULL, NULL, OnBluetoothFunction, NULL),
MENU_ITEM_BIND(TEXT_BATTERY, NULL, NULL, NULL, OnBatteryFunction, NULL),
MENU_ITEM_BIND(TEXT_STORE, NULL, NULL, NULL, OnStorageFunction, NULL),
MENU_ITEM_BIND(TEXT_MORE, Hmi_MoreSetEnter, Hmi_MoreSetExit, Hmi_MoreSetLoad, Hmi_MoreSetTask, NULL),
};
static void ShowSetMenu(MenuShow_t *ptShowInfo)
{
uint8_t showNum = 3;
menusize_t tmpselect;
Menu_LimitShowListNum(ptShowInfo, &showNum);
printf("\e[0;30;46m ------------- %s ------------- \e[0m\n", get_text(ptShowInfo->uMenuDesc.textId));
for (int i = 0; i < showNum; i++)
{
tmpselect = i + ptShowInfo->showBaseItem;
if (tmpselect == ptShowInfo->selectItem)
{
printf("\e[0;30;47m %d. %-16s\e[0m |\n", tmpselect + 1, get_text(ptShowInfo->uItemsListDesc[tmpselect].textId));
}
else
{
printf("\e[7;30;47m %d. %-16s\e[0m |\n", tmpselect + 1, get_text(ptShowInfo->uItemsListDesc[tmpselect].textId));
}
}
}
void Hmi_SetEnter(const MenuItemInfo_t *pItemInfo)
{
Menu_Bind(sg_SetMenuTable, MENU_GET_NUM(sg_SetMenuTable), ShowSetMenu);
}
void Hmi_SetLoad(const MenuItemInfo_t *pItemInfo)
{
}
void Hmi_SetExit(const MenuItemInfo_t *pItemInfo)
{
}
void Hmi_SetTask(const MenuItemInfo_t *pItemInfo)
{
int cmd;
printf("%s(0-%s; 1-%s; 2-%s; 3-%s; 4-%s): ",
get_text(TEXT_SELECT_OPTION), get_text(TEXT_RETURN), get_text(TEXT_RETURN_MAIN_MENU),
get_text(TEXT_ENTER), get_text(TEXT_NEXT), get_text(TEXT_PREVIOUS));
scanf(" %d", &cmd);
switch (cmd)
{
case 0:
Menu_Exit(false);
break;
case 1:
Menu_Reset();
break;
case 2:
Menu_Enter();
break;
case 3:
Menu_SelectNext(true);
break;
case 4:
Menu_SelectPrevious(true);
break;
default:
break;
}
}
static void OnLanguageFunction(const MenuItemInfo_t *pItemInfo)
{
int cmd;
printf("--------------------------\n");
printf(" %s%s\n", get_text(TEXT_LANGUAGE), get_text(TEXT_FUNCTION_TEST));
printf("--------------------------\n");
printf("%s(0-中文; 1-English): ",
get_text(TEXT_SELECT_OPTION));
scanf(" %d", &cmd);
if (cmd == 0)
{
set_language(SYSTEM_LANGUAGE_CHINESE);
}
else
{
set_language(SYSTEM_LANGUAGE_ENGLISH);
}
Menu_Exit(false);
}
static void OnBluetoothFunction(const MenuItemInfo_t *pItemInfo)
{
int cmd;
printf("--------------------------\n");
printf(" %s%s\n", get_text(TEXT_BLUETOOTH), get_text(TEXT_FUNCTION_TEST));
printf("--------------------------\n");
printf("%s(0-%s): ",
get_text(TEXT_SELECT_OPTION), get_text(TEXT_EXIT));
scanf(" %d", &cmd);
if (cmd == 0)
{
Menu_Exit(false);
}
}
static void OnBatteryFunction(const MenuItemInfo_t *pItemInfo)
{
int cmd;
printf("--------------------------\n");
printf(" %s%s\n", get_text(TEXT_BATTERY), get_text(TEXT_FUNCTION_TEST));
printf("--------------------------\n");
printf("%s(0-%s): ",
get_text(TEXT_SELECT_OPTION), get_text(TEXT_EXIT));
scanf(" %d", &cmd);
if (cmd == 0)
{
Menu_Exit(false);
}
}
static void OnStorageFunction(const MenuItemInfo_t *pItemInfo)
{
int cmd;
printf("--------------------------\n");
printf(" %s%s\n", get_text(TEXT_STORE), get_text(TEXT_FUNCTION_TEST));
printf("--------------------------\n");
printf("%s(0-%s): ",
get_text(TEXT_SELECT_OPTION), get_text(TEXT_EXIT));
scanf(" %d", &cmd);
if (cmd == 0)
{
Menu_Exit(false);
}
}

View File

@ -0,0 +1,67 @@
#include "hmi_video.h"
#include "menu.h"
#include "language.h"
#include <stdio.h>
#include <string.h>
#ifdef _WIN32
#include <windows.h>
#else
#include <unistd.h>
#endif
const MenuImage_t sgc_VideoImage = {
"vvvvvvvvvv",
"#"
};
void Hmi_VideoLoad(const MenuItemInfo_t *pItemInfo)
{
printf("--------------------------\n");
printf(" %s %s...\n", get_text(pItemInfo->uMenuDesc.textId), get_text(TEXT_LOADING));
printf("--------------------------\n");
#ifdef _WIN32
Sleep(1000);
#else
sleep(1);
#endif
CLEAR();
MOVETO(0, 0);
}
void Hmi_VideoExit(const MenuItemInfo_t *pItemInfo)
{
CLEAR();
MOVETO(0, 0);
printf("--------------------------\n");
printf(" %s %s...\n", get_text(pItemInfo->uMenuDesc.textId), get_text(TEXT_EXIT));
printf("--------------------------\n");
#ifdef _WIN32
Sleep(1000);
#else
sleep(1);
#endif
}
void Hmi_VideoTask(const MenuItemInfo_t *pItemInfo)
{
int cmd;
printf("--------------------------\n");
printf(" %s%s\n", get_text(pItemInfo->uMenuDesc.textId), get_text(TEXT_FUNCTION_TEST));
printf("--------------------------\n");
printf("%s(0-%s): ",
get_text(TEXT_SELECT_OPTION), get_text(TEXT_EXIT));
scanf(" %d", &cmd);
if (cmd == 0)
{
Menu_Exit(false);
}
}

140
examples/hmi/src/mainhmi.c Normal file
View File

@ -0,0 +1,140 @@
#include "mainhmi.h"
#include "hmi_camera.h"
#include "hmi_music.h"
#include "hmi_set.h"
#include "hmi_video.h"
#include "hmi_common.h"
#include "menu.h"
#include "language.h"
#include <stdio.h>
#include <string.h>
MenuList_t sg_MainMenuTable[] =
{
MENU_ITEM_BIND(TEXT_MUSIC, Hmi_MusicEnter, Hmi_MusicExit, Hmi_MusicLoad, Hmi_MusicTask, (MenuImage_t *)&sgc_MusicImage),
MENU_ITEM_BIND(TEXT_VIDEO, NULL, Hmi_VideoExit, Hmi_VideoLoad, Hmi_VideoTask, (MenuImage_t *)&sgc_VideoImage),
MENU_ITEM_BIND(TEXT_CAMERA, Hmi_CameraEnter, Hmi_CameraExit, Hmi_CameraLoad, Hmi_CameraTask, (MenuImage_t *)&sgc_CameraImage),
MENU_ITEM_BIND(TEXT_SETTING, Hmi_SetEnter, Hmi_SetExit, Hmi_SetLoad, Hmi_SetTask, (MenuImage_t *)&sgc_SettingImage),
};
static void ShowMainMenu(MenuShow_t *ptShowInfo)
{
uint8_t showNum = 3;
MenuImage_t *pMenuImage;
menusize_t tmpselect;
Menu_LimitShowListNum(ptShowInfo, &showNum);
printf("\e[0;30;47m ------------- %s ------------- \e[0m\n", get_text(ptShowInfo->uMenuDesc.textId));
for (int i = 0; i < showNum; i++)
{
tmpselect = i + ptShowInfo->showBaseItem;
pMenuImage = (MenuImage_t *)ptShowInfo->pItemsListExtendData[tmpselect];
if (tmpselect == ptShowInfo->selectItem)
{
printf("\e[0;30;47m %-10s \e[0m", pMenuImage->pImageFrame);
}
else
{
printf("\e[7;30;47m %-10s \e[0m", pMenuImage->pImageFrame);
}
}
printf("\n");
for (int i = 0; i < showNum; i++)
{
tmpselect = i + ptShowInfo->showBaseItem;
pMenuImage = (MenuImage_t *)ptShowInfo->pItemsListExtendData[tmpselect];
if (tmpselect == ptShowInfo->selectItem)
{
printf("\e[0;30;47m %-s%-8s%-s \e[0m", pMenuImage->pImage, get_text(ptShowInfo->uItemsListDesc[tmpselect].textId), pMenuImage->pImage);
}
else
{
printf("\e[7;30;47m %-s%-8s%-s \e[0m", pMenuImage->pImage, get_text(ptShowInfo->uItemsListDesc[tmpselect].textId), pMenuImage->pImage);
}
}
printf("\n");
for (int i = 0; i < showNum; i++)
{
tmpselect = i + ptShowInfo->showBaseItem;
pMenuImage = (MenuImage_t *)ptShowInfo->pItemsListExtendData[tmpselect];
if (tmpselect == ptShowInfo->selectItem)
{
printf("\e[0;30;47m %-10s \e[0m", pMenuImage->pImageFrame);
}
else
{
printf("\e[7;30;47m %-10s \e[0m", pMenuImage->pImageFrame);
}
}
printf("\n");
}
void Hmi_LoadMainHmi(const MenuItemInfo_t *pItemInfo)
{
Menu_Bind(sg_MainMenuTable, MENU_GET_NUM(sg_MainMenuTable), ShowMainMenu);
}
void Hmi_MainTask(const MenuItemInfo_t *pItemInfo)
{
int cmd;
printf("%s(0-%s; 2-%s; 3-%s; 4-%s; 5-%s(%s); 6-%s(%s); 7-%s(%s)): ",
get_text(TEXT_SELECT_OPTION), get_text(TEXT_EXIT_MAIN_MENU),
get_text(TEXT_ENTER), get_text(TEXT_NEXT), get_text(TEXT_PREVIOUS),
get_text(TEXT_MUSIC), get_text(TEXT_SHORTCUT),
get_text(TEXT_LANGUAGE), get_text(TEXT_SHORTCUT),
get_text(TEXT_MORE_SETTING), get_text(TEXT_SHORTCUT));
scanf(" %d", &cmd);
switch (cmd)
{
case 0:
Menu_MainExit();
break;
case 1:
Menu_Reset();
break;
case 2:
Menu_Enter();
break;
case 3:
Menu_SelectNext(true);
break;
case 4:
Menu_SelectPrevious(true);
break;
case 5:
Menu_ShortcutEnter(true, 1, 0);
break;
case 6:
Menu_ShortcutEnter(true, 2, 3, 0);
break;
case 7:
Menu_ShortcutEnter(true, 2, 3, 4);
break;
default:
break;
}
}

View File

@ -0,0 +1,70 @@
#include "language.h"
static SystemLanguage_e sg_eSystemLanguage = SYSTEM_LANGUAGE_CHINESE;
const char *(sg_kSystemLanguage[TEXT_ALL])[SYSTEM_LANGUAGE_ALL] =
{
[TEXT_MENU] = {"菜单", "menu"},
[TEXT_MAIN_MENU] = {"主菜单", "main menu"},
[TEXT_SELECT_OPTION] = {"选择操作", "select option"},
[TEXT_ENTER] = {"进入", "enter"},
[TEXT_EXIT] = {"退出", "exit"},
[TEXT_RETURN] = {"返回", "return"},
[TEXT_MUSIC] = {"音乐", "music"},
[TEXT_VIDEO] = {"视频", "video"},
[TEXT_CAMERA] = {"摄像机", "camera"},
[TEXT_SETTING] = {"设置", "setting"},
[TEXT_SHORTCUT] = {"快捷", "shortcut"},
[TEXT_MORE] = {"更多", "more"},
[TEXT_LANGUAGE] = {"语言", "language"},
[TEXT_NEXT] = {"下一个", "next"},
[TEXT_PREVIOUS] = {"上一个", "previous"},
[TEXT_FUNCTION_TEST] = {"功能测试", "functional testing"},
[TEXT_TEST] = {"测试", "test"},
[TEXT_BLUETOOTH] = {"蓝牙", "bluetooth"},
[TEXT_BATTERY] = {"电池", "battery"},
[TEXT_STORE] = {"储存", "store"},
[TEXT_UPGRADE] = {"升级", "upgrade"},
[TEXT_ABOUT] = {"关于", "about"},
[TEXT_PHOTO] = {"拍照", "Photo"},
[TEXT_PHOTOGRAPHY] = {"摄影", "photography"},
[TEXT_LOADING] = {"加载中", "loading"},
[TEXT_STOPPING_PLAYBACK] = {"正在停止播放", "stopping playback"},
[TEXT_EXIT_MAIN_MENU] = {"退出主菜单", "exit main menu"},
[TEXT_RETURN_MAIN_MENU] = {"返回主菜单", "return main menu"},
[TEXT_MORE_SETTING] = {"更多设置", "more setting"},
};
void set_language(SystemLanguage_e lang)
{
if (lang >= 0 && lang < SYSTEM_LANGUAGE_ALL)
{
sg_eSystemLanguage = lang;
}
}
const char *get_text(TextId_e id)
{
static const char *pszNullString = "N/A";
if (id >= 0 && id < TEXT_ALL)
{
return sg_kSystemLanguage[id][sg_eSystemLanguage];
}
return pszNullString; // 未找到对应的文本
}
const char *get_text_by_language(SystemLanguage_e lang, TextId_e id)
{
static const char *pszNullString = "N/A";
if (id >= 0 && id < TEXT_ALL)
{
return sg_kSystemLanguage[id][lang];
}
return pszNullString; // 未找到对应的文本
}

View File

@ -0,0 +1,52 @@
#ifndef LANGUAGE_H
#define LANGUAGE_H
typedef enum
{
SYSTEM_LANGUAGE_CHINESE = 0,
SYSTEM_LANGUAGE_ENGLISH,
SYSTEM_LANGUAGE_ALL,
} SystemLanguage_e;
typedef enum
{
TEXT_MENU = 0, // 菜单
TEXT_MAIN_MENU, // 主菜单
TEXT_SELECT_OPTION, // 选择操作
TEXT_ENTER, // 进入
TEXT_EXIT, // 退出
TEXT_RETURN, // 返回
TEXT_MUSIC, // 音乐
TEXT_VIDEO, // 视频
TEXT_CAMERA, // 摄像机
TEXT_SETTING, // 设置
TEXT_SHORTCUT, // 快捷
TEXT_MORE, // 更多
TEXT_LANGUAGE, // 语言
TEXT_NEXT, // 下一个
TEXT_PREVIOUS, // 上一个
TEXT_FUNCTION_TEST, // 功能测试
TEXT_TEST, // 测试
TEXT_BLUETOOTH, // 蓝牙
TEXT_BATTERY, // 电池
TEXT_STORE, // 储存
TEXT_UPGRADE, // 升级
TEXT_ABOUT, // 关于
TEXT_PHOTO, // 拍照
TEXT_PHOTOGRAPHY, // 摄影
TEXT_LOADING, // 加载中
TEXT_STOPPING_PLAYBACK, // 正在停止播放
TEXT_EXIT_MAIN_MENU, // 退出主菜单
TEXT_RETURN_MAIN_MENU, // 返回主菜单
TEXT_MORE_SETTING, // 更多设置
TEXT_ALL,
} TextId_e;
void set_language(SystemLanguage_e lang);
const char *get_text(TextId_e id);
const char *get_text_by_language(SystemLanguage_e lang, TextId_e id);
#endif

666
menu.c Normal file
View File

@ -0,0 +1,666 @@
/**
**********************************************************************************************************************
* @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;
}

181
menu.h Normal file
View File

@ -0,0 +1,181 @@
/**
**********************************************************************************************************************
* @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 <stdint.h>
#include <stdbool.h>
#include <stddef.h>
#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