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#[derive(Clone, Debug)]
576pub enum GamepadEvent {
577    Connected {
578        id: GamepadId,
579        name: String,
580    },
581    Disconnected {
582        id: GamepadId,
583    },
584    Button {
585        id: GamepadId,
586        button: GamepadButton,
587        pressed: bool,
588    },
589    Axis {
590        id: GamepadId,
591        axis: GamepadAxis,
592        /// -1.0..=1.0 (sticks) or 0.0..=1.0 (triggers). Backends deadzone.
593        value: f32,
594    },
595}
596
597impl GamepadEvent {
598    pub fn id(&self) -> GamepadId {
599        match *self {
600            GamepadEvent::Connected { id, .. } => id,
601            GamepadEvent::Disconnected { id } => id,
602            GamepadEvent::Button { id, .. } => id,
603            GamepadEvent::Axis { id, .. } => id,
604        }
605    }
606}
607
608#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Default)]
609pub enum InputMode {
610    #[default]
611    Touch,
612    /// Keyboard, Tab, arrow/D-pad, or other non-pointer navigation.
613    Keyboard,
614}
615
616thread_local! {
617    static INPUT_MODE: Cell<InputMode> = const { Cell::new(InputMode::Touch) };
618}
619
620/// Current input mode (Compose `InputModeManager.inputMode`).
621///
622/// Composition-local override ([`crate::locals::with_input_mode`]) wins over
623/// the thread default.
624#[inline]
625pub fn input_mode() -> InputMode {
626    crate::locals::local_input_mode().unwrap_or_else(default_input_mode)
627}
628
629pub(crate) fn default_input_mode() -> InputMode {
630    INPUT_MODE.get()
631}
632
633/// Force the global default input mode and request a frame.
634#[inline]
635pub fn set_input_mode_default(mode: InputMode) {
636    INPUT_MODE.set(mode);
637    crate::request_frame();
638}
639
640/// Request a new input mode. Returns `true` if the mode changed.
641///
642/// On change, requests a frame so focus chrome can appear/disappear.
643pub fn request_input_mode(mode: InputMode) -> bool {
644    let prev = INPUT_MODE.get();
645    if prev == mode {
646        return false;
647    }
648    INPUT_MODE.set(mode);
649    crate::frame_clock::request_frame();
650    true
651}
652
653/// `true` when focus indication should paint (focused **and** keyboard mode).
654#[inline]
655pub fn is_focus_visible(focused: bool) -> bool {
656    focused && input_mode() == InputMode::Keyboard
657}
658
659#[cfg(test)]
660mod input_mode_tests {
661    use super::*;
662    use crate::frame_clock::take_frame_request;
663    use crate::modifier::{Interaction, MutableInteractionSource};
664
665    #[test]
666    fn request_input_mode_changes_and_requests_frame() {
667        set_input_mode_default(InputMode::Touch);
668        let _ = take_frame_request();
669
670        assert!(!request_input_mode(InputMode::Touch));
671        assert!(!take_frame_request());
672
673        assert!(request_input_mode(InputMode::Keyboard));
674        assert_eq!(input_mode(), InputMode::Keyboard);
675        assert!(take_frame_request());
676
677        assert!(request_input_mode(InputMode::Touch));
678        assert_eq!(input_mode(), InputMode::Touch);
679        set_input_mode_default(InputMode::Touch);
680    }
681
682    #[test]
683    fn focus_visible_requires_keyboard_mode() {
684        set_input_mode_default(InputMode::Touch);
685        let src = MutableInteractionSource::new();
686        src.emit(Interaction::Focus);
687        assert!(src.source().collect_is_focused());
688        assert!(!src.source().collect_is_focus_visible());
689
690        set_input_mode_default(InputMode::Keyboard);
691        assert!(src.source().collect_is_focus_visible());
692
693        set_input_mode_default(InputMode::Touch);
694        src.emit(Interaction::Unfocus);
695    }
696
697    #[test]
698    fn with_input_mode_overrides_global() {
699        set_input_mode_default(InputMode::Touch);
700        crate::locals::with_input_mode(InputMode::Keyboard, || {
701            assert_eq!(input_mode(), InputMode::Keyboard);
702        });
703        assert_eq!(input_mode(), InputMode::Touch);
704    }
705}