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    F(u8), // F1-F12
493    Unknown,
494}
495
496#[derive(Clone, Copy, Debug, PartialEq, Eq)]
497pub enum KeyEventType {
498    /// Key pressed down.
499    Down,
500    /// Key released.
501    Up,
502    /// Unknown or unsupported event type.
503    Unknown,
504}
505
506#[derive(Clone, Debug)]
507pub struct KeyEvent {
508    pub key: Key,
509    pub modifiers: Modifiers,
510    pub is_repeat: bool,
511    /// Whether this is a key-down or key-up event.
512    pub event_type: KeyEventType,
513    /// UTF-16 code point for character keys, or 0 for non-characters.
514    /// Matches Compose's `utf16CodePoint`.
515    pub utf16_code_point: u16,
516    /// Physical key position, layout-independent. `None` for synthetic events.
517    pub physical: Option<PhysicalKey>,
518}
519
520#[derive(Clone, Debug)]
521pub struct TextInputEvent {
522    pub text: String,
523}
524
525#[derive(Clone, Debug)]
526pub enum ImeEvent {
527    /// IME composition started
528    Start,
529    /// Composition text updated
530    Update {
531        text: String,
532        cursor: Option<(usize, usize)>, // (start, end) of composition range
533    },
534    /// Composition committed (finalized)
535    Commit(String),
536    /// Composition cancelled
537    Cancel,
538}
539
540#[derive(Clone, Debug)]
541pub enum InputEvent {
542    Pointer(PointerEvent),
543    Key(KeyEvent),
544    Text(TextInputEvent),
545    Ime(ImeEvent),
546    Gamepad(GamepadEvent),
547}
548
549/// Opaque gamepad handle. Backend-local index, stable for the connection
550/// lifetime. Survives across frames; invalid after [`GamepadEvent::Disconnected`].
551#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash)]
552pub struct GamepadId(pub u32);
553
554/// Standard-layout buttons (SDL gamecontroller mapping positions).
555/// Backends translate hardware codes to these; unknown buttons are dropped.
556#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
557pub enum GamepadButton {
558    /// Bottom face button (A / Cross). UI default: activate.
559    South,
560    /// Right face button (B / Circle). UI default: back.
561    East,
562    /// Left face button (X / Square).
563    West,
564    /// Top face button (Y / Triangle).
565    North,
566    Start,
567    Select,
568    LeftShoulder,
569    RightShoulder,
570    LeftStick,
571    RightStick,
572    DPadUp,
573    DPadDown,
574    DPadLeft,
575    DPadRight,
576}
577
578/// Analog axes, normalized to -1.0..=1.0. Triggers report 0.0..=1.0.
579#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
580pub enum GamepadAxis {
581    LeftStickX,
582    LeftStickY,
583    RightStickX,
584    RightStickY,
585    LeftTrigger,
586    RightTrigger,
587}
588
589/// Motion sensor a gamepad can report.
590#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
591pub enum SensorKind {
592    /// Angular velocity in degrees per second.
593    Gyroscope,
594    /// Proper acceleration in g.
595    Accelerometer,
596}
597
598/// One reading from a motion sensor, in the device frame.
599#[derive(Clone, Copy, Debug, PartialEq)]
600pub struct SensorSample {
601    pub kind: SensorKind,
602    /// `[x, y, z]`, scaled per [`SensorKind`].
603    pub data: [f32; 3],
604}
605
606/// Lowercases and strips punctuation, so a driver name matches a pad name
607/// despite interface suffixes ("Wireless Controller (Vendor: 054c)").
608pub fn normalize_device_name(name: &str) -> String {
609    name.chars()
610        .filter(|c| c.is_alphanumeric())
611        .flat_map(char::to_lowercase)
612        .collect()
613}
614
615impl SensorSample {
616    pub fn gyroscope(x: f32, y: f32, z: f32) -> Self {
617        Self {
618            kind: SensorKind::Gyroscope,
619            data: [x, y, z],
620        }
621    }
622
623    pub fn accelerometer(x: f32, y: f32, z: f32) -> Self {
624        Self {
625            kind: SensorKind::Accelerometer,
626            data: [x, y, z],
627        }
628    }
629}
630
631#[derive(Clone, Debug)]
632pub enum GamepadEvent {
633    Connected {
634        id: GamepadId,
635        name: String,
636        /// USB vendor and product ids, `0` where the platform reports none.
637        vendor_id: u16,
638        product_id: u16,
639    },
640    Disconnected {
641        id: GamepadId,
642    },
643    Button {
644        id: GamepadId,
645        button: GamepadButton,
646        pressed: bool,
647    },
648    Axis {
649        id: GamepadId,
650        axis: GamepadAxis,
651        /// -1.0..=1.0 (sticks) or 0.0..=1.0 (triggers). Backends deadzone.
652        value: f32,
653    },
654}
655
656impl GamepadEvent {
657    pub fn id(&self) -> GamepadId {
658        match *self {
659            GamepadEvent::Connected { id, .. } => id,
660            GamepadEvent::Disconnected { id } => id,
661            GamepadEvent::Button { id, .. } => id,
662            GamepadEvent::Axis { id, .. } => id,
663        }
664    }
665}
666
667#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Default)]
668pub enum InputMode {
669    #[default]
670    Touch,
671    /// Keyboard, Tab, arrow/D-pad, or other non-pointer navigation.
672    Keyboard,
673}
674
675thread_local! {
676    static INPUT_MODE: Cell<InputMode> = const { Cell::new(InputMode::Touch) };
677}
678
679/// Current input mode (Compose `InputModeManager.inputMode`).
680///
681/// Composition-local override ([`crate::locals::with_input_mode`]) wins over
682/// the thread default.
683#[inline]
684pub fn input_mode() -> InputMode {
685    crate::locals::local_input_mode().unwrap_or_else(default_input_mode)
686}
687
688pub(crate) fn default_input_mode() -> InputMode {
689    INPUT_MODE.get()
690}
691
692/// Force the global default input mode and request a frame.
693#[inline]
694pub fn set_input_mode_default(mode: InputMode) {
695    INPUT_MODE.set(mode);
696    crate::request_frame();
697}
698
699/// Request a new input mode. Returns `true` if the mode changed.
700///
701/// On change, requests a frame so focus chrome can appear/disappear.
702pub fn request_input_mode(mode: InputMode) -> bool {
703    let prev = INPUT_MODE.get();
704    if prev == mode {
705        return false;
706    }
707    INPUT_MODE.set(mode);
708    crate::frame_clock::request_frame();
709    true
710}
711
712/// `true` when focus indication should paint (focused **and** keyboard mode).
713#[inline]
714pub fn is_focus_visible(focused: bool) -> bool {
715    focused && input_mode() == InputMode::Keyboard
716}
717
718#[cfg(test)]
719mod input_mode_tests {
720    use super::*;
721    use crate::frame_clock::take_frame_request;
722    use crate::modifier::{Interaction, MutableInteractionSource};
723
724    #[test]
725    fn request_input_mode_changes_and_requests_frame() {
726        set_input_mode_default(InputMode::Touch);
727        let _ = take_frame_request();
728
729        assert!(!request_input_mode(InputMode::Touch));
730        assert!(!take_frame_request());
731
732        assert!(request_input_mode(InputMode::Keyboard));
733        assert_eq!(input_mode(), InputMode::Keyboard);
734        assert!(take_frame_request());
735
736        assert!(request_input_mode(InputMode::Touch));
737        assert_eq!(input_mode(), InputMode::Touch);
738        set_input_mode_default(InputMode::Touch);
739    }
740
741    #[test]
742    fn focus_visible_requires_keyboard_mode() {
743        set_input_mode_default(InputMode::Touch);
744        let src = MutableInteractionSource::new();
745        src.emit(Interaction::Focus);
746        assert!(src.source().collect_is_focused());
747        assert!(!src.source().collect_is_focus_visible());
748
749        set_input_mode_default(InputMode::Keyboard);
750        assert!(src.source().collect_is_focus_visible());
751
752        set_input_mode_default(InputMode::Touch);
753        src.emit(Interaction::Unfocus);
754    }
755
756    #[test]
757    fn with_input_mode_overrides_global() {
758        set_input_mode_default(InputMode::Touch);
759        crate::locals::with_input_mode(InputMode::Keyboard, || {
760            assert_eq!(input_mode(), InputMode::Keyboard);
761        });
762        assert_eq!(input_mode(), InputMode::Touch);
763    }
764}