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