Skip to main content

apace_dom/events/
driver.rs

1use crate::Document;
2use apace_traits::events::{
3    ApacePointerEvent, ApacePointerId, DomEvent, DomEventData, EventState, Point, PointerCoords,
4    UiEvent,
5};
6use apace_traits::node_id::NodeId;
7use std::collections::VecDeque;
8
9pub trait EventHandler {
10    fn handle_event(
11        &mut self,
12        chain: &[NodeId],
13        event: &mut DomEvent,
14        doc: &mut dyn Document,
15        event_state: &mut EventState,
16    );
17}
18
19pub struct NoopEventHandler;
20impl EventHandler for NoopEventHandler {
21    fn handle_event(
22        &mut self,
23        _chain: &[NodeId],
24        _event: &mut DomEvent,
25        _doc: &mut dyn Document,
26        _event_state: &mut EventState,
27    ) {
28        // Do nothing
29    }
30}
31
32pub struct EventDriver<'doc, Handler: EventHandler> {
33    doc: &'doc mut dyn Document,
34    handler: Handler,
35    queue: VecDeque<DomEvent>,
36}
37
38impl<'doc, Handler: EventHandler> EventDriver<'doc, Handler> {
39    pub fn new(doc: &'doc mut dyn Document, handler: Handler) -> Self {
40        EventDriver {
41            doc,
42            handler,
43            queue: VecDeque::with_capacity(4),
44        }
45    }
46
47    pub fn handle_pointer_move(&mut self, event: &ApacePointerEvent) -> Option<NodeId> {
48        let mut doc = self.doc.inner_mut();
49
50        let prev_hover_node_id = doc.hover_node_id;
51        let changed = doc.set_hover_to(event.page_x(), event.page_y());
52        let hover_node_id = doc.hover_node_id;
53
54        drop(doc);
55
56        if !changed {
57            return prev_hover_node_id;
58        }
59
60        self.dispatch_hover_change(prev_hover_node_id, hover_node_id, event);
61
62        hover_node_id
63    }
64
65    /// End hover when the pointer leaves the document, dispatching out/leave
66    /// events to the previously hovered chain
67    pub fn handle_pointer_leave(&mut self, event: &ApacePointerEvent) {
68        let mut doc = self.doc.inner_mut();
69        let prev_hover_node_id = doc.hover_node_id;
70        doc.clear_hover();
71        drop(doc);
72
73        self.dispatch_hover_change(prev_hover_node_id, None, event);
74    }
75
76    fn dispatch_hover_change(
77        &mut self,
78        prev_hover_node_id: Option<NodeId>,
79        hover_node_id: Option<NodeId>,
80        event: &ApacePointerEvent,
81    ) {
82        let doc = self.doc.inner();
83        let mut old_chain = prev_hover_node_id
84            .map(|id| doc.node_chain(id))
85            .unwrap_or_default();
86        let mut new_chain = hover_node_id
87            .map(|id| doc.node_chain(id))
88            .unwrap_or_default();
89        old_chain.reverse();
90        new_chain.reverse();
91
92        // Find the difference in the node chain of the last hovered objected and the newest
93        let old_len = old_chain.len();
94        let new_len = new_chain.len();
95
96        let first_difference_index = old_chain
97            .iter()
98            .zip(&new_chain)
99            .position(|(old, new)| old != new)
100            .unwrap_or_else(|| old_len.min(new_len));
101
102        drop(doc);
103
104        let is_mouse = event.is_mouse();
105
106        if let Some(target) = prev_hover_node_id {
107            self.handle_dom_event(DomEvent::new(
108                target,
109                DomEventData::PointerOut(event.clone()),
110            ));
111            if is_mouse {
112                self.handle_dom_event(DomEvent::new(target, DomEventData::MouseOut(event.clone())));
113            }
114
115            // Send an mouseleave event to all old elements on the chain
116            for node_id in old_chain
117                .get(first_difference_index..)
118                .unwrap_or(&[])
119                .iter()
120            {
121                self.handle_dom_event(DomEvent::new(
122                    *node_id,
123                    DomEventData::PointerLeave(event.clone()),
124                ));
125                if is_mouse {
126                    self.handle_dom_event(DomEvent::new(
127                        *node_id,
128                        DomEventData::MouseLeave(event.clone()),
129                    ));
130                }
131            }
132        }
133
134        if let Some(target) = hover_node_id {
135            self.handle_dom_event(DomEvent::new(
136                target,
137                DomEventData::PointerOver(event.clone()),
138            ));
139
140            if is_mouse {
141                self.handle_dom_event(DomEvent::new(
142                    target,
143                    DomEventData::MouseOver(event.clone()),
144                ));
145            }
146
147            // Send an mouseenter event to all new elements on the chain
148            for node_id in new_chain
149                .get(first_difference_index..)
150                .unwrap_or(&[])
151                .iter()
152            {
153                self.handle_dom_event(DomEvent::new(
154                    *node_id,
155                    DomEventData::PointerEnter(event.clone()),
156                ));
157
158                if is_mouse {
159                    self.handle_dom_event(DomEvent::new(
160                        *node_id,
161                        DomEventData::MouseEnter(event.clone()),
162                    ));
163                }
164            }
165        }
166    }
167
168    pub fn handle_ui_event(&mut self, event: UiEvent) {
169        let doc = self.doc.inner();
170
171        // A non-hovering pointer (finger) ends hover once it is lifted or cancelled
172        let mut hover_end_event = None;
173        let mut hover_node_id = doc.hover_node_id;
174        let focussed_node_id = doc.focus_node_id;
175        let prev_hover = doc.hover_state();
176        drop(doc);
177
178        // A document the pointer has left has no say over the cursor
179        let is_leave = matches!(event, UiEvent::PointerLeave(_));
180
181        // Update document input state (hover, focus, active, etc)
182        match &event {
183            UiEvent::PointerMove(event) => {
184                hover_node_id = self.handle_pointer_move(event);
185            }
186            UiEvent::PointerDown(event) => {
187                hover_node_id = self.handle_pointer_move(event);
188                let mut doc = self.doc.inner_mut();
189                doc.active_node();
190                doc.set_mousedown_node_id(hover_node_id);
191            }
192            UiEvent::PointerUp(event) => {
193                hover_node_id = self.handle_pointer_move(event);
194                let mut doc = self.doc.inner_mut();
195                doc.unactive_node();
196
197                if event.is_primary && matches!(event.id, ApacePointerId::Finger(_)) {
198                    hover_end_event = Some(event.clone());
199                }
200            }
201            UiEvent::PointerCancel(event) => {
202                hover_node_id = self.handle_pointer_move(event);
203                let mut doc = self.doc.inner_mut();
204                doc.unactive_node();
205
206                if event.is_primary && matches!(event.id, ApacePointerId::Finger(_)) {
207                    hover_end_event = Some(event.clone());
208                }
209            }
210            UiEvent::PointerLeave(event) => {
211                self.handle_pointer_leave(event);
212                hover_node_id = None;
213            }
214            _ => {}
215        };
216
217        let target = match event {
218            UiEvent::PointerMove(_) => hover_node_id,
219            UiEvent::PointerUp(_) => hover_node_id,
220            UiEvent::PointerDown(_) => hover_node_id,
221            UiEvent::PointerCancel(_) => hover_node_id,
222            UiEvent::PointerLeave(_) => hover_node_id,
223            UiEvent::Wheel(_) => hover_node_id,
224            UiEvent::KeyUp(_) => focussed_node_id,
225            UiEvent::KeyDown(_) => focussed_node_id,
226            UiEvent::Ime(_) => focussed_node_id,
227            UiEvent::AppleStandardKeybinding(_) => focussed_node_id,
228        };
229        // Fall back to the root element. A document without a root element (e.g. an
230        // empty iframe sub-document) has no event target, so there is nothing to do.
231        let Some(target) = target.or_else(|| self.doc.inner().try_root_element().map(|el| el.id))
232        else {
233            return;
234        };
235
236        match event {
237            UiEvent::PointerMove(data) => {
238                self.handle_pointer_event(
239                    target,
240                    data,
241                    DomEventData::PointerMove,
242                    Some(DomEventData::MouseMove),
243                    DomEventData::TouchMove,
244                );
245            }
246            UiEvent::PointerUp(data) => {
247                self.handle_pointer_event(
248                    target,
249                    data,
250                    DomEventData::PointerUp,
251                    Some(DomEventData::MouseUp),
252                    DomEventData::TouchEnd,
253                );
254            }
255            UiEvent::PointerDown(data) => {
256                self.handle_pointer_event(
257                    target,
258                    data,
259                    DomEventData::PointerDown,
260                    Some(DomEventData::MouseDown),
261                    DomEventData::TouchStart,
262                );
263            }
264            UiEvent::PointerCancel(data) => {
265                // `pointercancel` has no mouse-compatibility event, but does
266                // generate a `touchcancel` for touch-like inputs.
267                self.handle_pointer_event(
268                    target,
269                    data,
270                    DomEventData::PointerCancel,
271                    None::<fn(ApacePointerEvent) -> DomEventData>,
272                    DomEventData::TouchCancel,
273                );
274            }
275            // Out/leave events were dispatched by `handle_pointer_leave`
276            UiEvent::PointerLeave(_) => {}
277            UiEvent::Wheel(data) => {
278                self.handle_dom_event(DomEvent::new(target, DomEventData::Wheel(data)))
279            }
280            UiEvent::KeyUp(data) => {
281                self.handle_dom_event(DomEvent::new(target, DomEventData::KeyUp(data)))
282            }
283            UiEvent::KeyDown(data) => {
284                self.handle_dom_event(DomEvent::new(target, DomEventData::KeyDown(data)))
285            }
286            UiEvent::Ime(data) => {
287                self.handle_dom_event(DomEvent::new(target, DomEventData::Ime(data)))
288            }
289            UiEvent::AppleStandardKeybinding(data) => {
290                let mut dom_event =
291                    DomEvent::new(target, DomEventData::AppleStandardKeybinding(data));
292                self.run_default_action(&mut dom_event);
293                self.process_queue();
294            }
295        };
296
297        // Update document input state (hover, focus, active, etc)
298        if let Some(event) = hover_end_event {
299            self.handle_pointer_leave(&event);
300        }
301
302        // Update the cursor once the event (including any forwarding to a
303        // sub-document) has been fully handled
304        let doc = self.doc.inner();
305        if !is_leave && doc.hover_state() != prev_hover {
306            doc.update_cursor();
307        }
308    }
309
310    pub fn handle_dom_event(&mut self, event: DomEvent) {
311        self.queue.push_back(event);
312        self.process_queue();
313    }
314
315    fn handle_pointer_event(
316        &mut self,
317        target: NodeId,
318        data: ApacePointerEvent,
319        make_ptr_data: impl FnOnce(ApacePointerEvent) -> DomEventData,
320        make_mouse_data: Option<impl FnOnce(ApacePointerEvent) -> DomEventData>,
321        make_touch_data: impl FnOnce(ApacePointerEvent) -> DomEventData,
322    ) {
323        let mut ptr_event = DomEvent::new(target, make_ptr_data(data.clone()));
324        let mut event_state = EventState::default();
325        event_state = self.run_handler_event(&mut ptr_event, event_state);
326
327        // Generate the corresponding compatibility event (mouse events for the
328        // mouse, touch events for fingers and pen/stylus input) and expose it to
329        // script. The default action is always run on the pointer event so that
330        // the shell layer and default actions remain pointer-based.
331        //
332        // `pointercancel` has no mouse equivalent, so `make_mouse_data` is `None`
333        // in that case and no mouse event is generated.
334        if !event_state.is_cancelled() {
335            if data.is_mouse() {
336                if let Some(make_mouse_data) = make_mouse_data {
337                    let mut mouse_event = DomEvent::new(target, make_mouse_data(data));
338                    event_state = self.run_handler_event(&mut mouse_event, event_state);
339                }
340            } else if data.is_finger() || data.is_pen() {
341                let mut touch_event = DomEvent::new(target, make_touch_data(data));
342                event_state = self.run_handler_event(&mut touch_event, event_state);
343            }
344        }
345
346        if !event_state.is_cancelled() {
347            self.run_default_action(&mut ptr_event);
348        }
349        self.process_queue();
350    }
351
352    fn process_queue(&mut self) {
353        while let Some(mut event) = self.queue.pop_front() {
354            let event_state = self.run_handler_event(&mut event, EventState::default());
355            if !event_state.is_cancelled() {
356                self.run_default_action(&mut event);
357            }
358        }
359    }
360
361    fn adjust_element_coords(
362        &self,
363        target: NodeId,
364        coords: &PointerCoords,
365        element: &mut Point<f32>,
366    ) {
367        if let Some(rect) = self.doc.inner().get_client_bounding_rect(target) {
368            element.x = coords.client_x - rect.x as f32;
369            element.y = coords.client_y - rect.y as f32;
370        }
371    }
372
373    fn run_handler_event(
374        &mut self,
375        event: &mut DomEvent,
376        initial_event_state: EventState,
377    ) -> EventState {
378        let chain = if event.bubbles {
379            let doc = self.doc.inner();
380            doc.node_chain(event.target)
381        } else {
382            vec![event.target]
383        };
384
385        match &mut event.data {
386            DomEventData::PointerMove(data)
387            | DomEventData::PointerDown(data)
388            | DomEventData::PointerUp(data)
389            | DomEventData::PointerCancel(data)
390            | DomEventData::PointerEnter(data)
391            | DomEventData::PointerLeave(data)
392            | DomEventData::PointerOver(data)
393            | DomEventData::PointerOut(data)
394            | DomEventData::MouseMove(data)
395            | DomEventData::MouseDown(data)
396            | DomEventData::MouseUp(data)
397            | DomEventData::MouseEnter(data)
398            | DomEventData::MouseLeave(data)
399            | DomEventData::MouseOver(data)
400            | DomEventData::MouseOut(data)
401            | DomEventData::TouchStart(data)
402            | DomEventData::TouchEnd(data)
403            | DomEventData::TouchMove(data)
404            | DomEventData::TouchCancel(data)
405            | DomEventData::Click(data)
406            | DomEventData::ContextMenu(data)
407            | DomEventData::DoubleClick(data) => {
408                self.adjust_element_coords(event.target, &data.coords, &mut data.element)
409            }
410            DomEventData::Wheel(data) => {
411                self.adjust_element_coords(event.target, &data.coords, &mut data.element)
412            }
413            _ => {}
414        }
415
416        let mut event_state = initial_event_state;
417        self.handler
418            .handle_event(&chain, event, self.doc, &mut event_state);
419
420        event_state
421    }
422
423    fn run_default_action(&mut self, event: &mut DomEvent) {
424        let mut doc = self.doc.inner_mut();
425        doc.handle_dom_event(event, |new_evt| self.queue.push_back(new_evt));
426    }
427}