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}
181
182///
183
184///
185
186#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Default)]
187pub enum InputMode {
188    #[default]
189    Touch,
190    /// Keyboard, Tab, arrow/D-pad, or other non-pointer navigation.
191    Keyboard,
192}
193
194thread_local! {
195    static INPUT_MODE: Cell<InputMode> = const { Cell::new(InputMode::Touch) };
196}
197
198/// Current input mode (Compose `InputModeManager.inputMode`).
199///
200/// Composition-local override ([`crate::locals::with_input_mode`]) wins over
201/// the thread default.
202#[inline]
203pub fn input_mode() -> InputMode {
204    crate::locals::local_input_mode().unwrap_or_else(|| INPUT_MODE.get())
205}
206
207/// Force the global default input mode (no frame request). Prefer
208/// [`request_input_mode`] from event handlers.
209#[inline]
210pub fn set_input_mode_default(mode: InputMode) {
211    INPUT_MODE.set(mode);
212}
213
214/// Request a new input mode. Returns `true` if the mode changed.
215///
216/// On change, requests a frame so focus chrome can appear/disappear.
217pub fn request_input_mode(mode: InputMode) -> bool {
218    let prev = INPUT_MODE.get();
219    if prev == mode {
220        return false;
221    }
222    INPUT_MODE.set(mode);
223    crate::frame_clock::request_frame();
224    true
225}
226
227/// `true` when focus indication should paint (focused **and** keyboard mode).
228#[inline]
229pub fn is_focus_visible(focused: bool) -> bool {
230    focused && input_mode() == InputMode::Keyboard
231}
232
233#[cfg(test)]
234mod input_mode_tests {
235    use super::*;
236    use crate::frame_clock::take_frame_request;
237    use crate::modifier::{Interaction, MutableInteractionSource};
238
239    #[test]
240    fn request_input_mode_changes_and_requests_frame() {
241        set_input_mode_default(InputMode::Touch);
242        let _ = take_frame_request();
243
244        assert!(!request_input_mode(InputMode::Touch));
245        assert!(!take_frame_request());
246
247        assert!(request_input_mode(InputMode::Keyboard));
248        assert_eq!(input_mode(), InputMode::Keyboard);
249        assert!(take_frame_request());
250
251        assert!(request_input_mode(InputMode::Touch));
252        assert_eq!(input_mode(), InputMode::Touch);
253        set_input_mode_default(InputMode::Touch);
254    }
255
256    #[test]
257    fn focus_visible_requires_keyboard_mode() {
258        set_input_mode_default(InputMode::Touch);
259        let src = MutableInteractionSource::new();
260        src.emit(Interaction::Focus);
261        assert!(src.source().collect_is_focused());
262        assert!(!src.source().collect_is_focus_visible());
263
264        set_input_mode_default(InputMode::Keyboard);
265        assert!(src.source().collect_is_focus_visible());
266
267        set_input_mode_default(InputMode::Touch);
268        src.emit(Interaction::Unfocus);
269    }
270
271    #[test]
272    fn with_input_mode_overrides_global() {
273        set_input_mode_default(InputMode::Touch);
274        crate::locals::with_input_mode(InputMode::Keyboard, || {
275            assert_eq!(input_mode(), InputMode::Keyboard);
276        });
277        assert_eq!(input_mode(), InputMode::Touch);
278    }
279}