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}