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