Event System
This page explains how input events flow through Guido.
Event Flow
Wayland → Platform → App → Widget Tree
│
├─ MouseMove
├─ MouseEnter/MouseLeave
├─ MouseDown/MouseUp
└─ Scroll
Event Types
Mouse Movement
#![allow(unused)]
fn main() {
Event::MouseMove { x, y }
Event::MouseEnter
Event::MouseLeave
}
Tracked for hover states. The platform layer determines which widget the cursor is over.
Mouse Buttons
#![allow(unused)]
fn main() {
Event::MouseDown { x, y, button }
Event::MouseUp { x, y, button }
}
Used for click detection and pressed states.
Scrolling
#![allow(unused)]
fn main() {
Event::Scroll { dx, dy, source }
}
dx- Horizontal scroll amountdy- Vertical scroll amountsource- Wheel or touchpad
Event Propagation
Events propagate from children to parents (bubble up):
- Event received at root
- Hit test finds deepest widget under cursor
- Event sent to that widget first
- If not handled, bubbles to parent
- Continues until handled or reaches root
#![allow(unused)]
fn main() {
fn event(&mut self, tree: &mut Tree, id: WidgetId, event: &Event) -> EventResponse {
// Check children first (innermost)
for &child_id in self.children.iter().rev() {
// Get child bounds from Tree
let child_bounds = tree.get_bounds(child_id).unwrap_or_default();
if child_bounds.contains(event.position()) {
let response = tree.with_widget_mut(child_id, |child, cid, tree| {
child.event(tree, cid, event)
});
if response == Some(EventResponse::Handled) {
return EventResponse::Handled;
}
}
}
// Then handle locally
if self.handles_event(event) {
return EventResponse::Handled;
}
EventResponse::Ignored
}
}
Hit Testing
Basic Hit Test
#![allow(unused)]
fn main() {
fn contains(&self, x: f32, y: f32) -> bool {
x >= self.x && x <= self.x + self.width &&
y >= self.y && y <= self.y + self.height
}
}
With Corner Radius
Clicks outside rounded corners don’t register:
#![allow(unused)]
fn main() {
// SDF-based hit test
let dist = sdf_rounded_rect(point, bounds, radius, k);
dist <= 0.0 // Inside if distance is negative
}
With Transforms
Inverse transform applied to test point:
#![allow(unused)]
fn main() {
fn contains_transformed(&self, x: f32, y: f32) -> bool {
let (local_x, local_y) = self.transform.inverse().transform_point(x, y);
self.bounds.contains(local_x, local_y)
}
}
Event Handlers
Containers register callbacks:
#![allow(unused)]
fn main() {
container()
.on_click(|| println!("Clicked!"))
.on_hover(|hovered| println!("Hover: {}", hovered))
.on_scroll(|dx, dy, source| println!("Scroll"))
}
Internally stored as optional closures:
#![allow(unused)]
fn main() {
pub struct Container {
on_click: Option<Box<dyn Fn()>>,
on_hover: Option<Box<dyn Fn(bool)>>,
on_scroll: Option<Box<dyn Fn(f32, f32, ScrollSource)>>,
}
}
State Layer Integration
The state layer system uses events internally:
- MouseEnter → Set hover state true
- MouseLeave → Set hover state false
- MouseDown → Set pressed state true, record click point
- MouseUp → Set pressed state false, trigger ripple contraction
#![allow(unused)]
fn main() {
fn event(&mut self, event: &Event) -> EventResponse {
match event {
Event::MouseEnter => {
self.hover_state = true;
}
Event::MouseDown { x, y, .. } => {
self.pressed_state = true;
self.press_point = Some((*x, *y));
}
// ...
}
}
}
EventResponse
Widgets return whether they handled the event:
#![allow(unused)]
fn main() {
pub enum EventResponse {
Handled, // Stop propagation
Ignored, // Continue to parent
}
}
Platform Integration
Wayland Events
The platform layer receives Wayland protocol events:
#![allow(unused)]
fn main() {
// From wl_pointer
fn pointer_motion(x: f32, y: f32) {
self.cursor_x = x;
self.cursor_y = y;
self.dispatch(Event::MouseMove { x, y });
}
fn pointer_button(button: u32, state: ButtonState) {
match state {
ButtonState::Pressed => self.dispatch(Event::MouseDown { ... }),
ButtonState::Released => self.dispatch(Event::MouseUp { ... }),
}
}
}
Event Loop
Uses calloop for event loop integration:
#![allow(unused)]
fn main() {
// Main loop
loop {
// 1. Process Wayland events
event_queue.dispatch_pending()?;
// 2. Layout and paint
widget.layout(constraints);
widget.paint(&mut ctx);
// 3. Render to screen
renderer.render(&ctx);
}
}
Keyboard Events
Currently not implemented. Future work includes:
- Key press/release events
- Focus management
- Text input for text fields