344 lines
12 KiB
C
Raw Normal View History

2025-05-26 18:48:47 +08:00
#include "Menu.h"
/**********************************************************
* v1.8 By:Adam
*
* :
* OLED补丁,OLED显示图像函数Y轴可以是负数,;
* , ;
* PWM输出;
* , ;
* ;
* ;
* ;
* !;
*
* , OLED_Update() ;
* OLED显示函数以江协科技的OLED显示屏驱动程序为基础进行了少量修改
* OLED显示屏使用软件IIC通信: SCL接到PB8, SDA接到PB9;
*
*
* :https://pan.baidu.com/s/1bZPWCKaiNbb-l1gpAv6QNg?pwd=KYWS
* GitHub:https://github.com/AdamLoong/Embedded_Menu_Simple
* B站UP:
* ( ̀ ω ́ )
**********************************************************/
/*菜单全局属性*/
struct MenuProperty Menu_Global = {
.Cursor_Actual_X = 0, // 当前光标位置X
.Cursor_Actual_Y = 63, // 当前光标位置Y
.Cursor_Actual_W = 0, // 当前光标尺寸宽
.Cursor_Actual_H = 0, // 当前光标尺寸高
.CursorStyle = reverse, // 光标风格;
.Cursor_ActSpeed = 0.15, // 光标动画速度系数;
.Slide_ActSpeed = 2, // 滚动动画速度系数;
.Font_Width = 8, // 字体宽度 8 或 6
.Font_Height = 16, // 字体高度
.Line_Height = 16, // 行高
.Layout_Margin = 0, // 页边距
.Window_X = 0, // 窗口位置X
.Window_Y = 0, // 窗口位置Y
.Window_W = 128, // 窗口宽度
.Window_H = 64, // 窗口高度
};
/*菜单用到的按键函数独立出来,方便移植和修改,比如没有编码器可以用上下两个按键代替;*/
int8_t Menu_RollEvent(void) // 菜单滚动
{
// if (Key_Up_Get()) // 按键上接到PB15;
// {
// return 1;
// }
// if (Key_Down_Get()) // 按键下接到PB13;
// {
// return -1;
// }
// return Encoder_Get_Div4(); // 旋钮编码器PA8,PA9;
}
int8_t Menu_EnterEvent(void) // 菜单确认
{
// return Key_Enter_Get(); // 确认键接到PB14;
}
int8_t Menu_BackEvent(void) // 菜单返回
{
// return Key_Back_Get(); // 返回键接到PB12;
}
/**
*
*
*
* ,
*/
void Menu_RunMenu(struct Option_Class *Option_List)
{
int8_t Roll_Event = 0; // 记录菜单滚动事件
int8_t Show_i = 0; // 显示起始下标
int8_t Show_i_previous = 4; // 显示起始下标的前一个状态(用于比较)
int8_t Show_offset; // 显示Y轴的偏移
int8_t Cat_i = 1; // 选中下标默认为1,(因为Option_List[0]为"<<<")
int8_t Cur_i = 0; // 光标下标默认为0
int8_t Option_MaxNum = 0;
for (Option_MaxNum = 0; Option_List[Option_MaxNum].String[0] != '.'; Option_MaxNum++) // 计算选项列表长度
{
// Option_List[Option_MaxNum].StrLen = Menu_GetOptionStrLen(Option_List[Option_MaxNum].String); // 顺手计算选项名字长度
}
Option_MaxNum--;
/*光标下标限制等于窗口高度减去上下页边距再除以行高*/
int8_t Cur_i_Ceiling = (Menu_Global.Window_H - Menu_Global.Layout_Margin * 2) / Menu_Global.Line_Height; // 计算光标限制位置;
/**********************************************************/
while (1)
{
if (Menu_EnterEvent())
{
/*如果功能不为空则执行功能,否则返回*/
if (Option_List[Cat_i].func)
{
Option_List[Cat_i].func();
}
else
{
return;
}
}
if (Menu_BackEvent())
{
return;
}
/*根据按键事件更改选中下标和光标下标*/
Roll_Event = Menu_RollEvent();
if (Roll_Event)
{
/*更新下标*/
Cur_i += Roll_Event;
Cat_i += Roll_Event;
/*限制选中下标*/
if (Cat_i > Option_MaxNum)
{
Cat_i = Option_MaxNum;
Cat_i = 0;
}
if (Cat_i < 0)
{
Cat_i = 0;
}
/*限制光标下标*/
if (Cur_i >= Cur_i_Ceiling)
{
Cur_i = Cur_i_Ceiling - 1;
Cur_i = 0;
}
if (Cur_i > Option_MaxNum)
{
Cur_i = Option_MaxNum;
}
if (Cur_i < 0) // 踩坑记录: (Cur_i >= Cur_i_Ceiling) Cur_i_Ceiling 有可能是负数, 如果放在(Cur_i < 0)后面判断, 则 Cur_i 会变成负数, 造成程序卡死; 结论: 如果进行位置限制判断, 变量判断应该放在前,常量判断应该放在后;
{
Cur_i = 0;
}
}
/**********************************************************/
OLED_Clear();
/*计算显示起始下标*/
Show_i = Cat_i - Cur_i;
if (1) // 增加显示偏移量实现平滑移动
{
if (Show_i - Show_i_previous) // 如果下标有偏移
{
Show_offset = (Show_i - Show_i_previous) * Menu_Global.Line_Height; // 计算显示偏移量
Show_i_previous = Show_i;
}
if (Show_offset)
{
Show_offset /= Menu_Global.Slide_ActSpeed; // 显示偏移量逐渐归零
}
}
for (int8_t i = -1; i < Cur_i_Ceiling + 1; i++) // 遍历显示选项(遍历 i 从 -1 开始到 Cur_i_Ceiling + 1 结束, 是为了菜单滚动的时候首行和尾行不会有空白);
{
if (Show_i + i < 0)
{
continue;
}
if (Show_i + i > Option_MaxNum)
{
break;
}
/*菜单格式化打印函数会返回打印的字符串长度*/
Option_List[Show_i + i].StrLen =
/*使用格式化字符串打印, 支持添加一个(float)变量*/
Menu_PrintfOptionStr(
/*显示从窗口X起点, 加上页边距*/
2 + Menu_Global.Window_X + Menu_Global.Layout_Margin,
/*显示从窗口Y起点, 加上页边距, 加上行偏移, 加上显示偏移, 加上垂直居中*/
Menu_Global.Window_Y + (Menu_Global.Layout_Margin) + (i * Menu_Global.Line_Height) + (Show_offset) + ((Menu_Global.Line_Height - Menu_Global.Font_Height) / 2),
/*显示宽度范围减去双倍(左右)页边距*/
Menu_Global.Window_W - Menu_Global.Layout_Margin * 2,
/*显示高度就是行高(或字高)*/
Menu_Global.Line_Height,
/*显示字符的宽度*/
Menu_Global.Font_Width,
/*要显示的字符串*/
Option_List[Show_i + i].String,
/*可选变量*/
*Option_List[Show_i + i].Variable);
}
// 调用显示光标函数
Menu_ShowCursor(Menu_Global.Window_X + Menu_Global.Layout_Margin, // 光标左起点加上页边距
Menu_Global.Window_Y + Menu_Global.Layout_Margin + (Cur_i * Menu_Global.Line_Height), // 光标上起点加上光标行偏移
4 + Option_List[Cat_i].StrLen * Menu_Global.Font_Width + Menu_Global.Layout_Margin, // 光标宽度等于字符串长度乘以字符宽度加上页边距(跟随字符串)
// Menu_Global.Window_W - Menu_Global.Layout_Margin * 2,// 光标宽度等于窗口宽度减去左右页边距(等长)(二选一注释)
Menu_Global.Line_Height, // 光标高度就是行高
Menu_Global.CursorStyle, // 光标状态
Menu_Global.Cursor_ActSpeed); // 光标速度
// OLED_DrawRectangle(Menu_Global.Window_X, Menu_Global.Window_Y, Menu_Global.Window_W, Menu_Global.Window_H, 0); // 显示窗口边框,根据个人喜好选择
/**********************************************************/
/*调试信息*/
// OLED_ShowSignedNum(110, 48, Cur_i, 2, OLED_6X8);
// OLED_ShowSignedNum(110, 56, Cat_i, 2, OLED_6X8);
// int delay = 1000000; while(delay--);
/**********************************************************/
OLED_Update();
}
}
/**
* ;
* String
*
* ,;
*/
uint8_t Menu_GetOptionStrLen(char *String)
{
uint8_t i = 0, len = 0;
while (String[i] != '\0') // 遍历字符串的每个字符
{
if (String[i] > '~') // 如果不属于英文字符长度加2
{
len += 2;
i += 3;
}
else // 属于英文字符长度加1
{
len += 1;
i += 1;
}
}
return len;
}
/**
* 使printf函数打印格式化字符串()
* X 0~127
* Y 0~63
* Width 0~127
* Height 0~63
* FontSize
* OLED_8X16 816
* OLED_6X8 68
* format ASCII码可见字符组成的字符串
* ...
* ***********************************
*
*/
uint8_t Menu_PrintfOptionStr(uint8_t X, uint8_t Y, uint8_t Width, uint8_t Height, uint8_t FontSize, char *format, ...)
{
char String[30]; // 定义字符数组
va_list arg; // 定义可变参数列表数据类型的变量arg
va_start(arg, format); // 从format开始接收参数列表到arg变量
vsprintf(String, format, arg); // 使用vsprintf打印格式化字符串和参数列表到字符数组中
va_end(arg); // 结束变量arg
return OLED_ShowStringArea(X, Y, Width, Height, String, FontSize); // OLED显示字符数组字符串并返回字符串
}
/**
*
* Actual_Value
* Target_Value
* Act_Speed
* :
* , ,
*/
float Menu_CurveMigration(float Actual_Value, float Target_Value, float Act_Speed)
{
if ((Target_Value - Actual_Value) > 1)
{
Actual_Value += (Target_Value - Actual_Value) * Act_Speed + 1;
}
else if ((Target_Value - Actual_Value) < -1)
{
Actual_Value += (Target_Value - Actual_Value) * Act_Speed - 1;
}
else
{
Actual_Value = Target_Value;
}
return Actual_Value;
}
/**
*
* Target_Cur_X X位置
* Target_Cur_Y Y位置
* Target_Cur_W
* Target_Cur_H
* Cur_Act_Speed : 0 < Cur_Act_Speed <=1
*
* , ;
*/
void Menu_ShowCursor(float Target_Cur_X, float Target_Cur_Y, float Target_Cur_W, float Target_Cur_H, enum CursorStyle CurStyle, float Cur_Act_Speed)
{
Menu_Global.Cursor_Actual_X = Menu_CurveMigration(Menu_Global.Cursor_Actual_X, Target_Cur_X, Cur_Act_Speed);
Menu_Global.Cursor_Actual_Y = Menu_CurveMigration(Menu_Global.Cursor_Actual_Y, Target_Cur_Y, Cur_Act_Speed);
Menu_Global.Cursor_Actual_W = Menu_CurveMigration(Menu_Global.Cursor_Actual_W, Target_Cur_W, Cur_Act_Speed);
Menu_Global.Cursor_Actual_H = Menu_CurveMigration(Menu_Global.Cursor_Actual_H, Target_Cur_H, Cur_Act_Speed);
if (CurStyle == reverse)
{
OLED_ReverseArea(Menu_Global.Cursor_Actual_X, Menu_Global.Cursor_Actual_Y, Menu_Global.Cursor_Actual_W, Menu_Global.Cursor_Actual_H); // 反相光标
}
else if (CurStyle == mouse)
{
OLED_ShowString(Menu_Global.Cursor_Actual_X + Menu_Global.Cursor_Actual_W, Menu_Global.Cursor_Actual_Y + (Menu_Global.Cursor_Actual_H - 6) / 2, "<-", OLED_6X8); // 尾巴光标
}
else if (CurStyle == frame)
{
OLED_DrawRectangle(Menu_Global.Cursor_Actual_X, Menu_Global.Cursor_Actual_Y, Menu_Global.Cursor_Actual_W, Menu_Global.Cursor_Actual_H, 0); // 矩形光标
}
}
/***
* : ;
* :
* :
* : 使memcpy高效率把壁纸数据复制到显存;
*/
void Menu_ShowWallpaper(const uint8_t *Wallpaper)
{
memcpy(OLED_DisplayBuf, Wallpaper, 1024);
}