Skip to main content

ferroui_base/input/
pen_device.rs

1use super::raw::{IRawInputEventArgs, RawPointerEventArgs, RawPointerEventType};
2use super::{
3    CaptureSource, IInputDevice, IInputRoot, IPenDevice, IPointer, IPointerDevice, InputElement, IntermediatePoints,
4    KeyModifiers, MouseButton, Pointer, PointerEventArgs, PointerPointProperties, PointerPressedEventArgs,
5    PointerReleasedEventArgs, PointerType,
6};
7use crate::{Point, Rect, Ref, Size, Thickness};
8use std::any::Any;
9use std::cell::{Cell, RefCell};
10use std::collections::HashMap;
11use std::rc::Rc;
12
13/// Represents a pen/stylus device.
14pub struct PenDevice {
15    pointers: RefCell<HashMap<i64, Rc<Pointer>>>,
16    release_pointer_on_pen_up: bool,
17    click_count: Cell<i32>,
18    last_click_rect: Cell<Rect>,
19    last_click_time: Cell<u64>,
20    last_mouse_down_button: Cell<MouseButton>,
21    disposed: Cell<bool>,
22}
23
24impl PenDevice {
25    /// Creates a pen device. `release_pointer_on_pen_up` tells whether the
26    /// pointer is released when the pen is lifted.
27    pub fn new(release_pointer_on_pen_up: bool) -> Rc<PenDevice> {
28        Rc::new(PenDevice {
29            pointers: RefCell::new(HashMap::new()),
30            release_pointer_on_pen_up,
31            click_count: Cell::new(0),
32            last_click_rect: Cell::new(Rect::default()),
33            last_click_time: Cell::new(0),
34            last_mouse_down_button: Cell::new(MouseButton::None),
35            disposed: Cell::new(false),
36        })
37    }
38
39    fn process_raw_pointer_event(&self, e: &RawPointerEventArgs) {
40        let existing = self.pointers.borrow().get(&e.raw_pointer_id()).cloned();
41
42        let pointer = match existing {
43            Some(pointer) => pointer,
44            None => {
45                if e.type_() == RawPointerEventType::LeftButtonUp || e.type_() == RawPointerEventType::TouchEnd {
46                    return;
47                }
48
49                let is_primary = self.pointers.borrow().is_empty();
50                let pointer = Pointer::new(Pointer::get_next_free_id(), PointerType::Pen, is_primary);
51                self.pointers.borrow_mut().insert(e.raw_pointer_id(), pointer.clone());
52                pointer
53            }
54        };
55
56        let point = e.point();
57        let props = PointerPointProperties::with_pen_state_and_contact_rect(
58            e.input_modifiers(),
59            e.type_().to_update_kind(),
60            point.twist,
61            point.pressure,
62            point.x_tilt,
63            point.y_tilt,
64            point.contact_rect(),
65        );
66        let key_modifiers = e.input_modifiers().to_key_modifiers();
67        let hit_test = e.input_hit_test_result().1;
68
69        /// Releases the pointer when the event is done, even on a panic.
70        struct Release<'a> {
71            device: &'a PenDevice,
72            raw_pointer_id: i64,
73            pointer: Rc<Pointer>,
74            should_release: Cell<bool>,
75        }
76
77        impl Drop for Release<'_> {
78            fn drop(&mut self) {
79                if self.should_release.get() {
80                    self.device.pointers.borrow_mut().remove(&self.raw_pointer_id);
81                    self.pointer.dispose();
82                }
83            }
84        }
85
86        let release = Release {
87            device: self,
88            raw_pointer_id: e.raw_pointer_id(),
89            pointer: pointer.clone(),
90            should_release: Cell::new(false),
91        };
92
93        match e.type_() {
94            RawPointerEventType::LeaveWindow => release.should_release.set(true),
95            RawPointerEventType::CancelCapture => pointer.platform_capture_lost(),
96            RawPointerEventType::LeftButtonDown
97            | RawPointerEventType::RightButtonDown
98            | RawPointerEventType::MiddleButtonDown
99            | RawPointerEventType::XButton1Down
100            | RawPointerEventType::XButton2Down => {
101                let handled = self.pen_down(
102                    &pointer,
103                    e.timestamp(),
104                    e.root(),
105                    e.position(),
106                    props,
107                    key_modifiers,
108                    hit_test,
109                    e.platform_input_event_cookie(),
110                );
111                e.set_handled(handled);
112            }
113            RawPointerEventType::LeftButtonUp
114            | RawPointerEventType::RightButtonUp
115            | RawPointerEventType::MiddleButtonUp
116            | RawPointerEventType::XButton1Up
117            | RawPointerEventType::XButton2Up => {
118                if self.release_pointer_on_pen_up {
119                    release.should_release.set(true);
120                }
121
122                let handled =
123                    self.pen_up(&pointer, e.timestamp(), e.root(), e.position(), props, key_modifiers, hit_test);
124                e.set_handled(handled);
125            }
126            RawPointerEventType::Move => {
127                let handled = Self::pen_move(
128                    &pointer,
129                    e.timestamp(),
130                    e.root(),
131                    e.position(),
132                    props,
133                    key_modifiers,
134                    hit_test,
135                    e.intermediate_points(),
136                );
137                e.set_handled(handled);
138            }
139            _ => {}
140        }
141    }
142
143    #[allow(clippy::too_many_arguments)]
144    fn pen_down(
145        &self,
146        pointer: &Rc<Pointer>,
147        timestamp: u64,
148        root: &Rc<dyn IInputRoot>,
149        p: Point,
150        properties: PointerPointProperties,
151        input_modifiers: KeyModifiers,
152        hit_test: Option<Ref<InputElement>>,
153        platform_input_event_cookie: Option<Rc<dyn Any>>,
154    ) -> bool {
155        let Some(source) = pointer.captured().or(hit_test) else { return false };
156
157        pointer.capture_with_source(Some(&source), CaptureSource::Implicit);
158
159        if let Some(settings) = source.get_platform_settings() {
160            let double_click_time = settings.get_double_tap_time(PointerType::Pen).as_secs_f64() * 1000.0;
161            let double_click_size = settings.get_double_tap_size(PointerType::Pen);
162
163            if !self.last_click_rect.get().contains(p)
164                || timestamp.wrapping_sub(self.last_click_time.get()) as f64 > double_click_time
165            {
166                self.click_count.set(0);
167            }
168
169            self.click_count.set(self.click_count.get() + 1);
170            self.last_click_time.set(timestamp);
171            self.last_click_rect.set(
172                Rect::from_position_size(p, Size::default())
173                    .inflate_thickness(Thickness::symmetric(double_click_size.width / 2.0, double_click_size.height / 2.0)),
174            );
175        }
176
177        self.last_mouse_down_button.set(properties.pointer_update_kind.get_mouse_button());
178
179        let e = PointerPressedEventArgs::new(
180            &source,
181            pointer.clone(),
182            &root.root_element(),
183            p,
184            timestamp,
185            properties,
186            input_modifiers,
187            self.click_count.get(),
188        )
189        .with_platform_input_event_cookie(platform_input_event_cookie);
190
191        source.raise_event(&e);
192        e.handled()
193    }
194
195    #[allow(clippy::too_many_arguments)]
196    fn pen_move(
197        pointer: &Rc<Pointer>,
198        timestamp: u64,
199        root: &Rc<dyn IInputRoot>,
200        p: Point,
201        properties: PointerPointProperties,
202        input_modifiers: KeyModifiers,
203        hit_test: Option<Ref<InputElement>>,
204        intermediate_points: Option<IntermediatePoints>,
205    ) -> bool {
206        let gesture_recognizer = pointer.captured_gesture_recognizer();
207        let source = gesture_recognizer
208            .as_ref()
209            .and_then(|recognizer| recognizer.target())
210            .or_else(|| pointer.captured())
211            .or(hit_test);
212        let Some(source) = source else { return false };
213
214        let e = PointerEventArgs::new(
215            Some(InputElement::pointer_moved_event()),
216            &source,
217            pointer.clone(),
218            Some(&root.root_element()),
219            p,
220            timestamp,
221            properties,
222            input_modifiers,
223        )
224        .with_previous_points(intermediate_points);
225
226        match &gesture_recognizer {
227            Some(gesture_recognizer) => gesture_recognizer.pointer_moved_internal(&e),
228            None => source.raise_event(&e),
229        }
230
231        e.handled()
232    }
233
234    #[allow(clippy::too_many_arguments)]
235    fn pen_up(
236        &self,
237        pointer: &Rc<Pointer>,
238        timestamp: u64,
239        root: &Rc<dyn IInputRoot>,
240        p: Point,
241        properties: PointerPointProperties,
242        input_modifiers: KeyModifiers,
243        hit_test: Option<Ref<InputElement>>,
244    ) -> bool {
245        let gesture_recognizer = pointer.captured_gesture_recognizer();
246        let source = gesture_recognizer
247            .as_ref()
248            .and_then(|recognizer| recognizer.target())
249            .or_else(|| pointer.captured())
250            .or(hit_test);
251        let Some(source) = source else { return false };
252
253        let e = PointerReleasedEventArgs::new(
254            &source,
255            pointer.clone(),
256            &root.root_element(),
257            p,
258            timestamp,
259            properties,
260            input_modifiers,
261            self.last_mouse_down_button.get(),
262        );
263
264        /// Releases the implicit capture even if a handler panics.
265        struct Finally<'a>(&'a PenDevice, &'a Pointer);
266
267        impl Drop for Finally<'_> {
268            fn drop(&mut self) {
269                self.1.capture_lost(CaptureSource::Implicit);
270                self.0.last_mouse_down_button.set(MouseButton::None);
271            }
272        }
273
274        {
275            let _finally = Finally(self, pointer);
276            match &gesture_recognizer {
277                Some(gesture_recognizer) => gesture_recognizer.pointer_released_internal(&e),
278                None => source.raise_event(&e),
279            }
280        }
281
282        e.handled()
283    }
284
285    /// Releases the device and its pointers.
286    pub fn dispose(&self) {
287        if self.disposed.get() {
288            return;
289        }
290
291        let values: Vec<_> = self.pointers.borrow_mut().drain().map(|(_, pointer)| pointer).collect();
292        self.disposed.set(true);
293
294        for p in values {
295            p.dispose();
296        }
297    }
298}
299
300impl IInputDevice for PenDevice {
301    fn process_raw_event(&self, e: &dyn IRawInputEventArgs) {
302        if !e.handled() {
303            if let Some(margs) = e.downcast_ref::<RawPointerEventArgs>() {
304                self.process_raw_pointer_event(margs);
305            }
306        }
307    }
308
309    fn as_any(&self) -> &dyn Any {
310        self
311    }
312
313    fn as_pointer_device(&self) -> Option<&dyn IPointerDevice> {
314        Some(self)
315    }
316}
317
318impl IPointerDevice for PenDevice {
319    fn try_get_pointer(&self, ev: &RawPointerEventArgs) -> Option<Rc<dyn IPointer>> {
320        self.pointers.borrow().get(&ev.raw_pointer_id()).map(|pointer| pointer.clone() as Rc<dyn IPointer>)
321    }
322}
323
324impl IPenDevice for PenDevice {}