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