Skip to main content

telar_ui_core/
pointer.rs

1use geometry_core::Rect;
2use platform_core::Event;
3use ui_tree::EventResult;
4
5pub(crate) fn pointer_coords(event: &Event) -> Option<(f64, f64)> {
6    match event {
7        Event::PointerMoved { x, y, .. } => Some((*x, *y)),
8        Event::PointerPressed { x, y, .. } => Some((*x, *y)),
9        Event::PointerReleased { x, y, .. } => Some((*x, *y)),
10        _ => None,
11    }
12}
13
14/// Applies the full affine inverse of `matrix` to all pointer-coordinate events. Returns `None` for
15/// non-pointer events or when `matrix` is degenerate (det ≈ 0), so callers fall back to the original.
16///
17/// Public because a component that paints a subtree under a [`RenderNode::Transform`] it chose itself — a
18/// hand-placed rail or panel, rather than a laid-out one — has to put the same transform's inverse on the
19/// events it forwards there, or its hit-testing drifts from what is on screen.
20pub fn transform_pointer(event: &Event, matrix: [f32; 6]) -> Option<Event> {
21    let inv = geometry_core::Transform::from_array(matrix).invert()?;
22    // Map in f64 so pointer coordinates keep their precision; Transform::apply would round-trip through f32.
23    let apply = |world_x: f64, world_y: f64| -> (f64, f64) {
24        let local_x = inv.a as f64 * world_x + inv.c as f64 * world_y + inv.e as f64;
25        let local_y = inv.b as f64 * world_x + inv.d as f64 * world_y + inv.f as f64;
26        (local_x, local_y)
27    };
28    match event {
29        Event::PointerMoved { x, y, source } => {
30            let (local_x, local_y) = apply(*x, *y);
31            Some(Event::PointerMoved {
32                x: local_x,
33                y: local_y,
34                source: source.clone(),
35            })
36        }
37        Event::PointerPressed {
38            x,
39            y,
40            button,
41            source,
42        } => {
43            let (local_x, local_y) = apply(*x, *y);
44            Some(Event::PointerPressed {
45                x: local_x,
46                y: local_y,
47                button: button.clone(),
48                source: source.clone(),
49            })
50        }
51        Event::PointerReleased {
52            x,
53            y,
54            button,
55            source,
56        } => {
57            let (local_x, local_y) = apply(*x, *y);
58            Some(Event::PointerReleased {
59                x: local_x,
60                y: local_y,
61                button: button.clone(),
62                source: source.clone(),
63            })
64        }
65        _ => None,
66    }
67}
68
69pub(crate) fn offset_pointer(event: &Event, dx: f64, dy: f64) -> Option<Event> {
70    transform_pointer(event, [1.0, 0.0, 0.0, 1.0, dx as f32, dy as f32])
71}
72
73// Returns a reference to `event` when the pointer is inside `rect`, or None when it is outside. Non-pointer events always pass through (returns Some). Callers use None to short-circuit to Ignored.
74pub(crate) fn clip_pointer_event<'a>(event: &'a Event, rect: Rect) -> Option<&'a Event> {
75    match pointer_coords(event) {
76        Some((x, y)) if !rect.contains(x as f32, y as f32) => None,
77        _ => Some(event),
78    }
79}
80
81// Like dispatch_to_children but skips entries for which the predicate returns false. For each entry, `should_dispatch` is called first — if it returns false the entry is skipped; then `get_result` is called to obtain the EventResult for that entry.
82pub(crate) fn dispatch_to_children_filtered<T, P, D>(
83    children: &mut [T],
84    mut should_dispatch: P,
85    mut get_result: D,
86) -> EventResult
87where
88    P: FnMut(&T) -> bool,
89    D: FnMut(&mut T) -> EventResult,
90{
91    for entry in children.iter_mut() {
92        if !should_dispatch(entry) {
93            continue;
94        }
95        if get_result(entry) == EventResult::Handled {
96            return EventResult::Handled;
97        }
98    }
99    EventResult::Ignored
100}
101
102pub(crate) fn dispatch_container_event(
103    children: &mut crate::layout_item::TrackedChildren,
104    event: &Event,
105) -> EventResult {
106    // Moves AND releases broadcast to every child regardless of position: a widget that armed a press or
107    // began a drag inside its bounds must still receive the release even when the pointer has since moved
108    // outside (pointer-capture semantics). Each widget's release handler is guarded by its own armed/drag
109    // state, so broadcasting never double-fires an unrelated widget. Position filtering (below) applies
110    // only to presses, where the target is chosen by hit-test.
111    if matches!(
112        event,
113        Event::PointerMoved { .. } | Event::PointerReleased { .. }
114    ) {
115        let mut any_handled = false;
116        for child in children.iter() {
117            if child.item.borrow_mut().on_event(event) == EventResult::Handled {
118                any_handled = true;
119            }
120        }
121        return if any_handled {
122            EventResult::Handled
123        } else {
124            EventResult::Ignored
125        };
126    }
127    let pointer_pos = pointer_coords(event).map(|(x, y)| (x as f32, y as f32));
128    dispatch_to_children_filtered(
129        children,
130        |child| match pointer_pos {
131            Some((pointer_x, pointer_y)) => child
132                .rect
133                .as_ref()
134                .map_or(true, |sig| sig.get().contains(pointer_x, pointer_y)),
135            None => true,
136        },
137        |child| child.item.borrow_mut().on_event(event),
138    )
139}