Skip to main content

repose_core/
input.rs

1use crate::Vec2;
2use std::cell::Cell;
3use std::rc::Rc;
4
5#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
6pub struct PointerId(pub u64);
7
8#[derive(Clone, Copy, Debug, PartialEq, Eq)]
9pub enum PointerKind {
10    Mouse,
11    Touch,
12    Pen,
13}
14
15/// What produced a scroll delta.
16///
17/// A wheel notch and a one-finger drag are the same `Vec2` by the time they
18/// reach a handler, which is why a canvas cannot decide "zoom" from the value
19/// alone. Two-finger pan and pinch are *not* scrolls: they arrive as
20/// [`crate::shortcuts::Gesture`] actions instead.
21#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
22pub enum ScrollSource {
23    /// A mouse wheel or trackpad.
24    Wheel,
25    /// A single-finger drag across a surface.
26    Touch,
27}
28
29#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
30pub enum PointerButton {
31    Primary,   // Left mouse, touch
32    Secondary, // Right mouse
33    Tertiary,  // Middle mouse
34}
35
36#[derive(Clone, Copy, Debug, PartialEq, Eq)]
37pub enum PointerEventPass {
38    /// Top-down pass: ancestor -> descendant. Allows ancestors to preview or
39    /// intercept events before descendants see them.
40    Initial,
41    /// Bottom-up pass: descendant -> ancestor. The primary pass where gesture
42    /// handlers react to and consume events. A child that consumes its event
43    /// prevents the parent from reacting (Compose's requireUnconsumed).
44    Main,
45    /// Top-down pass: ancestor -> descendant. Allows descendants to learn
46    /// about events consumed by ancestors during the Main pass.
47    Final,
48}
49
50#[derive(Clone, Copy, Debug)]
51pub enum PointerEventKind {
52    Down(PointerButton),
53    Up(PointerButton),
54    Move,
55    Cancel,
56    Enter,
57    Leave,
58}
59
60#[derive(Clone, Debug)]
61pub struct PointerEvent {
62    pub id: PointerId,
63    pub kind: PointerKind,
64    pub event: PointerEventKind,
65    /// Position relative to `origin` (hit-region top-left), in physical px.
66    pub position: Vec2,
67    /// Top-left of the hit region this event is being delivered to (physical px).
68    pub origin: Vec2,
69    /// Absolute pointer position in window/surface physical pixels.
70    pub window_position: Vec2,
71    pub pressure: f32,
72    pub modifiers: Modifiers,
73    /// Shared consumed state -> every clone of this event points to the same
74    /// Cell. Calling `consume()` on any clone marks it consumed for all clones.
75    pub consumed: Rc<Cell<bool>>,
76}
77
78impl PointerEvent {
79    pub fn new(
80        id: PointerId,
81        kind: PointerKind,
82        event: PointerEventKind,
83        position: Vec2,
84        pressure: f32,
85        modifiers: Modifiers,
86    ) -> Self {
87        Self {
88            id,
89            kind,
90            event,
91            position,
92            origin: Vec2::ZERO,
93            window_position: position,
94            pressure,
95            modifiers,
96            consumed: Rc::new(Cell::new(false)),
97        }
98    }
99
100    /// Absolute position in window/surface physical pixels.
101    pub fn position_in_window(&self) -> Vec2 {
102        self.window_position
103    }
104
105    /// Mark this event as consumed. Once consumed, subsequent handlers in the
106    /// same pass should skip processing it (equivalent to Compose's
107    /// `PointerInputChange.consume()`).
108    pub fn consume(&self) {
109        self.consumed.set(true);
110    }
111
112    /// Returns `true` if `consume()` was called on this event or any clone of it.
113    pub fn is_consumed(&self) -> bool {
114        self.consumed.get()
115    }
116}
117
118#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash)]
119pub struct Modifiers {
120    pub shift: bool,
121    pub ctrl: bool,
122    pub alt: bool,
123    pub meta: bool,    // Cmd on Mac, Win key on Windows
124    pub command: bool, // egui like (Cmd on macOS, Ctrl elsewhere)
125}
126
127/// Physical key position, layout-independent (W3C `KeyboardEvent.code`)
128#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
129pub enum PhysicalKey {
130    KeyA,
131    KeyB,
132    KeyC,
133    KeyD,
134    KeyE,
135    KeyF,
136    KeyG,
137    KeyH,
138    KeyI,
139    KeyJ,
140    KeyK,
141    KeyL,
142    KeyM,
143    KeyN,
144    KeyO,
145    KeyP,
146    KeyQ,
147    KeyR,
148    KeyS,
149    KeyT,
150    KeyU,
151    KeyV,
152    KeyW,
153    KeyX,
154    KeyY,
155    KeyZ,
156    Digit0,
157    Digit1,
158    Digit2,
159    Digit3,
160    Digit4,
161    Digit5,
162    Digit6,
163    Digit7,
164    Digit8,
165    Digit9,
166    Minus,
167    Equal,
168    BracketLeft,
169    BracketRight,
170    Backslash,
171    Semicolon,
172    Quote,
173    Backquote,
174    Comma,
175    Period,
176    Slash,
177    IntlBackslash,
178    IntlRo,
179    IntlYen,
180    Escape,
181    F1,
182    F2,
183    F3,
184    F4,
185    F5,
186    F6,
187    F7,
188    F8,
189    F9,
190    F10,
191    F11,
192    F12,
193    PrintScreen,
194    ScrollLock,
195    Pause,
196    Insert,
197    Home,
198    PageUp,
199    Delete,
200    End,
201    PageDown,
202    ArrowRight,
203    ArrowLeft,
204    ArrowDown,
205    ArrowUp,
206    NumLock,
207    NumpadDivide,
208    NumpadMultiply,
209    NumpadSubtract,
210    NumpadAdd,
211    NumpadEnter,
212    NumpadDecimal,
213    Numpad0,
214    Numpad1,
215    Numpad2,
216    Numpad3,
217    Numpad4,
218    Numpad5,
219    Numpad6,
220    Numpad7,
221    Numpad8,
222    Numpad9,
223    Backspace,
224    Tab,
225    Space,
226    CapsLock,
227    Enter,
228    ShiftLeft,
229    ShiftRight,
230    ControlLeft,
231    ControlRight,
232    AltLeft,
233    AltRight,
234    SuperLeft,
235    SuperRight,
236    ContextMenu,
237    Unidentified,
238}
239
240impl PhysicalKey {
241    /// W3C `code` name (`KeyW`, `Digit1`, `Space`, ...).
242    pub fn name(self) -> &'static str {
243        match self {
244            PhysicalKey::KeyA => "KeyA",
245            PhysicalKey::KeyB => "KeyB",
246            PhysicalKey::KeyC => "KeyC",
247            PhysicalKey::KeyD => "KeyD",
248            PhysicalKey::KeyE => "KeyE",
249            PhysicalKey::KeyF => "KeyF",
250            PhysicalKey::KeyG => "KeyG",
251            PhysicalKey::KeyH => "KeyH",
252            PhysicalKey::KeyI => "KeyI",
253            PhysicalKey::KeyJ => "KeyJ",
254            PhysicalKey::KeyK => "KeyK",
255            PhysicalKey::KeyL => "KeyL",
256            PhysicalKey::KeyM => "KeyM",
257            PhysicalKey::KeyN => "KeyN",
258            PhysicalKey::KeyO => "KeyO",
259            PhysicalKey::KeyP => "KeyP",
260            PhysicalKey::KeyQ => "KeyQ",
261            PhysicalKey::KeyR => "KeyR",
262            PhysicalKey::KeyS => "KeyS",
263            PhysicalKey::KeyT => "KeyT",
264            PhysicalKey::KeyU => "KeyU",
265            PhysicalKey::KeyV => "KeyV",
266            PhysicalKey::KeyW => "KeyW",
267            PhysicalKey::KeyX => "KeyX",
268            PhysicalKey::KeyY => "KeyY",
269            PhysicalKey::KeyZ => "KeyZ",
270            PhysicalKey::Digit0 => "Digit0",
271            PhysicalKey::Digit1 => "Digit1",
272            PhysicalKey::Digit2 => "Digit2",
273            PhysicalKey::Digit3 => "Digit3",
274            PhysicalKey::Digit4 => "Digit4",
275            PhysicalKey::Digit5 => "Digit5",
276            PhysicalKey::Digit6 => "Digit6",
277            PhysicalKey::Digit7 => "Digit7",
278            PhysicalKey::Digit8 => "Digit8",
279            PhysicalKey::Digit9 => "Digit9",
280            PhysicalKey::Minus => "Minus",
281            PhysicalKey::Equal => "Equal",
282            PhysicalKey::BracketLeft => "BracketLeft",
283            PhysicalKey::BracketRight => "BracketRight",
284            PhysicalKey::Backslash => "Backslash",
285            PhysicalKey::Semicolon => "Semicolon",
286            PhysicalKey::Quote => "Quote",
287            PhysicalKey::Backquote => "Backquote",
288            PhysicalKey::Comma => "Comma",
289            PhysicalKey::Period => "Period",
290            PhysicalKey::Slash => "Slash",
291            PhysicalKey::IntlBackslash => "IntlBackslash",
292            PhysicalKey::IntlRo => "IntlRo",
293            PhysicalKey::IntlYen => "IntlYen",
294            PhysicalKey::Escape => "Escape",
295            PhysicalKey::F1 => "F1",
296            PhysicalKey::F2 => "F2",
297            PhysicalKey::F3 => "F3",
298            PhysicalKey::F4 => "F4",
299            PhysicalKey::F5 => "F5",
300            PhysicalKey::F6 => "F6",
301            PhysicalKey::F7 => "F7",
302            PhysicalKey::F8 => "F8",
303            PhysicalKey::F9 => "F9",
304            PhysicalKey::F10 => "F10",
305            PhysicalKey::F11 => "F11",
306            PhysicalKey::F12 => "F12",
307            PhysicalKey::PrintScreen => "PrintScreen",
308            PhysicalKey::ScrollLock => "ScrollLock",
309            PhysicalKey::Pause => "Pause",
310            PhysicalKey::Insert => "Insert",
311            PhysicalKey::Home => "Home",
312            PhysicalKey::PageUp => "PageUp",
313            PhysicalKey::Delete => "Delete",
314            PhysicalKey::End => "End",
315            PhysicalKey::PageDown => "PageDown",
316            PhysicalKey::ArrowRight => "ArrowRight",
317            PhysicalKey::ArrowLeft => "ArrowLeft",
318            PhysicalKey::ArrowDown => "ArrowDown",
319            PhysicalKey::ArrowUp => "ArrowUp",
320            PhysicalKey::NumLock => "NumLock",
321            PhysicalKey::NumpadDivide => "NumpadDivide",
322            PhysicalKey::NumpadMultiply => "NumpadMultiply",
323            PhysicalKey::NumpadSubtract => "NumpadSubtract",
324            PhysicalKey::NumpadAdd => "NumpadAdd",
325            PhysicalKey::NumpadEnter => "NumpadEnter",
326            PhysicalKey::NumpadDecimal => "NumpadDecimal",
327            PhysicalKey::Numpad0 => "Numpad0",
328            PhysicalKey::Numpad1 => "Numpad1",
329            PhysicalKey::Numpad2 => "Numpad2",
330            PhysicalKey::Numpad3 => "Numpad3",
331            PhysicalKey::Numpad4 => "Numpad4",
332            PhysicalKey::Numpad5 => "Numpad5",
333            PhysicalKey::Numpad6 => "Numpad6",
334            PhysicalKey::Numpad7 => "Numpad7",
335            PhysicalKey::Numpad8 => "Numpad8",
336            PhysicalKey::Numpad9 => "Numpad9",
337            PhysicalKey::Backspace => "Backspace",
338            PhysicalKey::Tab => "Tab",
339            PhysicalKey::Space => "Space",
340            PhysicalKey::CapsLock => "CapsLock",
341            PhysicalKey::Enter => "Enter",
342            PhysicalKey::ShiftLeft => "ShiftLeft",
343            PhysicalKey::ShiftRight => "ShiftRight",
344            PhysicalKey::ControlLeft => "ControlLeft",
345            PhysicalKey::ControlRight => "ControlRight",
346            PhysicalKey::AltLeft => "AltLeft",
347            PhysicalKey::AltRight => "AltRight",
348            PhysicalKey::SuperLeft => "SuperLeft",
349            PhysicalKey::SuperRight => "SuperRight",
350            PhysicalKey::ContextMenu => "ContextMenu",
351            PhysicalKey::Unidentified => "Unidentified",
352        }
353    }
354
355    /// Parse a W3C `code` name. Unknown names map to `Unidentified`
356    /// (never `None`: every physical press has a position, even when
357    /// the platform cannot name it).
358    pub fn from_name(name: &str) -> Self {
359        match name {
360            "KeyA" => PhysicalKey::KeyA,
361            "KeyB" => PhysicalKey::KeyB,
362            "KeyC" => PhysicalKey::KeyC,
363            "KeyD" => PhysicalKey::KeyD,
364            "KeyE" => PhysicalKey::KeyE,
365            "KeyF" => PhysicalKey::KeyF,
366            "KeyG" => PhysicalKey::KeyG,
367            "KeyH" => PhysicalKey::KeyH,
368            "KeyI" => PhysicalKey::KeyI,
369            "KeyJ" => PhysicalKey::KeyJ,
370            "KeyK" => PhysicalKey::KeyK,
371            "KeyL" => PhysicalKey::KeyL,
372            "KeyM" => PhysicalKey::KeyM,
373            "KeyN" => PhysicalKey::KeyN,
374            "KeyO" => PhysicalKey::KeyO,
375            "KeyP" => PhysicalKey::KeyP,
376            "KeyQ" => PhysicalKey::KeyQ,
377            "KeyR" => PhysicalKey::KeyR,
378            "KeyS" => PhysicalKey::KeyS,
379            "KeyT" => PhysicalKey::KeyT,
380            "KeyU" => PhysicalKey::KeyU,
381            "KeyV" => PhysicalKey::KeyV,
382            "KeyW" => PhysicalKey::KeyW,
383            "KeyX" => PhysicalKey::KeyX,
384            "KeyY" => PhysicalKey::KeyY,
385            "KeyZ" => PhysicalKey::KeyZ,
386            "Digit0" => PhysicalKey::Digit0,
387            "Digit1" => PhysicalKey::Digit1,
388            "Digit2" => PhysicalKey::Digit2,
389            "Digit3" => PhysicalKey::Digit3,
390            "Digit4" => PhysicalKey::Digit4,
391            "Digit5" => PhysicalKey::Digit5,
392            "Digit6" => PhysicalKey::Digit6,
393            "Digit7" => PhysicalKey::Digit7,
394            "Digit8" => PhysicalKey::Digit8,
395            "Digit9" => PhysicalKey::Digit9,
396            "Minus" => PhysicalKey::Minus,
397            "Equal" => PhysicalKey::Equal,
398            "BracketLeft" => PhysicalKey::BracketLeft,
399            "BracketRight" => PhysicalKey::BracketRight,
400            "Backslash" => PhysicalKey::Backslash,
401            "Semicolon" => PhysicalKey::Semicolon,
402            "Quote" => PhysicalKey::Quote,
403            "Backquote" => PhysicalKey::Backquote,
404            "Comma" => PhysicalKey::Comma,
405            "Period" => PhysicalKey::Period,
406            "Slash" => PhysicalKey::Slash,
407            "IntlBackslash" => PhysicalKey::IntlBackslash,
408            "IntlRo" => PhysicalKey::IntlRo,
409            "IntlYen" => PhysicalKey::IntlYen,
410            "Escape" => PhysicalKey::Escape,
411            "F1" => PhysicalKey::F1,
412            "F2" => PhysicalKey::F2,
413            "F3" => PhysicalKey::F3,
414            "F4" => PhysicalKey::F4,
415            "F5" => PhysicalKey::F5,
416            "F6" => PhysicalKey::F6,
417            "F7" => PhysicalKey::F7,
418            "F8" => PhysicalKey::F8,
419            "F9" => PhysicalKey::F9,
420            "F10" => PhysicalKey::F10,
421            "F11" => PhysicalKey::F11,
422            "F12" => PhysicalKey::F12,
423            "PrintScreen" => PhysicalKey::PrintScreen,
424            "ScrollLock" => PhysicalKey::ScrollLock,
425            "Pause" => PhysicalKey::Pause,
426            "Insert" => PhysicalKey::Insert,
427            "Home" => PhysicalKey::Home,
428            "PageUp" => PhysicalKey::PageUp,
429            "Delete" => PhysicalKey::Delete,
430            "End" => PhysicalKey::End,
431            "PageDown" => PhysicalKey::PageDown,
432            "ArrowRight" => PhysicalKey::ArrowRight,
433            "ArrowLeft" => PhysicalKey::ArrowLeft,
434            "ArrowDown" => PhysicalKey::ArrowDown,
435            "ArrowUp" => PhysicalKey::ArrowUp,
436            "NumLock" => PhysicalKey::NumLock,
437            "NumpadDivide" => PhysicalKey::NumpadDivide,
438            "NumpadMultiply" => PhysicalKey::NumpadMultiply,
439            "NumpadSubtract" => PhysicalKey::NumpadSubtract,
440            "NumpadAdd" => PhysicalKey::NumpadAdd,
441            "NumpadEnter" => PhysicalKey::NumpadEnter,
442            "NumpadDecimal" => PhysicalKey::NumpadDecimal,
443            "Numpad0" => PhysicalKey::Numpad0,
444            "Numpad1" => PhysicalKey::Numpad1,
445            "Numpad2" => PhysicalKey::Numpad2,
446            "Numpad3" => PhysicalKey::Numpad3,
447            "Numpad4" => PhysicalKey::Numpad4,
448            "Numpad5" => PhysicalKey::Numpad5,
449            "Numpad6" => PhysicalKey::Numpad6,
450            "Numpad7" => PhysicalKey::Numpad7,
451            "Numpad8" => PhysicalKey::Numpad8,
452            "Numpad9" => PhysicalKey::Numpad9,
453            "Backspace" => PhysicalKey::Backspace,
454            "Tab" => PhysicalKey::Tab,
455            "Space" => PhysicalKey::Space,
456            "CapsLock" => PhysicalKey::CapsLock,
457            "Enter" => PhysicalKey::Enter,
458            "ShiftLeft" => PhysicalKey::ShiftLeft,
459            "ShiftRight" => PhysicalKey::ShiftRight,
460            "ControlLeft" => PhysicalKey::ControlLeft,
461            "ControlRight" => PhysicalKey::ControlRight,
462            "AltLeft" => PhysicalKey::AltLeft,
463            "AltRight" => PhysicalKey::AltRight,
464            "SuperLeft" | "MetaLeft" => PhysicalKey::SuperLeft,
465            "SuperRight" | "MetaRight" => PhysicalKey::SuperRight,
466            "ContextMenu" => PhysicalKey::ContextMenu,
467            _ => PhysicalKey::Unidentified,
468        }
469    }
470}
471
472#[derive(Clone, Debug, PartialEq, Eq, Hash)]
473pub enum Key {
474    Character(char),
475    Enter,
476    Tab,
477    Backspace,
478    Delete,
479    Insert,
480    Escape,
481    ArrowLeft,
482    ArrowRight,
483    ArrowUp,
484    ArrowDown,
485    Home,
486    End,
487    PageUp,
488    PageDown,
489    Space,
490    ShiftLeft,
491    ShiftRight,
492    ControlLeft,
493    ControlRight,
494    AltLeft,
495    AltRight,
496    MetaLeft,
497    MetaRight,
498    CapsLock,
499    NumLock,
500    ScrollLock,
501    Pause,
502    PrintScreen,
503    ContextMenu,
504    Numpad(char),
505    NumpadEnter,
506    MediaPlay,
507    MediaPause,
508    MediaPlayPause,
509    MediaStop,
510    MediaNext,
511    MediaPrevious,
512    VolumeUp,
513    VolumeDown,
514    VolumeMute,
515    F(u8), // F1-F12
516    Unknown,
517}
518
519#[derive(Clone, Copy, Debug, PartialEq, Eq)]
520pub enum KeyEventType {
521    /// Key pressed down.
522    Down,
523    /// Key released.
524    Up,
525    /// Unknown or unsupported event type.
526    Unknown,
527}
528
529#[derive(Clone, Debug)]
530pub struct KeyEvent {
531    pub key: Key,
532    pub modifiers: Modifiers,
533    pub is_repeat: bool,
534    /// Whether this is a key-down or key-up event.
535    pub event_type: KeyEventType,
536    /// UTF-16 code point for character keys, or 0 for non-characters.
537    /// Matches Compose's `utf16CodePoint`.
538    pub utf16_code_point: u16,
539    /// Physical key position, layout-independent. `None` for synthetic events.
540    pub physical: Option<PhysicalKey>,
541}
542
543impl KeyEvent {
544    pub fn is_ctrl_pressed(&self) -> bool {
545        self.modifiers.ctrl
546    }
547    pub fn is_alt_pressed(&self) -> bool {
548        self.modifiers.alt
549    }
550    pub fn is_shift_pressed(&self) -> bool {
551        self.modifiers.shift
552    }
553    pub fn is_meta_pressed(&self) -> bool {
554        self.modifiers.meta
555    }
556}
557
558#[derive(Clone, Debug)]
559pub struct TextInputEvent {
560    pub text: String,
561}
562
563#[derive(Clone, Debug)]
564pub enum ImeEvent {
565    /// IME composition started
566    Start,
567    /// Composition text updated
568    Update {
569        text: String,
570        cursor: Option<(usize, usize)>, // (start, end) of composition range
571    },
572    /// Composition committed (finalized)
573    Commit(String),
574    /// Composition cancelled
575    Cancel,
576}
577
578#[derive(Clone, Debug)]
579pub enum InputEvent {
580    Pointer(PointerEvent),
581    Key(KeyEvent),
582    Text(TextInputEvent),
583    Ime(ImeEvent),
584    Gamepad(GamepadEvent),
585}
586
587/// Opaque gamepad handle. Backend-local index, stable for the connection
588/// lifetime. Survives across frames; invalid after [`GamepadEvent::Disconnected`].
589#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash)]
590pub struct GamepadId(pub u32);
591
592/// Standard-layout buttons (SDL gamecontroller mapping positions).
593/// Backends translate hardware codes to these; unknown buttons are dropped.
594#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
595pub enum GamepadButton {
596    /// Bottom face button (A / Cross). UI default: activate.
597    South,
598    /// Right face button (B / Circle). UI default: back.
599    East,
600    /// Left face button (X / Square).
601    West,
602    /// Top face button (Y / Triangle).
603    North,
604    Start,
605    Select,
606    LeftShoulder,
607    RightShoulder,
608    LeftStick,
609    RightStick,
610    DPadUp,
611    DPadDown,
612    DPadLeft,
613    DPadRight,
614}
615
616/// Analog axes, normalized to -1.0..=1.0. Triggers report 0.0..=1.0.
617#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
618pub enum GamepadAxis {
619    LeftStickX,
620    LeftStickY,
621    RightStickX,
622    RightStickY,
623    LeftTrigger,
624    RightTrigger,
625}
626
627/// Motion sensor a gamepad can report.
628#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
629pub enum SensorKind {
630    /// Angular velocity in degrees per second.
631    Gyroscope,
632    /// Proper acceleration in g.
633    Accelerometer,
634}
635
636/// One reading from a motion sensor, in the device frame.
637#[derive(Clone, Copy, Debug, PartialEq)]
638pub struct SensorSample {
639    pub kind: SensorKind,
640    /// `[x, y, z]`, scaled per [`SensorKind`].
641    pub data: [f32; 3],
642}
643
644/// Lowercases and strips punctuation, so a driver name matches a pad name
645/// despite interface suffixes ("Wireless Controller (Vendor: 054c)").
646pub fn normalize_device_name(name: &str) -> String {
647    name.chars()
648        .filter(|c| c.is_alphanumeric())
649        .flat_map(char::to_lowercase)
650        .collect()
651}
652
653impl SensorSample {
654    pub fn gyroscope(x: f32, y: f32, z: f32) -> Self {
655        Self {
656            kind: SensorKind::Gyroscope,
657            data: [x, y, z],
658        }
659    }
660
661    pub fn accelerometer(x: f32, y: f32, z: f32) -> Self {
662        Self {
663            kind: SensorKind::Accelerometer,
664            data: [x, y, z],
665        }
666    }
667}
668
669#[derive(Clone, Debug)]
670pub enum GamepadEvent {
671    Connected {
672        id: GamepadId,
673        name: String,
674        /// USB vendor and product ids, `0` where the platform reports none.
675        vendor_id: u16,
676        product_id: u16,
677    },
678    Disconnected {
679        id: GamepadId,
680    },
681    Button {
682        id: GamepadId,
683        button: GamepadButton,
684        pressed: bool,
685    },
686    Axis {
687        id: GamepadId,
688        axis: GamepadAxis,
689        /// -1.0..=1.0 (sticks) or 0.0..=1.0 (triggers). Backends deadzone.
690        value: f32,
691    },
692}
693
694impl GamepadEvent {
695    pub fn id(&self) -> GamepadId {
696        match *self {
697            GamepadEvent::Connected { id, .. } => id,
698            GamepadEvent::Disconnected { id } => id,
699            GamepadEvent::Button { id, .. } => id,
700            GamepadEvent::Axis { id, .. } => id,
701        }
702    }
703}
704
705#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Default)]
706pub enum InputMode {
707    #[default]
708    Touch,
709    /// Keyboard, Tab, arrow/D-pad, or other non-pointer navigation.
710    Keyboard,
711}
712
713thread_local! {
714    static INPUT_MODE: Cell<InputMode> = const { Cell::new(InputMode::Touch) };
715}
716
717/// Current input mode (Compose `InputModeManager.inputMode`).
718///
719/// Composition-local override ([`crate::locals::with_input_mode`]) wins over
720/// the thread default.
721#[inline]
722pub fn input_mode() -> InputMode {
723    crate::locals::local_input_mode().unwrap_or_else(default_input_mode)
724}
725
726pub(crate) fn default_input_mode() -> InputMode {
727    INPUT_MODE.get()
728}
729
730/// Force the global default input mode and request a frame.
731#[inline]
732pub fn set_input_mode_default(mode: InputMode) {
733    INPUT_MODE.set(mode);
734    crate::request_frame();
735}
736
737/// Request a new input mode. Returns `true` if the mode changed.
738///
739/// On change, requests a frame so focus chrome can appear/disappear.
740pub fn request_input_mode(mode: InputMode) -> bool {
741    let prev = INPUT_MODE.get();
742    if prev == mode {
743        return false;
744    }
745    INPUT_MODE.set(mode);
746    crate::frame_clock::request_frame();
747    true
748}
749
750/// `true` when focus indication should paint (focused **and** keyboard mode).
751#[inline]
752pub fn is_focus_visible(focused: bool) -> bool {
753    focused && input_mode() == InputMode::Keyboard
754}
755
756#[cfg(test)]
757mod input_mode_tests {
758    use super::*;
759    use crate::frame_clock::take_frame_request;
760    use crate::modifier::{Interaction, MutableInteractionSource};
761
762    #[test]
763    fn request_input_mode_changes_and_requests_frame() {
764        set_input_mode_default(InputMode::Touch);
765        let _ = take_frame_request();
766
767        assert!(!request_input_mode(InputMode::Touch));
768        assert!(!take_frame_request());
769
770        assert!(request_input_mode(InputMode::Keyboard));
771        assert_eq!(input_mode(), InputMode::Keyboard);
772        assert!(take_frame_request());
773
774        assert!(request_input_mode(InputMode::Touch));
775        assert_eq!(input_mode(), InputMode::Touch);
776        set_input_mode_default(InputMode::Touch);
777    }
778
779    #[test]
780    fn focus_visible_requires_keyboard_mode() {
781        set_input_mode_default(InputMode::Touch);
782        let src = MutableInteractionSource::new();
783        src.emit(Interaction::Focus);
784        assert!(src.source().collect_is_focused());
785        assert!(!src.source().collect_is_focus_visible());
786
787        set_input_mode_default(InputMode::Keyboard);
788        assert!(src.source().collect_is_focus_visible());
789
790        set_input_mode_default(InputMode::Touch);
791        src.emit(Interaction::Unfocus);
792    }
793
794    #[test]
795    fn with_input_mode_overrides_global() {
796        set_input_mode_default(InputMode::Touch);
797        crate::locals::with_input_mode(InputMode::Keyboard, || {
798            assert_eq!(input_mode(), InputMode::Keyboard);
799        });
800        assert_eq!(input_mode(), InputMode::Touch);
801    }
802}