Skip to main content

kas_core/event/cx/
press.rs

1// Licensed under the Apache License, Version 2.0 (the "License");
2// you may not use this file except in compliance with the License.
3// You may obtain a copy of the License in the LICENSE-APACHE file or at:
4//     https://www.apache.org/licenses/LICENSE-2.0
5
6//! Event handling: mouse and touch events
7
8mod mouse;
9mod touch;
10pub(crate) mod velocity;
11
12#[allow(unused)] use super::{Event, EventState}; // for doc-links
13use super::{EventCx, IsUsed};
14#[allow(unused)] use crate::Events; // for doc-links
15use crate::Id;
16use crate::event::{CursorIcon, MouseButton, Unused, Used};
17use crate::geom::{Coord, DVec2, Offset, Vec2};
18use cast::{Cast, CastApprox, Conv};
19pub(crate) use mouse::Mouse;
20pub(crate) use touch::Touch;
21use winit::event::FingerId;
22
23/// Controls the types of events delivered by [`PressStart::grab`]
24#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)]
25pub enum GrabMode {
26    /// Deliver [`Event::PressEnd`] only for each grabbed press
27    Click,
28    /// Deliver [`Event::PressMove`] and [`Event::PressEnd`] for each grabbed press
29    Grab,
30    /// Deliver [`Event::Pan`] events
31    ///
32    /// Scaling and rotation are optional.
33    Pan { scale: bool, rotate: bool },
34}
35
36impl GrabMode {
37    /// [`GrabMode::Pan`] without scaling or rotation
38    pub const PAN_NONE: GrabMode = GrabMode::Pan {
39        scale: false,
40        rotate: false,
41    };
42    /// [`GrabMode::Pan`] with scaling only
43    pub const PAN_SCALE: GrabMode = GrabMode::Pan {
44        scale: true,
45        rotate: false,
46    };
47    /// [`GrabMode::Pan`] with rotation only
48    pub const PAN_ROTATE: GrabMode = GrabMode::Pan {
49        scale: false,
50        rotate: true,
51    };
52    /// [`GrabMode::Pan`] with scaling and rotation
53    pub const PAN_FULL: GrabMode = GrabMode::Pan {
54        scale: true,
55        rotate: true,
56    };
57
58    /// True for "pan" variants
59    #[inline]
60    pub fn is_pan(self) -> bool {
61        matches!(self, GrabMode::Pan { .. })
62    }
63}
64
65/// Source of a [`Press`] event
66///
67/// This identifies the source of a click, touch or pointer motion. It
68/// identifies which mouse button is pressed (if any) and whether this is a
69/// double-click (see [`Self::repetitions`]).
70///
71/// This may be used to track a click/touch, but note that identifiers may be
72/// re-used after the event completes, thus an [`Event::PressStart`] with
73/// `PressSource` equal to a prior instance does not indicate that the same
74/// finger / mouse was used.
75/// Further note: identifying multiple mice is not currently supported.
76#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
77pub struct PressSource(u64);
78
79impl PressSource {
80    const FLAG_TOUCH: u64 = 1 << 63;
81
82    /// Construct a mouse source
83    pub(crate) fn mouse(button: Option<MouseButton>, repetitions: u32) -> Self {
84        let r = (repetitions as u64) << 32;
85        debug_assert!(r & Self::FLAG_TOUCH == 0);
86        // Note: MouseButton::try_from_u8 returns None on u8::MAX
87        let b = button.map(|b| b as u8).unwrap_or(u8::MAX);
88        Self(r | b as u64)
89    }
90
91    /// Construct a touch source
92    pub(crate) fn touch(finger_id: FingerId) -> Self {
93        let id = u64::conv(finger_id.into_raw());
94        // Investigation shows that almost all sources use a 32-bit identifier.
95        // The only exceptional winit backend is iOS, which uses a pointer.
96        assert!(id & Self::FLAG_TOUCH == 0);
97        Self(Self::FLAG_TOUCH | id)
98    }
99
100    /// Returns true if this represents a mouse event
101    #[inline]
102    pub fn is_mouse(self) -> bool {
103        self.0 & Self::FLAG_TOUCH == 0
104    }
105
106    /// Returns true if this represents a touch event
107    #[inline]
108    pub fn is_touch(self) -> bool {
109        self.0 & Self::FLAG_TOUCH != 0
110    }
111
112    /// Returns the finger identifier if this represents a touch event
113    fn finger_id(self) -> Option<FingerId> {
114        if self.is_touch() {
115            let id = self.0 & !Self::FLAG_TOUCH;
116            Some(FingerId::from_raw(id.cast()))
117        } else {
118            None
119        }
120    }
121
122    /// Identify the mouse button used
123    ///
124    /// This returns `Some(button)` for mouse events with a button. It returns
125    /// `None` for touch events and mouse events without a button (e.g. motion).
126    pub fn mouse_button(self) -> Option<MouseButton> {
127        if self.is_mouse() {
128            let b = self.0 as u8;
129            MouseButton::try_from_u8(b)
130        } else {
131            None
132        }
133    }
134
135    /// Returns true if this represents the left mouse button or a touch event
136    #[inline]
137    pub fn is_primary(self) -> bool {
138        self.is_touch() || self.mouse_button() == Some(MouseButton::Left)
139    }
140
141    /// Returns true if this represents the right mouse button
142    #[inline]
143    pub fn is_secondary(self) -> bool {
144        self.mouse_button() == Some(MouseButton::Right)
145    }
146
147    /// Returns true if this represents the middle mouse button
148    #[inline]
149    pub fn is_tertiary(self) -> bool {
150        self.mouse_button() == Some(MouseButton::Middle)
151    }
152
153    /// The `repetitions` value
154    ///
155    /// This is 1 for a single-click and all touch events, 2 for a double-click,
156    /// 3 for a triple-click, etc. For [`Event::PointerMove`] without a grab this is 0.
157    #[inline]
158    pub fn repetitions(self) -> u32 {
159        if self.is_mouse() {
160            // Top 32 bits is repetitions
161            (self.0 >> 32) as u32
162        } else {
163            // Touch events do not support repetitions.
164            1
165        }
166    }
167}
168
169/// Details of press start events
170///
171/// This type dereferences to [`PressSource`].
172#[crate::autoimpl(Deref using self.source)]
173#[derive(Clone, Debug, PartialEq)]
174pub struct PressStart {
175    /// Source of the press
176    pub source: PressSource,
177    /// Identifier of the widget currently under the press
178    pub id: Option<Id>,
179    /// Current coordinate
180    position: DVec2,
181}
182
183impl std::ops::AddAssign<Offset> for PressStart {
184    #[inline]
185    fn add_assign(&mut self, offset: Offset) {
186        self.position += DVec2::conv(offset);
187    }
188}
189
190impl PressStart {
191    /// Get the current press coordinate
192    #[inline]
193    pub fn coord(&self) -> Coord {
194        self.position.cast_approx()
195    }
196
197    /// Grab pan/move/press-end events for widget `id`
198    ///
199    /// There are three types of grab ([`GrabMode`]):
200    ///
201    /// -   `Click`: send the corresponding [`Event::PressEnd`] only
202    /// -   `Grab` (the default): send [`Event::PressMove`] and [`Event::PressEnd`]
203    /// -   Pan modes: send [`Event::Pan`] on motion.
204    ///     Note: this is most useful when grabbing multiple touch events.
205    ///
206    /// Only a single mouse grab is allowed at any one time; requesting a
207    /// second will cancel the first (sending [`Event::PressEnd`] with
208    /// `success: false`).
209    ///
210    /// [`EventState::is_depressed`] will return true for the grabbing widget.
211    /// Call [`EventCx::set_grab_depress`] on `PressMove` to update the
212    /// grab's depress target. (This is done automatically for
213    /// [`GrabMode::Click`], and ends automatically when the grab ends.)
214    ///
215    /// This method uses the builder pattern. On completion, [`Used`]
216    /// is returned. It is expected that the requested press/pan events are all
217    /// "used" ([`Used`]).
218    #[inline]
219    pub fn grab(&self, id: Id, mode: GrabMode) -> GrabBuilder {
220        GrabBuilder {
221            id,
222            source: self.source,
223            position: self.position,
224            mode,
225            icon: None,
226        }
227    }
228
229    /// Convenience wrapper for [`Self::grab`] using [`GrabMode::Click`]
230    #[inline]
231    pub fn grab_click(&self, id: Id) -> GrabBuilder {
232        self.grab(id, GrabMode::Click)
233    }
234
235    /// Convenience wrapper for [`Self::grab`] using [`GrabMode::Grab`]
236    #[inline]
237    pub fn grab_move(&self, id: Id) -> GrabBuilder {
238        self.grab(id, GrabMode::Grab)
239    }
240}
241
242/// Details of press events
243///
244/// This type dereferences to [`PressSource`].
245#[crate::autoimpl(Deref using self.source)]
246#[derive(Clone, Debug, PartialEq, Eq)]
247pub struct Press {
248    /// Source of the press
249    pub source: PressSource,
250    /// Identifier of the widget currently under the press
251    pub id: Option<Id>,
252    /// Current coordinate
253    pub coord: Coord,
254}
255
256/// Bulider pattern (see [`PressStart::grab`])
257///
258/// Conclude by calling [`Self::complete`].
259#[must_use]
260pub struct GrabBuilder {
261    id: Id,
262    source: PressSource,
263    position: DVec2,
264    mode: GrabMode,
265    icon: Option<CursorIcon>,
266}
267
268impl GrabBuilder {
269    /// Set pointer icon (default: do not set)
270    #[inline]
271    pub fn with_icon(self, icon: CursorIcon) -> Self {
272        self.with_opt_icon(Some(icon))
273    }
274
275    /// Optionally set pointer icon (default: do not set)
276    #[inline]
277    pub fn with_opt_icon(mut self, icon: Option<CursorIcon>) -> Self {
278        self.icon = icon;
279        self
280    }
281
282    /// Complete the grab, providing the [`EventCx`]
283    ///
284    /// In case of an existing grab for the same [`source`](Press::source),
285    /// - If the [`Id`] differs this fails (returns [`Unused`])
286    /// - If the [`MouseButton`] differs this fails (technically this is a
287    ///   different `source`, but simultaneous grabs of multiple mouse buttons
288    ///   are not supported).
289    /// - If one grab is a [pan](GrabMode::is_pan) and the other is not, this fails
290    /// - [`GrabMode::Click`] may be upgraded to [`GrabMode::Grab`]
291    /// - Changing from one pan mode to another is an error
292    /// - Mouse button repetitions may be increased; decreasing is an error
293    /// - A [`CursorIcon`] may be set
294    /// - The depress target is re-set to the grabbing widget
295    ///
296    /// Note: error conditions are only checked in debug builds. These cases
297    /// may need revision.
298    pub fn complete(self, cx: &mut EventCx) -> IsUsed {
299        let GrabBuilder {
300            id,
301            source,
302            position,
303            mode,
304            icon,
305        } = self;
306        log::trace!(target: "kas_core::event", "grab_press: start_id={id}, source={source:?}");
307        let success = if let Some(button) = source.mouse_button() {
308            cx.mouse.start_grab(
309                button,
310                source.repetitions(),
311                id.clone(),
312                position,
313                mode,
314                icon.unwrap_or_default(),
315            )
316        } else if let Some(finger_id) = source.finger_id() {
317            cx.touch.start_grab(finger_id, id.clone(), position, mode)
318        } else {
319            false
320        };
321
322        if success {
323            cx.redraw();
324            Used
325        } else {
326            Unused
327        }
328    }
329}
330
331/// Mouse and touch methods
332impl EventState {
333    /// Check whether the given widget is visually depressed
334    pub fn is_depressed(&self, w_id: &Id) -> bool {
335        for (_, id) in &self.key_depress {
336            if *id == w_id {
337                return true;
338            }
339        }
340        if self
341            .mouse
342            .grab
343            .as_ref()
344            .map(|grab| *w_id == grab.depress)
345            .unwrap_or(false)
346        {
347            return true;
348        }
349        for grab in self.touch.touch_grab.iter() {
350            if *w_id == grab.depress {
351                return true;
352            }
353        }
354        for popup in &self.popups {
355            if *w_id == popup.desc.parent {
356                return true;
357            }
358        }
359        false
360    }
361
362    /// Get whether the widget is under the mouse pointer
363    #[inline]
364    pub fn is_under_mouse(&self, w_id: &Id) -> bool {
365        *w_id == self.mouse.over
366            && self
367                .mouse
368                .grab
369                .as_ref()
370                .is_none_or(|grab| grab.start_id == w_id)
371    }
372
373    /// Returns true if there is a mouse or touch grab on `id` or any descendant of `id`
374    pub fn any_grab_on(&self, id: &Id) -> bool {
375        if self
376            .mouse
377            .grab
378            .as_ref()
379            .map(|grab| grab.start_id == id)
380            .unwrap_or(false)
381        {
382            return true;
383        }
384        self.touch.touch_grab.iter().any(|grab| grab.start_id == id)
385    }
386
387    /// Get velocity of the mouse pointer or a touch
388    ///
389    /// The velocity is calculated at the time this method is called using
390    /// existing samples of motion.
391    ///
392    /// Where the `source` is a mouse this always succeeds.
393    /// For touch events this requires an active grab and is not
394    /// guaranteed to succeed; currently only a limited number of presses with
395    /// mode [`GrabMode::Grab`] are tracked for velocity.
396    pub fn press_velocity(&self, source: PressSource) -> Option<Vec2> {
397        let evc = self.config().event();
398        if source.is_mouse() {
399            Some(self.mouse.samples.velocity(evc.kinetic_timeout()))
400        } else if let Some(finger_id) = source.finger_id() {
401            self.touch.velocity(finger_id, evc)
402        } else {
403            unreachable!()
404        }
405    }
406}
407
408impl<'a> EventCx<'a> {
409    /// Set the pointer icon
410    ///
411    /// This is normally called from [`Events::handle_mouse_over`]. In other
412    /// cases, calling this method may be ineffective. The icon is
413    /// automatically "unset" when the widget is no longer under the mouse.
414    ///
415    /// See also [`EventCx::set_grab_icon`]: if a mouse grab
416    /// ([`PressStart::grab`]) is active, its icon takes precedence.
417    pub fn set_mouse_over_icon(&mut self, icon: CursorIcon) {
418        // Note: this is acted on by EventState::update
419        self.mouse.icon = icon;
420    }
421
422    /// Set a grab's depress target
423    ///
424    /// When a grab on mouse or touch input is in effect
425    /// ([`PressStart::grab`]), the widget owning the grab may set itself
426    /// or any other widget as *depressed* ("pushed down"). Each grab depresses
427    /// at most one widget, thus setting a new depress target clears any
428    /// existing target. Initially a grab depresses its owner.
429    ///
430    /// This effect is purely visual. A widget is depressed when one or more
431    /// grabs targets the widget to depress, or when a keyboard binding is used
432    /// to activate a widget (for the duration of the key-press).
433    ///
434    /// Assumption: this method will only be called by handlers of a grab (i.e.
435    /// recipients of [`Event::PressStart`] after initiating a successful grab,
436    /// [`Event::PressMove`] or [`Event::PressEnd`]).
437    ///
438    /// Queues a redraw and returns `true` if the depress target changes,
439    /// otherwise returns `false`.
440    pub fn set_grab_depress(&mut self, source: PressSource, target: Option<Id>) -> bool {
441        let mut old = None;
442        let mut redraw = false;
443        if source.is_mouse() {
444            if let Some(grab) = self.mouse.grab.as_mut() {
445                redraw = grab.depress != target;
446                old = grab.depress.take();
447                grab.depress = target.clone();
448            }
449        } else if let Some(finger_id) = source.finger_id() {
450            if let Some(grab) = self.touch.get_touch(finger_id) {
451                redraw = grab.depress != target;
452                old = grab.depress.take();
453                grab.depress = target.clone();
454            }
455        } else {
456            unreachable!()
457        }
458
459        if redraw {
460            log::trace!(target: "kas_core::event", "set_grab_depress: target={target:?}");
461            self.opt_redraw(old);
462            self.opt_redraw(target);
463        }
464        redraw
465    }
466
467    /// Update the mouse pointer icon used during a grab
468    ///
469    /// This only succeeds if widget `id` has an active mouse-grab (see
470    /// [`PressStart::grab`]). The icon will be reset when the mouse-grab
471    /// ends.
472    pub fn set_grab_icon(&mut self, id: &Id, icon: CursorIcon) {
473        if let Some(grab) = self.mouse.grab.as_mut()
474            && grab.start_id == *id
475        {
476            grab.icon = icon;
477        }
478    }
479}