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}