Skip to main content

ferroui_base/input/
mouse_device.rs

1use super::raw::{
2    IRawInputEventArgs, RawMouseWheelEventArgs, RawPointerEventArgs, RawPointerEventType, RawPointerGestureEventArgs,
3};
4use super::{
5    CaptureSource, IInputDevice, IInputRoot, IMouseDevice, IPointer, IPointerDevice, InputElement, IntermediatePoints,
6    KeyModifiers, MouseButton, Pointer, PointerDeltaEventArgs, PointerEventArgs, PointerPointProperties,
7    PointerPressedEventArgs, PointerReleasedEventArgs, PointerType, PointerWheelEventArgs,
8};
9use crate::interactivity::RoutedEvent;
10use crate::{Point, Rect, Ref, Size, Thickness, Vector};
11use std::any::Any;
12use std::cell::{Cell, RefCell};
13use std::rc::Rc;
14
15crate::ferro_thread_local! {
16    static PRIMARY: RefCell<Option<Rc<MouseDevice>>> = const { RefCell::new(None) };
17}
18
19/// Represents a mouse device: turns raw pointer events into routed pointer
20/// events on the element under the pointer or capturing it.
21pub struct MouseDevice {
22    click_count: Cell<i32>,
23    last_click_rect: Cell<Rect>,
24    last_click_time: Cell<u64>,
25    pointer: Rc<Pointer>,
26    disposed: Cell<bool>,
27    last_mouse_down_button: Cell<MouseButton>,
28}
29
30impl MouseDevice {
31    /// Creates a mouse device with a pointer of its own.
32    pub fn new() -> Rc<MouseDevice> {
33        Self::with_pointer(Pointer::new(Pointer::get_next_free_id(), PointerType::Mouse, true))
34    }
35
36    /// Creates a mouse device for an existing pointer.
37    pub fn with_pointer(pointer: Rc<Pointer>) -> Rc<MouseDevice> {
38        Rc::new(MouseDevice {
39            click_count: Cell::new(0),
40            last_click_rect: Cell::new(Rect::default()),
41            last_click_time: Cell::new(0),
42            pointer,
43            disposed: Cell::new(false),
44            last_mouse_down_button: Cell::new(MouseButton::None),
45        })
46    }
47
48    /// The primary mouse device of the current thread, created on first
49    /// use.
50    pub fn primary() -> Rc<MouseDevice> {
51        if let Some(primary) = PRIMARY.with(|primary| primary.borrow().clone()) {
52            return primary;
53        }
54        let primary = MouseDevice::new();
55        PRIMARY.with(|slot| *slot.borrow_mut() = Some(primary.clone()));
56        primary
57    }
58
59    /// Forgets the primary mouse device, so that tests start with a fresh
60    /// one.
61    pub fn reset_primary_for_unit_tests() {
62        let old = PRIMARY.with(|primary| primary.take());
63        drop(old);
64    }
65
66    /// The pointer of the mouse.
67    pub fn pointer(&self) -> &Rc<Pointer> {
68        &self.pointer
69    }
70
71    fn button_count(props: &PointerPointProperties) -> i32 {
72        let mut rv = 0;
73        if props.is_left_button_pressed {
74            rv += 1;
75        }
76        if props.is_middle_button_pressed {
77            rv += 1;
78        }
79        if props.is_right_button_pressed {
80            rv += 1;
81        }
82        if props.is_x_button_1_pressed {
83            rv += 1;
84        }
85        if props.is_x_button_2_pressed {
86            rv += 1;
87        }
88        rv
89    }
90
91    fn process_raw_pointer_event(&self, raw: &dyn IRawInputEventArgs, e: &RawPointerEventArgs) {
92        if let Some(mouse) = e.device().as_any().downcast_ref::<MouseDevice>() {
93            if mouse.disposed.get() {
94                return;
95            }
96        }
97
98        let props = Self::create_properties(e);
99        let key_modifiers = e.input_modifiers().to_key_modifiers();
100        let root = e.root();
101        let hit_test = e.input_hit_test_result().1;
102
103        match e.type_() {
104            RawPointerEventType::LeaveWindow | RawPointerEventType::NonClientLeftButtonDown => self.leave_window(),
105            RawPointerEventType::LeftButtonDown
106            | RawPointerEventType::RightButtonDown
107            | RawPointerEventType::MiddleButtonDown
108            | RawPointerEventType::XButton1Down
109            | RawPointerEventType::XButton2Down => {
110                let handled = if Self::button_count(&props) > 1 {
111                    self.mouse_move(
112                        e.timestamp(),
113                        root,
114                        e.position(),
115                        props,
116                        key_modifiers,
117                        e.intermediate_points(),
118                        hit_test,
119                    )
120                } else {
121                    self.mouse_down(
122                        e.timestamp(),
123                        root,
124                        e.position(),
125                        props,
126                        key_modifiers,
127                        e.platform_input_event_cookie(),
128                    )
129                };
130                e.set_handled(handled);
131            }
132            RawPointerEventType::LeftButtonUp
133            | RawPointerEventType::RightButtonUp
134            | RawPointerEventType::MiddleButtonUp
135            | RawPointerEventType::XButton1Up
136            | RawPointerEventType::XButton2Up => {
137                let handled = if Self::button_count(&props) != 0 {
138                    self.mouse_move(
139                        e.timestamp(),
140                        root,
141                        e.position(),
142                        props,
143                        key_modifiers,
144                        e.intermediate_points(),
145                        hit_test,
146                    )
147                } else {
148                    self.mouse_up(e.timestamp(), root, e.position(), props, key_modifiers, hit_test)
149                };
150                e.set_handled(handled);
151            }
152            RawPointerEventType::Move => {
153                let handled = self.mouse_move(
154                    e.timestamp(),
155                    root,
156                    e.position(),
157                    props,
158                    key_modifiers,
159                    e.intermediate_points(),
160                    hit_test,
161                );
162                e.set_handled(handled);
163            }
164            RawPointerEventType::Wheel => {
165                let delta = raw
166                    .downcast_ref::<RawMouseWheelEventArgs>()
167                    .expect("a raw wheel event must be a RawMouseWheelEventArgs")
168                    .delta();
169                let handled = self.mouse_wheel(e.timestamp(), root, e.position(), props, delta, key_modifiers, hit_test);
170                e.set_handled(handled);
171            }
172            RawPointerEventType::Magnify | RawPointerEventType::Rotate | RawPointerEventType::Swipe => {
173                let delta = raw
174                    .downcast_ref::<RawPointerGestureEventArgs>()
175                    .expect("a raw gesture event must be a RawPointerGestureEventArgs")
176                    .delta();
177                let routed_event = match e.type_() {
178                    RawPointerEventType::Magnify => InputElement::pointer_touch_pad_gesture_magnify_event(),
179                    RawPointerEventType::Rotate => InputElement::pointer_touch_pad_gesture_rotate_event(),
180                    _ => InputElement::pointer_touch_pad_gesture_swipe_event(),
181                };
182                let handled =
183                    self.gesture(routed_event, e.timestamp(), root, e.position(), props, delta, key_modifiers, hit_test);
184                e.set_handled(handled);
185            }
186            RawPointerEventType::CancelCapture => self.platform_capture_lost(),
187            RawPointerEventType::TouchBegin
188            | RawPointerEventType::TouchUpdate
189            | RawPointerEventType::TouchEnd
190            | RawPointerEventType::TouchCancel => {}
191        }
192    }
193
194    fn leave_window(&self) {}
195
196    fn create_properties(args: &RawPointerEventArgs) -> PointerPointProperties {
197        PointerPointProperties::new(args.input_modifiers(), args.type_().to_update_kind())
198    }
199
200    fn mouse_down(
201        &self,
202        timestamp: u64,
203        root: &Rc<dyn IInputRoot>,
204        p: Point,
205        properties: PointerPointProperties,
206        input_modifiers: KeyModifiers,
207        platform_input_event_cookie: Option<Rc<dyn Any>>,
208    ) -> bool {
209        let root_element = root.root_element();
210        let source = self.pointer.captured().or_else(|| root_element.input_hit_test(p));
211
212        let Some(source) = source else { return false };
213
214        self.pointer.capture_with_source(Some(&source), CaptureSource::Implicit);
215
216        if let Some(settings) = source.get_platform_settings() {
217            let double_click_time = settings.get_double_tap_time(PointerType::Mouse).as_secs_f64() * 1000.0;
218            let double_click_size = settings.get_double_tap_size(PointerType::Mouse);
219
220            if !self.last_click_rect.get().contains(p)
221                || timestamp.wrapping_sub(self.last_click_time.get()) as f64 > double_click_time
222            {
223                self.click_count.set(0);
224            }
225
226            self.click_count.set(self.click_count.get() + 1);
227            self.last_click_time.set(timestamp);
228            self.last_click_rect.set(
229                Rect::from_position_size(p, Size::default())
230                    .inflate_thickness(Thickness::symmetric(double_click_size.width / 2.0, double_click_size.height / 2.0)),
231            );
232        }
233
234        self.last_mouse_down_button.set(properties.pointer_update_kind.get_mouse_button());
235
236        let e = PointerPressedEventArgs::new(
237            &source,
238            self.pointer.clone(),
239            &root_element,
240            p,
241            timestamp,
242            properties,
243            input_modifiers,
244            self.click_count.get(),
245        )
246        .with_platform_input_event_cookie(platform_input_event_cookie);
247
248        source.raise_event(&e);
249        e.handled()
250    }
251
252    #[allow(clippy::too_many_arguments)]
253    fn mouse_move(
254        &self,
255        timestamp: u64,
256        root: &Rc<dyn IInputRoot>,
257        p: Point,
258        properties: PointerPointProperties,
259        input_modifiers: KeyModifiers,
260        intermediate_points: Option<IntermediatePoints>,
261        hit_test: Option<Ref<InputElement>>,
262    ) -> bool {
263        let gesture_recognizer = self.pointer.captured_gesture_recognizer();
264        let source = gesture_recognizer
265            .as_ref()
266            .and_then(|recognizer| recognizer.target())
267            .or_else(|| self.pointer.captured())
268            .or(hit_test);
269        let Some(source) = source else { return false };
270        let root_element = root.root_element();
271
272        let e = PointerEventArgs::new(
273            Some(InputElement::pointer_moved_event()),
274            &source,
275            self.pointer.clone(),
276            Some(&root_element),
277            p,
278            timestamp,
279            properties,
280            input_modifiers,
281        )
282        .with_previous_points(intermediate_points);
283
284        match &gesture_recognizer {
285            Some(gesture_recognizer) => gesture_recognizer.pointer_moved_internal(&e),
286            None => source.raise_event(&e),
287        }
288
289        e.handled()
290    }
291
292    fn mouse_up(
293        &self,
294        timestamp: u64,
295        root: &Rc<dyn IInputRoot>,
296        p: Point,
297        props: PointerPointProperties,
298        input_modifiers: KeyModifiers,
299        hit_test: Option<Ref<InputElement>>,
300    ) -> bool {
301        let gesture_recognizer = self.pointer.captured_gesture_recognizer();
302        let source = gesture_recognizer
303            .as_ref()
304            .and_then(|recognizer| recognizer.target())
305            .or_else(|| self.pointer.captured())
306            .or(hit_test);
307        let Some(source) = source else { return false };
308        let root_element = root.root_element();
309
310        let e = PointerReleasedEventArgs::new(
311            &source,
312            self.pointer.clone(),
313            &root_element,
314            p,
315            timestamp,
316            props,
317            input_modifiers,
318            self.last_mouse_down_button.get(),
319        );
320
321        /// Releases the implicit capture even if a handler panics.
322        struct Finally<'a>(&'a MouseDevice);
323
324        impl Drop for Finally<'_> {
325            fn drop(&mut self) {
326                self.0.pointer.capture_lost(CaptureSource::Implicit);
327                self.0.last_mouse_down_button.set(MouseButton::None);
328            }
329        }
330
331        {
332            let _finally = Finally(self);
333            match &gesture_recognizer {
334                Some(gesture_recognizer) => gesture_recognizer.pointer_released_internal(&e),
335                None => source.raise_event(&e),
336            }
337        }
338
339        e.handled()
340    }
341
342    #[allow(clippy::too_many_arguments)]
343    fn mouse_wheel(
344        &self,
345        timestamp: u64,
346        root: &Rc<dyn IInputRoot>,
347        p: Point,
348        props: PointerPointProperties,
349        delta: Vector,
350        input_modifiers: KeyModifiers,
351        hit_test: Option<Ref<InputElement>>,
352    ) -> bool {
353        let Some(source) = self.pointer.captured().or(hit_test) else { return false };
354        let root_element = root.root_element();
355
356        let e = PointerWheelEventArgs::new(
357            &source,
358            self.pointer.clone(),
359            &root_element,
360            p,
361            timestamp,
362            props,
363            input_modifiers,
364            delta,
365        );
366
367        source.raise_event(&e);
368        e.handled()
369    }
370
371    /// Raises one of the touchpad gesture events (magnify, rotate, swipe).
372    #[allow(clippy::too_many_arguments)]
373    fn gesture(
374        &self,
375        routed_event: &RoutedEvent<PointerDeltaEventArgs>,
376        timestamp: u64,
377        root: &Rc<dyn IInputRoot>,
378        p: Point,
379        props: PointerPointProperties,
380        delta: Vector,
381        input_modifiers: KeyModifiers,
382        hit_test: Option<Ref<InputElement>>,
383    ) -> bool {
384        let Some(source) = self.pointer.captured().or(hit_test) else { return false };
385        let root_element = root.root_element();
386
387        let e = PointerDeltaEventArgs::new(
388            Some(routed_event),
389            &source,
390            self.pointer.clone(),
391            &root_element,
392            p,
393            timestamp,
394            props,
395            input_modifiers,
396            delta,
397        );
398
399        source.raise_event(&e);
400        e.handled()
401    }
402
403    /// Releases the mouse device and its pointer.
404    pub fn dispose(&self) {
405        self.disposed.set(true);
406        self.pointer.dispose();
407    }
408
409    /// Notifies the device that the capture of its pointer was lost in the
410    /// platform.
411    pub fn platform_capture_lost(&self) {
412        self.pointer.platform_capture_lost();
413    }
414}
415
416impl IInputDevice for MouseDevice {
417    fn process_raw_event(&self, e: &dyn IRawInputEventArgs) {
418        if !e.handled() {
419            if let Some(margs) = e.downcast_ref::<RawPointerEventArgs>() {
420                self.process_raw_pointer_event(e, margs);
421            }
422        }
423    }
424
425    fn as_any(&self) -> &dyn Any {
426        self
427    }
428
429    fn as_pointer_device(&self) -> Option<&dyn IPointerDevice> {
430        Some(self)
431    }
432}
433
434impl IPointerDevice for MouseDevice {
435    fn try_get_pointer(&self, _ev: &RawPointerEventArgs) -> Option<Rc<dyn IPointer>> {
436        Some(self.pointer.clone())
437    }
438}
439
440impl IMouseDevice for MouseDevice {}