很久之前,写过一个简易的多级菜单,如今回头看,我都不敢承认那玩意是自己写的,于是打算重新写过一个,并且做成一个简易的GUI。原来用的OLED,现在改成了TFT(ST7789驱动)。但是答题的思路还是不变的。
先说说思路,由于选择的是一个没有触摸的屏幕,于是就要加上物理按键。这样才能操作屏幕嘛。
按照面向对象的思想,我将整个GUI结构分为三类:按键,显示,动作。
- 按键用来控制屏幕,进行上滑下滑切入切出菜单;
- 显示则作为每一级菜单的背景,并且将菜单设置为静态显示(即每次切换只显示一次);
- 动作则用来链接各种显示功能。例如一级菜单切入二级菜单这么个动作;
- 除此之外就是各种动态效果,这部分暂时还未开工。
接下来看看menu.c文件的内容
#include <stdio.h>
#include "string.h"
#include "lcd_init.h"
#include "lcd.h"
#include "main.h"
#include "menu.h"
//多级菜单的级数
#define Menu_Select 3
//选项显示缓存区
char buf[100];
//按键标志初始值
uint8_t List_Number = 1;
//主次菜单切换
static uint8_t Menu_Change = 0;
//菜单刷新标志位
static uint8_t Menu_Refresh_Flag = 0;
//up/down控制按键标志位
static uint8_t UD_Action_Flag = 0;
extern unsigned char BMP_1[];
extern unsigned char BMP_2[];
extern unsigned char BMP_3[];
extern unsigned char BMP_4[];
extern unsigned char BMP_5[];
extern unsigned char BMP_6[];
extern unsigned char BMP_7[];
extern unsigned char BMP_8[];
extern unsigned char BMP_9[];
/******************************************************************************
* @brief 绘制一条垂线或者横线
* @param x1,y1 起始坐标
x2,y2 终点坐标
color 颜色
* @retval 无
******************************************************************************/
void Draw_Line(u16 x1, u16 y1, u16 x2, u16 y2, u16 color)
{
u16 i = 0;
if (x1 == x2)
{
for (i = y1; i <= y2; i++)
{
LCD_DrawPoint(x1, i, color);
}
}
if (y1 == y2)
{
for (i = x1; i <= x2; i++)
{
LCD_DrawPoint(i, y1, color);
}
}
}
/******************************************************************************
* @brief 绘制一个方框
* @param x1,y1 起始坐标
x2,y2 终点坐标
color 颜色
* @retval 无
******************************************************************************/
void Draw_Rectangle(u16 x1, u16 y1, u16 x2, u16 y2, u16 color)
{
Draw_Line(x1, y1, x2, y1, color);
Draw_Line(x1, y1, x1, y2, color);
Draw_Line(x1, y2, x2, y2, color);
Draw_Line(x2, y1, x2, y2, color);
}
/******************************************************************************
* @brief 绘制选项框
* @param x1,y1 起始坐标
x2,y2 终点坐标
color 颜色
index 选项框的厚度
* @retval 无
******************************************************************************/
void Draw_Box(u16 x1, u16 y1, u16 x2, u16 y2, u16 color, u16 index)
{
u16 i = 0;
for (i = 0; i < index; i++)
{
Draw_Rectangle(x1 + i, y1 + i, x2 - i, y2 - i, color);
}
}
/******************************************************************************
* @brief 绘制窗体
* @param x1,y1 起始坐标
x2,y2 终点坐标
Box_Height 选项框的高度
Menu_List 选项框个数
* @retval 无
******************************************************************************/
void Draw_Windows(void)
{
Draw_Box(0, 0, 240, 240, LBBLUE, 5);
Draw_Box(0, 36, 240, 41, LBBLUE, 5);
sprintf(buf, " WINDOWS1 ");
LCD_ShowString(5, 5, (uint8_t*)buf, WHITE, LBBLUE, 32, 0);
}
/******************************************************************************
* @brief 绘制圆角框
* @param x0,y0 圆心坐标
color 颜色
length 框体长度
* @retval 无
******************************************************************************/
void Draw_Circal_Box(u16 x0, u16 y0, u8 r, u16 color, u8 length)
{
int a, b;
a = 0;
b = r;
while (a <= b)
{
LCD_DrawPoint(x0 - b, y0 - a, color);
LCD_DrawPoint(x0 - b, y0 + a, color);
LCD_DrawPoint(x0 - a, y0 + b, color);
LCD_DrawPoint(x0 - a, y0 - b, color);
LCD_DrawPoint(x0 + b + length, y0 - a, color);
LCD_DrawPoint(x0 + b + length, y0 + a, color);
LCD_DrawPoint(x0 + a + length, y0 - b, color);
LCD_DrawPoint(x0 + a + length, y0 + b, color);
a++;
if ((a * a + b * b) > (r * r))//判断要画的点是否过远
{
b--;
}
}
LCD_DrawLine(x0, y0 - r, x0 + length, y0 - r, color);
LCD_DrawLine(x0, y0 + r, x0 + length, y0 + r, color);
}
/******************************************************************************
* @brief 绘制实心圆
* @param x0,y0 圆心坐标
color 颜色
* @retval 无
******************************************************************************/
void Draw_Circal_Solid(u16 x0, u16 y0, u8 r, u16 color)
{
int a, b, c;
a = 0;
b = r;
for (c = r; c > 0; c--)
{
Draw_Circle(x0, y0, c - 1, color);
}
}
/******************************************************************************
* @brief 显示初始菜单,自己任意配置
* @param x,y 显示坐标
fc 字的颜色
bc 字的背景色
sizey 字号
mode: 0非叠加模式 1叠加模式
Box_Height 选项框的高度
* @retval 无
******************************************************************************/
void Display_Initial_Menu(u16 x,u16 y,u16 fc,u16 bc,u8 sizey,u8 mode,u8 Box_Height)
{
sprintf(buf,"DISPLAY");
LCD_ShowString(x, y + ( Box_Height * 0 ), (uint8_t *)buf, fc, bc, sizey, mode);
sprintf(buf,"VOICE");
LCD_ShowString(x, y + ( Box_Height * 1 ), (uint8_t *)buf, fc, bc, sizey, mode);
sprintf(buf,"NOTICE");
LCD_ShowString(x, y + ( Box_Height * 2 ), (uint8_t *)buf, fc, bc, sizey, mode);
sprintf(buf,"BATTERY");
LCD_ShowString(x, y + ( Box_Height * 3 ), (uint8_t *)buf, fc, bc, sizey, mode);
sprintf(buf,"MEMORY");
LCD_ShowString(x, y + ( Box_Height * 4 ), (uint8_t *)buf, fc, bc, sizey, mode);
sprintf(buf,"ABOUT");
LCD_ShowString(x, y + ( Box_Height * 5 ), (uint8_t *)buf, fc, bc, sizey, mode);
}
/******************************************************************************
* @brief 显示二级菜单,自己任意配置
* @param x,y 显示坐标
fc 字的颜色
bc 字的背景色
sizey 字号
mode: 0非叠加模式 1叠加模式
Box_Height 选项框的高度
* @retval 无
******************************************************************************/
void Display_Second_Menu(u16 x,u16 y,u16 fc,u16 bc,u8 sizey,u8 mode,u8 Box_Height)
{
sprintf(buf,"DISPLAY");
LCD_ShowString(x, y + ( Box_Height * 0 ), (uint8_t *)buf, fc, bc, sizey, mode);
sprintf(buf,"VOICE");
LCD_ShowString(x, y + ( Box_Height * 1 ), (uint8_t *)buf, fc, bc, sizey, mode);
sprintf(buf,"NOTICE");
LCD_ShowString(x, y + ( Box_Height * 2 ), (uint8_t *)buf, fc, bc, sizey, mode);
sprintf(buf,"BATTERY");
LCD_ShowString(x, y + ( Box_Height * 3 ), (uint8_t *)buf, fc, bc, sizey, mode);
}
/******************************************************************************
* @brief 显示三级菜单,自己任意配置
* @param x,y 显示坐标
fc 字的颜色
bc 字的背景色
sizey 字号
mode: 0非叠加模式 1叠加模式
Box_Height 选项框的高度
* @retval 无
******************************************************************************/
void Display_Third_Menu(void)
{
LCD_ShowPicture( 25, 15, 50, 50, BMP_1); //1
LCD_ShowPicture( 95, 15, 50, 50, BMP_2); //2
LCD_ShowPicture(175, 15, 50, 50, BMP_3); //3
LCD_ShowPicture( 15, 95, 50, 50, BMP_4); //4
LCD_ShowPicture( 95, 95, 50, 50, BMP_5); //5
LCD_ShowPicture(175, 95, 50, 50, BMP_6); //6
LCD_ShowPicture( 15, 175, 50, 50, BMP_7); //7
LCD_ShowPicture( 95, 175, 50, 50, BMP_8); //8
LCD_ShowPicture(175, 175, 50, 50, BMP_9); //9
}
/******************************************************************************
* @brief 注册up控制按键
* @param Menu_List:控制的列表项数目
* @retval 无
* eg:一个页面有6个选项,就让 Menu_List = 6
******************************************************************************/
void Button_Up_Click(uint8_t Menu_List)
{
if (Button_Click_Event_1)
{
if (List_Number > 1)
List_Number = List_Number - 1;
else if (List_Number == 1)
List_Number = Menu_List;
else
List_Number = List_Number;
UD_Action_Flag = 2;
//显示列表项标号
LCD_ShowIntNum(220, 220, List_Number, 1, WHITE, BLACK, 16);
}
}
/******************************************************************************
* @brief 注册down控制按键
* @param 控制的列表项数目
* @retval 无
******************************************************************************/
void Button_Down_Click(uint8_t Menu_List)
{
if (Button_Click_Event_2)
{
if (List_Number < Menu_List)
List_Number = List_Number + 1;
else if (List_Number == Menu_List)
List_Number = 1;
else
List_Number = List_Number;
UD_Action_Flag = 1;
//显示列表项标号
LCD_ShowIntNum(220, 220, List_Number, 1, WHITE, BLACK, 16);
}
}
/******************************************************************************
* @brief 注册菜单切入控制按键
* @param Desk_Num 菜单级数
* @retval 无
****************************************************************************/
void Button_Menu_Enter(u8 Desk_Num)
{
LCD_ShowIntNum(200,220,Menu_Change,1,WHITE,BLACK,16);
//切入菜单,即进入下一级菜单
if(Button_Click_Event_3)
{
Action_Menu_Change(0); //载入下一级菜单动作
Menu_Refresh_Flag = 1;
if (Menu_Change < Desk_Num - 1)
Menu_Change = Menu_Change + 1;
else if (Menu_Change == Desk_Num - 1)
Menu_Change = Desk_Num - 1;
//显示菜单级标号
LCD_ShowIntNum(220, 220, List_Number, 1, WHITE, BLACK, 16);
}
}
/******************************************************************************
* @brief 注册菜单切出控制按键
* @param Desk_Num 菜单级数
* @retval 无
****************************************************************************/
void Button_Menu_Exit(u8 Desk_Num)
{
LCD_ShowIntNum(200, 220, Menu_Change, 1, WHITE, BLACK, 16);
//切出菜单,即返回上一级菜单
if (Button_Click_Event_4)
{
Action_Menu_Change(1); //载入上一级菜单的动作
Menu_Refresh_Flag = 1;
if (Menu_Change > 0)
Menu_Change = Menu_Change - 1;
if (Menu_Change < Desk_Num - 2)
Menu_Change = 0;
//显示菜单级标号
LCD_ShowIntNum(220, 220, List_Number, 1, WHITE, BLACK, 16);
}
}
/******************************************************************************
* @brief 注册菜单切换控制按键搭配的动作
* @param select 事件标志
* @retval 无
****************************************************************************/
u8 List = 0,List2 = 0;
void Action_Menu_Change(u8 select)
{
switch (select)
{
case 0:
switch (Menu_Change)
{
case 0:
List = List_Number;
List_Number = 1;
LCD_Fill(0, 0, LCD_W, LCD_H, BLACK);
break;
case 1:
List2 = List_Number;
List_Number = 1;
LCD_Fill(0, 0, LCD_W, LCD_H, BLACK);
break;
//case ...3级、4级菜单
}
break;
case 1:
switch (Menu_Change)
{
case 1:
List_Number = List;
LCD_Fill(0, 0, LCD_W, LCD_H, BLACK);
break;
case 2:
List_Number = List2;
LCD_Fill(0, 0, LCD_W, LCD_H, BLACK);
break;
//case ...3级、4级菜单
}
default:
break;
}
}
/******************************************************************************
* @brief 注册选项框上滑动作
* @param x1,y1 起始坐标
x2,y2 终点坐标
Box_Height 选项框的高度
Menu_List 选项框个数
* @retval 无
******************************************************************************/
void Action_Box_Up(u16 x1,u16 y1,u16 x2,u16 y2,uint8_t Menu_List, uint8_t Box_Height)
{
if(UD_Action_Flag == 2)
{
UD_Action_Flag = 0;
if(List_Number >= 1 && List_Number < Menu_List)
{
LCD_DrawLine(x1, y2+Box_Height*(List_Number), x2, y2+Box_Height*(List_Number), BLACK);
LCD_DrawLine(x1, y1+Box_Height*(List_Number), x1, y2+Box_Height*(List_Number), BLACK);
LCD_DrawLine(x2, y1+Box_Height*(List_Number), x2, y2+Box_Height*(List_Number), BLACK);
printf("%d",List_Number);
}
if(List_Number == Menu_List)
{
LCD_DrawRectangle(x1, y1 + Box_Height*(0), x2, y2+Box_Height*(0), BLACK);
}
}
}
/******************************************************************************
* @brief 注册选项框下滑动作
* @param x1,y1 起始坐标
x2,y2 终点坐标
Box_Height 选项框的高度
Menu_List 选项框个数
* @retval 无
******************************************************************************/
void Action_Box_Down(u16 x1, u16 y1, u16 x2, u16 y2, uint8_t Menu_List, uint8_t Box_Height)
{
if (UD_Action_Flag == 1)
{
UD_Action_Flag = 0;
if (List_Number > 1 && List_Number <= Menu_List)
{
LCD_DrawLine(x1, y1 + Box_Height * (List_Number - 2), x2, y1 + Box_Height * (List_Number - 2), BLACK);
LCD_DrawLine(x1, y1 + Box_Height * (List_Number - 2), x1, y2 + Box_Height * (List_Number - 2), BLACK);
LCD_DrawLine(x2, y1 + Box_Height * (List_Number - 2), x2, y2 + Box_Height * (List_Number - 2), BLACK);
printf("%d", List_Number);
}
if (List_Number == 1)
{
LCD_DrawRectangle(x1, y1 + Box_Height * (Menu_List - 1), x2, y2 + Box_Height * (Menu_List - 1), BLACK);
}
}
}
/******************************************************************************
* @brief 绘制选项框
* @param x1,y1 起始坐标
x2,y2 终点坐标
Box_Height 选项框的高度
Menu_List 选项框个数
* @retval 无
******************************************************************************/
void Draw_Option_Box(u16 x1, u16 y1, u16 x2, u16 y2, uint8_t Menu_List, uint8_t Box_Height)
{
LCD_DrawRectangle(x1, y1 + Box_Height * (List_Number - 1), x2, y2 + Box_Height * (List_Number - 1), BLUE);
}
/******************************************************************************
* @brief 注册菜单
* @param 无
* @retval 无
******************************************************************************/
void Menu_Display_Control(void)
{
Button_Menu_Enter(Menu_Select); //菜单等级控制
Button_Menu_Exit(Menu_Select);
switch (Menu_Change)
{
case 0:
if (Menu_Refresh_Flag == 1)
{
Display_Initial_Menu(10, 3, WHITE, BLACK, 24, 0, 30); //注册一个0级菜单
Menu_Refresh_Flag = 0;
}
Draw_Option_Box(3, 0, 200, 30, 6, 30); //注册选项框
Action_Box_Up(3, 0, 200, 30, 6, 30); //添加选项框的上滑动作
Action_Box_Down(3, 0, 200, 30, 6, 30); //添加选项框的下滑动作
Button_Down_Click(6); //注册down控制按钮
Button_Up_Click(6); //注册up控制按钮
break;
case 1:
if (Menu_Refresh_Flag == 1)
{
Display_Second_Menu(10, 3, RED, BLACK, 32, 0, 59); //注册一个1级菜单
Menu_Refresh_Flag = 0;
}
Draw_Option_Box(3, 0, 150, 59, 4, 59); //注册选项框
Action_Box_Up(3, 0, 150, 59, 4, 59); //添加选项框的上滑动作
Action_Box_Down(3, 0, 150, 59, 4, 59); //添加选项框的下滑动作
Button_Down_Click(4); //注册down控制按钮
Button_Up_Click(4); //注册up控制按钮
break;
case 2:
if (Menu_Refresh_Flag == 1)
{
Display_Third_Menu(); //注册一个2级菜单
Menu_Refresh_Flag = 0;
}
Draw_Option_Box(0, 0, 80, 79, 3, 79); //注册选项框
Action_Box_Up(0, 0, 80,79, 3, 79); //添加选项框的上滑动作
Action_Box_Down(0, 0, 80, 79, 3, 79); //添加选项框的下滑动作
Button_Down_Click(3); //注册down控制按钮
Button_Up_Click(3); //注册up控制按钮
break;
default:
Menu_Refresh_Flag = 0;
break;
}
}
重点:如何创建菜单
如果使用其中自带的选项框,那么只需要修改两处即可
一是 Action_Menu_Change(u8 select) 函数,在其中添加 List_Number 变量的继承;
二是Menu_Display_Control(void) 函数中注册新的菜单。
重点在第二个函数
- 首先要确定菜单级数
- 然后申请一个或多个新的菜单,并且在Menu_Refresh_Flag == 1的情况下申请,不然会不断刷新屏幕
- 然后注册选项框,注册选项框的动作,动作可以自行修改
- 最后注册按键
接下来是menu.h文件内容
#ifndef __menu_H_
#define __menu_H_
#include <stdio.h>
#include "string.h"
#include "stdint.h"
#include "main.h"
#define Button_Click_Event_1 HAL_GPIO_ReadPin(KEY1_GPIO_Port, KEY1_Pin) == GPIO_PIN_RESET //up控制按键
#define Button_Click_Event_2 HAL_GPIO_ReadPin(KEY2_GPIO_Port, KEY2_Pin) == GPIO_PIN_RESET //down控制按键
#define Button_Click_Event_3 HAL_GPIO_ReadPin(KEY3_GPIO_Port, KEY3_Pin) == GPIO_PIN_RESET //菜单切入控制按键
#define Button_Click_Event_4 HAL_GPIO_ReadPin(KEY4_GPIO_Port, KEY4_Pin) == GPIO_PIN_RESET //菜单切出控制按键
void Display_Initial_Menu(u16 x,u16 y,u16 fc,u16 bc,u8 sizey,u8 mode,u8 Box_Height);
void Display_Second_Menu(u16 x,u16 y,u16 fc,u16 bc,u8 sizey,u8 mode,u8 Box_Height);
void Display_Third_Menu(void);
void Draw_Box(u16 x1, u16 y1, u16 x2, u16 y2, u16 color, u16 index);
void Draw_Line(u16 x1, u16 y1, u16 x2, u16 y2, u16 color);
void Draw_Rectangle(u16 x1, u16 y1, u16 x2, u16 y2, u16 color);
void Draw_Option_Box(u16 x1, u16 y1, u16 x2, u16 y2, uint8_t Menu_List, uint8_t Box_Height);
void Draw_Windows(void);
void Draw_Circal_Box(u16 x0,u16 y0,u8 r,u16 color, u8 length);
void Draw_Circal_Solid(u16 x0, u16 y0, u8 r, u16 color);
void Button_Down_Click(uint8_t Menu_List);
void Button_Up_Click(uint8_t Menu_List);
void Button_Menu_Enter(u8 Desk_Num);
void Button_Menu_Exit(u8 Desk_Num);
void Action_Box_Up(u16 x1, u16 y1, u16 x2, u16 y2, uint8_t Menu_List, uint8_t Box_Height);
void Action_Box_Down(u16 x1, u16 y1, u16 x2, u16 y2, uint8_t Menu_List, uint8_t Box_Height);
void Action_Menu_Change(u8 select);
void Menu_Display_Control(void);
//void DA(void);
#endif
最后是main.c的内容
在while循环里添加 Menu_Display_Control(); 即可,菜单的其他配置都在menu.c中配置完成。
int main(void)
{
HAL_Init();
SystemClock_Config();
MX_GPIO_Init();
MX_USART2_UART_Init();
LCD_Init();//LCD初始化
LCD_Fill(0,0,LCD_W,LCD_H,BLACK);
Display_Initial_Menu(10, 3, WHITE, BLACK, 24, 0, 30); //注册一个0级菜单,防止刚开机不显示东东西
LCD_ShowIntNum(220, 220, List_Number, 1, WHITE, BLACK, 16);
while (1)
{
/* USER CODE END WHILE */
/* USER CODE BEGIN 3 */
Menu_Display_Control();
}
/* USER CODE END 3 */
}