事件系统
事件系统位于 core/event.h,由 core::dsl::Runtime 在每次 update 时统一驱动。页面和组件不直接读 GLFW 鼠标状态,也不自己维护 hover / pressed。
PointerEvent
struct PointerEvent {
double x;
double y;
double deltaX;
double deltaY;
bool down;
bool pressedThisFrame;
bool releasedThisFrame;
};
x / y:当前指针坐标,已按pointerScale转换到 framebuffer 命中测试坐标。deltaX / deltaY:与上一次读取位置的差值。down:鼠标左键当前是否按下。pressedThisFrame:本帧从未按下变为按下。releasedThisFrame:本帧从按下变为未按下。
当前封装了鼠标左键 pointer、文本输入、滚轮和拖拽事件。右键、多指针、IME 组合态显示还没有下沉。
InteractionState
struct InteractionState {
bool hover;
bool pressed;
bool clicked;
bool pressStarted;
bool released;
bool drag;
bool active;
bool changed;
double dragStartX;
double dragStartY;
double dragDeltaX;
double dragDeltaY;
};
hover:当前指针命中该元素,并且它是最上层命中的 interactive 元素。pressed:该元素处于按下捕获状态,且鼠标左键仍按住。clicked:在该元素上按下,释放时仍回到该元素范围内。pressStarted:本帧在该元素上开始按下。released:本帧释放了该元素的按下捕获。active:该元素捕获了当前按下序列。drag:按下后移动超过 2 像素。changed:交互状态在本帧发生变化。
按下捕获是底层交互系统的一部分:在某个元素上按下后,拖动期间不会让其它元素抢走这次按下序列。拖出再释放不会触发 click,拖出后再拖回元素内释放会触发 click。
DSL 写法
通用 builder 现在支持交互:
ui.text("link")
.size(240.0f, 32.0f)
.text("Open project")
.interactive()
.onClick([] {
core::platform::openUrl("https://github.com/sudoevolve/EUI-NEO");
})
.build();
Row / Column / Stack / Rect / Text / Image 都可以通过 .interactive() 和 .onClick(...) 进入命中测试。.onClick(...) 会自动开启 interactive,并把 cursor 设置为手型。
通用交互方法:
.interactive(true)
.disabled(false)
.enabled(true)
.cursor(core::CursorShape::Hand)
.onClick(callback)
.focusable()
.onFocusChanged(callback)
.onTextInput(callback)
.onScroll(callback)
.onDrag(callback)
Rect 状态
只有 Rect 目前内置 hover / pressed 视觉状态:
ui.rect("button.bg")
.size(240.0f, 70.0f)
.states(normal, hover, pressed)
.transition(0.16f, core::Ease::OutCubic)
.onClick(callback)
.build();
.states(normal, hover, pressed) 会设置:
- normal color
- hover color
- pressed color
interactive = true- 手型 cursor
Runtime 使用 SmoothedValue<float> 维护 hover / press blend,再混合颜色:
normal -> hover -> pressed
组件中的事件
组件不要自己读鼠标,也不要保存 InteractionState。组件只声明 DSL 树,把点击回调交给 Runtime:
ui.stack(id)
.size(w, h)
.visualStateFrom(id + ".bg", 0.965f)
.content([&] {
ui.rect(id + ".bg")
.size(w, h)
.states(normal, hover, pressed)
.onClick(onClick_)
.build();
ui.text(id + ".label")
.size(w, h)
.text(text_)
.build();
})
.build();
components::button(ui, id) 就是这种写法:内部背景 rect 负责状态和点击,外层 stack 用 visualStateFrom 跟随按压缩放。
Runtime 流程
每次 update 大致流程:
1. readPointerEvent(window, pointerScale) 读取 pointer。 2. consumeInputEvents() 消费 GLFW char / key / scroll callback 收集到的输入。 3. 鼠标按下时更新 focused element。 4. 从 DSL 树中找出最上层命中的 interactive 元素;如果已有 active 捕获元素,则优先使用捕获元素。 5. 更新每个 interactive 元素的 InteractionState。 6. 派发 onClick / onDrag / onScroll / onTextInput,并在需要时设置 needsCompose()。 7. 根据 hover 设置 GLFW 手型 cursor。 8. Rect 根据交互状态推进 hover / press 缓动并标记 dirty rect。
外部链接
跨平台打开 URL 使用 core/platform.h:
#include "core/platform.h"
core::platform::openUrl("https://github.com/sudoevolve/EUI-NEO");
当前实现:
- Windows:
ShellExecuteA,CMake 链接shell32。 - Linux:调用
xdg-open。 - macOS:调用
open。
openUrl 返回 bool 表示是否成功发起打开动作。它不等待浏览器或外部程序结束。
与脏区渲染
交互状态变化不会默认整页重绘:
- hover / press:只标记对应 rect 的 dirty rect。
- transition:标记旧视觉区域和新视觉区域的 union。
- click 修改 app 状态:Runtime 设置
needsCompose(),app/dsl_app.h重新 compose,并保守触发 full redraw。
当前限制
- 已有基础键盘文本输入、focus manager、选择和剪贴板事件;IME 组合态和撤销栈还没做。
- 没有事件冒泡和捕获阶段。
- 没有公开 hover 回调;拖拽通过
.onDrag(...)支持。 - transform 后的 hit-test 仍按布局矩形计算。
- Text / Image 可以接收 click,但没有内置 hover / pressed 视觉状态;需要视觉状态时通常叠一个 rect 或用组件封装。