- 首次提交
- 新增:五种默认弹窗的标题滚动版本
- 修复了无动画运行还一直刷屏的BUG
- STM32的示例工程新增对SH1107 OLED驱动芯片的支持
- 示例工程新增思澈芯片例程
- 例程使用思澈官方SF32lb52-nano-n16-r16开发板
- 该例程可作为在RT-Thread中使用ESGUI的参考
- 修复了一些BUG,新增ESGUI_Def.h文件,统一类型定义,方便移植
- 将一些不太严谨的语句改严谨了
- 修复了ESGUI类型冲突的BUG
- 新增ESP-IDF V6.02框架下的ESP32P4芯片例程
- 该例程可作为在FreeRTOS中使用ESGUI的参考
- ESGUI自带的Canvas绘图库字体查找方式更改为二分查找
- ESGUI字体生成器适配二分查找
- 更新示例工程的ESGUI版本
- 刚刚忘记更新字体生成器了,现在补上
-
-
无锁命令队列(单生产者-单消费者),Async 函数可从任意任务/中断安全调用,UI 线程在
ESGUI_Tick内统一处理(Actor 模式) -
ESGUI_ENABLE_MULTITHREAD多线程支持开关:=1 走队列,=0 退化为直接同步调用(零队列开销) -
ESGUI_SYNC_MODE双同步后端:=0 编译器原子(GCC/Clang,多核安全);=1 纯软件 volatile(零原子内建,不支持多核) -
新增生产者收件箱(
ESGUI_ProducerBox_*):多任务调用 UI 的"单生产者收拢"方案,每个任务一个 SPSC 收件箱,UI 线程自动轮询收拢,多生产者解决方案 -
Async 异步函数(单生产者模型)
- 页面栈:
ESGUI_PushPageAsync/ESGUI_PopPageAsync/ESGUI_ShowPopupAsync/ESGUI_ClosePopupAsync - 覆盖层:
ESGUI_OverlayAddAsync/ESGUI_OverlayRemoveAsync/ESGUI_OverlaySetVisibleAsync - 其他:
ESGUI_HandleActionAsync/ESGUI_AnimStartAsync/ESGUI_AnimStopAllAsync
- 页面栈:
-
- 常驻组件层,独立于页面/弹窗,始终叠加在最上层(z-order = 注册顺序)
- 支持任意任务异步增删/显隐,
always_dirty每帧强制重绘
-
- 定点透视投影、零浮点;模型变换(缩放 / 绕X / 绕Y / 绕Z / 平移)
- 内置 3D 菜单页面(
ESGUI_ENABLE_3D_MENU),焦点模型带旋转动画 - 3D模型数据生成AI提示词.txt,可以把提示词丢给AI,让AI帮你生成模型数据
-
- STM32示例工程默认不开启多线程支持 - ESP32工程默认开启多线程支持 - SIFLI工程默认开启多线程支持ESGUI_PageDefaltVtbl.c更名为ESGUI_DefaultConfig.c**(默认虚函数表实现),配置头统一为ESGUI_DefaultConfig.hESGUI_T的draw_data成员更名为draw_ctx**- 示例页面与
ESGUI配置同步 - 示例工程文件结构**
- 新增
page/目录:存放菜单页面描述(文本页/BMP页/3D页等) - 新增
model/目录:存放 3D 模型数据(顶点/边) - 条件编译裁剪宏真正接线(三角形/缓动曲线可裁剪)、8 对齐透明位图快速路径、过渡遮罩去除法
-
- 字体生成器生成的字体文件
NULL未定义问题,现已改为ESGUI_NULL - 长文本作为首条时进入页面不滚动的问题
- 字体生成器生成的字体文件
ESGUI(Embedded Simple GUI)是一个面向单色 OLED(如 SSD1315/SSD1306)的轻量级菜单框架。 采用 纯 C 编写、零浮点运算、零动态内存分配、零 RTOS 依赖,专为资源受限的 MCU 设计。
介绍视频:https://www.bilibili.com/video/BV1xFTw6iEbj?vd_source=605419deeeaedb82f5f8918bda063219
典型应用场景:128×64 单色屏、STM32/GD32 等 Cortex-M 内核、无外部显存或显存极小的嵌入式设备。
| 特性 | 说明 |
|---|---|
| 零浮点 | 所有动画与进度使用千分比(0~1000),无 float/double,无 FPU 也能流畅运行 |
| 零动态内存 | 页面数据、弹窗数据、动画节点全部使用静态内存池,无 malloc/free |
| 零 RTOS 依赖 | 线程安全靠自研无锁命令队列实现,不依赖任何操作系统/锁/汇编 |
| 多线程支持 | Async 函数可从任意任务/中断调用;多任务场景用生产者收件箱"单生产者收拢" |
| 页式显存映射 | 显存 Buffer 与 SSD1315 GDDRAM 1:1 页式映射,送屏无需转置 |
| 分块刷新 | 支持按条带(Strip)分块刷新,几 KB RAM 即可驱动大屏 |
| 动画系统 | 内置线性/缓入/缓出/回弹/冲过/弹跳等曲线,支持热更新目标值与 must_complete 阻塞 |
| 3D 线框渲染 | 定点透视投影(零浮点),模型变换 + 内置 3D 菜单页面 |
| Overlay 覆盖层 | 常驻组件层,独立于页面/弹窗,始终叠加在最上层,支持异步增删/显隐 |
| 页面栈管理 | 最大 8 级菜单深度(可宏定义调整),支持 Push/Pop 过渡动画,动画期间自动屏蔽按键防误触 |
| 虚函数表架构 | 每个页面/弹窗自带 esgui_page_vtable_t,所有显示效果与输入处理均可被用户完全覆盖 |
| 模块化裁剪 | 通过宏开关编译时剔除不需要的页面类型、弹窗、动画曲线、3D 模块,极致压缩 Flash |
| UTF-8 文本 | 支持中英文混排,自动换行,超长文本自动滚动 |
ESGUI 采用**"核心框架 + 默认虚函数表"**的两层架构:
┌─────────────────────────────────────┐
│ 用户自定义页面(可选) │ ← 继承 vtable,完全重写或局部覆盖
│ ESGUI_DefaultConfig.c / .h │ ← 默认虚函数表:文本菜单、BMP菜单、3D菜单、5种弹窗
│ ESGUI_DefaultConfig.h │ ← 编译时配置(尺寸/开关/动画参数/多线程/队列)
├─────────────────────────────────────┤
│ ESGUI.c / ESGUI.h │ ← 框架核心:生命周期、事件路由、Tick 驱动、命令队列
│ ESGUI_Menu.c / .h │ ← 菜单控制器:页面栈、弹窗、动作分发
│ ESGUI_Anim.c / .h │ ← 动画引擎:静态池、千分比插值、缓动曲线
│ ESGUI_Event.c / .h │ ← 事件定义(按键/编码器/触摸)
│ ESGUI_Widget.c / .h │ ← 基础控件:进度条、焦点框、复选框等
│ ESGUI_3D.c / .h │ ← 3D 线框渲染:定点投影、模型变换
│ ESGUI_UseCanvas.c / .h │ ← Canvas 适配层:绑定分块刷新到框架
│ BSP/ │ ← 底层绘图库:画布、图元、文本、位图
└─────────────────────────────────────┘
每个页面/弹窗都是一个 ESGUI_MenuPage_T 或 ESGUI_PopWindow_T,内含一个 const esgui_page_vtable_t *vtbl:
typedef struct {
void (*on_create)(ESGUI_MenuPage_T *page); // 分配资源,初始化页面
void (*on_destroy)(ESGUI_MenuPage_T *page); // 释放资源
void (*on_draw)(ESGUI_MenuPage_T *page); // 绘制背景 + 所有项
uint16_t (*special_item_draw)(...); // 特殊条目标记绘制
uint16_t (*get_special_item_draw_w)(...); // 特殊条目宽度计算
ESGUI_MenuAction_T (*on_input)(...); // 按键/编码器输入处理
void (*on_focus_change)(...); // 焦点变化通知(启动动画)
void (*on_page_chenge)(...); // 页面切换回调(Push/Pop 动画)
} esgui_page_vtable_t;这意味着:
- 你可以只替换
on_draw实现一套完全不同的视觉风格(比如从列表改为网格)。 - 可以只替换
on_input实现自定义的按键逻辑(比如旋转编码器带加速度)。 - 可以继承默认实现,只覆盖其中一两个函数,复用其余代码。
- ESGUI_DefaultConfig.c 只是框架附带的一套"默认皮肤",不是框架本身。你可以完全删掉它,用自己的 vtable 实现替代。
ESGUI_Git/
├── ESGUI/ # 框架核心源码
│ ├── ESGUI.c / ESGUI.h # 框架入口:Init / FeedKey / Tick / 命令队列 / Async / Overlay
│ ├── ESGUI_Menu.c / .h # 菜单控制器:页面栈、弹窗、动作分发
│ ├── ESGUI_Event.c / .h # 事件定义(按键/编码器/触摸)
│ ├── ESGUI_Anim.c / .h # 动画引擎:缓动曲线、静态池、千分比插值
│ ├── ESGUI_DefaultConfig.c / .h # 【默认虚函数表】文本/BMP/3D 菜单 + 5 种弹窗(原 ESGUI_PageDefaltVtbl.c 更名)
│ ├── ESGUI_DefaultConfig.h # 【编译配置】尺寸、开关、动画参数、多线程、命令队列、3D
│ ├── ESGUI_Widget.c / .h # 控件:进度条、焦点框、复选框、单选框
│ ├── ESGUI_3D.c / .h # 3D 线框渲染:定点透视投影、模型变换
│ ├── ESGUI_UseCanvas.c / .h # Canvas 适配层:绑定分块刷新到 ESGUI
│ ├── BSP/
│ │ ├── ESGUI_BSP_Canvas.c / .h # 画布与分块刷新引擎
│ │ ├── ESGUI_BSP_draw.c / .h # 基础图元:点、线、矩形、圆、圆角矩形、三角形
│ │ ├── ESGUI_BSP_Text.c / .h # UTF-8 文本渲染与页式字模
│ │ └── ESGUI_BSP_BMP.c / .h # 1bpp 页式位图绘制
│ └── Font/
│ ├── eui_test_font.c / .h # 示例字体(12px,英文+部分中文)
│ └── ... # 用户生成的自定义字体
├── ESGUI字体生成器.exe # 字体生成工具
└── example/ # 示例工程(STM32 / ESP32 / 思澈 等)
├── app/
│ ├── page/ # 【V2.0.0 新增】菜单页面描述(文本页/BMP页/3D页等)
│ └── model/ # 【V2.0.0 新增】3D 模型数据(顶点/边数组)
└── ...
ESGUI_DefaultConfig.h 控制默认虚函数表与框架行为的编译内容,所有宏均可用 -D 在编译命令行覆盖。
| 宏 | 默认值 | 说明 |
|---|---|---|
ESGUI_ENABLE_MULTITHREAD |
1 | 1=Async 走无锁队列(多线程);0=退化为直接同步调用(单线程,零队列开销) |
ESGUI_CMD_QUEUE_SIZE |
32 | 主命令队列大小(须 2 的幂) |
ESGUI_SYNC_MODE |
0 | 0=编译器原子(GCC/Clang,多核安全);1=纯软件 volatile(任何编译器,单核) |
ESGUI_MAX_PRODUCER |
4 | 生产者收件箱最大数量(多生产者收拢) |
ESGUI_PRODUCER_BOX_CAP |
8 | 单个收件箱容量(须 2 的幂) |
| 宏 | 默认值 | 说明 |
|---|---|---|
ESGUI_MAX_MENU_DEPTH |
8 | 菜单栈最大深度(可在 ESGUI_Menu.h 独立调整) |
ESGUI_ENABLE_TEXT_MENU |
1 | 默认文本菜单页面 |
ESGUI_ENABLE_BMP_MENU |
1 | 默认图形(BMP)菜单页面 |
ESGUI_ENABLE_POPUP_MESSAGE |
1 | 消息弹窗 |
ESGUI_ENABLE_POPUP_BOOL |
1 | 布尔弹窗(OK/Cancel) |
ESGUI_ENABLE_POPUP_VALUE |
1 | 数值调节弹窗 |
ESGUI_ENABLE_POPUP_TEXTLIST |
1 | 文本列表弹窗 |
ESGUI_ENABLE_POPUP_BMPLIST |
1 | 图片列表弹窗 |
| 宏 | 默认值 | 说明 |
|---|---|---|
ESGUI_ENABLE_3D |
1 | 3D 线框渲染模块 |
ESGUI_ENABLE_3D_MENU |
1 | 3D 菜单页面(需同时开启 ESGUI_ENABLE_3D) |
ESGUI_MAX_OVERLAY |
4 | Overlay 覆盖层最大数量 |
ESGUI_ENABLE_DRAW_TRIANGLE |
1 | 三角形绘制图元 |
ESGUI_ANIM_ENABLE_OVERSHOOT |
1 | 冲过缓动曲线 |
ESGUI_ANIM_ENABLE_BOUNCE |
1 | 弹跳缓动曲线 |
ESGUI_ANIM_ENABLE_STEP |
1 | 阶跃缓动曲线 |
| 宏 | 默认值 | 说明 |
|---|---|---|
ESGUI_PAGE_TRANSITION_TYPE |
0 | 0=百叶窗淡入淡出,1=缩放过渡 |
ESGUI_PAGE_TRANSITION_ANIM_TIME |
250 | 页面过渡动画时长(ms) |
ESGUI_ITEM_SPACING |
3 | 文本条目间距(像素) |
在编译命令中加
-DESGUI_ENABLE_TEXT_MENU=0 -DESGUI_ENABLE_3D=0即可完全剔除文本菜单与 3D 代码,显著减小 Flash。
#include "ESGUI.h"
#include "ESGUI_UseCanvas.h"
#define SCREEN_W 128
#define SCREEN_H 64
#define STRIP_H 16 // 条带高度(8 的倍数,越大 RAM 占用越高)
uint8_t canvas_buf[SCREEN_W * STRIP_H / 8]; // 条带显存
Canvas canvas;
CanvasStripIter canvas_iter;
ESGUI_T ui;/* 回调:按 OK 进入下一页 */
ESGUI_MenuAction_T on_enter_sub(ESGUI_MenuPage_T *page, void *arg) {
extern ESGUI_MenuPage_T sub_page;
return (ESGUI_MenuAction_T){ACT_PUSH_PAGE, &sub_page};
}
/* 回调:按 BACK 返回 */
ESGUI_MenuAction_T on_pop(ESGUI_MenuPage_T *page, void *arg) {
return (ESGUI_MenuAction_T){ACT_POP_PAGE, NULL};
}
/* 条目结构:{x, y, label, icon, on_enter, arg} */
ESGUI_MenuItem_T main_items[] = {
{0, 0, "Setting", NULL, on_enter_sub, NULL},
{0, 0, "Info", NULL, on_enter_sub, NULL},
{0, 0, "Exit", NULL, on_pop, NULL},
};
ESGUI_MenuPage_T main_page;void ui_init(void) {
/* 绑定画布(分块刷新) */
ESGUI_BindCanvas(&ui, &canvas, &canvas_iter,
canvas_buf, SCREEN_W, SCREEN_H, STRIP_H);
/* 创建默认文本菜单页面(使用默认 vtable) */
ESGUI_DefaltTextMenuCreate(&main_page, main_items, "Main Menu", 3);
/* 初始化 UI,传入首页面 */
ESGUI_Init(&ui, &main_page, ESGUI_CanvasRefresh_CB, ESGUI_AnimTick_CB);
}
/* 主循环(1ms ~ 10ms 周期) */
void ui_loop(uint32_t tick_ms, ESGUI_EventCode_t key) {
ESGUI_FeedKey(&ui, key, tick_ms); // 输入事件(多线程模式下入队,可改由任意任务调用)
ESGUI_Tick(&ui, tick_ms); // 动画 + 绘制(必须由唯一 UI 线程调用)
}唯一需要用户实现的弱函数(__WEAK):
/* 送屏回调:将当前条带数据写入 OLED */
void ESGUI_UseCanvasFlush(int x0, int y0, int x1, int y1,
const uint8_t *buff, void *user) {
// 例如:SSD1315_I2C_Write(x0, y0, x1, y1, buff);
}ESGUI 采用单 UI 线程 + 无锁命令队列(Actor / 消息传递模式,与 LVGL 一致):
任意任务/中断 ──投递命令──> 无锁队列 ──> ESGUI_Tick(唯一消费者)──> 动画 + 绘制
所有跨任务交互都通过"投递命令"完成,不共享任何 UI 状态,因此不需要锁、不需要上下文 ID、不依赖 RTOS。库内无任何 __asm__ / 关中断 / 硬件原子指令(纯软件模式下),完全可移植。
#define ESGUI_ENABLE_MULTITHREAD 1 // 1=开启(默认);0=关闭,Async 退化为直接同步调用#define ESGUI_SYNC_MODE 0
// 0 = 自动适配(默认):
// GCC/Clang/ARMCC6 → 内建原子 __atomic_*(多核安全,生产/消费可分核运行)
// 其他编译器(MSVC/IAR/ARMCC5)→ 自动退化为 volatile(单核语义)
// 1 = 纯软件:仅用 volatile,零内建原子、零 asm、零 RTOS,任何 C 编译器可用
// (限定:单核 + 单生产者 + 单消费者)只有一个任务/中断调用 UI 接口时,直接使用 Async 函数即可(从任意任务或中断调用,无锁、不阻塞):
/* 任意任务 / 中断中 */
ESGUI_FeedKey(&ui, EVT_KEY_OK, tick_ms); // 按键
ESGUI_PushPageAsync(&ui, &sub_page); // 入栈
ESGUI_PopPageAsync(&ui); // 出栈
ESGUI_ShowPopupAsync(&ui, &popup); // 弹窗
ESGUI_ClosePopupAsync(&ui); // 关弹窗
ESGUI_HandleActionAsync(&ui, (ESGUI_MenuAction_T){ACT_REFRESH, NULL});
ESGUI_OverlayAddAsync(&ui, &overlay); // 覆盖层
ESGUI_AnimStartAsync(&ui, &anim_cfg); // 动画UI 线程只需照常调用 ESGUI_Tick,命令在 Tick 内统一出队处理。
C 语言没有"当前任务"概念,固定签名的 Async 函数无法自动识别调用者,因此多任务场景采用每任务一个 SPSC 收件箱,UI 线程统一收拢——每个收件箱仍满足"单生产者",同步原语不变:
任务A ──> boxA(SPSC)──┐
任务B ──> boxB(SPSC)──┼──> ESGUI_Tick 轮询所有收件箱 → 统一处理(唯一消费者)
任务C ──> boxC(SPSC)──┘
static ESGUI_ProducerBox_T net_box; /* 例:网络任务专属收件箱 */
/* init 阶段(任务启动前,单线程)执行一次 */
void app_init(void) {
ESGUI_ProducerBoxInit(&net_box);
ESGUI_ProducerBoxRegister(&ui, &net_box); /* 上限 ESGUI_MAX_PRODUCER */
}
/* 网络任务内(任意时刻,无锁) */
void net_task(void *arg) {
...
ESGUI_ProducerBoxPushAction(&net_box,
(ESGUI_MenuAction_T){ACT_SHOW_POPUP, &pop_window}); /* 弹窗/切页等动作 */
ESGUI_ProducerBoxPushKey(&net_box, EVT_KEY_OK, tick); /* 按键事件 */
/* 或构造任意命令:ESGUI_ProducerBoxPush(&net_box, &cmd); */
}UI 线程侧无需任何额外代码,ESGUI_Tick 会自动"先清主队列、再轮询所有已注册收件箱",命令与主队列走同一处理逻辑。
- 单消费者:
ESGUI_Tick只能由唯一的 UI 线程调用;所有绘制、动画、页面/弹窗操作都在其中完成。 - 主命令队列单生产者:
ESGUI_FeedKey/ESGUI_*Async只能由一个任务/中断调用;多个任务请用收件箱(且每个收件箱只允许其所属任务写入,不能两个任务共用一个箱)。 - 纯软件模式单核:
ESGUI_SYNC_MODE=1时生产与消费必须在同一 CPU 核上;双核分核运行(如 ESP32-S3 双核)必须用=0(编译器原子)。 - 原子模式依赖编译器:
ESGUI_SYNC_MODE=0需要 GCC/Clang 内建原子(GCC、Clang、ARM Compiler 6、新版本 IAR 均支持);MSVC/老 IAR/ARMCC5 请用=1。 - 生命周期约定:
ESGUI_AnimStartAsync传入的anim_t *配置必须保持有效,直到 UI 线程处理完该命令(用静态/全局变量,不要用栈上临时变量)。 - 初始化时序:
ESGUI_Init、ESGUI_BindCanvas、ESGUI_ProducerBoxRegister等共享状态写入必须在任务启动前、单线程的 init 阶段完成。 - 队列满丢弃:主队列/收件箱满时新命令被直接丢弃(不阻塞调用方),高频投递场景请调大容量(须 2 的幂)。
- 关闭多线程:
ESGUI_ENABLE_MULTITHREAD=0时 Async 函数直接同步执行,只能在单线程主循环内调用。
ESGUI_3D.c/.h 提供零浮点的定点透视投影线框渲染:
#include "ESGUI_3D.h"
/* 模型:局部坐标顶点 + 边 */
static const ESGUI_3DPoint_T cube_pts[] = {
{-10,-10,-10}, { 10,-10,-10}, { 10,-10, 10}, {-10,-10, 10},
{-10, 10,-10}, { 10, 10,-10}, { 10, 10, 10}, {-10, 10, 10},
};
static const ESGUI_3DEdge_T cube_edges[] = {
{0,1},{1,2},{2,3},{3,0},{4,5},{5,6},{6,7},{7,4},
{0,4},{1,5},{2,6},{3,7},
};
static const ESGUI_3D_T cube = { cube_pts, 8, cube_edges, 12 };
/* 变换:平移 + 旋转 + 缩放(全定点) */
ESGUI_3DTransform_T t;
ESGUI_3DTransformInit(&t);
t.ty = 15; //沿y轴平移(右手坐标系)
t.scale_q8 = 256; //缩放倍率为1,即模型原始大小
ESGUI_3DTransformRotate(&t, ESGUI_3D_AXIS_Y, 30); //旋转
/* 绘制(恒等变换时零变换开销) */
ESGUI_3DWireframeDiagram(&canvas, &cube, &t, 80, 128, 64, EUI_MODE_SET);- 投影、三角(
ESGUI_3DTransformPoints)、旋转全部定点整数运算,无float/sqrt。 - 内置 3D 菜单页面:
ESGUI_Default3DMenuCreate(&page, title, items, num),焦点模型带旋转动画。 - 模型数据放入示例工程
model/目录。
覆盖层是常驻显示组件,独立于页面/弹窗,始终叠加在最上层(注册顺序即 z-order,先注册的在底层):
typedef struct {
void (*on_draw)(ESGUI_Overlay_T *ov); /* 绘制回调(必填) */
void *render_ctx; /* 渲染上下文(框架注入) */
void *user_data; /* 用户私有数据 */
uint8_t visible : 1; /* 1=可见 */
uint8_t always_dirty : 1; /* 1=每帧强制重绘(常驻动态内容) */
} ESGUI_Overlay_T;static ESGUI_Overlay_T clock_ov = { .on_draw = clock_on_draw, .always_dirty = 1 };
/* 任意任务 / 中断(异步) */
ESGUI_OverlayAddAsync(&ui, &clock_ov);
ESGUI_OverlaySetVisibleAsync(&ui, &clock_ov, false);
ESGUI_OverlayRemoveAsync(&ui, &clock_ov);
/* 或 UI 线程内同步 */
ESGUI_OverlayAdd(&ui, &clock_ov);数量上限由 ESGUI_MAX_OVERLAY 配置(默认 4)。
ESGUI_Tick 函数中,框架已把 ui->draw_ctx 注入ESGUI_Overlay_T的render_ctx成员:
static void hud_on_draw(ESGUI_Overlay_T *ov) {
Your_draw_ctx *c = ov->render_ctx; /* 框架自动注入 ui->draw_ctx* */
Your_ui_draw_hline(c, 0, 127, 10); /* 画一条 HUD 分隔线 */
}注意:
render_ctx由框架在每次刷新前自动注入,不要手动赋值。
如果你不满意默认效果,不需要修改框架核心,只需提供自己的 vtable:
/* 自定义绘制:比如把文本菜单改成横向标签页 */
void my_custom_draw(ESGUI_MenuPage_T *page) {
// 完全自定义的绘制逻辑,可调用 BSP 层的 eui_draw_xxx
}
/* 自定义输入:比如把上下键改成左右键控制 */
ESGUI_MenuAction_T my_custom_input(ESGUI_MenuPage_T *page, ESGUI_EventCode_t e) {
// ...
}
static const esgui_page_vtable_t my_vtable = {
.on_create = esgui_text_menu_defalt_on_create, // 复用默认创建逻辑
.on_destroy = esgui_text_menu_defalt_on_destroy,
.on_draw = my_custom_draw, // 替换为自己的绘制
.on_input = my_custom_input, // 替换为自己的输入
.on_focus_change = esgui_text_menu_defalt_on_focus_change,
.on_page_chenge = esgui_text_menu_default_on_page_change,
};
/* 创建页面时绑定自定义 vtable */
void MyTextMenuCreate(ESGUI_MenuPage_T *page, ...) {
memset(page, 0, sizeof(*page));
page->vtbl = &my_vtable;
// ...
}动画系统同样可被自定义:
- 默认效果使用
ESGUI_Anim.c中的anim_start()驱动焦点框、进度条、页面过渡。 - 你可以在自定义 vtable 中调用
anim_start(),也可以完全不用动画系统,直接同步修改属性。
ESGUI 内置轻量级动画引擎,所有视觉变化(焦点框移动、进度条增长、页面滑入、弹窗弹出)均由动画驱动。
anim_t a = {0};
anim_set_var(&a, &my_var); // 关联目标变量
anim_set_exec_cb(&a, my_callback); // 每帧写入回调
anim_set_values(&a, 0, 100); // 起始值 → 目标值
anim_set_time(&a, 300); // 300ms
anim_set_path(&a, ANIM_PATH_EASE_OUT); // 缓动曲线
anim_start(&a); // 启动(若同变量已有动画,自动热更新终点)- must_complete:页面退出动画可标记为必须完成,框架会阻塞真正的
Pop/Destroy直到动画结束,防止画面撕裂。 - 热更新:编码器快速转动时,同一变量不会启动多个动画,而是直接修改终点并延续。
文本菜单支持在条目字符串尾部附加标记符,自动渲染控件:
/* 标记格式:文本 + '\x03' + '/' + 类型码 */
{"WiFi\x03/0", NULL, on_toggle, &wifi_en}, // 方形复选框
{"Mode\x03/1", NULL, on_toggle, &mode_sel}, // 圆形单选框
{"Vol \x03/2", NULL, NULL, &volume}, // 数值显示(arg 指向 int16)ESGUI 使用 页式 1bpp 字模(与 SSD1315 GDDRAM 同布局),工具链如下:
- 位图用 PCtoLCD2002 / Image2LCD 生成常规水平字模(高位在前、从左到右、从上到下)。
- 字体使用本仓库提供的
ESGUI字体生成器.exe将水平字模转换为 页式格式。 - 将生成的
.c文件放入ESGUI/Font/,并在代码中替换ESGUI_DEFAULT_FONT宏。
BMP 菜单使用的图片同样需为 页式 1bpp,可用上述工具生成。
example/ 目录包含多个可编译工程模板(STM32 HAL / 标准库 / ESP-IDF / 思澈),使用框架默认虚函数表,演示:
- 文本菜单 / BMP 菜单 / 3D 菜单切换
- 五种弹窗
- 长文本滚动与页面过渡动画
- 分块刷新绑定 SSD1315
- 多线程(Async + 生产者收件箱,基于 FreeRTOS / RT-Thread)
- 3D 线框图绘制与旋转
V2.0.0 起,示例工程的用户代码统一按功能分目录组织:
example/<芯片>/app/
├── page/ # 菜单页面描述(text_page.c、bmp_page.c、model_page.c 等)
└── model/ # 3D 模型数据(顶点/边数组)
https://www.bilibili.com/video/BV1xFTw6iEbj?vd_source=605419deeeaedb82f5f8918bda063219
本项目采用 MIT License,可自由用于商业或非商业项目。
详见仓库根目录 LICENSE 文件。
欢迎提交 Issue 或 PR。如果你制作了新的页面类型、缓动曲线、3D 模型或硬件适配层,欢迎分享!