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)]
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#[derive(Clone, Debug, PartialEq, Eq, Hash)]
111pub enum Key {
112    Character(char),
113    Enter,
114    Tab,
115    Backspace,
116    Delete,
117    Insert,
118    Escape,
119    ArrowLeft,
120    ArrowRight,
121    ArrowUp,
122    ArrowDown,
123    Home,
124    End,
125    PageUp,
126    PageDown,
127    Space,
128    F(u8), // F1-F12
129    Unknown,
130}
131
132#[derive(Clone, Copy, Debug, PartialEq, Eq)]
133pub enum KeyEventType {
134    /// Key pressed down.
135    Down,
136    /// Key released.
137    Up,
138    /// Unknown or unsupported event type.
139    Unknown,
140}
141
142#[derive(Clone, Debug)]
143pub struct KeyEvent {
144    pub key: Key,
145    pub modifiers: Modifiers,
146    pub is_repeat: bool,
147    /// Whether this is a key-down or key-up event.
148    pub event_type: KeyEventType,
149    /// UTF-16 code point for character keys, or 0 for non-characters.
150    /// Matches Compose's `utf16CodePoint`.
151    pub utf16_code_point: u16,
152}
153
154#[derive(Clone, Debug)]
155pub struct TextInputEvent {
156    pub text: String,
157}
158
159#[derive(Clone, Debug)]
160pub enum ImeEvent {
161    /// IME composition started
162    Start,
163    /// Composition text updated
164    Update {
165        text: String,
166        cursor: Option<(usize, usize)>, // (start, end) of composition range
167    },
168    /// Composition committed (finalized)
169    Commit(String),
170    /// Composition cancelled
171    Cancel,
172}
173
174#[derive(Clone, Debug)]
175pub enum InputEvent {
176    Pointer(PointerEvent),
177    Key(KeyEvent),
178    Text(TextInputEvent),
179    Ime(ImeEvent),
180    Gamepad(GamepadEvent),
181}
182
183/// Opaque gamepad handle. Backend-local index, stable for the connection
184/// lifetime. Survives across frames; invalid after [`GamepadEvent::Disconnected`].
185#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash)]
186pub struct GamepadId(pub u32);
187
188/// Standard-layout buttons (SDL gamecontroller mapping positions).
189/// Backends translate hardware codes to these; unknown buttons are dropped.
190#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
191pub enum GamepadButton {
192    /// Bottom face button (A / Cross). UI default: activate.
193    South,
194    /// Right face button (B / Circle). UI default: back.
195    East,
196    /// Left face button (X / Square).
197    West,
198    /// Top face button (Y / Triangle).
199    North,
200    Start,
201    Select,
202    LeftShoulder,
203    RightShoulder,
204    LeftStick,
205    RightStick,
206    DPadUp,
207    DPadDown,
208    DPadLeft,
209    DPadRight,
210}
211
212/// Analog axes, normalized to -1.0..=1.0. Triggers report 0.0..=1.0.
213#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
214pub enum GamepadAxis {
215    LeftStickX,
216    LeftStickY,
217    RightStickX,
218    RightStickY,
219    LeftTrigger,
220    RightTrigger,
221}
222
223#[derive(Clone, Debug)]
224pub enum GamepadEvent {
225    Connected {
226        id: GamepadId,
227        name: String,
228    },
229    Disconnected {
230        id: GamepadId,
231    },
232    Button {
233        id: GamepadId,
234        button: GamepadButton,
235        pressed: bool,
236    },
237    Axis {
238        id: GamepadId,
239        axis: GamepadAxis,
240        /// -1.0..=1.0 (sticks) or 0.0..=1.0 (triggers). Backends deadzone.
241        value: f32,
242    },
243}
244
245impl GamepadEvent {
246    pub fn id(&self) -> GamepadId {
247        match *self {
248            GamepadEvent::Connected { id, .. } => id,
249            GamepadEvent::Disconnected { id } => id,
250            GamepadEvent::Button { id, .. } => id,
251            GamepadEvent::Axis { id, .. } => id,
252        }
253    }
254}
255
256#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Default)]
257pub enum InputMode {
258    #[default]
259    Touch,
260    /// Keyboard, Tab, arrow/D-pad, or other non-pointer navigation.
261    Keyboard,
262}
263
264thread_local! {
265    static INPUT_MODE: Cell<InputMode> = const { Cell::new(InputMode::Touch) };
266}
267
268/// Current input mode (Compose `InputModeManager.inputMode`).
269///
270/// Composition-local override ([`crate::locals::with_input_mode`]) wins over
271/// the thread default.
272#[inline]
273pub fn input_mode() -> InputMode {
274    crate::locals::local_input_mode().unwrap_or_else(|| INPUT_MODE.get())
275}
276
277/// Force the global default input mode (no frame request). Prefer
278/// [`request_input_mode`] from event handlers.
279#[inline]
280pub fn set_input_mode_default(mode: InputMode) {
281    INPUT_MODE.set(mode);
282}
283
284/// Request a new input mode. Returns `true` if the mode changed.
285///
286/// On change, requests a frame so focus chrome can appear/disappear.
287pub fn request_input_mode(mode: InputMode) -> bool {
288    let prev = INPUT_MODE.get();
289    if prev == mode {
290        return false;
291    }
292    INPUT_MODE.set(mode);
293    crate::frame_clock::request_frame();
294    true
295}
296
297/// `true` when focus indication should paint (focused **and** keyboard mode).
298#[inline]
299pub fn is_focus_visible(focused: bool) -> bool {
300    focused && input_mode() == InputMode::Keyboard
301}
302
303#[cfg(test)]
304mod input_mode_tests {
305    use super::*;
306    use crate::frame_clock::take_frame_request;
307    use crate::modifier::{Interaction, MutableInteractionSource};
308
309    #[test]
310    fn request_input_mode_changes_and_requests_frame() {
311        set_input_mode_default(InputMode::Touch);
312        let _ = take_frame_request();
313
314        assert!(!request_input_mode(InputMode::Touch));
315        assert!(!take_frame_request());
316
317        assert!(request_input_mode(InputMode::Keyboard));
318        assert_eq!(input_mode(), InputMode::Keyboard);
319        assert!(take_frame_request());
320
321        assert!(request_input_mode(InputMode::Touch));
322        assert_eq!(input_mode(), InputMode::Touch);
323        set_input_mode_default(InputMode::Touch);
324    }
325
326    #[test]
327    fn focus_visible_requires_keyboard_mode() {
328        set_input_mode_default(InputMode::Touch);
329        let src = MutableInteractionSource::new();
330        src.emit(Interaction::Focus);
331        assert!(src.source().collect_is_focused());
332        assert!(!src.source().collect_is_focus_visible());
333
334        set_input_mode_default(InputMode::Keyboard);
335        assert!(src.source().collect_is_focus_visible());
336
337        set_input_mode_default(InputMode::Touch);
338        src.emit(Interaction::Unfocus);
339    }
340
341    #[test]
342    fn with_input_mode_overrides_global() {
343        set_input_mode_default(InputMode::Touch);
344        crate::locals::with_input_mode(InputMode::Keyboard, || {
345            assert_eq!(input_mode(), InputMode::Keyboard);
346        });
347        assert_eq!(input_mode(), InputMode::Touch);
348    }
349}