Skip to main content

kas_core/event/
components.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 components
7
8use super::*;
9use crate::cast::traits::*;
10use crate::geom::{Coord, Offset, Rect, Size, Vec2};
11use crate::{ActionMoved, Id};
12use kas_macros::{autoimpl, impl_default};
13use std::time::Instant;
14
15const TIMER_SELECT: TimerHandle = TimerHandle::new(1 << 60, true);
16const TIMER_KINETIC: TimerHandle = TimerHandle::new((1 << 60) + 1, true);
17const KINETIC_RESIDUAL_VEL_REDUCTION_FACTOR: f32 = 0.5;
18
19/// Details used to initiate kinetic scrolling
20#[derive(Clone, Debug, Default, PartialEq)]
21pub struct KineticStart {
22    vel: Vec2,
23    rest: Vec2,
24}
25
26/// Kinetic scrolling model
27#[derive(Clone, Debug)]
28pub struct Kinetic {
29    press: Option<PressSource>,
30    t_step: Instant,
31    vel: Vec2,
32    rest: Vec2,
33}
34
35impl Default for Kinetic {
36    #[inline]
37    fn default() -> Self {
38        let now = Instant::now();
39
40        Kinetic {
41            press: None,
42            t_step: now,
43            vel: Vec2::ZERO,
44            rest: Vec2::ZERO,
45        }
46    }
47}
48
49impl Kinetic {
50    /// Call on [`Event::PressStart`]
51    pub fn press_start(&mut self, press: PressSource) {
52        self.press = Some(press);
53    }
54
55    /// Call on [`Event::PressMove`]
56    ///
57    /// Returns true if component should immediately scroll by delta
58    pub fn press_move(&mut self, press: PressSource) -> bool {
59        self.press == Some(press) && self.vel == Vec2::ZERO
60    }
61
62    /// Call on [`Event::PressEnd`]
63    ///
64    /// Returns true when a frame timer ([`EventState::request_frame_timer`])
65    /// should be requested (see [`Self::step`]).
66    pub fn press_end(&mut self, press: PressSource, vel: Vec2) -> bool {
67        if self.press != Some(press) {
68            return false;
69        }
70        self.press = None;
71
72        self.vel += vel;
73        if self.vel.distance_l_inf() < 1.0 {
74            self.stop();
75            false
76        } else {
77            self.t_step = Instant::now();
78            true
79        }
80    }
81
82    /// Call on [`Scroll::Kinetic`] to immediately start (or accelerate) scrolling
83    ///
84    /// Returns any offset which should be applied immediately.
85    pub fn start(&mut self, start: KineticStart) -> Offset {
86        self.vel += start.vel;
87        let d = self.rest + start.rest;
88        let delta = Offset::conv_trunc(d);
89        self.rest = d - Vec2::conv(delta);
90        self.t_step = Instant::now();
91        delta
92    }
93
94    /// Call this regularly using a frame timer
95    ///
96    /// On [`Self::press_end`] and while in motion ([`Self::is_scrolling`]), a
97    /// frame timer ([`EventState::request_frame_timer`]) should be requested
98    /// and used to call this method.
99    pub fn step(&mut self, cx: &EventState) -> Option<Offset> {
100        let evc = cx.config().event();
101        let now = Instant::now();
102        let dur = (now - self.t_step).as_secs_f32();
103        self.t_step = now;
104
105        if let Some(source) = self.press {
106            let decay_sub = evc.kinetic_grab_sub();
107            let v = cx.press_velocity(source).unwrap_or_default();
108            self.vel -= v.abs().min(Vec2::splat(decay_sub * dur)) * -v.sign();
109        }
110
111        let (decay_mul, decay_sub) = evc.kinetic_decay();
112        let v = self.vel * decay_mul.powf(dur);
113        self.vel = v - v.abs().min(Vec2::splat(decay_sub * dur)) * v.sign();
114
115        if self.press.is_none() && self.vel.distance_l_inf() < 1.0 {
116            self.stop();
117            return None;
118        }
119
120        let d = self.vel * dur + self.rest;
121        let delta = Offset::conv_trunc(d);
122        self.rest = d - Vec2::conv(delta);
123
124        Some(delta)
125    }
126
127    /// Stop scrolling immediately
128    #[inline]
129    pub fn stop(&mut self) {
130        self.vel = Vec2::ZERO;
131    }
132
133    /// Stop scrolling on any axis were `delta` is non-zero
134    ///
135    /// Returns a [`KineticStart`] message from the residual velocity and delta.
136    pub fn stop_with_residual(&mut self, delta: Offset) -> KineticStart {
137        let mut start = KineticStart::default();
138        if delta.0 != 0 {
139            start.vel.0 = self.vel.0 * KINETIC_RESIDUAL_VEL_REDUCTION_FACTOR;
140            start.rest.0 = self.rest.0 + f32::conv(delta.0);
141            self.vel.0 = 0.0;
142            self.rest.0 = 0.0;
143        }
144        if delta.1 != 0 {
145            start.vel.1 = self.vel.1 * KINETIC_RESIDUAL_VEL_REDUCTION_FACTOR;
146            start.rest.1 = self.rest.1 + f32::conv(delta.1);
147            self.vel.1 = 0.0;
148            self.rest.1 = 0.0;
149        }
150        start
151    }
152
153    /// True while kinetic scrolling
154    #[inline]
155    pub fn is_scrolling(&self) -> bool {
156        self.vel != Vec2::ZERO
157    }
158}
159
160/// Logic for a scroll region
161///
162/// This struct handles some scroll logic. It does not provide scroll bars.
163#[derive(Clone, Debug, Default)]
164pub struct ScrollComponent {
165    max_offset: Offset,
166    offset: Offset,
167    kinetic: Kinetic,
168}
169
170impl ScrollComponent {
171    /// True if kinetic scrolling is active
172    #[inline]
173    pub fn is_kinetic_scrolling(&self) -> bool {
174        self.kinetic.is_scrolling()
175    }
176
177    /// Get the maximum offset
178    ///
179    /// Note: the minimum offset is always zero.
180    #[inline]
181    pub fn max_offset(&self) -> Offset {
182        self.max_offset
183    }
184
185    /// Get the current offset
186    ///
187    /// To translate a coordinate from the outer region to a coordinate of the
188    /// scrolled region, add this offset.
189    #[inline]
190    pub fn offset(&self) -> Offset {
191        self.offset
192    }
193
194    /// Set sizes:
195    ///
196    /// -   `window_size`: size of scroll region on the outside
197    /// -   `content_size`: size of scroll region on the inside (usually larger)
198    ///
199    /// Returns an [`ActionMoved`] indicating whether the scroll offset changed.
200    #[must_use]
201    pub fn set_sizes(&mut self, window_size: Size, content_size: Size) -> Option<ActionMoved> {
202        let max_offset = (Offset::conv(content_size) - Offset::conv(window_size)).max(Offset::ZERO);
203        if max_offset == self.max_offset {
204            return None;
205        }
206        self.max_offset = max_offset;
207        self.set_offset(self.offset)
208    }
209
210    /// Set the scroll offset
211    ///
212    /// The offset is clamped to the available scroll range.
213    ///
214    /// Also cancels any kinetic scrolling, but only if `offset` is not equal
215    /// to the current offset.
216    #[must_use]
217    pub fn set_offset(&mut self, offset: Offset) -> Option<ActionMoved> {
218        let offset = offset.clamp(Offset::ZERO, self.max_offset);
219        if offset == self.offset {
220            None
221        } else {
222            self.kinetic.stop();
223            self.offset = offset;
224            Some(ActionMoved)
225        }
226    }
227
228    /// Scroll to make the given `rect` visible
229    ///
230    /// Inputs:
231    ///
232    /// -   `rect`: the rect to focus in child's coordinate space
233    /// -   `window_rect`: the rect of the scroll window
234    ///
235    /// Sets [`Scroll::Rect`] to ensure correct scrolling of parents.
236    #[must_use]
237    pub fn focus_rect(
238        &mut self,
239        cx: &mut EventCx,
240        rect: Rect,
241        window_rect: Rect,
242    ) -> Option<ActionMoved> {
243        let action = self.self_focus_rect(rect, window_rect);
244        cx.set_scroll(Scroll::Rect(rect - self.offset));
245        action
246    }
247
248    /// Scroll self to make the given `rect` visible
249    ///
250    /// This is identical to [`Self::focus_rect`] except that it does not call
251    /// [`EventCx::set_scroll`], thus will not affect ancestors.
252    #[cfg_attr(not(feature = "internal_doc"), doc(hidden))]
253    #[cfg_attr(docsrs, doc(cfg(internal_doc)))]
254    #[must_use]
255    pub fn self_focus_rect(&mut self, rect: Rect, window_rect: Rect) -> Option<ActionMoved> {
256        self.kinetic.stop();
257        let max_vis = rect.pos - window_rect.pos;
258        let extra_size = Offset::conv(rect.size) - Offset::conv(window_rect.size);
259        let min_vis = max_vis + extra_size;
260        let center = max_vis + extra_size / 2;
261        let lb = (min_vis + center) / 2;
262        let ub = (max_vis + center) / 2;
263        let offset = self.offset.max(lb).min(ub);
264        self.set_offset(offset)
265    }
266
267    /// Handle a [`Scroll`] action
268    pub fn scroll(&mut self, cx: &mut EventCx, id: Id, window_rect: Rect, scroll: Scroll) {
269        match scroll {
270            Scroll::None | Scroll::Scrolled => (),
271            Scroll::Offset(delta) => {
272                self.scroll_by_delta(cx, delta);
273            }
274            Scroll::Kinetic(start) => {
275                let delta = self.kinetic.start(start);
276                let delta = self.scroll_self_by_delta(cx, delta);
277                if delta == Offset::ZERO {
278                    cx.set_scroll(Scroll::Scrolled);
279                } else {
280                    cx.set_scroll(Scroll::Kinetic(self.kinetic.stop_with_residual(delta)));
281                }
282                if self.kinetic.is_scrolling() {
283                    cx.request_frame_timer(id, TIMER_KINETIC);
284                }
285            }
286            Scroll::Rect(rect) => {
287                let action = self.focus_rect(cx, rect, window_rect);
288                cx.action_moved(action);
289            }
290        }
291    }
292
293    /// Scroll self, returning any excess delta
294    ///
295    /// Caller is expected to call [`EventCx::set_scroll`].
296    fn scroll_self_by_delta(&mut self, cx: &mut EventState, d: Offset) -> Offset {
297        let mut delta = d;
298        let offset = (self.offset - d).clamp(Offset::ZERO, self.max_offset);
299        if offset != self.offset {
300            delta = d - (self.offset - offset);
301            self.offset = offset;
302            cx.region_moved();
303        }
304        delta
305    }
306
307    fn scroll_by_delta(&mut self, cx: &mut EventCx, d: Vec2) {
308        let delta = d + self.kinetic.rest;
309        let offset = delta.cast_nearest();
310        self.kinetic.rest = delta - Vec2::conv(offset);
311        let delta = self.scroll_self_by_delta(cx, offset);
312        cx.set_scroll(if delta != Offset::ZERO {
313            Scroll::Offset(delta.cast())
314        } else {
315            Scroll::Scrolled
316        });
317    }
318
319    /// Use an event to scroll, if possible
320    ///
321    /// Consumes the following events: `Command`, `Scroll`, `PressStart`,
322    /// `PressMove`, `PressEnd`, `Timer(pl)` where `pl == (1<<60) + 1`.
323    /// May request timer updates.
324    ///
325    /// Implements scroll by Home/End, Page Up/Down and arrow keys, by mouse
326    /// wheel and touchpad.
327    ///
328    /// `PressStart` is consumed only if the maximum scroll offset is non-zero
329    /// and event configuration enables panning for this press `source` (may
330    /// depend on modifiers), and if so grabs press events from this `source`.
331    /// `PressMove` is used to scroll by the motion delta and to track speed;
332    /// `PressEnd` initiates kinetic-scrolling if the speed is high enough.
333    pub fn scroll_by_event(
334        &mut self,
335        cx: &mut EventCx,
336        event: Event,
337        id: Id,
338        window_rect: Rect,
339    ) -> IsUsed {
340        match event {
341            Event::Command(cmd, _) => {
342                let offset = match cmd {
343                    Command::Home => Offset::ZERO,
344                    Command::End => self.max_offset,
345                    cmd => {
346                        let delta = match cmd {
347                            Command::Left => ScrollDelta::Lines(1.0, 0.0),
348                            Command::Right => ScrollDelta::Lines(-1.0, 0.0),
349                            Command::Up => ScrollDelta::Lines(0.0, 1.0),
350                            Command::Down => ScrollDelta::Lines(0.0, -1.0),
351                            Command::PageUp | Command::PageDown => {
352                                let mut v = 0.5 * f32::conv(window_rect.size.1);
353                                if cmd == Command::PageDown {
354                                    v = -v;
355                                }
356                                ScrollDelta::PixelDelta(Vec2(0.0, v))
357                            }
358                            _ => return Unused,
359                        };
360                        self.scroll_by_delta(cx, delta.as_offset(cx));
361                        return Used;
362                    }
363                };
364                cx.action_moved(self.set_offset(offset));
365                cx.set_scroll(Scroll::Rect(window_rect));
366            }
367            Event::Scroll(delta) => {
368                self.kinetic.stop();
369                self.scroll_by_delta(cx, delta.as_offset(cx));
370            }
371            Event::PressStart(press)
372                if self.max_offset != Offset::ZERO && cx.config_enable_pan(*press) =>
373            {
374                let _ = press
375                    .grab(id, GrabMode::Grab)
376                    .with_icon(CursorIcon::Grabbing)
377                    .complete(cx);
378                self.kinetic.press_start(press.source);
379            }
380            Event::PressMove { press, delta }
381                if self.max_offset != Offset::ZERO && cx.config_enable_pan(*press) =>
382            {
383                if self.kinetic.press_move(press.source) {
384                    self.scroll_by_delta(cx, delta);
385                }
386            }
387            Event::PressEnd { press, .. }
388                if self.max_offset != Offset::ZERO && cx.config_enable_pan(*press) =>
389            {
390                if let Some(velocity) = cx.press_velocity(press.source)
391                    && self.kinetic.press_end(press.source, velocity)
392                {
393                    cx.request_frame_timer(id, TIMER_KINETIC);
394                }
395            }
396            Event::Timer(TIMER_KINETIC) => {
397                if let Some(delta) = self.kinetic.step(cx) {
398                    let delta = self.scroll_self_by_delta(cx, delta);
399                    let scroll = if delta == Offset::ZERO {
400                        Scroll::Scrolled
401                    } else {
402                        Scroll::Kinetic(self.kinetic.stop_with_residual(delta))
403                    };
404                    cx.set_scroll(scroll);
405                }
406
407                if self.kinetic.is_scrolling() {
408                    cx.request_frame_timer(id, TIMER_KINETIC);
409                }
410            }
411            _ => return Unused,
412        }
413        Used
414    }
415}
416
417#[impl_default(TextPhase::None)]
418#[autoimpl(Clone, Debug, PartialEq)]
419enum TextPhase {
420    None,
421    PressStart(PressSource, Coord), // source, coord
422    Pan(PressSource),               // source
423    Cursor(PressSource, Coord),     // source
424}
425
426/// Handles text selection and panning from mouse and touch events
427#[derive(Clone, Debug, Default)]
428pub struct TextInput {
429    phase: TextPhase,
430}
431
432/// Result of [`TextInput::handle`]
433#[autoimpl(Clone, Debug)]
434pub enum TextInputAction {
435    /// Event is used, no action
436    Used,
437    /// Event not used
438    Unused,
439    /// Start of click or selection
440    ///
441    /// The text cursor and selection anchors should be placed at the closest
442    /// position to `coord`.
443    ///
444    /// This corresponds to a mouse click (down). It may be followed by
445    /// [`Self::PressMove`] and will be concluded by [`Self::PressMove`] unless
446    /// cancelled by calling [`TextInput::stop_selecting`].
447    PressStart {
448        /// The click coordinate
449        coord: Coord,
450        /// Whether to clear any prior selection (true unless Shift is held)
451        clear: bool,
452        /// Number of clicks in sequence (e.g. 2 for double-click)
453        repeats: u32,
454    },
455    /// Drag-motion of pointer
456    ///
457    /// This is always preceeded by [`Self::PressStart`].
458    ///
459    /// The text cursor should be placed at the closest position to `coord`,
460    /// creating a selection from the anchor placed by [`Self::PressStart`].
461    ///
462    /// If `repeats > 1` then the selection may be expanded (e.g. to word or
463    /// line mode).
464    PressMove {
465        coord: Coord,
466        /// Number of clicks in sequence (e.g. 2 for double-click)
467        repeats: u32,
468    },
469    /// Release of click or touch event
470    ///
471    /// This may or may not be preceeded by [`Self::PressStart`]: touch events
472    /// without motion or sufficient delay to enter selection mode will yield
473    /// this variant without a preceeding [`Self::PressStart`].
474    ///
475    /// This may or may not be preceeded by [`Self::PressMove`].
476    ///
477    /// Handling should be stateful: if this follows [`Self::PressMove`] then it
478    /// terminates selection mode. If this is not preceeded by
479    /// [`Self::PressStart`] then the cursor should be placed at the closest
480    /// position to `coord`. If this is not preceeded by [`Self::PressMove`]
481    /// then it may be considered a "click" action (e.g. to follow a link).
482    ///
483    /// The widget may wish to request keyboard input focus or IME focus.
484    /// The widget should set the primary buffer (Unix).
485    PressEnd { coord: Coord },
486}
487
488impl TextInput {
489    /// Handle input events
490    ///
491    /// Consumes the following events: `PressStart`, `PressMove`, `PressEnd`,
492    /// `Timer(pl)` where `pl == 1<<60 || pl == (1<<60)+1`.
493    /// May request press grabs and timer updates.
494    ///
495    /// May call [`EventCx::set_scroll`] to initiate scrolling.
496    pub fn handle(&mut self, cx: &mut EventCx, w_id: Id, event: Event) -> TextInputAction {
497        use TextInputAction as Action;
498        match event {
499            Event::PressStart(press) if press.is_primary() => {
500                let mut action = Action::Used;
501                let icon = if press.is_touch() {
502                    self.phase = TextPhase::PressStart(*press, press.coord());
503                    let delay = cx.config().event().touch_select_delay();
504                    cx.request_timer(w_id.clone(), TIMER_SELECT, delay);
505                    None
506                } else if press.is_mouse() {
507                    if cx.config_enable_mouse_text_pan() {
508                        self.phase = TextPhase::Pan(*press);
509                        Some(CursorIcon::Grabbing)
510                    } else {
511                        self.phase = TextPhase::Cursor(*press, press.coord());
512                        action = Action::PressStart {
513                            coord: press.coord(),
514                            clear: !cx.modifiers().shift_key(),
515                            repeats: press.repetitions(),
516                        };
517                        None
518                    }
519                } else {
520                    unreachable!()
521                };
522                press
523                    .grab(w_id, GrabMode::Grab)
524                    .with_opt_icon(icon)
525                    .complete(cx);
526                action
527            }
528            Event::PressMove { press, delta } => match self.phase {
529                TextPhase::PressStart(source, start_coord) if *press == source => {
530                    let delta = press.coord - start_coord;
531                    if cx.config_test_pan_thresh(delta) {
532                        self.phase = TextPhase::Pan(source);
533                        cx.set_scroll(Scroll::Offset(delta.cast()));
534                    }
535                    Action::Used
536                }
537                TextPhase::Pan(source) if *press == source => {
538                    cx.set_scroll(Scroll::Offset(delta));
539                    Action::Used
540                }
541                TextPhase::Cursor(source, _) if *press == source => {
542                    self.phase = TextPhase::Cursor(source, press.coord);
543                    Action::PressMove {
544                        coord: press.coord,
545                        repeats: press.repetitions(),
546                    }
547                }
548                _ => Action::Used,
549            },
550            Event::PressEnd { press, .. } => match std::mem::take(&mut self.phase) {
551                TextPhase::None => Action::Used,
552                TextPhase::PressStart(_, coord) => Action::PressEnd { coord },
553                TextPhase::Pan(source) => {
554                    if *press == source
555                        && let Some(vel) = cx.press_velocity(source)
556                    {
557                        let rest = Vec2::ZERO;
558                        cx.set_scroll(Scroll::Kinetic(KineticStart { vel, rest }));
559                    }
560
561                    Action::Used
562                }
563                TextPhase::Cursor(_, coord) => Action::PressEnd { coord },
564            },
565            Event::Timer(TIMER_SELECT) => match self.phase {
566                TextPhase::PressStart(source, coord) => {
567                    self.phase = TextPhase::Cursor(source, coord);
568                    Action::PressStart {
569                        coord,
570                        clear: !cx.modifiers().shift_key(),
571                        repeats: 1,
572                    }
573                }
574                _ => Action::Unused,
575            },
576            _ => Action::Unused,
577        }
578    }
579
580    /// Is there an on-going selection action?
581    ///
582    /// This is true when the last action delivered was
583    /// [`TextInputAction::PressStart`] or [`TextInputAction::PressMove`].
584    #[inline]
585    pub fn is_selecting(&self) -> bool {
586        matches!(&self.phase, TextPhase::Cursor(_, _))
587    }
588
589    /// Interrupt an on-going selection action, if any
590    pub fn stop_selecting(&mut self) {
591        if self.is_selecting() {
592            self.phase = TextPhase::None;
593        }
594    }
595}
596
597#[impl_default(ClickPhase::None)]
598#[autoimpl(Clone, Debug, PartialEq)]
599enum ClickPhase {
600    None,
601    PressStart(PressSource, Coord), // source, coord
602    Pan(PressSource),               // source
603}
604
605/// Handles click actions while also allowing scrolling
606#[derive(Clone, Debug, Default)]
607pub struct ClickInput {
608    phase: ClickPhase,
609}
610
611/// Result of [`ClickInput::handle`]
612#[autoimpl(Clone, Debug)]
613pub enum ClickInputAction {
614    /// Event is used, no action
615    Used,
616    /// Event not used
617    Unused,
618    /// Start of a click / touch event
619    ///
620    /// This corresponds to a mouse click or touch action before determination
621    /// of whether the click action succeeds. It will be concluded by
622    /// [`Self::ClickEnd`].
623    ClickStart {
624        /// The click coordinate
625        coord: Coord,
626        /// Number of clicks in sequence (e.g. 2 for double-click)
627        repeats: u32,
628    },
629    /// End of a click / touch event
630    ///
631    /// If `success`, this is a button-release or touch finish; otherwise this
632    /// is a cancelled/interrupted grab. "Activation events" (e.g. clicking of a
633    /// button or menu item) should only happen on `success`. "Movement events"
634    /// such as panning, moving a slider or opening a menu should not be undone
635    /// when cancelling: the panned item or slider should be released as is, or
636    /// the menu should remain open.
637    ClickEnd { coord: Coord, success: bool },
638}
639
640impl ClickInput {
641    /// Handle input events
642    ///
643    /// Consumes the following events: `PressStart`, `PressMove`, `PressEnd`.
644    /// May request press grabs.
645    ///
646    /// May call [`EventCx::set_scroll`] to initiate scrolling.
647    pub fn handle(&mut self, cx: &mut EventCx, w_id: Id, event: Event) -> ClickInputAction {
648        use ClickInputAction as Action;
649        match event {
650            Event::PressStart(press) if press.is_primary() => {
651                let mut action = Action::Used;
652                let icon = if cx.config_enable_mouse_text_pan() {
653                    self.phase = ClickPhase::Pan(*press);
654                    Some(CursorIcon::Grabbing)
655                } else {
656                    self.phase = ClickPhase::PressStart(*press, press.coord());
657                    action = Action::ClickStart {
658                        coord: press.coord(),
659                        repeats: press.repetitions(),
660                    };
661                    None
662                };
663                press
664                    .grab(w_id, GrabMode::Grab)
665                    .with_opt_icon(icon)
666                    .complete(cx);
667                action
668            }
669            Event::PressMove { press, delta } => match self.phase {
670                ClickPhase::PressStart(source, start_coord) if *press == source => {
671                    let delta = press.coord - start_coord;
672                    if cx.config_test_pan_thresh(delta) {
673                        self.phase = ClickPhase::Pan(source);
674                        cx.set_scroll(Scroll::Offset(delta.cast()));
675                    }
676                    Action::Used
677                }
678                ClickPhase::Pan(source) if *press == source => {
679                    cx.set_scroll(Scroll::Offset(delta));
680                    Action::Used
681                }
682                _ => Action::Used,
683            },
684            Event::PressEnd { press, success } => match std::mem::take(&mut self.phase) {
685                ClickPhase::PressStart(source, _) if *press == source => Action::ClickEnd {
686                    coord: press.coord,
687                    success,
688                },
689                ClickPhase::Pan(source) => {
690                    if *press == source
691                        && let Some(vel) = cx.press_velocity(source)
692                    {
693                        let rest = Vec2::ZERO;
694                        cx.set_scroll(Scroll::Kinetic(KineticStart { vel, rest }));
695                    }
696
697                    Action::Used
698                }
699                _ => Action::Used,
700            },
701            _ => Action::Unused,
702        }
703    }
704}