事件循环与输入处理¶
概述¶
EmbeddedGUI 采用轮询式事件循环,由 egui_polling_work(core) 驱动。输入事件(触摸、键盘)通过队列缓冲,在主循环中统一分发到视图树。这种设计避免了中断上下文中的复杂处理,适合嵌入式系统的单线程模型。
主循环¶
egui_polling_work(core) 工作流程¶
egui_polling_work(core)
|
+-- egui_timer_polling_work(core) // 1. 处理定时器和动画
|
+-- egui_input_polling_work(core) // 2. 处理触摸输入队列
|
+-- egui_input_key_dispatch_work(core) // 3. 处理键盘输入队列
对应源码(src/core/egui_core.c):
void egui_polling_work(egui_core_t *core)
{
egui_timer_polling_work(core);
#if EGUI_CONFIG_FUNCTION_SUPPORT_TOUCH
if (!egui_input_check_idle(core))
{
egui_input_polling_work(core);
}
#endif
#if EGUI_CONFIG_FUNCTION_SUPPORT_KEY
if (!egui_input_check_key_idle(core))
{
egui_input_key_dispatch_work(core);
}
#endif
}
完整帧循环¶
在 porting 层,典型的主循环如下:
while (1)
{
egui_polling_work(&core); // 处理事件和定时器
if (egui_check_need_refresh(&core)) // 检查是否有脏区域
{
egui_core_draw_view_group_pre_work(&core); // 计算布局
egui_polling_refresh_display(&core); // PFB 分块渲染
}
}
触摸事件¶
事件类型¶
定义在 src/core/egui_motion_event.h:
事件类型 |
值 |
说明 |
|---|---|---|
|
1 |
手指按下 |
|
2 |
手指抬起 |
|
3 |
手指移动 |
|
4 |
事件取消 |
启用多点触控后还支持 ACTION_POINTER_DOWN、ACTION_POINTER_UP 和 ACTION_SCROLL。
事件结构体¶
struct egui_motion_event
{
egui_snode_t node; // 链表节点
uint8_t type; // 事件类型
uint32_t timestamp; // 时间戳 (ms)
egui_location_t location; // 屏幕坐标
};
输入注入¶
外部(中断或驱动)通过以下 API 向输入队列添加事件:
// 添加触摸事件
int egui_input_add_motion(uint8_t type, egui_dim_t x, egui_dim_t y);
// 多点触控
int egui_input_add_motion_multi(uint8_t type, uint8_t pointer_count,
egui_dim_t x1, egui_dim_t y1,
egui_dim_t x2, egui_dim_t y2);
事件存储在基于对象池的单向链表中,连续的 MOVE 事件会自动合并以减少队列压力。
Velocity Tracker¶
系统内置速度追踪器 egui_velocity_tracker_t,在每次 add_motion 时自动记录采样点。抬手时可获取滑动速度,用于惯性滚动等效果:
egui_float_t vx = egui_input_get_velocity_x(); // 像素/毫秒
egui_float_t vy = egui_input_get_velocity_y();
追踪器保留最近 200ms 内的采样点(EGUI_VELOCITY_TRACKER_LONGEST_PAST_TIME),通过线性回归计算速度。
事件分发机制¶
触摸事件的分发遵循 Android 风格的责任链模式,从根视图向下传递:
egui_core_process_input_motion(event)
|
v
root_view_group.dispatch_touch_event(event)
|
+-- on_intercept_touch_event() // group 是否拦截?
| |-- 返回 1: group 自己处理
| |-- 返回 0: 继续向下分发
|
+-- 遍历子视图(从后往前,即 Z 轴最高的优先)
| +-- 命中测试:event 坐标是否在子视图区域内
| +-- child.dispatch_touch_event(event)
| |-- api->on_touch 优先处理(可通过 override API 覆写)
| |-- on_touch_event 默认处理
|
+-- 无子视图消费 -> group.on_touch_event(event)
点击事件¶
当一个 clickable 的视图收到完整的 DOWN -> UP 序列时,触发点击:
int egui_view_on_touch_event(egui_view_t *self, egui_motion_event_t *event)
{
if (self->is_clickable)
{
switch (event->type)
{
case EGUI_MOTION_EVENT_ACTION_DOWN:
egui_view_set_pressed(self, true);
break;
case EGUI_MOTION_EVENT_ACTION_UP:
egui_view_set_pressed(self, false);
egui_view_perform_click(self); // 触发 on_click_listener
break;
}
return 1; // 消费事件
}
return 0;
}
键盘事件¶
启用 EGUI_CONFIG_FUNCTION_SUPPORT_KEY 后支持键盘输入。
事件类型¶
事件类型 |
说明 |
|---|---|
|
按键按下 |
|
按键释放 |
|
长按 |
|
重复 |
键码¶
系统定义了完整的键码枚举(enum egui_key_code),包括:
导航键:UP / DOWN / LEFT / RIGHT / TAB
动作键:ENTER / ESCAPE / BACKSPACE / DELETE / SPACE
数字键:0-9
字母键:A-Z
特殊字符:PERIOD / COMMA / MINUS 等
键盘事件分发¶
// 注入键盘事件
int egui_input_add_key(uint8_t type, uint8_t key_code,
uint8_t is_shift, uint8_t is_ctrl);
分发流程:
TAB 键触发焦点导航(需启用
EGUI_CONFIG_FUNCTION_SUPPORT_FOCUS)有焦点视图时,事件分发到焦点视图
无焦点时,事件分发到根视图组
ENTER 键在 clickable 视图上触发点击
点击监听器和触摸回调¶
点击监听器¶
最常用的交互方式,设置后视图自动变为 clickable:
static void on_my_button_click(egui_view_t *self)
{
// 处理点击
}
egui_view_set_on_click_listener(EGUI_VIEW_OF(&my_button), on_my_button_click);
触摸回调覆写¶
需要处理原始触摸事件时,可通过 egui_view_override_api_on_touch() 复制并覆写当前 view API 的 on_touch 回调;该回调优先于默认的 on_touch_event 执行:
static egui_view_api_t my_view_touch_api;
static int on_my_view_touch(egui_view_t *self, egui_motion_event_t *event)
{
switch (event->type)
{
case EGUI_MOTION_EVENT_ACTION_DOWN:
// 处理按下
return 1; // 返回 1 表示消费事件
case EGUI_MOTION_EVENT_ACTION_MOVE:
// 处理移动
return 1;
case EGUI_MOTION_EVENT_ACTION_UP:
// 处理抬起
return 1;
}
return 0; // 返回 0 表示不消费,继续默认处理
}
egui_view_override_api_on_touch(EGUI_VIEW_OF(&my_view), &my_view_touch_api, on_my_view_touch);
输入相关配置¶
配置宏 |
说明 |
|---|---|
|
启用触摸支持 |
|
启用键盘支持 |
|
启用多点触控 |
|
启用焦点管理 |
|
触摸事件缓冲池大小 |
|
键盘事件缓冲池大小 |