Skip to main content

ferroui_base/input/
pointer_over_pre_processor.rs

1use super::raw::{IRawInputEventArgs, RawDragEvent, RawPointerEventArgs, RawPointerEventType};
2use super::{
3    IInputDevice, IInputRoot, IPointer, InputElement, KeyModifiers, PointerEventArgs, PointerPointProperties,
4    PointerType,
5};
6use crate::reactive::IObserver;
7use crate::{PixelPoint, Point, Rect, Ref};
8use std::cell::{Cell, RefCell};
9use std::rc::Rc;
10
11/// Tracks which element the pointer is over for one input root, raising
12/// the pointer entered and exited events as it changes.
13///
14/// It observes the raw input of the input manager (subscribe it to
15/// [`IInputManager::pre_process`](super::IInputManager::pre_process)) and is
16/// told by its host when the scene changed under a resting pointer.
17pub struct PointerOverPreProcessor {
18    last_active_pointer_device: RefCell<Option<Rc<dyn IInputDevice>>>,
19    current_pointer: RefCell<Option<(Rc<dyn IPointer>, PixelPoint)>>,
20    last_known_position: Cell<Option<PixelPoint>>,
21    input_root: Rc<dyn IInputRoot>,
22}
23
24impl PointerOverPreProcessor {
25    /// Creates the pre-processor for an input root.
26    pub fn new(input_root: Rc<dyn IInputRoot>) -> Self {
27        Self {
28            last_active_pointer_device: RefCell::new(None),
29            current_pointer: RefCell::new(None),
30            last_known_position: Cell::new(None),
31            input_root,
32        }
33    }
34
35    /// The last known position of the pointer, in screen coordinates.
36    pub fn last_position(&self) -> Option<PixelPoint> {
37        self.last_known_position.get()
38    }
39
40    fn take_current_pointer(&self) -> Option<(Rc<dyn IPointer>, PixelPoint)> {
41        self.current_pointer.borrow_mut().take()
42    }
43
44    fn current_pointer(&self) -> Option<(Rc<dyn IPointer>, PixelPoint)> {
45        self.current_pointer.borrow().clone()
46    }
47
48    fn set_current_pointer(&self, value: Option<(Rc<dyn IPointer>, PixelPoint)>) {
49        let old = self.current_pointer.replace(value);
50        drop(old);
51    }
52
53    fn set_last_active_pointer_device(&self, value: Option<Rc<dyn IInputDevice>>) {
54        let old = self.last_active_pointer_device.replace(value);
55        drop(old);
56    }
57
58    fn is_this_root(&self, root: &Rc<dyn IInputRoot>) -> bool {
59        std::ptr::addr_eq(Rc::as_ptr(root), Rc::as_ptr(&self.input_root))
60    }
61
62    fn process(&self, value: &dyn IRawInputEventArgs) {
63        if let Some(drag_args) = value.downcast_ref::<RawDragEvent>() {
64            // When a platform drag operation is in progress, the application
65            // does not receive pointer move events until after the drop
66            // event. This is a problem because if a popup is shown at the
67            // pointer position in the drop event, it will be shown at the
68            // position at which the drag was initiated, not the position at
69            // which the drop occurred.
70            //
71            // Solve this by updating the last known pointer position when a
72            // drag event occurs.
73            self.last_known_position
74                .set(Some(self.input_root.root_element().point_to_screen(drag_args.location())));
75            return;
76        }
77
78        let Some(args) = value.downcast_ref::<RawPointerEventArgs>() else { return };
79
80        if !self.is_this_root(args.root()) {
81            return;
82        }
83
84        let device = value.device();
85        let Some(pointer_device) = device.as_pointer_device() else { return };
86
87        let is_last_active = self
88            .last_active_pointer_device
89            .borrow()
90            .as_ref()
91            .is_some_and(|last| std::ptr::addr_eq(Rc::as_ptr(last), Rc::as_ptr(device)));
92
93        if !is_last_active {
94            self.clear_pointer_over();
95
96            // Set the last active device before processing input, because
97            // clearing the pointer-over might be called and clear the last
98            // device.
99            self.set_last_active_pointer_device(Some(device.clone()));
100        }
101
102        let type_ = args.type_();
103        let root = args.root();
104
105        if matches!(
106            type_,
107            RawPointerEventType::LeaveWindow
108                | RawPointerEventType::NonClientLeftButtonDown
109                | RawPointerEventType::TouchCancel
110                | RawPointerEventType::TouchEnd
111        ) {
112            if let Some((last_pointer, last_position)) = self.take_current_pointer() {
113                self.clear_pointer_over_for(
114                    &last_pointer,
115                    root,
116                    0,
117                    Some(Self::point_to_client(root, last_position)),
118                    PointerPointProperties::new(args.input_modifiers(), type_.to_update_kind()),
119                    args.input_modifiers().to_key_modifiers(),
120                );
121            }
122        } else if matches!(type_, RawPointerEventType::TouchBegin | RawPointerEventType::TouchUpdate) {
123            self.last_known_position.set(Some(root.root_element().point_to_screen(args.position())));
124        } else if let Some(pointer) = pointer_device.try_get_pointer(args) {
125            if pointer.type_() != PointerType::Touch && type_ != RawPointerEventType::CancelCapture {
126                let element =
127                    Self::get_effective_pointer_over_element(args.input_hit_test_result().1, pointer.captured());
128
129                self.set_pointer_over(
130                    &pointer,
131                    root,
132                    element,
133                    args.timestamp(),
134                    args.position(),
135                    PointerPointProperties::new(args.input_modifiers(), type_.to_update_kind()),
136                    args.input_modifiers().to_key_modifiers(),
137                );
138            }
139        }
140    }
141
142    /// Called by the host when the part of the scene within `dirty_rect`
143    /// changed: re-evaluates which element a resting pointer is over.
144    pub fn scene_invalidated(&self, dirty_rect: Rect) {
145        let Some((pointer, position)) = self.current_pointer() else { return };
146
147        let root_element = self.input_root.root_element();
148        let client_point = Self::point_to_client(&self.input_root, position);
149
150        if dirty_rect.contains(client_point) {
151            let element = Self::get_effective_pointer_over_element(
152                root_element.input_hit_test(client_point),
153                pointer.captured(),
154            );
155
156            self.set_pointer_over(
157                &pointer,
158                &self.input_root,
159                element,
160                0,
161                client_point,
162                PointerPointProperties::NONE,
163                KeyModifiers::NONE,
164            );
165        } else if !root_element.bounds().contains(client_point) {
166            self.clear_pointer_over_for(
167                &pointer,
168                &self.input_root,
169                0,
170                Some(client_point),
171                PointerPointProperties::NONE,
172                KeyModifiers::NONE,
173            );
174        }
175    }
176
177    fn get_effective_pointer_over_element(
178        hit_test_element: Option<Ref<InputElement>>,
179        captured: Option<Ref<InputElement>>,
180    ) -> Option<Ref<InputElement>> {
181        if captured.is_some() && hit_test_element != captured {
182            None
183        } else {
184            hit_test_element
185        }
186    }
187
188    fn clear_pointer_over(&self) {
189        if let Some((pointer, position)) = self.current_pointer() {
190            let client_point = Self::point_to_client(&self.input_root, position);
191            self.clear_pointer_over_for(
192                &pointer,
193                &self.input_root,
194                0,
195                Some(client_point),
196                PointerPointProperties::NONE,
197                KeyModifiers::NONE,
198            );
199        }
200
201        self.set_current_pointer(None);
202        self.set_last_active_pointer_device(None);
203    }
204
205    fn clear_pointer_over_for(
206        &self,
207        pointer: &Rc<dyn IPointer>,
208        root: &Rc<dyn IInputRoot>,
209        timestamp: u64,
210        position: Option<Point>,
211        properties: PointerPointProperties,
212        input_modifiers: KeyModifiers,
213    ) {
214        let Some(element) = root.pointer_over_element() else { return };
215
216        let root_element = root.root_element();
217
218        // Do not pass the root visual when we have an unknown position, so
219        // `get_position` won't return invalid values.
220        let e = PointerEventArgs::new(
221            Some(InputElement::pointer_exited_event()),
222            &element,
223            pointer.clone(),
224            position.map(|_| &***root_element as &crate::Visual),
225            position.unwrap_or_default(),
226            timestamp,
227            properties,
228            input_modifiers,
229        );
230
231        if !element.is_attached_to_visual_tree() {
232            // The element has been removed from the visual tree so do a top
233            // down cleanup.
234            if root_element.is_pointer_over() {
235                Self::clear_children_pointer_over(&e, &root_element, true);
236            }
237        }
238
239        let mut current = Some(element);
240
241        while let Some(element) = current {
242            e.set_source(&element);
243            e.set_handled(false);
244            element.raise_event(&e);
245            current = Self::get_visual_parent(&element);
246        }
247
248        root.set_pointer_over_element(None);
249        root.set_cursor_element(pointer.captured());
250
251        self.set_last_active_pointer_device(None);
252        self.set_current_pointer(None);
253    }
254
255    fn clear_children_pointer_over(e: &PointerEventArgs, element: &Ref<InputElement>, clear_root: bool) {
256        if let Some(children) = element.visual_children_snapshot() {
257            for el in children.iter() {
258                if let Some(child) = el.cast::<InputElement>() {
259                    if child.is_pointer_over() {
260                        Self::clear_children_pointer_over(e, &child, true);
261                        break;
262                    }
263                }
264            }
265        }
266
267        if clear_root {
268            e.set_source(element);
269            e.set_handled(false);
270            element.raise_event(e);
271        }
272    }
273
274    #[allow(clippy::too_many_arguments)]
275    fn set_pointer_over(
276        &self,
277        pointer: &Rc<dyn IPointer>,
278        root: &Rc<dyn IInputRoot>,
279        element: Option<Ref<InputElement>>,
280        timestamp: u64,
281        position: Point,
282        properties: PointerPointProperties,
283        input_modifiers: KeyModifiers,
284    ) {
285        let pointer_over_element = root.pointer_over_element();
286        let screen_position = root.root_element().point_to_screen(position);
287        self.last_known_position.set(Some(screen_position));
288
289        if element != pointer_over_element {
290            match &element {
291                Some(element) => self.set_pointer_over_to_element(
292                    pointer,
293                    root,
294                    element,
295                    timestamp,
296                    position,
297                    properties,
298                    input_modifiers,
299                ),
300                None => self.clear_pointer_over_for(
301                    pointer,
302                    root,
303                    timestamp,
304                    Some(position),
305                    properties,
306                    input_modifiers,
307                ),
308            }
309        }
310
311        self.set_current_pointer(Some((pointer.clone(), screen_position)));
312    }
313
314    #[allow(clippy::too_many_arguments)]
315    fn set_pointer_over_to_element(
316        &self,
317        pointer: &Rc<dyn IPointer>,
318        root: &Rc<dyn IInputRoot>,
319        element: &Ref<InputElement>,
320        timestamp: u64,
321        position: Point,
322        properties: PointerPointProperties,
323        input_modifiers: KeyModifiers,
324    ) {
325        let mut branch: Option<Ref<InputElement>> = None;
326        let mut el = Some(element.clone());
327
328        while let Some(current) = el {
329            if current.is_pointer_over() {
330                branch = Some(current);
331                break;
332            }
333            el = Self::get_visual_parent(&current);
334        }
335
336        el = root.pointer_over_element();
337
338        let root_element = root.root_element();
339        let e = PointerEventArgs::new(
340            Some(InputElement::pointer_exited_event()),
341            el.clone().map(Ref::upcast::<crate::FerroObject>),
342            pointer.clone(),
343            Some(&root_element),
344            position,
345            timestamp,
346            properties,
347            input_modifiers,
348        );
349
350        if let (Some(old), Some(branch)) = (&el, &branch) {
351            if !old.is_attached_to_visual_tree() {
352                Self::clear_children_pointer_over(&e, branch, false);
353            }
354        }
355
356        while let Some(current) = el {
357            if Some(&current) == branch.as_ref() {
358                break;
359            }
360            e.set_source(&current);
361            e.set_handled(false);
362            current.raise_event(&e);
363            el = Self::get_visual_parent(&current);
364        }
365
366        root.set_pointer_over_element(Some(element.clone()));
367        el = Some(element.clone());
368        root.set_cursor_element(pointer.captured().or_else(|| Some(element.clone())));
369
370        e.set_routed_event(Some(InputElement::pointer_entered_event()));
371
372        while let Some(current) = el {
373            if Some(&current) == branch.as_ref() {
374                break;
375            }
376            e.set_source(&current);
377            e.set_handled(false);
378            current.raise_event(&e);
379            el = Self::get_visual_parent(&current);
380        }
381    }
382
383    fn get_visual_parent(e: &InputElement) -> Option<Ref<InputElement>> {
384        e.visual_parent().and_then(|parent| parent.downcast::<InputElement>().ok())
385    }
386
387    fn point_to_client(root: &Rc<dyn IInputRoot>, p: PixelPoint) -> Point {
388        root.root_element().point_to_client(p)
389    }
390}
391
392impl IObserver<Rc<dyn IRawInputEventArgs>> for PointerOverPreProcessor {
393    fn on_next(&self, value: Rc<dyn IRawInputEventArgs>) {
394        self.process(&*value);
395    }
396
397    fn on_completed(&self) {
398        self.clear_pointer_over();
399    }
400}