pub enum Event {
Pointer(PointerEvent),
Key(KeyboardEvent),
FileDrag(FileDragEvent),
PointerCapture(PointerCaptureEvent),
Ime(ImeEvent),
Focus(FocusEvent),
Window(WindowEvent),
Interaction(InteractionEvent),
Drag(DragEvent),
Custom(Box<dyn CustomEvent>),
Extracted,
}Expand description
The Floem Events.
§Event System Overview
Floem’s event system is inspired by the DOM event model, with events flowing through the view tree in multiple phases:
§Event Phases
Most events support three phases of propagation:
- Capture: Events travel from root to target, allowing ancestors to intercept early
- Target: The event reaches the actual target element
- Bubble: Events travel from target back to root, allowing ancestors to handle after target
There is also a broadcast phases where events propagate recursively depth first through the element tree. This is used internally only for keyboard events. This way your keyboard event listeners can run even without the view having focus.
Not all events propagate through all phases - see individual variants for details.
§Event Routing
Events are routed through the view tree using different strategies:
- Directed: Route to a specific target with customizable phases (keyboard events to focused view)
- Spatial: Route based on hit-testing at a point to find the top hit and then route directed to the hit (pointer events)
- Broadcast: Route to all views or a subtree (window resize events) that have registered a listener recursively depth first.
§Propagation Control
Event handlers can control propagation using EventCx:
cx.stop_immediate_propagation(): Stop all further propagation, including other listeners on the same target- Returning
EventPropagation::Stop: Stop propagation to next phase (bubble/capture), but allow other listeners on same target cx.prevent_default(): Prevent default browser-like behaviors (tab navigation, clicks, etc.)
§Event Lifecycle
- External events arrive from the window system (pointer, keyboard, window events)
- Events are routed through the view tree based on their type
- Synthetic events may be generated (e.g., Click from PointerDown+Up)
- Default behaviors execute if not prevented (drag thresholds, tab navigation)
- Pending events (synthetic or user-emitted) are processed
Variants§
Pointer(PointerEvent)
Pointer events from mice, pens, and touch input.
§Routing
- Spatial routing: Hit-tested at the pointer location
- Phases: Capture, Target, Bubble
- Exception:
PointerEnterandPointerLeaveuse Target phase only (no bubbling)
§Pointer Capture
While a pointer is down, an element can request pointer capture via
cx.request_pointer_capture(pointer_id). While captured, all pointer events
for that pointer are routed directly to the capturing element, bypassing hit-testing.
§Events
Down: Button pressed (triggers focus update and click tracking)Up: Button released (may generate Click/DoubleClick events)Move: Pointer moved (updates hover state, may start drag if threshold exceeded)Cancel: Gesture cancelled (releases capture, cancels pending clicks)Enter: Pointer entered this element (Target phase only, fired when hover state changes)Leave: Pointer left this element (Target phase only, fired when hover state changes)Scroll: Scroll wheel/touchpad inputGesture: Touchpad gestures (pinch, rotate)
§Default Actions
Call cx.prevent_default() to suppress these behaviors.
PointerEvent::Down: Moves keyboard focus to the hit element (fires syntheticFocusLost/FocusGainedevents). On macOS, shows the context menu on secondary button press.PointerEvent::Move: If the pointer has moved beyond the drag threshold while a button is held, begins a drag operation (DragSourceEvent::Startfires). While a drag is active, fires drag source/target move and enter/leave events as the pointer moves.PointerEvent::Up: Ends any active drag (firesDragSourceEvent::EndorDragSourceEvent::Canceldepending on whether a target accepted). Releases pointer capture unconditionally. On non-macOS platforms, shows the context menu on secondary button release.PointerEvent::Leave: Clears all hover state, firing syntheticPointerLeaveevents for all currently-hovered elements.PointerEvent::Cancel: Aborts any active drag (DragSourceEvent::Cancelfires). Releases pointer capture unconditionally.PointerEvent::Enter,PointerEvent::Scroll,PointerEvent::Gesture: No preventable default action.
§Example
fn handle(event: Event, cx: &mut EventCx) {
match event {
Event::Pointer(PointerEvent::Down(pe)) => {
if let Some(pointer_id) = pe.pointer.pointer_id {
cx.request_pointer_capture(pointer_id);
}
}
_ => {}
}
}Key(KeyboardEvent)
Keyboard events for key presses and releases.
§Routing
Key events are classified as either shortcut-like or typing keys,
which determines their routing strategy. See [KeyEventExt::is_shortcut_like].
§Typing keys (unmodified character input, arrows, etc.)
- Directed to focused element: Capture → Target → Bubble
- If no view has focus, the event is dropped
§Shortcut-like keys (Ctrl/Cmd/Alt combos, F-keys, Escape, Tab, etc.)
- Directed to focused element first: Capture → Target → Bubble
- Fallback: If unconsumed (or no view has focus), dispatched via the listener registry to all views that registered a key event listener. No ordering or propagation is respected in this fallback.
§Default Actions
Call cx.prevent_default() to suppress these behaviors.
Tab(KeyDown, no modifiers): Moves focus to the next focusable element.Shift+Tabmoves focus backwards.Alt+ArrowUp/Down/Left/Right(KeyDown): Directional focus navigation.Space,Enter,NumpadEnter(on key-up or repeat): Generates anInteractionEvent::Clickon the currently focused element. SeeEvent::is_keyboard_trigger.
§Example
fn handle(event: Event, cx: &mut EventCx) {
match event {
Event::Key(KeyboardEvent { key: Key::Character(c), state: KeyState::Down, .. }) => {
if c == "s" {
// Handle a "save" shortcut.
cx.prevent_default();
}
}
_ => {}
}
}FileDrag(FileDragEvent)
File drag and drop events.
§Routing
- Spatial routing: Hit-tested at the drag location
- Phases: Target phase only (no capture/bubble)
§Hover State
FileDrag events maintain separate hover state from pointer events. When a file drag enters the window, pointer hover state is cleared and file drag hover state takes over. On drop or leave, it reverts to pointer hover state.
§Events
Enter: Files dragged over this view (hover enter)Over: Files moved while over this viewLeave: Files dragged away from this view (hover leave)Drop: Files dropped on this view
§Default Actions
No preventable default action for any FileDrag variant.
§Example
fn handle(event: Event) {
if let Event::FileDrag(FileDragEvent::Drop(drop)) = event {
for path in drop.paths.iter() {
println!("Dropped file: {:?}", path);
}
}
}PointerCapture(PointerCaptureEvent)
Pointer capture state changes.
§Routing
- Directed to capture target: Sent only to the view gaining/losing capture
- Phases: Target phase only
§Capture Lifecycle
- View calls
cx.request_pointer_capture(pointer_id)(typically in PointerDown) - After current event completes,
Gainedis sent to the target - All pointer events for that pointer_id are now routed to this view
- Capture is released on PointerUp, PointerCancel, or explicit release
Lostis sent to the view that had capture
§Use Cases
- Implementing draggable elements
- Tracking gestures that extend beyond view boundaries
- Ensuring pointer up events are received even if pointer moves off element
§Default Actions
No preventable default action for any PointerCapture variant.
§Example
fn handle(event: Event, cx: &mut EventCx) {
if let Event::PointerCapture(PointerCaptureEvent::Gained(drag_token)) = event {
// Now we have capture, start tracking the drag
cx.start_drag(drag_token, DragConfig::default(), true);
}
}Ime(ImeEvent)
Input Method Editor (IME) events for composing text in languages like Chinese, Japanese, Korean.
§Routing
- Directed to focused view: Sent to the currently focused text input view
- Phases: Capture, Target, Bubble
§IME Composition
IME allows users to compose complex characters through multiple keystrokes. This is used for:
- Complex language input (Chinese, Japanese, Korean, etc.)
- Emoji pickers on some platforms
- Dead key combinations (accented characters)
Composition lifecycle:
Enabled: IME composition startedPreedit: Composition text updated (user is still typing)Commit: Final text committed (composition complete)Disabled: IME composition ended
§Default Actions
No preventable default action for any Ime variant.
§Example
fn handle(event: Event) {
match event {
Event::Ime(ImeEvent::Commit(text)) => {
insert_text(&text);
}
Event::Ime(ImeEvent::Preedit { text, cursor }) => {
show_preedit(&text, cursor);
}
_ => {}
}
}Focus(FocusEvent)
Focus-related events fired when keyboard focus changes.
§Routing
- Directed through focus path: Sent to views in the focus ancestry chain
- Phases: Capture, Target, Bubble
§Focus Model
Focus follows an ancestry chain from the focused view up to the root. When focus changes, the old and new paths are compared:
- Views that were in the old path but not the new path receive
FocusLost - Views that are in the new path but not the old path receive
FocusGained
§Focus Methods
Focus can be changed through:
- Pointer down (spatial focus)
- Tab/Shift+Tab (sequential navigation)
- Alt+Arrow (directional navigation)
- Programmatic:
view_id.request_focus()
§Keyboard Navigation
Only views with keyboard_navigable() set can receive focus via keyboard.
The :focus and :focus-visible style selectors update when focus changes.
§Default Actions
No preventable default action. Style re-resolution (:focus, :focus-visible) is
triggered before these events are dispatched and cannot be suppressed.
§Example
fn handle(event: Event) {
let mut cursor_visible = false;
match event {
Event::Focus(FocusEvent::Gained) => {
cursor_visible = true;
}
Event::Focus(FocusEvent::Lost) => {
cursor_visible = false;
}
_ => {}
}
let _ = cursor_visible;
}Window(WindowEvent)
Window-level events like resize, close, theme changes, update phases.
§Routing
- Broadcast to registered listeners: Only views that have registered window event listeners receive these events
- Phases: Target phase only
§Registration
Views register interest in window events through the event listener system. This avoids broadcasting to all views for events most don’t care about.
§Events
Resized: Window size changed (triggers responsive style updates)CloseRequested: User requested window closeDestroyed: Window is being destroyedThemeChanged: System theme changed (light/dark mode)RescaleRequested: DPI scale factor changed
§Default Actions
No preventable default action for any Window variant.
§Example
fn on_window_event(event: &Event) -> EventPropagation {
if let Event::Window(WindowEvent::Resized(size)) = event {
println!("Window resized to {}x{}", size.width, size.height);
}
EventPropagation::Continue
}Interaction(InteractionEvent)
High-level interaction events that abstract over pointer and keyboard input.
§Routing
- Directed to interaction target: Sent to the view that was clicked/interacted with
- Phases: Capture, Target, Bubble
§Event Generation
These events are synthetic - generated by Floem after analyzing lower-level pointer and keyboard events:
Click: Generated when pointer down+up occur on the same view within threshold, OR when Space/Enter pressed on focused viewDoubleClick: Generated when two clicks occur rapidly (count > 1)SecondaryClick: Generated from right-click (secondary button)
§Click Detection
A click is detected when:
- Pointer down occurs on a view
- Pointer doesn’t move beyond threshold distance
- Pointer up occurs within timeout
- Common ancestor between down and up targets receives the click
§Triggered By
Interaction events have cx.triggered_by set to the original pointer/keyboard
event that caused them. Use this to access original event details like modifiers.
§Default Actions
No preventable default action. These events are themselves generated as default actions of lower-level pointer and keyboard events.
§Example
let event = Event::Interaction(InteractionEvent::Click);
let triggered_by: Option<Event> = None;
if matches!(event, Event::Interaction(InteractionEvent::Click)) {
// Handle click regardless of whether it came from mouse or keyboard.
if let Some(Event::Pointer(_)) = triggered_by {
// Access pointer-specific details
}
}Drag(DragEvent)
Drag and drop events for implementing draggable elements and drop targets.
§Routing
- Source events: Directed to the view being dragged (Target phase only)
- Target events: Spatial routing via hit-testing (Target phase only, except Move which uses STANDARD phases)
§Drag Lifecycle
§Starting a Drag
Call draggable or .draggable_with_config() on a view to make it draggable:
my_view.draggable_with_config(|| {
DragConfig::default()
.with_custom_data(my_item_id)
})§During Drag
As the dragged element moves:
- Source receives:
Moveevents while dragging - Targets receive:
Enterwhen drag enters their bounds,Movewhile hovering,Leavewhen drag exits - Source receives:
Enter/Leavewhen entering/leaving valid drop targets
§Ending Drag
The drag ends with one of:
- Released over a target: Target receives
Dropand source receivesEndwithother_element: Some(target_id)simultaneously. - Released without a target: Source receives
Endwithother_element: None. - System cancel: Source receives
Cancelwhen the drag is aborted by aPointerCancelevent (e.g., touch interrupted).
§Example: Sortable List
// Make a view draggable with custom data
my_view
.on_event_stop(listener::DragTargetEnter, move |_, drag_enter| {
if let Some(custom_data) = &drag_enter.custom_data
&& let Some(dragged_id) = custom_data.downcast_ref::<usize>()
{
// Reorder items based on drag position
handle_reorder(*dragged_id, this_item_id);
}
})
.draggable_with_config(move || {
DragConfig::default()
.with_custom_data(item_id)
})§Default Actions
No preventable default action for any Drag variant.
Custom(Box<dyn CustomEvent>)
Custom user-defined events.
§Routing
- User-controlled: Routing determined by how the event is dispatched
- Phases: Specified when dispatching the event
§Defining Custom Events
Use the custom_event! macro to define your event type:
#[derive(Clone)]
struct DataChanged {
new_value: String,
}
custom_event!(DataChanged);This generates a DataChangedListener and implements the CustomEvent trait.
§Dispatching Custom Events
// Dispatch to a specific target
view_id.dispatch_event(
Event::new_custom(DataChanged {
new_value: "updated".to_string()
}),
RouteKind::Directed {
target: view_id.get_element_id(),
phases: Phases::TARGET,
},
);
// Or dispatch spatially (hit-test based)
view_id.dispatch_event(
Event::new_custom(MyPointerEvent { pos: point }),
RouteKind::Spatial {
point: Some(point),
phases: Phases::STANDARD,
},
);§Default Actions
No preventable default action. Routing and any behaviors are fully determined by the application when dispatching the event.
§Handling Custom Events
view.on_event_stop(DataChangedListener, |cx, event: &DataChanged| {
println!("Data changed to: {}", event.new_value);
})§Generic Custom Events
For generic events, each monomorphization gets its own listener:
#[derive(Clone)]
struct SelectionChanged<T: 'static> {
value: T,
}
custom_event!(SelectionChanged<T>);
// String and i32 versions are separate event types
dropdown.on_event_stop(SelectionChangedListener::<String>, |cx, event| {
// Receives SelectionChanged<String>
});Extracted
Sentinel value used internally when temporarily moving an event out of EventCx.
§Internal Use Only
This variant allows event handlers to receive both &mut EventCx and typed event
data without borrow conflicts and without cloning. The event is temporarily replaced with Extracted
while being passed to handlers.
If you see this variant in your event handler, you’re likely accessing cx.event
directly. Use the typed event data parameter passed to your handler instead:
// ❌ Don't do this:
let bad = |cx: &Cx| {
matches!(cx.event, Event::Pointer) // may observe temporary extraction state
};
// ✅ Do this instead:
let good = |event: &Event| {
matches!(event, Event::Pointer)
};Implementations§
Source§impl Event
impl Event
pub fn is_pointer(&self) -> bool
pub fn is_pointer_down(&self) -> bool
pub fn is_pointer_up(&self) -> bool
pub fn is_key_up(&self) -> bool
pub fn is_key_down(&self) -> bool
Sourcepub fn is_keyboard_trigger(&self) -> bool
pub fn is_keyboard_trigger(&self) -> bool
Enter, numpad enter and space cause a view to be activated with the keyboard
Sourcepub fn is_keyboard_trigger_start(&self) -> bool
pub fn is_keyboard_trigger_start(&self) -> bool
Enter, numpad enter and space cause a view to be activated with the keyboard
Sourcepub fn allow_disabled(&self) -> bool
pub fn allow_disabled(&self) -> bool
Returns whether this event should be delivered to disabled views.
Disabled views (marked via .disabled()) generally don’t receive interactive events,
but some events must still be delivered to maintain correct per-view internal state
or complete ongoing operations.
Note: This only affects whether individual views receive events in their event handlers. Global state tracking (hover paths, focus paths, etc.) is updated independently of whether views are disabled.
§Events Allowed on Disabled Views
- Hover state tracking:
PointerLeaveallow disabled views to update their internal hover state - Capture cleanup:
PointerCapture::Lostallows views to clean up internal state when they lose capture (e.g., if disabled mid-drag) - Drag lifecycle completion:
DragSourceLeave/End/Cancel events allow drags to complete properly if a view becomes disabled mid-drag. The drag was initiated when enabled, so the source view needs lifecycle events to clean up its internal drag state. Start and Move events are blocked since disabled views don’t need to initiate or track drags. - Window events: Window state changes (resize, theme change, etc.) may require internal updates even in disabled views
- Accessibility drops:
FileDrag::Dropcan be received by disabled views for accessibility reasons
§Events Blocked on Disabled Views
- Interactive pointer events: Down, Up, Move, Scroll, Gesture, Cancel
- Capture initiation:
PointerCapture::Gained(views shouldn’t gain new capture when disabled) - Focus changes: Focus events don’t fire for disabled views
- Keyboard input: Key and IME events
- User interactions: Click, DoubleClick, SecondaryClick
- Drag targets: Disabled views cannot receive dragged elements
- Drag source updates: Start and Move events (disabled views don’t initiate or track drags)
- File drag preview: File drag hover events (Enter, Move, Leave)
§Custom Events
Custom events implement their own allow_disabled() logic via the CustomEvent trait.
pub fn point(&self) -> Option<Point>
Sourcepub fn transform(self, transform: Affine) -> Event
pub fn transform(self, transform: Affine) -> Event
Transform this event from one coordinate space to another.
This method applies an affine transformation to all position-related data in the event, including pointer positions, drag positions, and file drag positions.
§Parameters
transform- An affine transform that maps from the source coordinate space to the target coordinate space. This transform is applied directly to event positions without inversion.
§Coordinate Space Mapping
If you want to transform an event from world/window coordinates to a view’s local coordinate space, you must pass the inverse of the view’s world transform:
let world_transform = box_tree.get_or_compute_world_transform(node_id)?; // local → world
let local_transform = world_transform.inverse(); // world → local
let local_event = event.transform(local_transform);Common use cases:
- World to local: Pass
world_transform.inverse()to convert window coordinates to view-local coordinates - Local to world: Pass
world_transformto convert view-local coordinates to window coordinates - Between views: Compose transforms as needed:
target_world.inverse() * source_world
§Event Types Transformed
This method transforms position data for:
PointerEventvariants (Down, Up, Move, Scroll, Gesture)FileDragEventvariants (Enter, Move, Leave, Dropped)DragTargetandDragSourceevents (both current and start positions)- Custom events (via their
transformmethod)
Other event types (Key, Focus, Window, etc.) are returned unchanged.
Sourcepub fn listener_keys(&self) -> SmallVec<[EventListenerKey; 4]>
pub fn listener_keys(&self) -> SmallVec<[EventListenerKey; 4]>
Returns all listener keys that this event should trigger.
Each event returns its specific listener key (e.g., PointerDown) plus any
broad category keys it belongs to (e.g., AnyPointer). This allows views
to listen for either specific events or entire event categories.
pub fn new_custom(custom: impl CustomEvent) -> Self
Sourcepub fn is_file_drag(&self) -> bool
pub fn is_file_drag(&self) -> bool
Returns true if the event is FileDrag.
Trait Implementations§
Auto Trait Implementations§
impl Freeze for Event
impl !RefUnwindSafe for Event
impl !Send for Event
impl !Sync for Event
impl Unpin for Event
impl UnsafeUnpin for Event
impl !UnwindSafe for Event
Blanket Implementations§
Source§impl<T> BorrowMut<T> for Twhere
T: ?Sized,
impl<T> BorrowMut<T> for Twhere
T: ?Sized,
Source§fn borrow_mut(&mut self) -> &mut T
fn borrow_mut(&mut self) -> &mut T
Source§impl<T> CloneToUninit for Twhere
T: Clone,
impl<T> CloneToUninit for Twhere
T: Clone,
§impl<T> Downcast for Twhere
T: Any,
impl<T> Downcast for Twhere
T: Any,
§fn into_any(self: Box<T>) -> Box<dyn Any>
fn into_any(self: Box<T>) -> Box<dyn Any>
Box<dyn Trait> (where Trait: Downcast) to Box<dyn Any>. Box<dyn Any> can
then be further downcast into Box<ConcreteType> where ConcreteType implements Trait.§fn into_any_rc(self: Rc<T>) -> Rc<dyn Any>
fn into_any_rc(self: Rc<T>) -> Rc<dyn Any>
Rc<Trait> (where Trait: Downcast) to Rc<Any>. Rc<Any> can then be
further downcast into Rc<ConcreteType> where ConcreteType implements Trait.§fn as_any(&self) -> &(dyn Any + 'static)
fn as_any(&self) -> &(dyn Any + 'static)
&Trait (where Trait: Downcast) to &Any. This is needed since Rust cannot
generate &Any’s vtable from &Trait’s.§fn as_any_mut(&mut self) -> &mut (dyn Any + 'static)
fn as_any_mut(&mut self) -> &mut (dyn Any + 'static)
&mut Trait (where Trait: Downcast) to &Any. This is needed since Rust cannot
generate &mut Any’s vtable from &mut Trait’s.§impl<T> Instrument for T
impl<T> Instrument for T
§fn instrument(self, span: Span) -> Instrumented<Self>
fn instrument(self, span: Span) -> Instrumented<Self>
§fn in_current_span(self) -> Instrumented<Self>
fn in_current_span(self) -> Instrumented<Self>
Source§impl<T> IntoEither for T
impl<T> IntoEither for T
Source§fn into_either(self, into_left: bool) -> Either<Self, Self>
fn into_either(self, into_left: bool) -> Either<Self, Self>
self into a Left variant of Either<Self, Self>
if into_left is true.
Converts self into a Right variant of Either<Self, Self>
otherwise. Read moreSource§fn into_either_with<F>(self, into_left: F) -> Either<Self, Self>
fn into_either_with<F>(self, into_left: F) -> Either<Self, Self>
self into a Left variant of Either<Self, Self>
if into_left(&self) returns true.
Converts self into a Right variant of Either<Self, Self>
otherwise. Read more