1use std::cell::RefCell;
2use std::collections::BTreeMap;
3use std::fmt;
4use std::rc::Rc;
5
6use gpui::{App, SharedString, Window};
7use rhai::{CustomType, TypeBuilder};
8
9use crate::{ComponentInstancePath, ScriptCallback, UiValue};
10
11#[derive(Clone, Debug, PartialEq)]
12pub struct UiEvent {
13 pub name: String,
14 pub payload: UiValue,
15}
16
17#[derive(Clone, Copy, Debug, Default, PartialEq)]
18pub struct LogicalPoint {
19 pub x: f64,
20 pub y: f64,
21}
22
23#[allow(clippy::struct_excessive_bools)]
24#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
25pub struct EventModifiers {
26 pub control: bool,
27 pub alt: bool,
28 pub shift: bool,
29 pub platform: bool,
30 pub function: bool,
31}
32
33impl EventModifiers {
34 #[must_use]
35 pub fn into_value(self) -> UiValue {
36 UiValue::Map(BTreeMap::from([
37 ("control".to_owned(), UiValue::Bool(self.control)),
38 ("alt".to_owned(), UiValue::Bool(self.alt)),
39 ("shift".to_owned(), UiValue::Bool(self.shift)),
40 ("platform".to_owned(), UiValue::Bool(self.platform)),
41 ("function".to_owned(), UiValue::Bool(self.function)),
42 ]))
43 }
44}
45
46#[derive(Clone, Debug, PartialEq)]
47pub struct PointerEventData {
48 pub pointer_id: u64,
49 pub pointer_type: String,
50 pub window: LogicalPoint,
51 pub local: LogicalPoint,
52 pub content: LogicalPoint,
53 pub movement: LogicalPoint,
54 pub velocity: LogicalPoint,
55 pub button: Option<String>,
56 pub buttons: Vec<String>,
57 pub modifiers: EventModifiers,
58 pub click_count: usize,
59 pub timestamp_ms: f64,
60 pub captured: bool,
61 pub target: Option<crate::GeometryBounds>,
62}
63
64impl PointerEventData {
65 #[must_use]
66 pub fn into_value(self) -> UiValue {
67 UiValue::Map(BTreeMap::from([
68 (
69 "pointer_id".to_owned(),
70 UiValue::Integer(i64::try_from(self.pointer_id).unwrap_or(i64::MAX)),
71 ),
72 (
73 "pointer_type".to_owned(),
74 UiValue::String(self.pointer_type),
75 ),
76 ("window".to_owned(), point_value(self.window)),
77 ("local".to_owned(), point_value(self.local)),
78 ("content".to_owned(), point_value(self.content)),
79 ("movement".to_owned(), point_value(self.movement)),
80 ("velocity".to_owned(), point_value(self.velocity)),
81 (
82 "button".to_owned(),
83 self.button.map_or(UiValue::Null, UiValue::String),
84 ),
85 (
86 "buttons".to_owned(),
87 UiValue::Array(self.buttons.into_iter().map(UiValue::String).collect()),
88 ),
89 ("modifiers".to_owned(), self.modifiers.into_value()),
90 (
91 "click_count".to_owned(),
92 UiValue::Integer(i64::try_from(self.click_count).unwrap_or(i64::MAX)),
93 ),
94 ("timestamp_ms".to_owned(), UiValue::Float(self.timestamp_ms)),
95 ("captured".to_owned(), UiValue::Bool(self.captured)),
96 (
97 "target".to_owned(),
98 self.target
99 .map_or(UiValue::Null, crate::GeometryBounds::into_value),
100 ),
101 ("pressure".to_owned(), UiValue::Null),
102 ("tilt_x".to_owned(), UiValue::Null),
103 ("tilt_y".to_owned(), UiValue::Null),
104 ]))
105 }
106}
107
108#[derive(Clone, Debug, PartialEq)]
109pub struct WheelEventData {
110 pub window: LogicalPoint,
111 pub local: LogicalPoint,
112 pub content: LogicalPoint,
113 pub delta: LogicalPoint,
114 pub precise: bool,
115 pub modifiers: EventModifiers,
116 pub timestamp_ms: f64,
117 pub target: Option<crate::GeometryBounds>,
118}
119
120impl WheelEventData {
121 #[must_use]
122 pub fn into_value(self) -> UiValue {
123 UiValue::Map(BTreeMap::from([
124 ("window".to_owned(), point_value(self.window)),
125 ("local".to_owned(), point_value(self.local)),
126 ("content".to_owned(), point_value(self.content)),
127 ("delta".to_owned(), point_value(self.delta)),
128 ("precise".to_owned(), UiValue::Bool(self.precise)),
129 ("modifiers".to_owned(), self.modifiers.into_value()),
130 ("timestamp_ms".to_owned(), UiValue::Float(self.timestamp_ms)),
131 (
132 "target".to_owned(),
133 self.target
134 .map_or(UiValue::Null, crate::GeometryBounds::into_value),
135 ),
136 ]))
137 }
138}
139
140fn point_value(point: LogicalPoint) -> UiValue {
141 UiValue::Map(BTreeMap::from([
142 ("x".to_owned(), UiValue::Float(point.x)),
143 ("y".to_owned(), UiValue::Float(point.y)),
144 ]))
145}
146
147#[derive(Clone, Copy, Debug, Eq, PartialEq)]
148pub enum EventPropagation {
149 Handled,
150 Propagate,
151}
152
153#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
154pub enum PropagationControl {
155 #[default]
156 Continue,
157 Stop,
158 StopImmediate,
159}
160
161#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
162pub enum PointerCaptureDirective {
163 #[default]
164 None,
165 Capture,
166 Release,
167}
168
169#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
171pub struct EventResponse {
172 prevent_default: bool,
173 propagation: PropagationControl,
174 pointer_capture: PointerCaptureDirective,
175}
176
177impl EventResponse {
178 #[must_use]
179 pub const fn new() -> Self {
180 Self {
181 prevent_default: false,
182 propagation: PropagationControl::Continue,
183 pointer_capture: PointerCaptureDirective::None,
184 }
185 }
186
187 #[must_use]
188 pub const fn prevent_default(mut self) -> Self {
189 self.prevent_default = true;
190 self
191 }
192
193 #[must_use]
194 pub const fn stop(mut self) -> Self {
195 self.propagation = PropagationControl::Stop;
196 self
197 }
198
199 #[must_use]
200 pub const fn stop_immediate(mut self) -> Self {
201 self.propagation = PropagationControl::StopImmediate;
202 self
203 }
204
205 #[must_use]
206 pub const fn capture_pointer(mut self) -> Self {
207 self.pointer_capture = PointerCaptureDirective::Capture;
208 self
209 }
210
211 #[must_use]
212 pub const fn release_pointer(mut self) -> Self {
213 self.pointer_capture = PointerCaptureDirective::Release;
214 self
215 }
216
217 #[must_use]
218 pub const fn default_prevented(self) -> bool {
219 self.prevent_default
220 }
221
222 #[must_use]
223 pub const fn propagation(self) -> PropagationControl {
224 self.propagation
225 }
226
227 #[must_use]
228 pub const fn pointer_capture(self) -> PointerCaptureDirective {
229 self.pointer_capture
230 }
231
232 #[must_use]
233 pub const fn stops_propagation(self) -> bool {
234 !matches!(self.propagation, PropagationControl::Continue)
235 }
236
237 pub(crate) fn merge(&mut self, response: Self) {
238 self.prevent_default |= response.prevent_default;
239 if !matches!(response.propagation, PropagationControl::Continue) {
240 self.propagation = response.propagation;
241 }
242 if !matches!(response.pointer_capture, PointerCaptureDirective::None) {
243 self.pointer_capture = response.pointer_capture;
244 }
245 }
246}
247
248impl From<EventPropagation> for EventResponse {
249 fn from(value: EventPropagation) -> Self {
250 match value {
251 EventPropagation::Handled => Self::new().stop(),
252 EventPropagation::Propagate => Self::new(),
253 }
254 }
255}
256
257impl From<EventResponse> for EventPropagation {
258 fn from(value: EventResponse) -> Self {
259 if value.stops_propagation() {
260 Self::Handled
261 } else {
262 Self::Propagate
263 }
264 }
265}
266
267impl CustomType for EventResponse {
268 fn build(mut builder: TypeBuilder<Self>) {
269 builder
270 .with_name("EventResponse")
271 .with_fn("prevent_default", |response: &mut Self| {
272 (*response).prevent_default()
273 })
274 .with_fn("stop", |response: &mut Self| (*response).stop())
275 .with_fn("stop_immediate", |response: &mut Self| {
276 (*response).stop_immediate()
277 })
278 .with_fn("capture_pointer", |response: &mut Self| {
279 (*response).capture_pointer()
280 })
281 .with_fn("release_pointer", |response: &mut Self| {
282 (*response).release_pointer()
283 });
284 }
285}
286
287#[derive(Clone, Copy, Debug, Eq, PartialEq)]
288pub enum EventPhase {
289 Capture,
290 Target,
291 Bubble,
292}
293
294#[derive(Clone, Debug, Default)]
295pub struct PointerCaptureRegistry {
296 active: Rc<RefCell<BTreeMap<u64, crate::NodeId>>>,
297}
298
299impl PointerCaptureRegistry {
300 #[must_use]
301 pub fn new() -> Self {
302 Self::default()
303 }
304
305 pub fn capture(&self, pointer_id: u64, node: crate::NodeId) {
306 self.active.borrow_mut().insert(pointer_id, node);
307 }
308
309 pub fn release(&self, pointer_id: u64) -> Option<crate::NodeId> {
310 self.active.borrow_mut().remove(&pointer_id)
311 }
312
313 pub(crate) fn clear(&self) {
314 self.active.borrow_mut().clear();
315 }
316
317 #[must_use]
318 pub fn captured(&self, pointer_id: u64) -> Option<crate::NodeId> {
319 self.active.borrow().get(&pointer_id).copied()
320 }
321
322 pub(crate) fn retain_nodes(&self, nodes: &std::collections::BTreeSet<crate::NodeId>) {
323 self.active
324 .borrow_mut()
325 .retain(|_, node| nodes.contains(node));
326 }
327
328 pub(crate) fn snapshot(&self) -> BTreeMap<u64, crate::NodeId> {
329 self.active.borrow().clone()
330 }
331
332 pub(crate) fn restore(&self, snapshot: BTreeMap<u64, crate::NodeId>) {
333 *self.active.borrow_mut() = snapshot;
334 }
335}
336
337type HostCallbackFn = dyn Fn(UiValue, &mut Window, &mut App) -> EventResponse;
338
339#[derive(Clone)]
340pub struct HostCallback {
341 label: SharedString,
342 handler: Rc<HostCallbackFn>,
343}
344
345impl HostCallback {
346 #[must_use]
352 pub fn new<R>(
353 label: impl Into<SharedString>,
354 handler: impl Fn(UiValue, &mut Window, &mut App) -> R + 'static,
355 ) -> Self
356 where
357 R: Into<EventResponse>,
358 {
359 let label = label.into();
360 assert!(
361 !label.as_ref().trim().is_empty(),
362 "HostCallback label cannot be empty"
363 );
364 Self {
365 label,
366 handler: Rc::new(move |payload, window, app| handler(payload, window, app).into()),
367 }
368 }
369
370 #[must_use]
371 pub fn label(&self) -> &str {
372 self.label.as_ref()
373 }
374
375 pub(crate) fn invoke(
376 &self,
377 payload: UiValue,
378 window: &mut Window,
379 app: &mut App,
380 ) -> EventResponse {
381 (self.handler)(payload, window, app)
382 }
383}
384
385#[derive(Clone, Debug, PartialEq)]
386pub struct UiEventBinding {
387 phase: EventPhase,
388 handler: UiEventHandler,
389}
390
391impl UiEventBinding {
392 #[must_use]
393 pub fn new(phase: EventPhase, handler: impl Into<UiEventHandler>) -> Self {
394 Self {
395 phase,
396 handler: handler.into(),
397 }
398 }
399
400 #[must_use]
401 pub const fn phase(&self) -> EventPhase {
402 self.phase
403 }
404
405 #[must_use]
406 pub const fn handler(&self) -> &UiEventHandler {
407 &self.handler
408 }
409
410 pub(crate) const fn handler_mut(&mut self) -> &mut UiEventHandler {
411 &mut self.handler
412 }
413}
414
415impl fmt::Debug for HostCallback {
416 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
417 formatter
418 .debug_struct("HostCallback")
419 .field("label", &self.label)
420 .finish_non_exhaustive()
421 }
422}
423
424impl PartialEq for HostCallback {
425 fn eq(&self, other: &Self) -> bool {
426 self.label == other.label && Rc::ptr_eq(&self.handler, &other.handler)
427 }
428}
429
430impl Eq for HostCallback {}
431
432#[derive(Clone, Debug, PartialEq)]
433pub enum UiEventHandler {
434 Script(ScriptCallback),
435 Host(HostCallback),
436 Native(crate::NativeHandlerRef),
437}
438
439impl UiEventHandler {
440 #[must_use]
441 pub fn diagnostic_label(&self) -> String {
442 match self {
443 Self::Script(callback) => format!("script:{}", callback.name()),
444 Self::Host(callback) => format!("host:{}", callback.label()),
445 Self::Native(reference) => {
446 format!("native:{}", reference.descriptor().id)
447 }
448 }
449 }
450
451 pub(crate) const fn as_script_mut(&mut self) -> Option<&mut ScriptCallback> {
452 match self {
453 Self::Script(callback) => Some(callback),
454 Self::Host(_) | Self::Native(_) => None,
455 }
456 }
457
458 #[must_use]
459 pub const fn as_script(&self) -> Option<&ScriptCallback> {
460 match self {
461 Self::Script(callback) => Some(callback),
462 Self::Host(_) | Self::Native(_) => None,
463 }
464 }
465}
466
467impl From<ScriptCallback> for UiEventHandler {
468 fn from(callback: ScriptCallback) -> Self {
469 Self::Script(callback)
470 }
471}
472
473impl From<HostCallback> for UiEventHandler {
474 fn from(callback: HostCallback) -> Self {
475 Self::Host(callback)
476 }
477}
478
479impl From<crate::NativeHandlerRef> for UiEventHandler {
480 fn from(reference: crate::NativeHandlerRef) -> Self {
481 Self::Native(reference)
482 }
483}
484
485#[derive(Clone, Debug, Default)]
486pub struct EventRouter {
487 handlers: BTreeMap<ComponentInstancePath, BTreeMap<String, Vec<UiEventBinding>>>,
488}
489
490impl EventRouter {
491 #[must_use]
492 pub fn new() -> Self {
493 Self::default()
494 }
495
496 pub fn bind(
497 &mut self,
498 target: ComponentInstancePath,
499 event: impl Into<String>,
500 callback: ScriptCallback,
501 ) {
502 self.bind_phase(target, event, EventPhase::Bubble, callback);
503 }
504
505 pub fn bind_phase(
506 &mut self,
507 target: ComponentInstancePath,
508 event: impl Into<String>,
509 phase: EventPhase,
510 handler: impl Into<UiEventHandler>,
511 ) {
512 self.handlers
513 .entry(target)
514 .or_default()
515 .entry(event.into())
516 .or_default()
517 .push(UiEventBinding::new(phase, handler));
518 }
519
520 pub fn unbind_component(&mut self, target: &ComponentInstancePath) {
521 self.handlers.remove(target);
522 }
523
524 pub fn dispatch<E>(
530 &self,
531 target: &ComponentInstancePath,
532 event: &UiEvent,
533 mut invoke: impl FnMut(
534 &ComponentInstancePath,
535 &UiEventHandler,
536 &UiEvent,
537 ) -> Result<EventResponse, E>,
538 ) -> Result<EventDispatchReport, E> {
539 let mut route = Vec::new();
540 let mut current = Some(target.clone());
541 while let Some(path) = current {
542 route.push(path.clone());
543 current = path.parent();
544 }
545 route.reverse();
546 let mut report = EventDispatchReport::default();
547 for path in &route {
548 if self.dispatch_phase(path, event, EventPhase::Capture, &mut invoke, &mut report)? {
549 return Ok(report);
550 }
551 }
552 if self.dispatch_phase(target, event, EventPhase::Target, &mut invoke, &mut report)? {
553 return Ok(report);
554 }
555 for path in route.iter().rev() {
556 if self.dispatch_phase(path, event, EventPhase::Bubble, &mut invoke, &mut report)? {
557 return Ok(report);
558 }
559 }
560 Ok(report)
561 }
562
563 fn dispatch_phase<E>(
564 &self,
565 path: &ComponentInstancePath,
566 event: &UiEvent,
567 phase: EventPhase,
568 invoke: &mut impl FnMut(
569 &ComponentInstancePath,
570 &UiEventHandler,
571 &UiEvent,
572 ) -> Result<EventResponse, E>,
573 report: &mut EventDispatchReport,
574 ) -> Result<bool, E> {
575 report.visited.push((path.clone(), phase));
576 let Some(bindings) = self
577 .handlers
578 .get(path)
579 .and_then(|handlers| handlers.get(&event.name))
580 else {
581 return Ok(false);
582 };
583 let mut stop_route = false;
584 for binding in bindings.iter().filter(|binding| binding.phase == phase) {
585 let response = invoke(path, &binding.handler, event)?;
586 report.response.merge(response);
587 report.invoked = report.invoked.saturating_add(1);
588 if matches!(response.propagation, PropagationControl::StopImmediate) {
589 report.stopped_at = Some(path.clone());
590 return Ok(true);
591 }
592 if matches!(response.propagation, PropagationControl::Stop) {
593 report.stopped_at = Some(path.clone());
594 stop_route = true;
595 }
596 }
597 Ok(stop_route)
598 }
599}
600
601#[derive(Clone, Debug, Default, Eq, PartialEq)]
602pub struct EventDispatchReport {
603 pub visited: Vec<(ComponentInstancePath, EventPhase)>,
604 pub stopped_at: Option<ComponentInstancePath>,
605 pub invoked: usize,
606 pub response: EventResponse,
607}
608
609#[cfg(test)]
610mod tests {
611 use std::cell::Cell;
612
613 use super::*;
614 use crate::{RuntimeEngine, UiNode};
615
616 struct DropSentinel(Rc<Cell<bool>>);
617
618 impl Drop for DropSentinel {
619 fn drop(&mut self) {
620 self.0.set(true);
621 }
622 }
623
624 #[test]
625 fn events_bubble_until_explicitly_handled() {
626 let mut runtime = RuntimeEngine::new();
627 let compiled = runtime
628 .compile(
629 r#"
630 fn view() { text("event") }
631 fn child_click(payload) { payload }
632 fn root_click(payload) { payload }
633 "#,
634 )
635 .unwrap();
636 let root = ComponentInstancePath::root("App", "root");
637 let child = root.child("Button", "save");
638 let mut router = EventRouter::new();
639 router.bind(
640 child.clone(),
641 "click",
642 runtime.callback(&compiled, "child_click").unwrap(),
643 );
644 router.bind(
645 root.clone(),
646 "click",
647 runtime.callback(&compiled, "root_click").unwrap(),
648 );
649 let event = UiEvent {
650 name: "click".to_owned(),
651 payload: UiValue::Null,
652 };
653 let mut order = Vec::new();
654 let report = router
655 .dispatch(&child, &event, |path, _, _| {
656 order.push(path.clone());
657 Ok::<_, ()>(
658 if path == &root {
659 EventPropagation::Handled
660 } else {
661 EventPropagation::Propagate
662 }
663 .into(),
664 )
665 })
666 .unwrap();
667
668 assert_eq!(order, vec![child, root.clone()]);
669 assert_eq!(report.stopped_at, Some(root));
670 assert_eq!(report.invoked, 2);
671 }
672
673 #[test]
674 fn capture_target_bubble_and_immediate_stop_are_ordered() {
675 let mut runtime = RuntimeEngine::new();
676 let compiled = runtime
677 .compile("fn view() { text(\"event\") } fn callback(payload) { payload }")
678 .unwrap();
679 let callback = runtime.callback(&compiled, "callback").unwrap();
680 let root = ComponentInstancePath::root("App", "root");
681 let child = root.child("Box", "child");
682 let mut router = EventRouter::new();
683 router.bind_phase(
684 root.clone(),
685 "pointer_down",
686 EventPhase::Capture,
687 callback.clone(),
688 );
689 router.bind_phase(
690 child.clone(),
691 "pointer_down",
692 EventPhase::Target,
693 callback.clone(),
694 );
695 router.bind_phase(
696 child.clone(),
697 "pointer_down",
698 EventPhase::Target,
699 callback.clone(),
700 );
701 router.bind_phase(root.clone(), "pointer_down", EventPhase::Bubble, callback);
702 let event = UiEvent {
703 name: "pointer_down".to_owned(),
704 payload: UiValue::Null,
705 };
706 let mut order = Vec::new();
707 let report = router
708 .dispatch(&child, &event, |path, _, _| {
709 order.push(path.clone());
710 Ok::<_, ()>(if order.len() == 2 {
711 EventResponse::new().prevent_default().stop_immediate()
712 } else {
713 EventResponse::new()
714 })
715 })
716 .unwrap();
717 assert_eq!(order, vec![root, child.clone()]);
718 assert_eq!(report.stopped_at, Some(child));
719 assert_eq!(report.invoked, 2);
720 assert!(report.response.default_prevented());
721 }
722
723 #[test]
724 fn stop_allows_remaining_same_phase_handlers_but_blocks_ancestors() {
725 let mut runtime = RuntimeEngine::new();
726 let compiled = runtime
727 .compile("fn view() { text(\"event\") } fn callback(payload) { payload }")
728 .unwrap();
729 let callback = runtime.callback(&compiled, "callback").unwrap();
730 let root = ComponentInstancePath::root("App", "root");
731 let child = root.child("Box", "child");
732 let mut router = EventRouter::new();
733 router.bind(child.clone(), "click", callback.clone());
734 router.bind(child.clone(), "click", callback.clone());
735 router.bind(root, "click", callback);
736 let mut invoked = 0;
737 let report = router
738 .dispatch(
739 &child,
740 &UiEvent {
741 name: "click".to_owned(),
742 payload: UiValue::Null,
743 },
744 |_, _, _| {
745 invoked += 1;
746 Ok::<_, ()>(if invoked == 1 {
747 EventResponse::new().stop()
748 } else {
749 EventResponse::new()
750 })
751 },
752 )
753 .unwrap();
754 assert_eq!(invoked, 2);
755 assert_eq!(report.stopped_at, Some(child));
756 }
757
758 #[test]
759 fn pointer_capture_is_node_scoped_and_clears_on_unmount() {
760 let mut tree = crate::RetainedUiTree::new();
761 tree.reconcile(crate::UiNode::text("drag")).unwrap();
762 let node = tree.root_id().unwrap();
763 let captures = PointerCaptureRegistry::new();
764 captures.capture(0, node);
765 assert_eq!(captures.captured(0), Some(node));
766 captures.retain_nodes(&std::collections::BTreeSet::new());
767 assert_eq!(captures.captured(0), None);
768 }
769
770 #[test]
771 fn host_callback_identity_debug_and_tree_drop_follow_rc_ownership() {
772 let callback = HostCallback::new("widget.input", |_, _, _| EventPropagation::Handled);
773 let cloned = callback.clone();
774 let other = HostCallback::new("widget.input", |_, _, _| EventPropagation::Handled);
775 assert_eq!(callback, cloned);
776 assert_ne!(callback, other);
777 let debug = format!("{callback:?}");
778 assert!(debug.contains("widget.input"));
779 assert!(!debug.contains("0x"));
780
781 let dropped = Rc::new(Cell::new(false));
782 {
783 let sentinel = DropSentinel(Rc::clone(&dropped));
784 let callback = HostCallback::new("widget.drop", move |_, _, _| {
785 let _ = &sentinel;
786 EventPropagation::Handled
787 });
788 let _tree = UiNode::text("host-owned").with_host_handler("click", callback);
789 }
790 assert!(dropped.get());
791 }
792
793 #[test]
794 fn rhai_cannot_construct_host_callbacks() {
795 let mut runtime = RuntimeEngine::new();
796 let compiled = runtime
797 .compile(
798 r#"
799 fn view() {
800 text("host").with_host_handler("click", ())
801 }
802 "#,
803 )
804 .unwrap();
805 assert!(runtime.render(&compiled).is_err());
806 }
807}