Skip to main content

rosin_core/
events.rs

1//! Types related to event handling and dispatch.
2
3use std::panic::Location;
4use std::path::PathBuf;
5use std::time::Duration;
6
7use accesskit::ActionRequest;
8use keyboard_types::KeyboardEvent;
9use kurbo::{Point, Rect, RoundedRect, Size, Vec2};
10use parley::{AlignmentOptions, Layout};
11use vello::Scene;
12
13use crate::prelude::*;
14use crate::{layout, text};
15
16/// Summary of the results of an event dispatch cycle.
17///
18/// This is returned to the platform after dispatching events.
19#[derive(Debug, Default, Copy, Clone)]
20pub struct DispatchInfo {
21    /// The total number of callbacks that were executed.
22    pub callback_count: u32,
23
24    /// Whether event propagation was stopped by an event handler calling [`EventCtx::stop_propagation`].
25    pub bubbling_stopped: bool,
26
27    /// Whether a request to close the window was intercepted and cancelled by a handler.
28    pub stop_window_close: bool,
29}
30
31/// The list of events that Nodes can register callbacks for.
32#[derive(Clone, Copy, Debug, PartialEq, Eq)]
33pub enum On {
34    /// This event is fired when AccessKit requests a semantic action on the node.
35    AccessibilityAction,
36
37    /// This event is fired once per display refresh, unless the node is disabled.
38    AnimationFrame,
39
40    /// This event is fired when a node loses focus.
41    Blur,
42
43    /// This event is fired when a callback requests change propagation from [`EventCtx::emit_change`], or when
44    /// a change is queued by the platform, such as when the text input handler modifies a text field.
45    ///
46    /// If a change event is sent to a node without an `On::Change` handler,
47    /// it will instead be queued on the nearest ancestor that does have one, if it exists.
48    Change,
49
50    /// This event is fired when an application command is triggered, such as selecting
51    /// an item in the main menu, a context menu, or clicking a modal dialog button.
52    ///
53    /// Commands from the main menu will always be sent to the root node.
54    Command,
55
56    /// This event is fired every time a node is added to the tree, even when it's disabled.
57    ///
58    /// If a node doesn't have an id, it will be treated as a new node and fire
59    /// every time the tree is rebuilt, which probably isn't what you want.
60    Create,
61
62    /// This event is fired every time a node is removed from the tree, even when it's disabled.
63    ///
64    /// If a node doesn't have an id, it will be treated as a new node and fire
65    /// every time the tree is rebuilt, which probably isn't what you want.
66    Destroy,
67
68    /// This event is fired by the platform after a file dialog closes.
69    FileDialog,
70
71    /// This event is fired when a node gains focus.
72    ///
73    /// Nodes with handlers for [`On::Focus`] are considered "focusable" and can be focused by
74    /// methods such as [`EventCtx::focus_next`] and [`EventCtx::focus_previous`].
75    Focus,
76
77    /// This event is fired when a keyboard key is pressed or released, unless the keypress was handled by an IME handler.
78    ///
79    /// Sent to the focused node (if any) and always to the root node.
80    Keyboard,
81
82    /// This event is fired when a pointer button is pressed while the pointer is over this node, or any of its descendants,
83    /// unless one of the descendants called `stop_propagation()` when handling the event.
84    ///
85    /// The event targets the frontmost node under the pointer first, then bubbles up
86    /// through its parents to the root, firing on every node along the way.
87    PointerDown,
88
89    /// This event is fired when the pointer first enters the node, or any of its descendants, unless the pointer is captured.
90    PointerEnter,
91
92    /// This event is fired when the pointer leaves the node, and all of its descendants, unless the pointer is captured.
93    PointerLeave,
94
95    /// This event is fired when the pointer moves while inside the node or any of its descendants,
96    /// unless one of the descendants called `stop_propagation()` when handling the event, or the pointer is captured.
97    ///
98    /// The event targets the frontmost node under the pointer first, then bubbles up
99    /// through its parents to the root, firing on every node along the way.
100    PointerMove,
101
102    /// This event is fired when a pointer button is released while over the node or any of its descendants,
103    /// unless one of the descendants called `stop_propagation()` when handling the event, or the pointer is captured.
104    ///
105    /// The event targets the frontmost node under the pointer first, then bubbles up
106    /// through its parents to the root, firing on every node along the way.
107    PointerUp,
108
109    /// This event is fired when the pointer wheel scrolls while the pointer is over the node or any of its descendants,
110    /// unless one of the descendants called `stop_propagation()` when handling the event, or the pointer is captured.
111    ///
112    /// The event targets the frontmost node under the pointer first, then bubbles up
113    /// through its parents to the root, firing on every node along the way.
114    PointerWheel,
115
116    /// This event is fired by the platform after a specified delay when a timer is requested.
117    Timer,
118
119    /// This event is fired on the root node when the window loses focus.
120    WindowBlur,
121
122    /// This event is fired on the root node when a window close is requested.
123    ///
124    /// If the callback calls [`EventCtx::stop_window_close`], the window will be prevented from closing.
125    WindowClose,
126
127    /// This event is fired on the root node when the window gains focus.
128    WindowFocus,
129}
130
131impl On {
132    /// Returns `true` if this event is a pointer event.
133    pub fn is_pointer(&self) -> bool {
134        matches!(self, On::PointerDown | On::PointerUp | On::PointerMove | On::PointerEnter | On::PointerLeave | On::PointerWheel)
135    }
136}
137
138/// A context provided to the [`on_measure`](crate::tree::Ui::on_measure) callback of a node.
139pub struct MeasureCtx<'a> {
140    pub style: &'a Style,
141    pub max_size: Option<Size>,
142}
143
144/// A context provided to the [`on_canvas`](crate::tree::Ui::on_canvas) callback of a node for rendering.
145///
146/// `CanvasCtx` provides access to the Vello [`Scene`] for issuing graphics commands in local space,
147/// as well as the node's layout information, computed styles, and interaction state (active/focused).
148pub struct CanvasCtx<'a> {
149    pub did_layout: bool,
150    pub is_active: bool,
151    pub is_enabled: bool,
152    pub is_focused: bool,
153    pub perf_info: &'a PerfInfo,
154    pub rect: &'a RoundedRect,
155    pub scene: &'a mut Scene,
156    pub style: &'a Style,
157    pub translation_map: TranslationMap,
158}
159
160impl<'a> CanvasCtx<'a> {
161    /// Returns the rect just inside the borders of the node in local space. Doesn't account for rounded corners.
162    #[inline]
163    pub fn padding_box(&self) -> Rect {
164        layout::padding_box(self.style, self.rect)
165    }
166
167    /// Returns the maximum width content could be before overflowing node.
168    #[inline]
169    pub fn max_content_width(&self) -> f32 {
170        layout::max_content_width(self.style, self.rect)
171    }
172
173    /// Draw text according to this node's CSS styles.
174    #[inline]
175    pub fn draw_text(&mut self, text: &str) {
176        let max_width = layout::max_content_width(self.style, self.rect);
177        let mut layout = text::layout_text(&self.style.get_font_layout_style(), Some(max_width), text);
178        let origin = layout::align_and_position_text(self.style, self.rect, &mut layout);
179        text::draw_text(self.scene, self.style, origin, &layout);
180    }
181
182    /// Draw text at the provided location according to this node's CSS styles.
183    #[inline]
184    pub fn draw_text_at_origin(&mut self, origin: Point, max_width: impl Into<Option<f32>>, text: &str) {
185        let max_width = max_width.into();
186        let mut layout = text::layout_text(&self.style.get_font_layout_style(), max_width, text);
187        layout.align(max_width, self.style.text_align.into(), AlignmentOptions::default());
188        text::draw_text(self.scene, self.style, origin, &layout);
189    }
190
191    /// Draws a text layout at the specified location.
192    #[inline]
193    pub fn draw_text_layout(&mut self, origin: Point, layout: &Layout<[u8; 4]>) {
194        text::draw_text(self.scene, self.style, origin, layout);
195    }
196}
197
198/// The result of a file dialog interaction.
199#[derive(Clone, Debug)]
200pub enum FileDialogResponse {
201    /// The user confirmed an Open operation.
202    /// Contains the list of selected files or folders.
203    Opened(Vec<PathBuf>),
204    /// The user confirmed a Save operation.
205    /// Contains the destination path.
206    Saved(PathBuf),
207    /// The user closed the dialog without confirming.
208    Cancelled,
209}
210
211impl FileDialogResponse {
212    /// Returns the first selected path regardless of whether the dialog was Open or Save.
213    pub fn path(&self) -> Option<&PathBuf> {
214        match self {
215            FileDialogResponse::Opened(paths) => paths.first(),
216            FileDialogResponse::Saved(path) => Some(path),
217            FileDialogResponse::Cancelled => None,
218        }
219    }
220
221    /// Returns all selected paths. If this was a Save dialog, it returns a vec with a single path.
222    pub fn paths(&self) -> Vec<PathBuf> {
223        match self {
224            FileDialogResponse::Opened(paths) => paths.clone(),
225            FileDialogResponse::Saved(path) => vec![path.clone()],
226            FileDialogResponse::Cancelled => Vec::new(),
227        }
228    }
229
230    /// Returns true if the response came from an Open dialog.
231    pub fn is_opened(&self) -> bool {
232        matches!(self, FileDialogResponse::Opened { .. })
233    }
234
235    /// Returns true if the response came from a Save dialog.
236    pub fn is_saved(&self) -> bool {
237        matches!(self, FileDialogResponse::Saved { .. })
238    }
239
240    /// Returns true if the user cancelled the dialog.
241    pub fn is_cancelled(&self) -> bool {
242        matches!(self, FileDialogResponse::Cancelled)
243    }
244}
245
246/// An identifier for a command originating in the platform.
247///
248/// This is typically associated with items in the main menu, context menus, or buttons on modal dialogs.
249#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
250#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
251pub struct CommandId(pub u32);
252
253impl From<u32> for CommandId {
254    fn from(value: u32) -> Self {
255        Self(value)
256    }
257}
258
259/// Event specific payload attached to an [`EventCtx`].
260#[derive(Clone, Debug)]
261pub enum EventInfo {
262    None,
263    Pointer(PointerEvent),
264    Keyboard(KeyboardEvent),
265    Animation(Duration),
266    File(FileDialogResponse),
267    Command(CommandId),
268    AccessibilityAction(ActionRequest),
269}
270
271#[derive(Copy, Clone)]
272pub(crate) enum FocusDirection {
273    None,
274    Next,
275    Previous,
276}
277
278/// Information about how long it took to render the previous frame.
279///
280/// Event callbacks are not included.
281#[derive(Default, Debug, Clone, Copy)]
282pub struct PerfInfo {
283    /// Number of frames that have been drawn since creating the viewport.
284    pub frame_number: u64,
285
286    /// How many nodes are in the tree.
287    pub node_count: usize,
288
289    /// How long it took to build the tree last frame.
290    pub build_time: Duration,
291
292    // How long it took to apply styles last frame.
293    pub style_time: Duration,
294
295    /// How long the layout phase took last frame. This may include a selector matching pass and a second layout pass.
296    pub layout_time: Duration,
297
298    /// Set to `true` if layout needed to be calculated twice last frame.
299    pub layout_twice: bool,
300
301    /// How long it took to build the Vello scene last frame.
302    pub scene_time: Duration,
303
304    /// How long it took to paint the surface last frame.
305    ///
306    /// Provided by the platform integration through [`Viewport::report_paint_time`].
307    pub paint_time: Duration,
308}
309
310impl PerfInfo {
311    #[inline]
312    pub fn total_time(&self) -> Duration {
313        self.build_time + self.style_time + self.layout_time + self.scene_time + self.paint_time
314    }
315
316    #[inline]
317    pub fn cpu_time(&self) -> Duration {
318        self.build_time + self.style_time + self.layout_time + self.scene_time
319    }
320
321    #[inline]
322    pub fn gpu_time(&self) -> Duration {
323        self.paint_time
324    }
325}
326
327/// A context provided to event handlers registered with [`Ui::event`].
328///
329/// [`EventCtx`] is the primary interface for nodes to respond to user input. It provides:
330/// - State Updates: Methods to set focus, capture the pointer, or mark a node as active.
331/// - Event Data: Access to specific event details like pointer coordinates or keyboard keys.
332/// - Propagation Control: Ability to stop events from bubbling up the tree or to prevent default window actions.
333/// - Layout Info: Access to the node's computed size and position.
334///
335/// It also provides a handle to the platform, allowing the callback
336/// to trigger system-level actions like opening URLs or changing the cursor.
337pub struct EventCtx<'a, H> {
338    pub(crate) active_node: Option<NodeId>,
339    pub(crate) captured_node: Option<NodeId>,
340    pub(crate) emit_change: bool,
341    pub(crate) event_type: On,
342    pub(crate) focus_direction: FocusDirection,
343    pub(crate) focused_node: Option<NodeId>,
344    pub(crate) handle: &'a H,
345    pub(crate) id: Option<NodeId>,
346    pub(crate) idx: usize,
347    pub(crate) info: EventInfo,
348    pub(crate) is_enabled: bool,
349    pub(crate) perf_info: &'a PerfInfo,
350    pub(crate) pointer_delta: Option<Vec2>,
351    pub(crate) rect: &'a RoundedRect,
352    pub(crate) stop_bubbling: bool,
353    pub(crate) stop_window_close: bool,
354    pub(crate) style: &'a Style,
355    pub(crate) translation_map: TranslationMap,
356    pub(crate) viewport_size: Size,
357}
358
359impl<'a, H> EventCtx<'a, H> {
360    /// Returns a handle provided by the platform integration.
361    /// The default platform integration returns a `rosin::handle::WindowHandle`.
362    #[inline]
363    pub fn platform(&self) -> &H {
364        self.handle
365    }
366
367    /// Sets the currently active node. This makes `:active` CSS selectors apply to the node. Purely cosmetic.
368    ///
369    /// You can pass `None` to deactivate.
370    #[inline]
371    pub fn set_active(&mut self, id: Option<NodeId>) {
372        self.active_node = id;
373    }
374
375    /// Returns `true` if the current node is active.
376    #[inline]
377    pub fn is_active(&self) -> bool {
378        self.id.is_some() && self.id == self.active_node
379    }
380
381    /// Returns `true` if the current node is enabled.
382    ///
383    /// A node's enabled status is controlled by a [`UIParam`] passed to [`Ui::enabled`] when building the tree.
384    #[inline]
385    pub fn is_enabled(&self) -> bool {
386        self.is_enabled
387    }
388
389    /// Sets the currently focused node. This makes `:focus` CSS selectors apply to the node, and routes [`On::Keyboard`] events to it.
390    ///
391    /// You can pass `None` to unfocus.
392    #[inline]
393    pub fn set_focus(&mut self, id: Option<NodeId>) {
394        self.focused_node = id;
395    }
396
397    /// Transfers focus to the next focusable node node in the tree.
398    /// If nothing is focused, the first focusable node in the tree will gain focus.
399    #[inline]
400    pub fn focus_next(&mut self) {
401        self.focus_direction = FocusDirection::Next;
402    }
403
404    /// Transfers focus to the previous focusable node node in the tree.
405    /// If nothing is focused, the last focusable node in the tree will gain focus.
406    #[inline]
407    pub fn focus_previous(&mut self) {
408        self.focus_direction = FocusDirection::Previous;
409    }
410
411    /// Returns `true` if the current node is focused.
412    #[inline]
413    pub fn is_focused(&self) -> bool {
414        self.id.is_some() && self.id == self.focused_node
415    }
416
417    /// Returns the size of the total drawable area of the viewport in logical pixels.
418    #[inline]
419    pub fn viewport_size(&self) -> Size {
420        self.viewport_size
421    }
422
423    /// Returns the computed style for this node.
424    #[inline]
425    pub fn style(&self) -> &Style {
426        self.style
427    }
428
429    /// Returns the final laid-out rectangle of this node.
430    #[inline]
431    pub fn rect(&self) -> &RoundedRect {
432        self.rect
433    }
434
435    /// Return the current node's id, if it has one.
436    ///
437    /// - In debug builds, this will log an error if there is no id for the node.
438    #[inline]
439    #[track_caller]
440    pub fn id(&self) -> Option<NodeId> {
441        if cfg!(debug_assertions) && self.id.is_none() {
442            // If a handler requests an id, it likely assumes that there is one.
443            // This should be loud, otherwise things will fail silently.
444            let location = Location::caller();
445            log::error!("id() must be called on a node with an id: {location}");
446        }
447        self.id
448    }
449
450    /// Returns the type of event that triggered this callback.
451    #[inline]
452    pub fn event_type(&self) -> On {
453        self.event_type
454    }
455
456    /// Returns the change in position between this pointer event and the previous, if available.
457    /// The first pointer event fired when the cursor enters the viewport will not have a delta.
458    #[inline]
459    pub fn pointer_delta(&self) -> Option<Vec2> {
460        self.pointer_delta
461    }
462
463    /// Returns some information about how long it took to render the previous frame.
464    #[inline]
465    pub fn perf_info(&self) -> &PerfInfo {
466        self.perf_info
467    }
468
469    /// Returns the global map of translations.
470    #[inline]
471    pub fn get_translation_map(&self) -> TranslationMap {
472        self.translation_map.clone()
473    }
474
475    /// Begins capturing pointer events. When captured, the pointer is treated as if it is always inside the node,
476    /// so [`On::PointerEnter`] and [`On::PointerLeave`] events will never fire.
477    ///
478    /// - In debug builds, this will log an error if there is no id for the node.
479    #[inline]
480    #[track_caller]
481    pub fn begin_pointer_capture(&mut self) {
482        if cfg!(debug_assertions) && self.id.is_none() {
483            let location = Location::caller();
484            log::error!("begin_pointer_capture() must be called on a node with an id: {location}");
485        }
486        self.captured_node = self.id;
487    }
488
489    /// Releases the pointer capture, so other nodes can start receiving pointer events again.
490    #[inline]
491    pub fn end_pointer_capture(&mut self) {
492        self.captured_node = None;
493    }
494
495    /// Returns `true` if this node has captured the pointer.
496    #[inline]
497    pub fn is_pointer_captured(&self) -> bool {
498        self.id.is_some() && self.id == self.captured_node
499    }
500
501    /// Stop a pointer event from bubbling up to ancestor nodes.
502    /// [`On::PointerEnter`] and [`On::PointerLeave`] events do not bubble.
503    /// - In debug builds, this will log an error if called from a non-pointer event.
504    #[inline]
505    #[track_caller]
506    pub fn stop_propagation(&mut self) {
507        if cfg!(debug_assertions) && !self.event_type.is_pointer() {
508            let location = Location::caller();
509            log::error!("stop_propagation() must be called from a pointer event: {location}");
510        }
511        self.stop_bubbling = true;
512    }
513
514    /// Requests an [`On::Change`] dispatch after this callback returns.
515    ///
516    /// The dispatcher will queue [`On::Change`] on this node (if it handles it),
517    /// otherwise on the first ancestor with an [`On::Change`] handler.
518    /// If the current event is [`On::Change`], it will only look for an ancestor
519    /// to avoid re-queuing the same handler.
520    #[inline]
521    pub fn emit_change(&mut self) {
522        self.emit_change = true;
523    }
524
525    /// Stops the window from closing.
526    ///
527    /// - In debug builds, this will log an error if not called from the root node's [`On::WindowClose`] handler.
528    #[inline]
529    #[track_caller]
530    pub fn stop_window_close(&mut self) {
531        if cfg!(debug_assertions) {
532            let location = Location::caller();
533            if self.event_type != On::WindowClose {
534                log::error!("stop_window_close() must be called from an On::WindowClose handler: {location}");
535            } else if self.idx != 0 {
536                log::error!("stop_window_close() must be called from the root node: {location}");
537            }
538        }
539        self.stop_window_close = true;
540    }
541
542    /// In pointer event handlers, this returns the complete pointer event info.
543    #[inline]
544    pub fn pointer(&self) -> Option<&PointerEvent> {
545        if let EventInfo::Pointer(event) = &self.info {
546            Some(event)
547        } else {
548            // we don't log an error in case client code wants to route multiple events to the same handler.
549            None
550        }
551    }
552
553    /// If available, returns the position of the pointer event relative to the top-left of the current node.
554    #[inline]
555    pub fn local_pointer_pos(&self) -> Option<Point> {
556        if let EventInfo::Pointer(event) = &self.info {
557            let vec = event.viewport_pos - self.rect.origin();
558            Some(Point::new(vec.x, vec.y))
559        } else {
560            // we don't log an error in case client code wants to route multiple events to the same handler.
561            None
562        }
563    }
564
565    /// In [`On::Keyboard`] handlers, this returns the keyboard event info.
566    #[inline]
567    pub fn keyboard(&self) -> Option<&KeyboardEvent> {
568        if let EventInfo::Keyboard(event) = &self.info {
569            Some(event)
570        } else {
571            // we don't log an error in case client code wants to route multiple events to the same handler.
572            None
573        }
574    }
575
576    /// In [`On::AnimationFrame`] handlers, this returns the duration since the last animation frame.
577    #[inline]
578    pub fn dt(&self) -> Option<&Duration> {
579        if let EventInfo::Animation(dt) = &self.info {
580            Some(dt)
581        } else {
582            // we don't log an error in case client code wants to route multiple events to the same handler.
583            None
584        }
585    }
586
587    /// In [`On::FileDialog`] handlers, this returns the information returned by the requested file dialog.
588    #[inline]
589    pub fn file_dialog_response(&self) -> Option<&FileDialogResponse> {
590        if let EventInfo::File(file) = &self.info {
591            Some(file)
592        } else {
593            // we don't log an error in case client code wants to route multiple events to the same handler.
594            None
595        }
596    }
597
598    /// In [`On::Command`] handlers, this returns the [`CommandId`] associated with the menu item picked.
599    #[inline]
600    pub fn command_id(&self) -> Option<CommandId> {
601        if let EventInfo::Command(cmd) = &self.info {
602            Some(*cmd)
603        } else {
604            // we don't log an error in case client code wants to route multiple events to the same handler.
605            None
606        }
607    }
608
609    /// In [`On::AccessibilityAction`] handlers, this returns the AccessKit action request info.
610    #[inline]
611    pub fn action_request(&self) -> Option<&ActionRequest> {
612        if let EventInfo::AccessibilityAction(req) = &self.info {
613            Some(req)
614        } else {
615            // we don't log an error in case client code wants to route multiple events to the same handler.
616            None
617        }
618    }
619
620    /// Returns the raw event payload for this callback.
621    ///
622    /// This is the same data accessed by the typed helpers like [`EventCtx::pointer`], [`EventCtx::keyboard`], etc.
623    #[inline]
624    pub fn info(&self) -> &EventInfo {
625        &self.info
626    }
627
628    /// Returns the rect just inside the borders of the node. Doesn't account for rounded corners.
629    #[inline]
630    pub fn padding_box(&self) -> Rect {
631        layout::padding_box(self.style, self.rect)
632    }
633
634    /// Returns the maximum width content could be before overflowing the node.
635    #[inline]
636    pub fn max_content_width(&self) -> f32 {
637        layout::max_content_width(self.style, self.rect)
638    }
639}
640
641/// A context provided to the [`Ui::on_accessibility`] callback of a node.
642pub struct AccessibilityCtx<'a> {
643    /// The node's ID.
644    pub id: NodeId,
645
646    /// The node's current text.
647    pub text: Option<&'a UIString>,
648
649    /// The global translation map.
650    pub translation_map: TranslationMap,
651
652    /// The AccessKit node to mutate role/name/value/actions/state/etc.
653    pub node: &'a mut accesskit::Node,
654}