Skip to main content

ferroui_ios/
input_handler.rs

1//! The input of a view: touches of fingers, of a pencil and of an
2//! indirect pointer become raw pointer events of the top-level, the
3//! presses of a keyboard or of a remote become raw key events and text,
4//! and the scroll events of a pointing device become wheel events that
5//! go on with inertia when the scrolling stops.
6//!
7//! The swipe gestures of a remote, which the reference handles on tvOS
8//! only, are not ported (`docs/porting/ios-platform.md`).
9
10use ferroui_base::input::raw::{RawKeyEventType, RawPointerEventType};
11use ferroui_base::input::{KeyDeviceType, PhysicalKey, RawInputModifiers};
12use ferroui_base::{Point, Vector};
13
14/// The device a touch belongs to.
15#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
16pub enum TouchDeviceKind {
17    /// A finger on the screen.
18    Touch,
19    /// A pencil.
20    Pen,
21    /// A pointer that is not on the screen (a mouse or a trackpad).
22    Mouse,
23}
24
25/// `UITouchTypeDirect` and the other values of `UITouchType`.
26pub mod touch_type {
27    pub const DIRECT: isize = 0;
28    pub const INDIRECT: isize = 1;
29    pub const STYLUS: isize = 2;
30    pub const INDIRECT_POINTER: isize = 3;
31}
32
33/// `UITouchPhaseBegan` and the other values of `UITouchPhase`.
34pub mod touch_phase {
35    pub const BEGAN: isize = 0;
36    pub const MOVED: isize = 1;
37    pub const STATIONARY: isize = 2;
38    pub const ENDED: isize = 3;
39    pub const CANCELLED: isize = 4;
40}
41
42/// The bits of `UIKeyModifierFlags`.
43pub mod key_modifier_flags {
44    pub const SHIFT: isize = 1 << 17;
45    pub const CONTROL: isize = 1 << 18;
46    pub const ALTERNATE: isize = 1 << 19;
47    pub const COMMAND: isize = 1 << 20;
48}
49
50/// The device of a touch of the given `UITouchType`; none for an indirect
51/// touch (the trackpad of a remote), which is handled with gestures.
52pub fn device_kind(touch_type: isize) -> Option<TouchDeviceKind> {
53    match touch_type {
54        touch_type::INDIRECT => None,
55        touch_type::STYLUS => Some(TouchDeviceKind::Pen),
56        touch_type::INDIRECT_POINTER => Some(TouchDeviceKind::Mouse),
57        _ => Some(TouchDeviceKind::Touch),
58    }
59}
60
61/// The raw event of a touch of a device in the given `UITouchPhase`.
62/// `is_right_click` tells whether the secondary button is in the button
63/// mask of the event.
64pub fn pointer_event_type(device: TouchDeviceKind, phase: isize, is_right_click: bool) -> RawPointerEventType {
65    match (device, phase) {
66        (TouchDeviceKind::Touch, touch_phase::BEGAN) => RawPointerEventType::TouchBegin,
67        (TouchDeviceKind::Touch, touch_phase::ENDED) => RawPointerEventType::TouchEnd,
68        (TouchDeviceKind::Touch, touch_phase::CANCELLED) => RawPointerEventType::TouchCancel,
69        (TouchDeviceKind::Touch, _) => RawPointerEventType::TouchUpdate,
70
71        (_, touch_phase::BEGAN) if is_right_click => RawPointerEventType::RightButtonDown,
72        (_, touch_phase::BEGAN) => RawPointerEventType::LeftButtonDown,
73        (_, touch_phase::ENDED) if is_right_click => RawPointerEventType::RightButtonUp,
74        (_, touch_phase::ENDED) => RawPointerEventType::LeftButtonUp,
75        (_, touch_phase::CANCELLED) => RawPointerEventType::LeaveWindow,
76        (_, _) => RawPointerEventType::Move,
77    }
78}
79
80/// The pressure of a touch. On iOS "1.0 represents the force of an average
81/// touch", where the framework expects 0.5 for "average". A touch whose
82/// maximum possible force is 0 has no force and gets the average.
83pub fn pressure(force: f64, maximum_possible_force: f64) -> f32 {
84    if maximum_possible_force == 0.0 {
85        0.5
86    } else {
87        force as f32 / 2.0
88    }
89}
90
91/// The modifiers of `UIKeyModifierFlags`.
92pub fn convert_modifier_keys(ui_modifier: Option<isize>) -> RawInputModifiers {
93    let mut modifier = RawInputModifiers::NONE;
94    if let Some(flags) = ui_modifier {
95        if flags & key_modifier_flags::SHIFT != 0 {
96            modifier |= RawInputModifiers::SHIFT;
97        }
98        if flags & key_modifier_flags::ALTERNATE != 0 {
99            modifier |= RawInputModifiers::ALT;
100        }
101        if flags & key_modifier_flags::CONTROL != 0 {
102            modifier |= RawInputModifiers::CONTROL;
103        }
104        if flags & key_modifier_flags::COMMAND != 0 {
105            modifier |= RawInputModifiers::META;
106        }
107    }
108
109    modifier
110}
111
112/// The timestamp of an event (seconds since the system started) in
113/// milliseconds; 0 without an event.
114pub fn ts(event_timestamp: Option<f64>) -> u64 {
115    match event_timestamp {
116        None => 0,
117        Some(timestamp) => (timestamp * 1000.0) as u64,
118    }
119}
120
121/// Whether a touch in the given phase is over.
122pub fn is_final_phase(phase: isize) -> bool {
123    phase == touch_phase::CANCELLED || phase == touch_phase::ENDED
124}
125
126/// `UIPressPhaseBegan` and the other values of `UIPressPhase`.
127pub mod press_phase {
128    pub const BEGAN: isize = 0;
129    pub const CHANGED: isize = 1;
130    pub const STATIONARY: isize = 2;
131    pub const ENDED: isize = 3;
132    pub const CANCELLED: isize = 4;
133}
134
135/// `UIPressTypeUpArrow` and the other values of `UIPressType` the handler
136/// knows.
137pub mod press_type {
138    pub const UP_ARROW: isize = 0;
139    pub const DOWN_ARROW: isize = 1;
140    pub const LEFT_ARROW: isize = 2;
141    pub const RIGHT_ARROW: isize = 3;
142    pub const SELECT: isize = 4;
143    pub const MENU: isize = 5;
144    pub const PLAY_PAUSE: isize = 6;
145    pub const PAGE_UP: isize = 30;
146    pub const PAGE_DOWN: isize = 31;
147}
148
149/// `UIGestureRecognizerStateBegan` and the other values of
150/// `UIGestureRecognizerState`.
151pub mod gesture_recognizer_state {
152    pub const POSSIBLE: isize = 0;
153    pub const BEGAN: isize = 1;
154    pub const CHANGED: isize = 2;
155    pub const ENDED: isize = 3;
156    pub const CANCELLED: isize = 4;
157    pub const FAILED: isize = 5;
158}
159
160/// The key table: the physical key of a usage of the keyboard page of the
161/// HID usage tables (`UIKeyboardHIDUsage`). The name of a row is the name
162/// of the constant of UIKit, which the build for iOS compares with the
163/// number.
164macro_rules! keys {
165    ($($name:ident = $usage:literal => $key:ident,)*) => {
166        /// The physical key of a `UIKeyboardHIDUsage`, for the usages the
167        /// table of the reference has.
168        pub fn physical_key_of_hid_usage(usage: isize) -> Option<PhysicalKey> {
169            match usage {
170                $($usage => Some(PhysicalKey::$key),)*
171                _ => None,
172            }
173        }
174
175        /// The usages of the key table.
176        pub const KEY_TABLE_USAGES: &[isize] = &[$($usage),*];
177
178        #[cfg(target_os = "ios")]
179        const _: () = {
180            use objc2_ui_kit::UIKeyboardHIDUsage;
181            $(assert!(UIKeyboardHIDUsage::$name.0 == $usage);)*
182        };
183    };
184}
185
186keys! {
187    KeyboardA = 0x04 => A,
188    KeyboardB = 0x05 => B,
189    KeyboardC = 0x06 => C,
190    KeyboardD = 0x07 => D,
191    KeyboardE = 0x08 => E,
192    KeyboardF = 0x09 => F,
193    KeyboardG = 0x0A => G,
194    KeyboardH = 0x0B => H,
195    KeyboardI = 0x0C => I,
196    KeyboardJ = 0x0D => J,
197    KeyboardK = 0x0E => K,
198    KeyboardL = 0x0F => L,
199    KeyboardM = 0x10 => M,
200    KeyboardN = 0x11 => N,
201    KeyboardO = 0x12 => O,
202    KeyboardP = 0x13 => P,
203    KeyboardQ = 0x14 => Q,
204    KeyboardR = 0x15 => R,
205    KeyboardS = 0x16 => S,
206    KeyboardT = 0x17 => T,
207    KeyboardU = 0x18 => U,
208    KeyboardV = 0x19 => V,
209    KeyboardW = 0x1A => W,
210    KeyboardX = 0x1B => X,
211    KeyboardY = 0x1C => Y,
212    KeyboardZ = 0x1D => Z,
213    Keyboard1 = 0x1E => Digit1,
214    Keyboard2 = 0x1F => Digit2,
215    Keyboard3 = 0x20 => Digit3,
216    Keyboard4 = 0x21 => Digit4,
217    Keyboard5 = 0x22 => Digit5,
218    Keyboard6 = 0x23 => Digit6,
219    Keyboard7 = 0x24 => Digit7,
220    Keyboard8 = 0x25 => Digit8,
221    Keyboard9 = 0x26 => Digit9,
222    Keyboard0 = 0x27 => Digit0,
223    KeyboardReturnOrEnter = 0x28 => Enter,
224    KeyboardEscape = 0x29 => Escape,
225    // See KeyboardDeleteForward for an actual Delete.
226    KeyboardDeleteOrBackspace = 0x2A => Backspace,
227    KeyboardTab = 0x2B => Tab,
228    KeyboardSpacebar = 0x2C => Space,
229    KeyboardHyphen = 0x2D => NumPadSubtract,
230    KeyboardEqualSign = 0x2E => NumPadEqual,
231    KeyboardOpenBracket = 0x2F => BracketLeft,
232    KeyboardCloseBracket = 0x30 => BracketRight,
233    KeyboardBackslash = 0x31 => Backslash,
234    KeyboardSemicolon = 0x33 => Semicolon,
235    KeyboardQuote = 0x34 => Quote,
236    KeyboardComma = 0x36 => Comma,
237    KeyboardPeriod = 0x37 => Period,
238    KeyboardSlash = 0x38 => Slash,
239    KeyboardCapsLock = 0x39 => CapsLock,
240    KeyboardF1 = 0x3A => F1,
241    KeyboardF2 = 0x3B => F2,
242    KeyboardF3 = 0x3C => F3,
243    KeyboardF4 = 0x3D => F4,
244    KeyboardF5 = 0x3E => F5,
245    KeyboardF6 = 0x3F => F6,
246    KeyboardF7 = 0x40 => F7,
247    KeyboardF8 = 0x41 => F8,
248    KeyboardF9 = 0x42 => F9,
249    KeyboardF10 = 0x43 => F10,
250    KeyboardF11 = 0x44 => F11,
251    KeyboardF12 = 0x45 => F12,
252    KeyboardPrintScreen = 0x46 => PrintScreen,
253    KeyboardScrollLock = 0x47 => ScrollLock,
254    KeyboardPause = 0x48 => Pause,
255    KeyboardInsert = 0x49 => Insert,
256    KeyboardHome = 0x4A => Home,
257    KeyboardPageUp = 0x4B => PageUp,
258    KeyboardDeleteForward = 0x4C => Delete,
259    KeyboardEnd = 0x4D => End,
260    KeyboardPageDown = 0x4E => PageDown,
261    KeyboardRightArrow = 0x4F => ArrowRight,
262    KeyboardLeftArrow = 0x50 => ArrowLeft,
263    KeyboardDownArrow = 0x51 => ArrowDown,
264    KeyboardUpArrow = 0x52 => ArrowUp,
265    KeypadNumLock = 0x53 => NumLock,
266    KeypadSlash = 0x54 => Slash,
267    KeypadAsterisk = 0x55 => NumPadMultiply,
268    KeypadHyphen = 0x56 => NumPadSubtract,
269    KeypadPlus = 0x57 => NumPadAdd,
270    KeypadEnter = 0x58 => Enter,
271    Keypad1 = 0x59 => NumPad1,
272    Keypad2 = 0x5A => NumPad2,
273    Keypad3 = 0x5B => NumPad3,
274    Keypad4 = 0x5C => NumPad4,
275    Keypad5 = 0x5D => NumPad5,
276    Keypad6 = 0x5E => NumPad6,
277    Keypad7 = 0x5F => NumPad7,
278    Keypad8 = 0x60 => NumPad8,
279    Keypad9 = 0x61 => NumPad9,
280    Keypad0 = 0x62 => NumPad0,
281    KeypadPeriod = 0x63 => Period,
282    KeyboardNonUSBackslash = 0x64 => IntlBackslash,
283    KeyboardF13 = 0x68 => F13,
284    KeyboardF14 = 0x69 => F14,
285    KeyboardF15 = 0x6A => F15,
286    KeyboardF16 = 0x6B => F16,
287    KeyboardF17 = 0x6C => F17,
288    KeyboardF18 = 0x6D => F18,
289    KeyboardF19 = 0x6E => F19,
290    KeyboardF20 = 0x6F => F20,
291    KeyboardF21 = 0x70 => F21,
292    KeyboardF22 = 0x71 => F22,
293    KeyboardF23 = 0x72 => F23,
294    KeyboardF24 = 0x73 => F24,
295    KeyboardSelect = 0x77 => Space,
296    KeyboardMute = 0x7F => AudioVolumeMute,
297    KeyboardVolumeUp = 0x80 => AudioVolumeUp,
298    KeyboardVolumeDown = 0x81 => AudioVolumeDown,
299    KeypadComma = 0x85 => NumPadComma,
300    KeyboardLeftControl = 0xE0 => ControlLeft,
301    KeyboardLeftShift = 0xE1 => ShiftLeft,
302    KeyboardLeftAlt = 0xE2 => AltLeft,
303    KeyboardLeftGUI = 0xE3 => MetaLeft,
304    KeyboardRightControl = 0xE4 => ControlRight,
305    KeyboardRightShift = 0xE5 => ShiftRight,
306    KeyboardRightAlt = 0xE6 => AltRight,
307    KeyboardRightGUI = 0xE7 => MetaRight,
308}
309
310/// What a press says of its key (`UIKey`): the usage, the modifier flags
311/// and the characters.
312#[derive(Clone, Copy, Debug, PartialEq, Eq)]
313pub struct PressKey<'a> {
314    pub key_code: isize,
315    pub modifier_flags: isize,
316    pub characters: &'a str,
317}
318
319/// A press as the raw key event describes it.
320#[derive(Clone, Debug, PartialEq, Eq)]
321pub struct TranslatedPress {
322    pub physical_key: PhysicalKey,
323    pub modifier: RawInputModifiers,
324    pub characters: Option<String>,
325    pub key_device_type: KeyDeviceType,
326}
327
328/// Translates a press: by its key when it has one that the key table
329/// knows (a keyboard, very likely), by its type otherwise (a remote, very
330/// likely).
331pub fn translate_press(key: Option<PressKey<'_>>, press_type: isize) -> TranslatedPress {
332    if let Some(ui_key) = key {
333        if let Some(physical_key) = physical_key_of_hid_usage(ui_key.key_code) {
334            return TranslatedPress {
335                physical_key,
336                modifier: convert_modifier_keys(Some(ui_key.modifier_flags)),
337                // The characters of a key that has none are the name of
338                // its input constant ("UIKeyInputUpArrow").
339                characters: (!ui_key.characters.starts_with("UIKey")).then(|| ui_key.characters.to_string()),
340                key_device_type: KeyDeviceType::Keyboard,
341            };
342        }
343    }
344
345    TranslatedPress {
346        physical_key: match press_type {
347            press_type::UP_ARROW => PhysicalKey::ArrowUp,
348            press_type::DOWN_ARROW => PhysicalKey::ArrowDown,
349            press_type::LEFT_ARROW => PhysicalKey::ArrowLeft,
350            press_type::RIGHT_ARROW => PhysicalKey::ArrowRight,
351            press_type::SELECT => PhysicalKey::Space,
352            press_type::MENU => PhysicalKey::ContextMenu,
353            press_type::PLAY_PAUSE => PhysicalKey::MediaPlayPause,
354            press_type::PAGE_UP => PhysicalKey::PageUp,
355            press_type::PAGE_DOWN => PhysicalKey::PageDown,
356            _ => PhysicalKey::None,
357        },
358        modifier: RawInputModifiers::NONE,
359        characters: None,
360        key_device_type: KeyDeviceType::Remote,
361    }
362}
363
364/// The raw key event of a press in the given `UIPressPhase`.
365pub fn key_event_type(phase: isize) -> RawKeyEventType {
366    match phase {
367        press_phase::BEGAN | press_phase::CHANGED | press_phase::STATIONARY => RawKeyEventType::KeyDown,
368        _ => RawKeyEventType::KeyUp,
369    }
370}
371
372/// Whether a press that was not handled as a key is text: only when it
373/// began, and when it has characters.
374pub fn press_is_text_input(handled: bool, phase: isize, characters: Option<&str>) -> bool {
375    !handled && phase == press_phase::BEGAN && characters.is_some_and(|characters| !characters.is_empty())
376}
377
378/// The rate the velocity of inertia scrolling falls by at every frame.
379pub const DECELERATION_RATE: f64 = 0.95;
380
381/// The wheel delta of active scrolling at a velocity (points per second).
382/// A much more sensitive scaling than the one of inertia, to match AppKit:
383/// macOS uses small deltas, so the divisor is large.
384pub fn active_scroll_delta(velocity: Point) -> Vector {
385    let scale_factor = 3000.0;
386    Vector::new(velocity.x / scale_factor, velocity.y / scale_factor)
387}
388
389/// The state of scrolling: where it began, and the velocity of the
390/// inertia that follows it.
391#[derive(Clone, Copy, Debug, Default, PartialEq)]
392pub struct MomentumScrolling {
393    cached_scroll_location: Option<Point>,
394    momentum_velocity_x: f64,
395    momentum_velocity_y: f64,
396}
397
398impl MomentumScrolling {
399    /// Scrolling began at a location, which the events keep until the
400    /// inertia stops.
401    pub fn begin(&mut self, location: Point) {
402        self.stop();
403        self.cached_scroll_location = Some(location);
404    }
405
406    /// The location scrolling began at.
407    pub fn cached_scroll_location(&self) -> Option<Point> {
408        self.cached_scroll_location
409    }
410
411    /// Scrolling ended at a velocity (points per second): inertia starts.
412    pub fn start_inertia(&mut self, velocity: Point) {
413        let scale_factor = 800.0;
414        self.momentum_velocity_x = velocity.x / scale_factor;
415        self.momentum_velocity_y = velocity.y / scale_factor;
416    }
417
418    /// Forgets the velocity and the location.
419    pub fn stop(&mut self) {
420        self.momentum_velocity_x = 0.0;
421        self.momentum_velocity_y = 0.0;
422        self.cached_scroll_location = None;
423    }
424
425    /// A frame of inertia: the location and the delta of the wheel event
426    /// to send, or nothing when the inertia is over (the state is then
427    /// stopped).
428    pub fn update(&mut self) -> Option<(Point, Vector)> {
429        self.momentum_velocity_x *= DECELERATION_RATE;
430        self.momentum_velocity_y *= DECELERATION_RATE;
431
432        let current_magnitude = (self.momentum_velocity_x * self.momentum_velocity_x
433            + self.momentum_velocity_y * self.momentum_velocity_y)
434            .sqrt();
435
436        match self.cached_scroll_location {
437            Some(location) if current_magnitude >= 0.0001 => {
438                Some((location, Vector::new(self.momentum_velocity_x, self.momentum_velocity_y)))
439            }
440            _ => {
441                self.stop();
442                None
443            }
444        }
445    }
446}
447
448#[cfg(target_os = "ios")]
449pub(crate) use uikit::InputHandler;
450
451#[cfg(target_os = "ios")]
452mod uikit {
453    use super::*;
454    use crate::extensions::to_point;
455    use crate::ferro_view::{FerroView, TopLevelImpl};
456    use ferroui_base::input::raw::{
457        IRawInputEventArgs, RawKeyEventArgs, RawMouseWheelEventArgs, RawPointerPoint, RawTextInputEventArgs,
458        RawTouchEventArgs,
459    };
460    use ferroui_base::input::{
461        IInputDevice, IntermediatePoints, KeyboardDevice, MouseDevice, PenDevice, TouchDevice,
462    };
463    use ferroui_controls::platform::ITopLevelImpl;
464    use objc2::rc::{Retained, Weak};
465    use objc2::{sel, MainThreadOnly, Message};
466    use objc2_foundation::{NSProcessInfo, NSRunLoop, NSRunLoopCommonModes, NSSet};
467    use objc2_quartz_core::CADisplayLink;
468    use objc2_ui_kit::{
469        UIEvent, UIEventButtonMask, UIPanGestureRecognizer, UIPress, UIPressesEvent, UITouch,
470    };
471    use std::cell::{Cell, LazyCell, RefCell};
472    use std::collections::HashMap;
473    use std::rc::Rc;
474
475    ferroui_base::ferro_thread_local! {
476        /// The identifier of the next touch, across the views of the
477        /// application.
478        static NEXT_TOUCH_POINT_ID: Cell<i64> = const { Cell::new(1) };
479    }
480
481    const _: () = {
482        use objc2_ui_kit::{UIKeyModifierFlags, UITouchPhase, UITouchType};
483        // The constants of this file are those of UIKit.
484        assert!(touch_type::DIRECT == UITouchType::Direct.0);
485        assert!(touch_type::INDIRECT == UITouchType::Indirect.0);
486        assert!(touch_type::STYLUS == UITouchType::Pencil.0);
487        assert!(touch_type::INDIRECT_POINTER == UITouchType::IndirectPointer.0);
488        assert!(touch_phase::BEGAN == UITouchPhase::Began.0);
489        assert!(touch_phase::MOVED == UITouchPhase::Moved.0);
490        assert!(touch_phase::STATIONARY == UITouchPhase::Stationary.0);
491        assert!(touch_phase::ENDED == UITouchPhase::Ended.0);
492        assert!(touch_phase::CANCELLED == UITouchPhase::Cancelled.0);
493        assert!(key_modifier_flags::SHIFT == UIKeyModifierFlags::Shift.0);
494        assert!(key_modifier_flags::CONTROL == UIKeyModifierFlags::Control.0);
495        assert!(key_modifier_flags::ALTERNATE == UIKeyModifierFlags::Alternate.0);
496        assert!(key_modifier_flags::COMMAND == UIKeyModifierFlags::Command.0);
497    };
498
499    const _: () = {
500        use objc2_ui_kit::{UIGestureRecognizerState, UIPressPhase, UIPressType};
501        assert!(press_phase::BEGAN == UIPressPhase::Began.0);
502        assert!(press_phase::CHANGED == UIPressPhase::Changed.0);
503        assert!(press_phase::STATIONARY == UIPressPhase::Stationary.0);
504        assert!(press_phase::ENDED == UIPressPhase::Ended.0);
505        assert!(press_phase::CANCELLED == UIPressPhase::Cancelled.0);
506        assert!(press_type::UP_ARROW == UIPressType::UpArrow.0);
507        assert!(press_type::DOWN_ARROW == UIPressType::DownArrow.0);
508        assert!(press_type::LEFT_ARROW == UIPressType::LeftArrow.0);
509        assert!(press_type::RIGHT_ARROW == UIPressType::RightArrow.0);
510        assert!(press_type::SELECT == UIPressType::Select.0);
511        assert!(press_type::MENU == UIPressType::Menu.0);
512        assert!(press_type::PLAY_PAUSE == UIPressType::PlayPause.0);
513        assert!(press_type::PAGE_UP == UIPressType::PageUp.0);
514        assert!(press_type::PAGE_DOWN == UIPressType::PageDown.0);
515        assert!(gesture_recognizer_state::POSSIBLE == UIGestureRecognizerState::Possible.0);
516        assert!(gesture_recognizer_state::BEGAN == UIGestureRecognizerState::Began.0);
517        assert!(gesture_recognizer_state::CHANGED == UIGestureRecognizerState::Changed.0);
518        assert!(gesture_recognizer_state::ENDED == UIGestureRecognizerState::Ended.0);
519        assert!(gesture_recognizer_state::CANCELLED == UIGestureRecognizerState::Cancelled.0);
520        assert!(gesture_recognizer_state::FAILED == UIGestureRecognizerState::Failed.0);
521    };
522
523    /// The milliseconds since the system started: the clock of the
524    /// timestamps of the events of UIKit.
525    fn tick_count() -> u64 {
526        (NSProcessInfo::processInfo().systemUptime() * 1000.0) as u64
527    }
528
529    /// Translates the touches of a view to the input of its top-level.
530    pub(crate) struct InputHandler {
531        view: Weak<FerroView>,
532        tl: Rc<TopLevelImpl>,
533        touch_device: Rc<TouchDevice>,
534        mouse_device: Rc<MouseDevice>,
535        pen_device: Rc<PenDevice>,
536        /// The identifiers of the touches that are down, by the address of
537        /// the touch object, which UIKit keeps for the life of a touch.
538        known_touches: RefCell<HashMap<usize, i64>>,
539        momentum: Cell<MomentumScrolling>,
540        momentum_display_link: RefCell<Option<Retained<CADisplayLink>>>,
541    }
542
543    impl InputHandler {
544        pub(crate) fn new(view: Weak<FerroView>, tl: Rc<TopLevelImpl>) -> Self {
545            Self {
546                view,
547                tl,
548                touch_device: TouchDevice::new(),
549                mouse_device: MouseDevice::new(),
550                pen_device: PenDevice::new(true),
551                known_touches: RefCell::new(HashMap::new()),
552                momentum: Cell::new(MomentumScrolling::default()),
553                momentum_display_link: RefCell::new(None),
554            }
555        }
556
557        fn invoke_input(&self, args: Rc<dyn IRawInputEventArgs>) {
558            if let Some(input) = self.tl.input() {
559                input(args);
560            }
561        }
562
563        /// Handles the touches of a `touchesBegan:withEvent:` and of the
564        /// three methods that follow it.
565        pub(crate) fn handle(&self, touches: &NSSet<UITouch>, evt: Option<&UIEvent>) {
566            let Some(view) = self.view.load() else {
567                return;
568            };
569
570            for t in touches.iter() {
571                let Some(kind) = device_kind(t.r#type().0) else {
572                    // Ignore indirect input, like the trackpad of a remote
573                    // controller, which is handled with gestures.
574                    continue;
575                };
576
577                let key = Retained::as_ptr(&t) as usize;
578                let id = *self.known_touches.borrow_mut().entry(key).or_insert_with(|| {
579                    NEXT_TOUCH_POINT_ID.with(|next| {
580                        let id = next.get();
581                        next.set(id + 1);
582                        id
583                    })
584                });
585
586                let device: Rc<dyn IInputDevice> = match kind {
587                    TouchDeviceKind::Pen => self.pen_device.clone(),
588                    TouchDeviceKind::Mouse => self.mouse_device.clone(),
589                    TouchDeviceKind::Touch => self.touch_device.clone(),
590                };
591
592                let modifiers = convert_modifier_keys(evt.map(|evt| evt.modifierFlags().0));
593                let is_right_click = evt.is_some_and(|evt| evt.buttonMask().contains(UIEventButtonMask::Secondary));
594                let phase = t.phase().0;
595
596                let to_pointer_point = {
597                    let view = view.clone();
598                    // The pressure of every point is the one of the touch
599                    // that is handled, as in the reference.
600                    let pressure = pressure(t.force(), t.maximumPossibleForce());
601                    move |touch: &UITouch| {
602                        let mut point = RawPointerPoint::new();
603                        point.position = to_point(touch.locationInView(Some(&view)));
604                        point.pressure = pressure;
605                        point
606                    }
607                };
608
609                let ev = RawTouchEventArgs::with_point(
610                    device,
611                    ts(evt.map(|evt| evt.timestamp())),
612                    view.input_root(),
613                    pointer_event_type(kind, phase, is_right_click),
614                    to_pointer_point(&t),
615                    modifiers,
616                    id,
617                );
618
619                if let Some(this_event) = evt {
620                    let this_event = this_event.retain();
621                    let touch = t.clone();
622                    let intermediate_points: IntermediatePoints = Rc::new(LazyCell::new(Box::new(move || {
623                        let coalesced = this_event.coalescedTouchesForTouch(&touch);
624                        let coalesced: Vec<Retained<UITouch>> =
625                            coalesced.map(|coalesced| coalesced.iter().collect()).unwrap_or_default();
626                        // Skip the last one, as it is the point that was
627                        // processed already.
628                        let count = coalesced.len().saturating_sub(1);
629                        Some(coalesced[..count].iter().map(|touch| to_pointer_point(touch)).collect())
630                    })));
631                    ev.set_intermediate_points(Some(intermediate_points));
632                }
633
634                let args: Rc<dyn IRawInputEventArgs> = Rc::new(ev);
635                if let Some(input) = self.tl.input() {
636                    input(args);
637                }
638
639                if is_final_phase(phase) {
640                    self.known_touches.borrow_mut().remove(&key);
641                }
642            }
643        }
644
645        /// Handles the presses of a `pressesBegan:withEvent:` and of the
646        /// three methods that follow it; tells whether one was handled.
647        ///
648        /// # Panics
649        /// Panics when the platform has no keyboard device (`use_ios`).
650        pub(crate) fn handle_presses(&self, presses: &NSSet<UIPress>, evt: Option<&UIPressesEvent>) -> bool {
651            let Some(view) = self.view.load() else {
652                return false;
653            };
654            let mtm = view.mtm();
655            let keyboard_device: Rc<dyn IInputDevice> = match KeyboardDevice::instance() {
656                Some(keyboard_device) => keyboard_device,
657                None => panic!("The keyboard device of the platform is not registered."),
658            };
659            let timestamp = ts(evt.map(|evt| evt.timestamp()));
660
661            let mut handled = false;
662            for p in presses.iter() {
663                let ui_key = p.key(mtm);
664                let characters = ui_key.as_ref().map(|ui_key| ui_key.characters().to_string());
665                let key = ui_key.as_ref().zip(characters.as_ref()).map(|(ui_key, characters)| PressKey {
666                    key_code: ui_key.keyCode().0,
667                    modifier_flags: ui_key.modifierFlags().0,
668                    characters,
669                });
670                let press = translate_press(key, p.r#type().0);
671                let phase = p.phase().0;
672
673                let key = press.physical_key.to_qwerty_key();
674                if key == ferroui_base::input::Key::None {
675                    continue;
676                }
677
678                let ev = Rc::new(RawKeyEventArgs::new(
679                    keyboard_device.clone(),
680                    timestamp,
681                    view.input_root(),
682                    key_event_type(phase),
683                    key,
684                    press.modifier,
685                    press.physical_key,
686                    press.characters.clone(),
687                    press.key_device_type,
688                ));
689
690                self.invoke_input(ev.clone());
691                handled |= ev.handled();
692
693                if press_is_text_input(ev.handled(), phase, press.characters.as_deref()) {
694                    let raw_text_event = Rc::new(RawTextInputEventArgs::new(
695                        keyboard_device.clone(),
696                        timestamp,
697                        view.input_root(),
698                        press.characters.unwrap_or_default(),
699                    ));
700                    self.invoke_input(raw_text_event.clone());
701                    handled |= raw_text_event.handled();
702                }
703            }
704
705            handled
706        }
707
708        /// Handles the pan gesture that only takes scroll events.
709        pub(crate) fn handle_scroll_wheel(&self, recognizer: &UIPanGestureRecognizer) {
710            let Some(view) = self.view.load() else {
711                return;
712            };
713
714            match recognizer.state().0 {
715                gesture_recognizer_state::BEGAN => {
716                    // Scrolling started: stop any previous inertia
717                    // scrolling and cache the current scroll location.
718                    self.stop_momentum_scrolling();
719                    let mut momentum = self.momentum.get();
720                    momentum.begin(to_point(recognizer.locationInView(Some(&view))));
721                    self.momentum.set(momentum);
722                }
723                gesture_recognizer_state::CHANGED => {
724                    // While scrolling is active, the scroll events are
725                    // sent.
726                    self.send_active_scroll_event(recognizer, &view);
727                }
728                gesture_recognizer_state::ENDED => {
729                    // When scrolling stops, inertia scrolling starts.
730                    // `update_inertia_scrolling` checks when the inertia
731                    // stops and calls `stop_momentum_scrolling`.
732                    self.start_inertia_scrolling(recognizer, &view);
733                }
734                gesture_recognizer_state::CANCELLED | gesture_recognizer_state::FAILED => {
735                    // If the gesture is cancelled or failed, stop.
736                    self.stop_momentum_scrolling();
737                }
738                _ => {}
739            }
740        }
741
742        fn send_wheel_event(&self, view: &FerroView, position: Point, delta: Vector) {
743            let device: Rc<dyn IInputDevice> = self.mouse_device.clone();
744            self.invoke_input(Rc::new(RawMouseWheelEventArgs::new(
745                device,
746                tick_count(),
747                view.input_root(),
748                position,
749                delta,
750                RawInputModifiers::NONE,
751            )));
752        }
753
754        fn send_active_scroll_event(&self, recognizer: &UIPanGestureRecognizer, view: &FerroView) {
755            let velocity = to_point(recognizer.velocityInView(Some(view)));
756            let position = self.momentum.get().cached_scroll_location().unwrap_or_default();
757            self.send_wheel_event(view, position, active_scroll_delta(velocity));
758        }
759
760        fn start_inertia_scrolling(&self, recognizer: &UIPanGestureRecognizer, view: &FerroView) {
761            let velocity = to_point(recognizer.velocityInView(Some(view)));
762            let mut momentum = self.momentum.get();
763            momentum.start_inertia(velocity);
764            self.momentum.set(momentum);
765
766            // SAFETY: the view responds to the selector (the class of the
767            // view declares `ferroUpdateInertiaScrolling:`, which takes
768            // the link, the one argument a display link passes); the link
769            // retains its target until it is invalidated, which
770            // `stop_momentum_scrolling` does. The link is created on the
771            // main thread, added to the run loop of the main thread and
772            // invalidated there; the mode is a constant of Foundation.
773            let link = unsafe {
774                let link = CADisplayLink::displayLinkWithTarget_selector(view, sel!(ferroUpdateInertiaScrolling:));
775                link.addToRunLoop_forMode(&NSRunLoop::mainRunLoop(), NSRunLoopCommonModes);
776                link
777            };
778            let previous = self.momentum_display_link.borrow_mut().replace(link);
779            if let Some(previous) = previous {
780                // The reference overwrites the field; a link that was
781                // still running would run on with the view retained, so
782                // it is invalidated here.
783                previous.invalidate();
784            }
785        }
786
787        fn stop_momentum_scrolling(&self) {
788            let link = self.momentum_display_link.borrow_mut().take();
789            if let Some(link) = link {
790                // Invalidating removes the link from all run loops.
791                link.invalidate();
792            }
793
794            let mut momentum = self.momentum.get();
795            momentum.stop();
796            self.momentum.set(momentum);
797        }
798
799        /// A frame of inertia scrolling, from the display link.
800        pub(crate) fn update_inertia_scrolling(&self) {
801            let mut momentum = self.momentum.get();
802            let event = momentum.update();
803            self.momentum.set(momentum);
804
805            match (event, self.view.load()) {
806                // The pan gesture goes on reporting the location of the
807                // pointer where it would be if it moved with the current
808                // velocity, though the pointer on the screen does not
809                // move. The location is cached when scrolling starts and
810                // kept until the inertia stops.
811                (Some((location, delta)), Some(view)) => self.send_wheel_event(&view, location, delta),
812                _ => self.stop_momentum_scrolling(),
813            }
814        }
815    }
816}
817
818#[cfg(test)]
819mod tests {
820    // Not from the reference, which has no tests of this file.
821    use super::*;
822
823    #[test]
824    fn fingers_pencils_and_pointers_have_their_devices() {
825        assert_eq!(Some(TouchDeviceKind::Touch), device_kind(touch_type::DIRECT));
826        assert_eq!(None, device_kind(touch_type::INDIRECT));
827        assert_eq!(Some(TouchDeviceKind::Pen), device_kind(touch_type::STYLUS));
828        assert_eq!(Some(TouchDeviceKind::Mouse), device_kind(touch_type::INDIRECT_POINTER));
829        // A type this version does not know is a touch.
830        assert_eq!(Some(TouchDeviceKind::Touch), device_kind(17));
831    }
832
833    #[test]
834    fn the_phases_of_a_finger_are_the_touch_events() {
835        let event = |phase| pointer_event_type(TouchDeviceKind::Touch, phase, false);
836        assert_eq!(RawPointerEventType::TouchBegin, event(touch_phase::BEGAN));
837        assert_eq!(RawPointerEventType::TouchUpdate, event(touch_phase::MOVED));
838        assert_eq!(RawPointerEventType::TouchUpdate, event(touch_phase::STATIONARY));
839        assert_eq!(RawPointerEventType::TouchEnd, event(touch_phase::ENDED));
840        assert_eq!(RawPointerEventType::TouchCancel, event(touch_phase::CANCELLED));
841        // The secondary button means nothing for a finger.
842        assert_eq!(RawPointerEventType::TouchBegin, pointer_event_type(TouchDeviceKind::Touch, touch_phase::BEGAN, true));
843    }
844
845    #[test]
846    fn the_phases_of_a_pencil_and_of_a_pointer_are_button_events() {
847        for device in [TouchDeviceKind::Pen, TouchDeviceKind::Mouse] {
848            let event = |phase, right| pointer_event_type(device, phase, right);
849            assert_eq!(RawPointerEventType::LeftButtonDown, event(touch_phase::BEGAN, false));
850            assert_eq!(RawPointerEventType::RightButtonDown, event(touch_phase::BEGAN, true));
851            assert_eq!(RawPointerEventType::Move, event(touch_phase::MOVED, false));
852            assert_eq!(RawPointerEventType::Move, event(touch_phase::STATIONARY, true));
853            assert_eq!(RawPointerEventType::LeftButtonUp, event(touch_phase::ENDED, false));
854            assert_eq!(RawPointerEventType::RightButtonUp, event(touch_phase::ENDED, true));
855            assert_eq!(RawPointerEventType::LeaveWindow, event(touch_phase::CANCELLED, false));
856        }
857    }
858
859    #[test]
860    fn the_average_force_is_half_the_pressure_range() {
861        assert_eq!(0.5, pressure(1.0, 6.67));
862        assert_eq!(1.0, pressure(2.0, 6.67));
863        assert_eq!(0.0, pressure(0.0, 6.67));
864        // A screen that measures no force.
865        assert_eq!(0.5, pressure(0.0, 0.0));
866    }
867
868    #[test]
869    fn the_modifier_flags_are_the_raw_modifiers() {
870        assert_eq!(RawInputModifiers::NONE, convert_modifier_keys(None));
871        assert_eq!(RawInputModifiers::NONE, convert_modifier_keys(Some(0)));
872        assert_eq!(RawInputModifiers::SHIFT, convert_modifier_keys(Some(key_modifier_flags::SHIFT)));
873        assert_eq!(RawInputModifiers::ALT, convert_modifier_keys(Some(key_modifier_flags::ALTERNATE)));
874        assert_eq!(RawInputModifiers::CONTROL, convert_modifier_keys(Some(key_modifier_flags::CONTROL)));
875        assert_eq!(RawInputModifiers::META, convert_modifier_keys(Some(key_modifier_flags::COMMAND)));
876        assert_eq!(
877            RawInputModifiers::SHIFT | RawInputModifiers::META,
878            convert_modifier_keys(Some(key_modifier_flags::SHIFT | key_modifier_flags::COMMAND | 1 << 16))
879        );
880    }
881
882    #[test]
883    fn the_timestamp_is_in_milliseconds() {
884        assert_eq!(0, ts(None));
885        assert_eq!(1500, ts(Some(1.5)));
886        assert_eq!(86_400_123, ts(Some(86_400.1234)));
887    }
888
889    #[test]
890    fn a_touch_is_over_when_it_ended_or_was_cancelled() {
891        assert!(is_final_phase(touch_phase::ENDED));
892        assert!(is_final_phase(touch_phase::CANCELLED));
893        assert!(!is_final_phase(touch_phase::BEGAN));
894        assert!(!is_final_phase(touch_phase::MOVED));
895        assert!(!is_final_phase(touch_phase::STATIONARY));
896    }
897
898    #[test]
899    fn the_key_table_has_the_keys_of_the_reference() {
900        assert_eq!(120, KEY_TABLE_USAGES.len());
901        assert_eq!(Some(PhysicalKey::A), physical_key_of_hid_usage(0x04));
902        assert_eq!(Some(PhysicalKey::Z), physical_key_of_hid_usage(0x1D));
903        assert_eq!(Some(PhysicalKey::Digit1), physical_key_of_hid_usage(0x1E));
904        assert_eq!(Some(PhysicalKey::Digit0), physical_key_of_hid_usage(0x27));
905        assert_eq!(Some(PhysicalKey::Enter), physical_key_of_hid_usage(0x28));
906        assert_eq!(Some(PhysicalKey::Backspace), physical_key_of_hid_usage(0x2A));
907        assert_eq!(Some(PhysicalKey::Delete), physical_key_of_hid_usage(0x4C));
908        assert_eq!(Some(PhysicalKey::F12), physical_key_of_hid_usage(0x45));
909        assert_eq!(Some(PhysicalKey::F24), physical_key_of_hid_usage(0x73));
910        assert_eq!(Some(PhysicalKey::ArrowUp), physical_key_of_hid_usage(0x52));
911        assert_eq!(Some(PhysicalKey::MetaLeft), physical_key_of_hid_usage(0xE3));
912        assert_eq!(Some(PhysicalKey::MetaRight), physical_key_of_hid_usage(0xE7));
913        // As in the reference: the hyphen and the equal sign of the main
914        // block are the keys of the numeric pad, the slash and the period
915        // of the numeric pad are the keys of the main block, and "select"
916        // is the space bar.
917        assert_eq!(Some(PhysicalKey::NumPadSubtract), physical_key_of_hid_usage(0x2D));
918        assert_eq!(Some(PhysicalKey::NumPadEqual), physical_key_of_hid_usage(0x2E));
919        assert_eq!(Some(PhysicalKey::Slash), physical_key_of_hid_usage(0x54));
920        assert_eq!(Some(PhysicalKey::Period), physical_key_of_hid_usage(0x63));
921        assert_eq!(Some(PhysicalKey::Space), physical_key_of_hid_usage(0x77));
922        // Not in the table: the non-US pound, the grave accent, the
923        // application key, the error codes.
924        for usage in [0x00, 0x01, 0x32, 0x35, 0x65, 0x66, 0x67, 0x74, 0x82, 0xE8, 0xFFFF] {
925            assert_eq!(None, physical_key_of_hid_usage(usage), "{usage:#x}");
926        }
927        // Every key of the table has a key of the QWERTY layout.
928        for usage in KEY_TABLE_USAGES {
929            let physical_key = physical_key_of_hid_usage(*usage).unwrap();
930            assert_ne!(ferroui_base::input::Key::None, physical_key.to_qwerty_key(), "{usage:#x}");
931        }
932    }
933
934    #[test]
935    fn a_press_with_a_key_of_the_table_is_a_keyboard_press() {
936        let press = translate_press(
937            Some(PressKey { key_code: 0x04, modifier_flags: key_modifier_flags::SHIFT, characters: "A" }),
938            press_type::SELECT,
939        );
940        assert_eq!(PhysicalKey::A, press.physical_key);
941        assert_eq!(RawInputModifiers::SHIFT, press.modifier);
942        assert_eq!(Some("A".to_string()), press.characters);
943        assert_eq!(KeyDeviceType::Keyboard, press.key_device_type);
944
945        // A key without characters reports the name of its constant.
946        let press = translate_press(
947            Some(PressKey { key_code: 0x52, modifier_flags: 0, characters: "UIKeyInputUpArrow" }),
948            press_type::UP_ARROW,
949        );
950        assert_eq!(PhysicalKey::ArrowUp, press.physical_key);
951        assert_eq!(None, press.characters);
952        assert_eq!(KeyDeviceType::Keyboard, press.key_device_type);
953    }
954
955    #[test]
956    fn a_press_without_a_key_of_the_table_is_a_press_of_a_remote() {
957        let of_type = |press_type| translate_press(None, press_type);
958        assert_eq!(PhysicalKey::ArrowUp, of_type(press_type::UP_ARROW).physical_key);
959        assert_eq!(PhysicalKey::ArrowDown, of_type(press_type::DOWN_ARROW).physical_key);
960        assert_eq!(PhysicalKey::ArrowLeft, of_type(press_type::LEFT_ARROW).physical_key);
961        assert_eq!(PhysicalKey::ArrowRight, of_type(press_type::RIGHT_ARROW).physical_key);
962        assert_eq!(PhysicalKey::Space, of_type(press_type::SELECT).physical_key);
963        assert_eq!(PhysicalKey::ContextMenu, of_type(press_type::MENU).physical_key);
964        assert_eq!(PhysicalKey::MediaPlayPause, of_type(press_type::PLAY_PAUSE).physical_key);
965        assert_eq!(PhysicalKey::PageUp, of_type(press_type::PAGE_UP).physical_key);
966        assert_eq!(PhysicalKey::PageDown, of_type(press_type::PAGE_DOWN).physical_key);
967        assert_eq!(PhysicalKey::None, of_type(32).physical_key);
968        assert_eq!(KeyDeviceType::Remote, of_type(press_type::SELECT).key_device_type);
969        assert_eq!(RawInputModifiers::NONE, of_type(press_type::SELECT).modifier);
970
971        // A key the table does not have (the grave accent) falls back to
972        // the type of the press, with nothing of the key.
973        let press = translate_press(
974            Some(PressKey { key_code: 0x35, modifier_flags: key_modifier_flags::SHIFT, characters: "~" }),
975            press_type::MENU,
976        );
977        assert_eq!(PhysicalKey::ContextMenu, press.physical_key);
978        assert_eq!(RawInputModifiers::NONE, press.modifier);
979        assert_eq!(None, press.characters);
980        assert_eq!(KeyDeviceType::Remote, press.key_device_type);
981    }
982
983    #[test]
984    fn the_phases_of_a_press_are_key_down_until_it_ends() {
985        assert_eq!(RawKeyEventType::KeyDown, key_event_type(press_phase::BEGAN));
986        assert_eq!(RawKeyEventType::KeyDown, key_event_type(press_phase::CHANGED));
987        assert_eq!(RawKeyEventType::KeyDown, key_event_type(press_phase::STATIONARY));
988        assert_eq!(RawKeyEventType::KeyUp, key_event_type(press_phase::ENDED));
989        assert_eq!(RawKeyEventType::KeyUp, key_event_type(press_phase::CANCELLED));
990        assert_eq!(RawKeyEventType::KeyUp, key_event_type(17));
991    }
992
993    #[test]
994    fn text_follows_a_key_down_that_was_not_handled() {
995        assert!(press_is_text_input(false, press_phase::BEGAN, Some("a")));
996        assert!(!press_is_text_input(true, press_phase::BEGAN, Some("a")));
997        assert!(!press_is_text_input(false, press_phase::CHANGED, Some("a")));
998        assert!(!press_is_text_input(false, press_phase::ENDED, Some("a")));
999        assert!(!press_is_text_input(false, press_phase::BEGAN, Some("")));
1000        assert!(!press_is_text_input(false, press_phase::BEGAN, None));
1001    }
1002
1003    #[test]
1004    fn active_scrolling_scales_the_velocity_down() {
1005        assert_eq!(Vector::new(1.0, -0.5), active_scroll_delta(Point::new(3000.0, -1500.0)));
1006        assert_eq!(Vector::new(0.0, 0.0), active_scroll_delta(Point::new(0.0, 0.0)));
1007    }
1008
1009    #[test]
1010    fn inertia_decelerates_until_it_stops() {
1011        let mut momentum = MomentumScrolling::default();
1012        // No inertia without a location.
1013        momentum.start_inertia(Point::new(800.0, 0.0));
1014        assert_eq!(None, momentum.update());
1015
1016        momentum.begin(Point::new(10.0, 20.0));
1017        assert_eq!(Some(Point::new(10.0, 20.0)), momentum.cached_scroll_location());
1018        momentum.start_inertia(Point::new(800.0, -1600.0));
1019        let (location, delta) = momentum.update().unwrap();
1020        assert_eq!(Point::new(10.0, 20.0), location);
1021        assert_eq!(Vector::new(0.95, -1.9), delta);
1022        let (location, delta) = momentum.update().unwrap();
1023        assert_eq!(Point::new(10.0, 20.0), location);
1024        assert!((delta.x - 0.9025).abs() < 1e-12 && (delta.y + 1.805).abs() < 1e-12);
1025
1026        // The velocity falls below the threshold after a number of
1027        // frames, and the state is then as it was at the start.
1028        let mut frames = 2;
1029        while momentum.update().is_some() {
1030            frames += 1;
1031            assert!(frames < 1000);
1032        }
1033        assert!((150..250).contains(&frames), "{frames}");
1034        assert_eq!(MomentumScrolling::default(), momentum);
1035    }
1036
1037    #[test]
1038    fn a_new_scroll_stops_the_inertia_of_the_last_one() {
1039        let mut momentum = MomentumScrolling::default();
1040        momentum.begin(Point::new(1.0, 1.0));
1041        momentum.start_inertia(Point::new(800.0, 800.0));
1042        momentum.begin(Point::new(5.0, 6.0));
1043        assert_eq!(Some(Point::new(5.0, 6.0)), momentum.cached_scroll_location());
1044        // The velocity of the last scroll is gone.
1045        assert_eq!(None, momentum.update());
1046    }
1047}