1use ferroui_base::input::raw::{RawKeyEventType, RawPointerEventType};
11use ferroui_base::input::{KeyDeviceType, PhysicalKey, RawInputModifiers};
12use ferroui_base::{Point, Vector};
13
14#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
16pub enum TouchDeviceKind {
17 Touch,
19 Pen,
21 Mouse,
23}
24
25pub 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
33pub 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
42pub 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
50pub 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
61pub 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
80pub 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
91pub 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
112pub 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
121pub fn is_final_phase(phase: isize) -> bool {
123 phase == touch_phase::CANCELLED || phase == touch_phase::ENDED
124}
125
126pub 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
135pub 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
149pub 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
160macro_rules! keys {
165 ($($name:ident = $usage:literal => $key:ident,)*) => {
166 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 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 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#[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#[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
328pub 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 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
364pub 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
372pub 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
378pub const DECELERATION_RATE: f64 = 0.95;
380
381pub 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#[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 pub fn begin(&mut self, location: Point) {
402 self.stop();
403 self.cached_scroll_location = Some(location);
404 }
405
406 pub fn cached_scroll_location(&self) -> Option<Point> {
408 self.cached_scroll_location
409 }
410
411 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 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 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 static NEXT_TOUCH_POINT_ID: Cell<i64> = const { Cell::new(1) };
479 }
480
481 const _: () = {
482 use objc2_ui_kit::{UIKeyModifierFlags, UITouchPhase, UITouchType};
483 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 fn tick_count() -> u64 {
526 (NSProcessInfo::processInfo().systemUptime() * 1000.0) as u64
527 }
528
529 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 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 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 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 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 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 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 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 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 self.send_active_scroll_event(recognizer, &view);
727 }
728 gesture_recognizer_state::ENDED => {
729 self.start_inertia_scrolling(recognizer, &view);
733 }
734 gesture_recognizer_state::CANCELLED | gesture_recognizer_state::FAILED => {
735 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 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 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 link.invalidate();
792 }
793
794 let mut momentum = self.momentum.get();
795 momentum.stop();
796 self.momentum.set(momentum);
797 }
798
799 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 (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 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 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 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 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 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 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 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 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 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 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 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 assert_eq!(None, momentum.update());
1046 }
1047}