Skip to main content

pane/
input.rs

1use gilrs::{Axis, Button, Event, Gilrs};
2use winit::event::{ElementState, MouseButton, WindowEvent};
3use winit::keyboard::{Key, NamedKey};
4
5// ── Direction index constants ──────────────────────────────────────────────────
6const LEFT: usize = 0;
7const RIGHT: usize = 1;
8const UP: usize = 2;
9const DOWN: usize = 3;
10
11// ── ArrowRepeat ───────────────────────────────────────────────────────────────
12
13/// Held-state and auto-repeat timer for a single arrow direction.
14///
15/// Kept private to `Input`; the public pulse bools (`arrow_left` etc.) are the
16/// outward-facing API.
17#[derive(Clone, Copy)]
18struct ArrowRepeat {
19    /// Whether the key/button is currently held down.
20    held: bool,
21    /// Seconds the key has been held; drives the initial-delay / repeat-rate logic.
22    timer: f32,
23}
24
25impl ArrowRepeat {
26    const fn new() -> Self {
27        Self {
28            held: false,
29            timer: 0.0,
30        }
31    }
32}
33
34// ── Input ─────────────────────────────────────────────────────────────────────
35
36/// Raw input state in grid units (1080-unit tall coordinate space).
37///
38/// Updated every frame from winit events via [`Input::handle_event`] and from
39/// gilrs events via [`Input::poll_gamepad`] or [`Input::handle_gamepad_event`].
40/// Single-frame pulse fields (`left_just_pressed`, `arrow_left`, …) are cleared
41/// by [`Input::begin_frame`] at the end of each frame.
42pub struct Input {
43    /// Optional owned gilrs instance.  Present in standalone mode; `None` in
44    /// overlay mode where the caller drives [`Input::handle_gamepad_event`] directly.
45    pub gilrs: Option<Gilrs>,
46
47    /// Cursor X position in grid units (origin at screen centre, right = +).
48    pub mouse_x: f32,
49    /// Cursor Y position in grid units (origin at screen centre, down = +).
50    pub mouse_y: f32,
51
52    /// `true` while the left mouse button is held.
53    pub left_pressed: bool,
54    /// `true` on the first frame the left mouse button goes down.
55    pub left_just_pressed: bool,
56    /// `true` on the first frame the left mouse button is released.
57    pub left_just_released: bool,
58
59    /// Lines scrolled this frame; positive = scroll down.
60    pub scroll_delta: f32,
61
62    /// Printable characters typed this frame (from `event.text`, handles IME / dead keys).
63    pub text_input: Vec<char>,
64
65    /// `true` if backspace was pressed this frame.
66    pub backspace: bool,
67    /// `true` if Enter/Return was pressed this frame.
68    pub enter: bool,
69    /// `true` if Escape was pressed this frame.
70    pub escape: bool,
71    /// `true` if Tab was pressed this frame.
72    pub tab: bool,
73
74    /// Arrow-left pulse this frame (fires on key-down and during auto-repeat).
75    pub arrow_left: bool,
76    /// Arrow-right pulse this frame (fires on key-down and during auto-repeat).
77    pub arrow_right: bool,
78    /// Arrow-up pulse this frame (fires on key-down and during auto-repeat).
79    pub arrow_up: bool,
80    /// Arrow-down pulse this frame (fires on key-down and during auto-repeat).
81    pub arrow_down: bool,
82
83    /// `true` while Shift is held.
84    pub shift: bool,
85    /// `true` while Ctrl is held.
86    pub ctrl: bool,
87
88    /// `true` if the 'A' key was pressed this frame (regardless of modifiers).
89    pub key_a: bool,
90    /// `true` if the 'C' key was pressed this frame.
91    pub key_c: bool,
92    /// `true` if the 'X' key was pressed this frame.
93    pub key_x: bool,
94    /// `true` if the 'V' key was pressed this frame.
95    pub key_v: bool,
96
97    /// `true` if Space or the gamepad South button was pressed this frame.
98    pub space: bool,
99    /// `true` if the gamepad Mode/Guide button was pressed this frame.
100    pub home: bool,
101
102    /// Timestamp of the last hardware keyboard event; used by [`Input::keyboard_present`].
103    pub last_hw_key: Option<std::time::Instant>,
104
105    /// Held-state and repeat timers for the four arrow directions.
106    /// Indexed by the `LEFT / RIGHT / UP / DOWN` constants.
107    arrows: [ArrowRepeat; 4],
108}
109
110impl Default for Input {
111    fn default() -> Self {
112        Self::new()
113    }
114}
115
116impl Input {
117    /// Create a zeroed `Input` with no gilrs instance.
118    #[must_use]
119    pub const fn new() -> Self {
120        Self {
121            gilrs: None,
122            mouse_x: 0.0,
123            mouse_y: 0.0,
124            left_pressed: false,
125            left_just_pressed: false,
126            left_just_released: false,
127            scroll_delta: 0.0,
128            text_input: Vec::new(),
129            backspace: false,
130            enter: false,
131            escape: false,
132            tab: false,
133            arrow_left: false,
134            arrow_right: false,
135            arrow_up: false,
136            arrow_down: false,
137            shift: false,
138            ctrl: false,
139            key_a: false,
140            key_c: false,
141            key_x: false,
142            key_v: false,
143            space: false,
144            home: false,
145            last_hw_key: None,
146            arrows: [ArrowRepeat::new(); 4],
147        }
148    }
149
150    /// Like [`Input::new`], but attempts to initialise gilrs for automatic gamepad polling.
151    #[must_use]
152    pub fn new_with_gilrs() -> Self {
153        let mut s = Self::new();
154        s.gilrs = Gilrs::new().ok();
155        s
156    }
157
158    /// Poll the owned gilrs instance (if any) and map events onto input flags.
159    ///
160    /// Uses `take` / put-back to avoid borrowing `self.gilrs` while dispatching
161    /// to `handle_gamepad_event`, eliminating the per-frame `Vec` allocation the
162    /// naive approach would require.
163    pub fn poll_gamepad(&mut self) {
164        let Some(mut g) = self.gilrs.take() else {
165            return;
166        };
167        while let Some(event) = g.next_event() {
168            self.handle_gamepad_event(event);
169        }
170        self.gilrs = Some(g);
171    }
172
173    /// Feed a single gilrs event in manually.
174    ///
175    /// Used in overlay mode where the caller owns the `Gilrs` instance and
176    /// drains it themselves, or in tests.
177    pub fn handle_gamepad_event(&mut self, event: Event) {
178        use gilrs::EventType::{AxisChanged, ButtonPressed, ButtonReleased};
179        match event.event {
180            ButtonPressed(Button::DPadLeft, _) => {
181                if !self.arrows[LEFT].held {
182                    self.arrow_left = true;
183                }
184                self.arrows[LEFT].held = true;
185            }
186            ButtonPressed(Button::DPadRight, _) => {
187                if !self.arrows[RIGHT].held {
188                    self.arrow_right = true;
189                }
190                self.arrows[RIGHT].held = true;
191            }
192            ButtonPressed(Button::DPadUp, _) => {
193                if !self.arrows[UP].held {
194                    self.arrow_up = true;
195                }
196                self.arrows[UP].held = true;
197            }
198            ButtonPressed(Button::DPadDown, _) => {
199                if !self.arrows[DOWN].held {
200                    self.arrow_down = true;
201                }
202                self.arrows[DOWN].held = true;
203            }
204
205            ButtonReleased(Button::DPadLeft, _) => {
206                self.arrows[LEFT].held = false;
207                self.arrows[LEFT].timer = 0.0;
208            }
209            ButtonReleased(Button::DPadRight, _) => {
210                self.arrows[RIGHT].held = false;
211                self.arrows[RIGHT].timer = 0.0;
212            }
213            ButtonReleased(Button::DPadUp, _) => {
214                self.arrows[UP].held = false;
215                self.arrows[UP].timer = 0.0;
216            }
217            ButtonReleased(Button::DPadDown, _) => {
218                self.arrows[DOWN].held = false;
219                self.arrows[DOWN].timer = 0.0;
220            }
221
222            ButtonPressed(Button::South, _) => {
223                self.space = true;
224            }
225            ButtonPressed(Button::East, _) => {
226                self.escape = true;
227            }
228            ButtonPressed(Button::Start, _) => {
229                self.enter = true;
230            }
231            ButtonPressed(Button::Mode, _) => {
232                self.home = true;
233            }
234            ButtonPressed(Button::LeftTrigger, _) => {
235                self.tab = true;
236                self.shift = true;
237            }
238            ButtonPressed(Button::RightTrigger, _) => {
239                self.tab = true;
240            }
241            ButtonReleased(Button::LeftTrigger, _) => {
242                self.shift = false;
243            }
244
245            AxisChanged(Axis::LeftStickX | Axis::DPadX, v, _) => self.axis_horizontal(v),
246            AxisChanged(Axis::LeftStickY, v, _) => self.axis_vertical_stick(v),
247            AxisChanged(Axis::DPadY, v, _) => self.axis_dpad_y(v),
248            _ => {}
249        }
250    }
251
252    /// Clear all single-frame pulse flags and advance arrow auto-repeat timers.
253    ///
254    /// Call exactly once per frame, **after** all widgets have read input but
255    /// **before** the next frame's events are collected.  Note that `shift` and
256    /// `ctrl` are *not* cleared here — they are held-modifier flags updated on
257    /// key press/release, not single-frame pulses.
258    pub fn begin_frame(&mut self, dt: f32) {
259        const INITIAL_DELAY: f32 = 0.4;
260        const REPEAT_RATE: f32 = 0.08;
261
262        self.left_just_pressed = false;
263        self.left_just_released = false;
264        self.scroll_delta = 0.0;
265        self.text_input.clear();
266        self.backspace = false;
267        self.enter = false;
268        self.escape = false;
269        self.tab = false;
270        self.arrow_left = false;
271        self.arrow_right = false;
272        self.arrow_up = false;
273        self.arrow_down = false;
274        self.space = false;
275        self.home = false;
276        self.key_a = false;
277        self.key_c = false;
278        self.key_x = false;
279        self.key_v = false;
280
281        // Auto-repeat: after INITIAL_DELAY seconds held, pulse at REPEAT_RATE Hz.
282        Self::tick_repeat(
283            self.arrows[LEFT].held,
284            &mut self.arrows[LEFT].timer,
285            &mut self.arrow_left,
286            dt,
287            INITIAL_DELAY,
288            REPEAT_RATE,
289        );
290        Self::tick_repeat(
291            self.arrows[RIGHT].held,
292            &mut self.arrows[RIGHT].timer,
293            &mut self.arrow_right,
294            dt,
295            INITIAL_DELAY,
296            REPEAT_RATE,
297        );
298        Self::tick_repeat(
299            self.arrows[UP].held,
300            &mut self.arrows[UP].timer,
301            &mut self.arrow_up,
302            dt,
303            INITIAL_DELAY,
304            REPEAT_RATE,
305        );
306        Self::tick_repeat(
307            self.arrows[DOWN].held,
308            &mut self.arrows[DOWN].timer,
309            &mut self.arrow_down,
310            dt,
311            INITIAL_DELAY,
312            REPEAT_RATE,
313        );
314    }
315
316    /// Returns `true` if a hardware keyboard key was pressed within the last 3 seconds.
317    ///
318    /// Used to decide whether to show the on-screen keyboard: if a physical
319    /// keyboard is present and recently active, suppress the OSK.
320    #[must_use]
321    pub fn keyboard_present(&self) -> bool {
322        self.last_hw_key
323            .is_some_and(|t| t.elapsed() < std::time::Duration::from_secs(3))
324    }
325
326    /// Feed a raw winit `WindowEvent` into the input state.
327    ///
328    /// `pw` and `ph` are the current window pixel dimensions; they are needed to
329    /// convert cursor pixel coordinates into the 1080-unit grid space.
330    pub fn handle_event(&mut self, event: &WindowEvent, pw: f32, ph: f32) {
331        match event {
332            WindowEvent::CursorMoved { position, .. } => {
333                let unit = ph / 1080.0;
334                let ox = ((pw / ph) * 1080.0).mul_add(-unit, pw) * 0.5;
335                self.mouse_x = ((pw / ph) * 1080.0).mul_add(-0.5, (position.x as f32 - ox) / unit);
336                self.mouse_y = position.y as f32 / unit - 540.0;
337            }
338
339            WindowEvent::MouseWheel { delta, .. } => {
340                self.scroll_delta += match delta {
341                    winit::event::MouseScrollDelta::LineDelta(_, y) => *y,
342                    winit::event::MouseScrollDelta::PixelDelta(pos) => pos.y as f32 / 20.0,
343                };
344            }
345
346            WindowEvent::MouseInput {
347                button: MouseButton::Left,
348                state,
349                ..
350            } => match state {
351                ElementState::Pressed => {
352                    self.left_pressed = true;
353                    self.left_just_pressed = true;
354                }
355                ElementState::Released => {
356                    self.left_pressed = false;
357                    self.left_just_released = true;
358                }
359            },
360
361            WindowEvent::KeyboardInput { event, .. } if event.state == ElementState::Pressed => {
362                self.handle_key_press(event);
363            }
364
365            WindowEvent::KeyboardInput { event, .. } if event.state == ElementState::Released => {
366                match &event.logical_key {
367                    Key::Named(NamedKey::Shift) => {
368                        self.shift = false;
369                    }
370                    Key::Named(NamedKey::Control) => {
371                        self.ctrl = false;
372                    }
373                    Key::Named(NamedKey::ArrowLeft) => {
374                        self.arrows[LEFT].held = false;
375                        self.arrows[LEFT].timer = 0.0;
376                    }
377                    Key::Named(NamedKey::ArrowRight) => {
378                        self.arrows[RIGHT].held = false;
379                        self.arrows[RIGHT].timer = 0.0;
380                    }
381                    Key::Named(NamedKey::ArrowUp) => {
382                        self.arrows[UP].held = false;
383                        self.arrows[UP].timer = 0.0;
384                    }
385                    Key::Named(NamedKey::ArrowDown) => {
386                        self.arrows[DOWN].held = false;
387                        self.arrows[DOWN].timer = 0.0;
388                    }
389                    _ => {}
390                }
391            }
392
393            _ => {}
394        }
395    }
396
397    // ── Private helpers ────────────────────────────────────────────────────────
398
399    /// Process a winit key-press event: update modifier flags, arrow held-state,
400    /// named-key pulses, printable-character list, and `last_hw_key` timestamp.
401    ///
402    /// `last_hw_key` is only updated for keys that a physical keyboard uniquely
403    /// provides — printable characters and Backspace. Arrow keys, Space, Enter,
404    /// Escape, Tab, Shift, and Control are all producible by a gamepad or the OSK
405    /// itself, so they must not suppress the OSK.
406    fn handle_key_press(&mut self, event: &winit::event::KeyEvent) {
407        match &event.logical_key {
408            Key::Named(NamedKey::Backspace) => {
409                self.last_hw_key = Some(std::time::Instant::now());
410                self.backspace = true;
411            }
412            Key::Named(NamedKey::Enter) => {
413                self.enter = true;
414            }
415            Key::Named(NamedKey::Escape) => {
416                self.escape = true;
417            }
418            Key::Named(NamedKey::Tab) => {
419                self.tab = true;
420            }
421            Key::Named(NamedKey::Shift) => {
422                self.shift = true;
423            }
424            Key::Named(NamedKey::Control) => {
425                self.ctrl = true;
426            }
427            Key::Named(NamedKey::Space) => {
428                self.space = true;
429            }
430            Key::Named(NamedKey::ArrowLeft) => {
431                if !self.arrows[LEFT].held {
432                    self.arrow_left = true;
433                }
434                self.arrows[LEFT].held = true;
435            }
436            Key::Named(NamedKey::ArrowRight) => {
437                if !self.arrows[RIGHT].held {
438                    self.arrow_right = true;
439                }
440                self.arrows[RIGHT].held = true;
441            }
442            Key::Named(NamedKey::ArrowUp) => {
443                if !self.arrows[UP].held {
444                    self.arrow_up = true;
445                }
446                self.arrows[UP].held = true;
447            }
448            Key::Named(NamedKey::ArrowDown) => {
449                if !self.arrows[DOWN].held {
450                    self.arrow_down = true;
451                }
452                self.arrows[DOWN].held = true;
453            }
454            Key::Character(s) => {
455                self.last_hw_key = Some(std::time::Instant::now());
456                match s.to_lowercase().as_str() {
457                    "a" => self.key_a = true,
458                    "c" => self.key_c = true,
459                    "x" => self.key_x = true,
460                    "v" => self.key_v = true,
461                    _ => {}
462                }
463            }
464            _ => {}
465        }
466        if let Some(text) = &event.text {
467            for ch in text.chars() {
468                if !ch.is_control() {
469                    self.text_input.push(ch);
470                }
471            }
472        }
473    }
474
475    /// Set held-state for one arrow direction and emit a pulse on the first frame.
476    /// Shared helper for a 1-D axis with a deadzone.
477    ///
478    /// Maps the signed axis value `v` onto two arrow directions (`neg_idx` fires
479    /// when `v < -DEADZONE`, `pos_idx` fires when `v > +DEADZONE`).  Returns
480    /// `(neg_pulse, pos_pulse)` so callers can set the corresponding public bool.
481    fn axis_2d(&mut self, v: f32, neg_idx: usize, pos_idx: usize) -> (bool, bool) {
482        const DEADZONE: f32 = 0.3;
483        if v < -DEADZONE {
484            let pulse = !self.arrows[neg_idx].held;
485            self.arrows[neg_idx].held = true;
486            self.arrows[pos_idx].held = false;
487            self.arrows[pos_idx].timer = 0.0;
488            (pulse, false)
489        } else if v > DEADZONE {
490            let pulse = !self.arrows[pos_idx].held;
491            self.arrows[pos_idx].held = true;
492            self.arrows[neg_idx].held = false;
493            self.arrows[neg_idx].timer = 0.0;
494            (false, pulse)
495        } else {
496            self.arrows[neg_idx].held = false;
497            self.arrows[neg_idx].timer = 0.0;
498            self.arrows[pos_idx].held = false;
499            self.arrows[pos_idx].timer = 0.0;
500            (false, false)
501        }
502    }
503
504    /// Map `LeftStickX` / `DPadX` to left/right arrows.
505    fn axis_horizontal(&mut self, v: f32) {
506        let (l, r) = self.axis_2d(v, LEFT, RIGHT);
507        if l {
508            self.arrow_left = true;
509        }
510        if r {
511            self.arrow_right = true;
512        }
513    }
514
515    /// Map `LeftStickY` to up/down arrows.  Positive Y = up on this axis.
516    fn axis_vertical_stick(&mut self, v: f32) {
517        // LeftStickY: positive = up, negative = down — so pos_idx = UP, neg_idx = DOWN.
518        let (d, u) = self.axis_2d(v, DOWN, UP);
519        if u {
520            self.arrow_up = true;
521        }
522        if d {
523            self.arrow_down = true;
524        }
525    }
526
527    /// Map `DPadY` to up/down arrows.  Polarity is reversed vs. the stick: negative = up.
528    fn axis_dpad_y(&mut self, v: f32) {
529        // DPadY: negative = up, positive = down — opposite polarity to LeftStickY.
530        let (u, d) = self.axis_2d(v, UP, DOWN);
531        if u {
532            self.arrow_up = true;
533        }
534        if d {
535            self.arrow_down = true;
536        }
537    }
538
539    /// Advance one auto-repeat timer and set `pulse` if a repeat tick fires this frame.
540    ///
541    /// After `initial` seconds held, a pulse fires every `rate` seconds.  The
542    /// edge-detection uses floor division so the rate is constant regardless of
543    /// variable frame times.
544    fn tick_repeat(
545        held: bool,
546        timer: &mut f32,
547        pulse: &mut bool,
548        dt: f32,
549        initial: f32,
550        rate: f32,
551    ) {
552        if !held {
553            *timer = 0.0;
554            return;
555        }
556        *timer += dt;
557        if *timer >= initial {
558            let repeat_t = *timer - initial;
559            let prev = (repeat_t - dt).max(0.0);
560            if (repeat_t / rate).floor() > (prev / rate).floor() {
561                *pulse = true;
562            }
563        }
564    }
565}