mirror of
https://github.com/RQNG/WouoUI.git
synced 2025-06-17 21:17:53 +00:00
465 lines
11 KiB
C++
465 lines
11 KiB
C++
/*
|
||
此程序用于实现稚晖君MonoUI风格的超丝滑菜单,使用0.96寸OLED显示,EC11旋转编码器控制。
|
||
|
||
UI示例是从我的另一个项目简化而来,只保留了列表部分,简化了添加和修改列表的操作,如需更多功能,请参考原项目:https://github.com/RQNG/Rapid-trigger-minipad
|
||
|
||
当前版本 v 1.0
|
||
|
||
示例实现了以下功能:
|
||
|
||
* 休眠模式扫描,同时可旋转旋钮
|
||
* 休眠模式下点按旋钮停止扫描,进入主菜单
|
||
* 主菜单下,旋转旋钮滚动列表,点按选择列表
|
||
* 列表长度无限制
|
||
* 动画速度可调,1 - 无穷大,数字越小动画速度越快,1没有动画,不支持小于1的数值
|
||
|
||
推荐把返回选项放在开头,个人认为体验比较好。
|
||
|
||
项目参考:
|
||
|
||
* 旋钮功能:https://zhuanlan.zhihu.com/p/453130384
|
||
* UI:https://www.bilibili.com/video/BV1HA411S7pv/ ; https://www.bilibili.com/video/BV1xd4y1C7BE/
|
||
|
||
本项目使用Apache 2.0开源协议,如需商用或借鉴,请阅读此协议。
|
||
|
||
欢迎关注我的B站账号,一个只分享osu!相关内容,很无聊的帐号。
|
||
|
||
用户名:音游玩的人,B站主页:https://space.bilibili.com/9182439?spm_id_from=..0.0
|
||
*/
|
||
|
||
/************************************* 旋钮相关 *************************************/
|
||
|
||
#define AIO PB12
|
||
#define BIO PB13
|
||
#define SW PB14
|
||
#define DEBOUNCE 50
|
||
|
||
uint8_t btn_id = 0;
|
||
uint8_t btn_flag = 0;
|
||
bool btn_val = false;
|
||
bool btn_val_last = false;
|
||
bool btn_pressed = false;
|
||
bool CW_1 = false;
|
||
bool CW_2 = false;
|
||
|
||
void btn_inter()
|
||
{
|
||
bool alv = digitalRead(AIO);
|
||
bool blv = digitalRead(BIO);
|
||
if (btn_flag == 0 && alv == LOW)
|
||
{
|
||
CW_1 = blv;
|
||
btn_flag = 1;
|
||
}
|
||
if (btn_flag && alv)
|
||
{
|
||
CW_2 = !blv;
|
||
if (CW_1 && CW_2)
|
||
{
|
||
btn_id = 0;
|
||
btn_pressed = true;
|
||
}
|
||
if (CW_1 == false && CW_2 == false)
|
||
{
|
||
btn_id = 1;
|
||
btn_pressed = true;
|
||
}
|
||
btn_flag = 0;
|
||
}
|
||
}
|
||
|
||
void btn_init()
|
||
{
|
||
pinMode(AIO, INPUT);
|
||
pinMode(BIO, INPUT);
|
||
pinMode(SW, INPUT_PULLUP);
|
||
attachInterrupt(digitalPinToInterrupt(AIO), btn_inter, CHANGE);
|
||
}
|
||
|
||
void btn_scan()
|
||
{
|
||
btn_val = digitalRead(SW);
|
||
if (btn_val != btn_val_last)
|
||
{
|
||
delay(DEBOUNCE);
|
||
btn_val_last = btn_val;
|
||
if (btn_val == LOW)
|
||
{
|
||
btn_pressed = true;
|
||
btn_id = 2;
|
||
}
|
||
}
|
||
}
|
||
|
||
/************************************* 屏幕和UI *************************************/
|
||
|
||
#include <U8g2lib.h>
|
||
#include <Wire.h>
|
||
|
||
#define SCL PB6
|
||
#define SDA PB7
|
||
#define RST U8X8_PIN_NONE
|
||
|
||
U8G2_SSD1306_128X64_NONAME_F_HW_I2C u8g2(U8G2_R0, SCL, SDA, RST);
|
||
|
||
//状态
|
||
enum
|
||
{
|
||
S_NONE,
|
||
S_DISAPPEAR,
|
||
};
|
||
|
||
//菜单结构体
|
||
typedef struct MENU
|
||
{
|
||
char *select;
|
||
} SELECT_LIST;
|
||
|
||
//UI变量
|
||
uint8_t ui_index; //目录变量
|
||
uint8_t ui_state; //状态变量
|
||
uint8_t *buf_ptr; //指向buf首地址的指针
|
||
uint16_t buf_len; //buf的长度
|
||
bool sleep_flag = true; //休眠标志
|
||
uint8_t disappear_step = 1; //消失步数
|
||
|
||
/********************************** 需要修改的东西 ***********************************/
|
||
|
||
//UI参数
|
||
#define SPEED 6 //动画速度,越小越快,1没有动画
|
||
#define PAGES 4 //页面数量,列表类页面的总数量
|
||
|
||
#define ui_select 0
|
||
#define x 1
|
||
#define y 2
|
||
#define y_trg 3
|
||
#define box_width 4
|
||
#define box_width_trg 5
|
||
#define box_y 6
|
||
#define box_y_trg 7
|
||
#define num 8
|
||
#define line_y 9
|
||
#define line_y_trg 10
|
||
int16_t ui_param[11][PAGES];
|
||
|
||
//目录
|
||
enum
|
||
{
|
||
M_SLEEP,
|
||
M_MAIN,
|
||
M_NUMB,
|
||
M_ALPH,
|
||
};
|
||
|
||
//主菜单内容
|
||
SELECT_LIST m_main[]
|
||
{
|
||
{"<< Sleep"},
|
||
{"- Number"},
|
||
{"- 1"},
|
||
{"- 11"},
|
||
{"- 111"},
|
||
{"- 1111"},
|
||
{"- 11111"},
|
||
{"- 111111"},
|
||
{"- 1111111"},
|
||
{"- 11111111"},
|
||
{"- 111111111"},
|
||
{"- 1111111111"},
|
||
{"- 1"},
|
||
{"- 1111111111"},
|
||
{"- 1"},
|
||
{"- 1111111111"},
|
||
{"- 1"},
|
||
{"- 1111111111"},
|
||
{"- 1"},
|
||
{"- 1111111111"},
|
||
{"- 1"},
|
||
{"- 1111111111"},
|
||
{"- 1"},
|
||
{"- 1111111111"},
|
||
{"- 1"},
|
||
};
|
||
|
||
//数字菜单内容
|
||
SELECT_LIST m_numb[]
|
||
{
|
||
{"<< Main"},
|
||
{"- 0"},
|
||
{"- 1"},
|
||
{"- 2"},
|
||
{"- 3"},
|
||
{"- 4"},
|
||
{"- 5"},
|
||
{"- 6"},
|
||
{"- 7"},
|
||
{"- 8"},
|
||
{"- 9"},
|
||
};
|
||
|
||
//睡眠页面处理函数
|
||
void sleep_proc()
|
||
{
|
||
//在这里执行功能
|
||
while (sleep_flag)
|
||
{
|
||
//需要扫描的功能
|
||
Serial.println("Scan");
|
||
|
||
//旋钮扫描
|
||
btn_scan();
|
||
if (btn_pressed)
|
||
{
|
||
btn_pressed = false;
|
||
switch (btn_id)
|
||
{
|
||
case 0:
|
||
//睡眠时顺时针旋转功能
|
||
Serial.println("Clockwise");
|
||
break;
|
||
|
||
case 1:
|
||
//睡眠时逆时针旋转功能
|
||
Serial.println("Anticlockwise");
|
||
break;
|
||
|
||
case 2:
|
||
ui_index = M_MAIN;
|
||
ui_state = S_NONE;
|
||
sleep_flag = false;
|
||
u8g2.setPowerSave(0);
|
||
break;
|
||
|
||
default: break;
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
//主菜单处理函数
|
||
void main_proc()
|
||
{
|
||
if (btn_pressed)
|
||
{
|
||
btn_pressed = false;
|
||
switch (btn_id)
|
||
{
|
||
case 0:
|
||
case 1:
|
||
rotate_switch(M_MAIN);
|
||
break;
|
||
|
||
case 2:
|
||
switch (ui_param[ui_select][M_MAIN])
|
||
{
|
||
case 0:
|
||
ui_index = M_SLEEP;
|
||
ui_state = S_NONE;
|
||
u8g2.setPowerSave(1);
|
||
sleep_flag = true;
|
||
break;
|
||
|
||
case 1:
|
||
ui_index = M_NUMB;
|
||
ui_state = S_DISAPPEAR;
|
||
break;
|
||
|
||
default: break;
|
||
}
|
||
|
||
default: break;
|
||
}
|
||
ui_param[box_width_trg][M_MAIN] = u8g2.getStrWidth(m_main[ui_param[ui_select][M_MAIN]].select) + ui_param[x][M_MAIN] * 2;
|
||
}
|
||
menu_ui_show(m_main, M_MAIN);
|
||
}
|
||
|
||
//数字菜单处理函数
|
||
void numb_proc()
|
||
{
|
||
if (btn_pressed)
|
||
{
|
||
btn_pressed = false;
|
||
switch (btn_id)
|
||
{
|
||
case 0:
|
||
case 1:
|
||
rotate_switch(M_NUMB);
|
||
break;
|
||
|
||
case 2:
|
||
switch (ui_param[ui_select][M_NUMB])
|
||
{
|
||
case 0:
|
||
ui_index = M_MAIN;
|
||
ui_state = S_DISAPPEAR;
|
||
break;
|
||
|
||
default: break;
|
||
}
|
||
|
||
default: break;
|
||
}
|
||
ui_param[box_width_trg][M_NUMB] = u8g2.getStrWidth(m_numb[ui_param[ui_select][M_NUMB]].select) + ui_param[x][M_NUMB] * 2;
|
||
}
|
||
menu_ui_show(m_numb, M_NUMB);
|
||
}
|
||
|
||
//总的UI进程
|
||
void ui_proc()
|
||
{
|
||
switch (ui_state)
|
||
{
|
||
case S_NONE:
|
||
u8g2.clearBuffer();
|
||
switch (ui_index)
|
||
{
|
||
//目录里所有页面都要放在这里
|
||
|
||
case M_SLEEP:
|
||
sleep_proc();
|
||
break;
|
||
|
||
case M_MAIN:
|
||
main_proc();
|
||
break;
|
||
|
||
case M_NUMB:
|
||
numb_proc();
|
||
break;
|
||
|
||
default: break;
|
||
}
|
||
break;
|
||
|
||
case S_DISAPPEAR:
|
||
disappear();
|
||
break;
|
||
|
||
default: break;
|
||
}
|
||
u8g2.sendBuffer();
|
||
}
|
||
|
||
//ui初始化
|
||
void ui_init()
|
||
{
|
||
for(int i = 0 ; i < PAGES ; i++)
|
||
{
|
||
ui_param[ui_select][i] = 0;
|
||
ui_param[x][i] = 4;
|
||
ui_param[y][i] = 0;
|
||
ui_param[y_trg][i] = 0;
|
||
}
|
||
|
||
ui_index = M_SLEEP;
|
||
ui_state = S_NONE;
|
||
|
||
//主菜单
|
||
ui_param[box_width][M_MAIN] = ui_param[box_width_trg][M_MAIN] = u8g2.getStrWidth(m_main[ui_param[ui_select][M_MAIN]].select) + ui_param[x][M_MAIN] * 2;
|
||
ui_param[num][M_MAIN] = sizeof(m_main) / sizeof(SELECT_LIST);
|
||
|
||
//数字菜单
|
||
ui_param[box_width][M_NUMB] = ui_param[box_width_trg][M_NUMB] = u8g2.getStrWidth(m_numb[ui_param[ui_select][M_NUMB]].select) + ui_param[x][M_NUMB] * 2;
|
||
ui_param[num][M_NUMB] = sizeof(m_numb) / sizeof(SELECT_LIST);
|
||
}
|
||
|
||
/************************************* 动画函数 *************************************/
|
||
|
||
//移动函数
|
||
void move(int16_t *a, int16_t *a_trg)
|
||
{
|
||
if (*a < *a_trg) *a += ceil(fabs((float)(*a_trg - *a) / SPEED));
|
||
else if (*a > *a_trg) *a -= ceil(fabs((float)(*a_trg - *a) / SPEED));
|
||
}
|
||
|
||
//消失函数
|
||
void disappear()
|
||
{
|
||
switch (disappear_step)
|
||
{
|
||
case 1: for (uint16_t i = 0; i < buf_len; ++i) if (i % 2 == 0) buf_ptr[i] = buf_ptr[i] & 0x55; break;
|
||
case 2: for (uint16_t i = 0; i < buf_len; ++i) if (i % 2 != 0) buf_ptr[i] = buf_ptr[i] & 0xAA; break;
|
||
case 3: for (uint16_t i = 0; i < buf_len; ++i) if (i % 2 == 0) buf_ptr[i] = buf_ptr[i] & 0x00; break;
|
||
case 4: for (uint16_t i = 0; i < buf_len; ++i) if (i % 2 != 0) buf_ptr[i] = buf_ptr[i] & 0x00; break;
|
||
|
||
default: ui_state = S_NONE; disappear_step = 0; break;
|
||
}
|
||
disappear_step++;
|
||
}
|
||
|
||
/************************************* 显示函数 *************************************/
|
||
|
||
//列表类页面通用显示函数
|
||
void menu_ui_show(struct MENU arr[], int16_t n)
|
||
{
|
||
for(uint8_t i = 0 ; i < ui_param[num][n] ; ++i) u8g2.drawStr(ui_param[x][n], 16 * i + ui_param[y][n] + 12, arr[i].select);
|
||
|
||
ui_param[line_y_trg][n] = ceil((ui_param[ui_select][n]) * ((float)64 / (ui_param[num][n] - 1)));
|
||
ui_param[box_width_trg][n] = u8g2.getStrWidth(arr[ui_param[ui_select][n]].select) + 8;
|
||
|
||
move(&ui_param[y][n], &ui_param[y_trg][n]);
|
||
move(&ui_param[box_y][n], &ui_param[box_y_trg][n]);
|
||
move(&ui_param[box_width][n], &ui_param[box_width_trg][n]);
|
||
move(&ui_param[line_y][n], &ui_param[line_y_trg][n]);
|
||
|
||
u8g2.drawBox(125, 0, 3, ui_param[line_y][n]);
|
||
u8g2.setDrawColor(2);
|
||
u8g2.drawRBox(0, ui_param[box_y][n], ui_param[box_width][n], 16, 1);
|
||
u8g2.setDrawColor(1);
|
||
}
|
||
|
||
/************************************* 处理函数 *************************************/
|
||
|
||
//列表类页面旋转时判断通用函数
|
||
void rotate_switch(int16_t n)
|
||
{
|
||
switch (btn_id)
|
||
{
|
||
case 0:
|
||
if (ui_param[ui_select][n] < 1) break;
|
||
ui_param[ui_select][n] -= 1;
|
||
if (ui_param[ui_select][n] < -(ui_param[y][n] / 16)) ui_param[y_trg][n] += 16;
|
||
else ui_param[box_y_trg][n] -= 16;
|
||
break;
|
||
|
||
case 1:
|
||
if ((ui_param[ui_select][n] + 2) > ui_param[num][n]) break;
|
||
ui_param[ui_select][n] += 1;
|
||
if ((ui_param[ui_select][n] + 1) > (4 - ui_param[y][n] / 16)) ui_param[y_trg][n] -= 16;
|
||
else ui_param[box_y_trg][n] += 16;
|
||
break;
|
||
|
||
default: break;
|
||
}
|
||
}
|
||
|
||
/************************************ 初始化函数 ************************************/
|
||
|
||
//OLED初始化函数
|
||
void oled_init()
|
||
{
|
||
u8g2.setBusClock(800000);
|
||
u8g2.begin();
|
||
u8g2.setFont(u8g2_font_wqy12_t_chinese1);
|
||
buf_ptr = u8g2.getBufferPtr();
|
||
buf_len = 8 * u8g2.getBufferTileHeight() * u8g2.getBufferTileWidth();
|
||
}
|
||
|
||
/************************************ 主循环函数 ************************************/
|
||
|
||
void setup()
|
||
{
|
||
oled_init();
|
||
ui_init();
|
||
btn_init();
|
||
|
||
Serial.begin(115200);
|
||
}
|
||
|
||
void loop()
|
||
{
|
||
btn_scan();
|
||
ui_proc();
|
||
}
|