Skip to main content

repame_shell/
staging.rs

1use std::cell::RefCell;
2use std::collections::{HashMap, HashSet};
3use std::rc::Rc;
4
5use glam::Vec2;
6use repame_input::{AimTracker, GamepadState, MouseState, StagedClick, TouchContact};
7use repose_core::input::{Key, KeyEvent, KeyEventType, PhysicalKey, PointerButton};
8use repose_core::runtime::Scheduler;
9
10#[derive(Default)]
11pub struct Staging {
12    pub held: HashSet<PhysicalKey>,
13    pub edges: Vec<PhysicalKey>,
14    pub window_focused: bool,
15    pub capture_armed: bool,
16    pub clicks: Vec<StagedClick>,
17    pub aim: AimTracker,
18    pub mouse_edges: Vec<(PointerButton, bool)>,
19    pub lmb_held: bool,
20    pub rmb_held: bool,
21    pub rmb_down_edge: bool,
22    pub capture_pending_physical: Option<PhysicalKey>,
23    pub capture_pending_key: Option<Key>,
24    pub capture_pending_mouse: Option<bool>,
25    pub touch_active: HashMap<u64, (Vec2, Vec2)>,
26    pub touch_new: HashSet<u64>,
27    pub pads: Vec<GamepadState>,
28    pub pad_live: bool,
29    pub view_width: f32,
30}
31
32impl Staging {
33    pub fn shared() -> Rc<RefCell<Self>> {
34        Rc::new(RefCell::new(Self {
35            window_focused: true,
36            view_width: 1280.0,
37            ..Default::default()
38        }))
39    }
40
41    pub fn handle_key(&mut self, ke: &KeyEvent) {
42        let down = matches!(ke.event_type, KeyEventType::Down);
43        if down && !ke.is_repeat {
44            match &ke.key {
45                Key::Backspace => {
46                    self.capture_pending_key = Some(Key::Backspace);
47                    return;
48                }
49                _ => {}
50            }
51            if self.capture_armed {
52                if let Some(key) = ke.physical {
53                    self.capture_pending_physical = Some(key);
54                } else {
55                    self.capture_pending_key = Some(ke.key.clone());
56                }
57                return;
58            }
59            if matches!(ke.key, Key::Escape | Key::Enter) {
60                return;
61            }
62        }
63        match &ke.key {
64            Key::Escape | Key::Enter => {}
65            Key::Character(_) => {
66                if let Some(key) = ke.physical {
67                    self.stage_physical(key, down, ke.is_repeat);
68                }
69            }
70            Key::ShiftLeft => self.stage_code(PhysicalKey::ShiftLeft, down, ke.is_repeat),
71            Key::ShiftRight => self.stage_code(PhysicalKey::ShiftRight, down, ke.is_repeat),
72            Key::Space => self.stage_code(PhysicalKey::Space, down, ke.is_repeat),
73            Key::Tab => self.stage_code(PhysicalKey::Tab, down, ke.is_repeat),
74            Key::ArrowUp => self.stage_code(PhysicalKey::ArrowUp, down, ke.is_repeat),
75            Key::ArrowDown => self.stage_code(PhysicalKey::ArrowDown, down, ke.is_repeat),
76            Key::ArrowLeft => self.stage_code(PhysicalKey::ArrowLeft, down, ke.is_repeat),
77            Key::ArrowRight => self.stage_code(PhysicalKey::ArrowRight, down, ke.is_repeat),
78            _ => {}
79        }
80    }
81
82    pub fn stage_physical(&mut self, key: PhysicalKey, down: bool, is_repeat: bool) {
83        if down {
84            if self.held.insert(key) && !is_repeat {
85                self.edges.push(key);
86            }
87        } else {
88            self.held.remove(&key);
89        }
90    }
91
92    fn stage_code(&mut self, code: PhysicalKey, down: bool, is_repeat: bool) {
93        self.stage_physical(code, down, is_repeat);
94    }
95
96    pub fn stage_physical_key(&mut self, key: PhysicalKey, down: bool) {
97        self.stage_physical(key, down, false);
98    }
99
100    pub fn set_window_focused(&mut self, focused: bool) {
101        self.window_focused = focused;
102        if !focused {
103            self.held.clear();
104            self.edges.clear();
105            self.capture_pending_physical = None;
106            self.capture_pending_key = None;
107            self.capture_pending_mouse = None;
108            self.lmb_held = false;
109            self.rmb_held = false;
110            self.aim = AimTracker::default();
111            self.clicks.clear();
112            self.mouse_edges.clear();
113            self.rmb_down_edge = false;
114            self.touch_active.clear();
115            self.touch_new.clear();
116            self.pads.clear();
117            self.pad_live = false;
118        }
119    }
120
121    pub fn feed_polled(&mut self, sched: &Scheduler) {
122        if !sched.window_focused {
123            self.set_window_focused(false);
124            return;
125        }
126        self.window_focused = true;
127        // Identity-key repair: the polled set IS the reverse map here, so
128        // missed releases drop without synthesizing press edges.
129        self.held.retain(|key| sched.held_keys.contains(key));
130        if !sched.mouse_primary {
131            self.lmb_held = false;
132        }
133        if !sched.mouse_secondary {
134            self.rmb_held = false;
135        }
136        if !sched.mouse_middle {
137            self.mouse_edges
138                .retain(|(button, _)| *button != PointerButton::Tertiary);
139        }
140        // The platform publishes the live contact
141        // table (`sched.touch_points`, physical px) every event, and
142        // `feed_polled` runs once per frame before the sampler.
143        if sched.touch_points.is_empty() {
144            if !self.touch_active.is_empty() {
145                self.touch_active.clear();
146                self.touch_new.clear();
147            }
148        } else {
149            let d = repose_core::locals::effective_density_scale().max(1e-6);
150            let live: std::collections::HashSet<u64> =
151                sched.touch_points.iter().map(|(id, _, _)| *id).collect();
152            self.touch_active
153                .retain(|id, _| live.contains(id) || self.touch_new.contains(id));
154            for (id, x, y) in sched.touch_points.iter() {
155                let p = Vec2::new(*x, *y) / d;
156                if let Some(contact) = self.touch_active.get_mut(id) {
157                    contact.1 = p;
158                } else {
159                    self.touch_active.insert(*id, (p, p));
160                    self.touch_new.insert(*id);
161                }
162            }
163        }
164    }
165
166    pub fn pick_down(&mut self, button: PointerButton) {
167        if self.capture_armed {
168            self.capture_pending_mouse = Some(button == PointerButton::Primary);
169            return;
170        }
171        self.mouse_edges.push((button, true));
172        if button == PointerButton::Primary {
173            self.lmb_held = true;
174        } else {
175            self.rmb_down_edge = true;
176            self.rmb_held = true;
177        }
178    }
179
180    pub fn pick_up(&mut self, button: PointerButton) {
181        self.mouse_edges.push((button, false));
182        if button == PointerButton::Primary {
183            self.lmb_held = false;
184        } else {
185            self.rmb_held = false;
186        }
187    }
188
189    pub fn take_capture_physical(&mut self) -> Option<PhysicalKey> {
190        self.capture_pending_physical.take()
191    }
192
193    pub fn take_capture_key(&mut self) -> Option<Key> {
194        self.capture_pending_key.take()
195    }
196
197    pub fn take_capture_mouse(&mut self) -> Option<bool> {
198        self.capture_pending_mouse.take()
199    }
200
201    pub fn take_rmb_down(&mut self) -> bool {
202        std::mem::replace(&mut self.rmb_down_edge, false)
203    }
204
205    pub fn stage_click(&mut self, world: Vec2, screen: [f32; 2], density: f32) {
206        let d = density.max(1e-6);
207        self.clicks.push(StagedClick {
208            world,
209            dp: [screen[0] / d, screen[1] / d],
210        });
211    }
212
213    pub fn take_clicks(&mut self) -> Vec<StagedClick> {
214        std::mem::take(&mut self.clicks)
215    }
216
217    pub fn take_edges(&mut self) -> Vec<PhysicalKey> {
218        std::mem::take(&mut self.edges)
219    }
220
221    pub fn take_mouse_edges(&mut self) -> Vec<(PointerButton, bool)> {
222        std::mem::take(&mut self.mouse_edges)
223    }
224
225    pub fn take_pads(&mut self) -> Vec<GamepadState> {
226        self.pad_live = !self.pads.is_empty();
227        std::mem::take(&mut self.pads)
228    }
229
230    pub fn stage_gamepad(&mut self, pad: GamepadState) {
231        self.pads.push(pad);
232        self.pad_live = true;
233    }
234
235    pub fn touch_down(&mut self, id: u64, screen_dp: Vec2) {
236        self.touch_active.insert(id, (screen_dp, screen_dp));
237        self.touch_new.insert(id);
238    }
239
240    pub fn touch_move(&mut self, id: u64, screen_dp: Vec2) {
241        if let Some(contact) = self.touch_active.get_mut(&id) {
242            contact.1 = screen_dp;
243        }
244    }
245
246    pub fn touch_up(&mut self, id: u64) {
247        self.touch_active.remove(&id);
248        self.touch_new.remove(&id);
249    }
250
251    pub fn touch_contacts(&mut self) -> Vec<TouchContact> {
252        let out: Vec<TouchContact> = self
253            .touch_active
254            .iter()
255            .map(|(id, (start, pos))| TouchContact {
256                id: *id,
257                start: *start,
258                pos: *pos,
259                just_pressed: self.touch_new.contains(id),
260            })
261            .collect();
262        self.touch_new.clear();
263        out
264    }
265
266    pub fn cursor_move(&mut self, phys_px: Vec2) {
267        self.aim.cursor_move(phys_px);
268    }
269
270    pub fn stage_hover(&mut self, world: Vec2, screen: [f32; 2]) {
271        self.aim.stage_hover(world, screen);
272    }
273
274    pub fn mouse_state(&self, allow_fire: bool) -> MouseState {
275        MouseState {
276            left_held: self.lmb_held && allow_fire,
277            left_pressed: !self.clicks.is_empty() && allow_fire,
278            right_held: self.rmb_held,
279            right_pressed: self.rmb_down_edge,
280        }
281    }
282}
283
284#[cfg(test)]
285mod tests {
286    use super::*;
287    use repose_core::input::{Key, Modifiers};
288
289    fn key(physical: PhysicalKey) -> KeyEvent {
290        KeyEvent {
291            key: Key::Character('x'),
292            modifiers: Modifiers::default(),
293            is_repeat: false,
294            event_type: KeyEventType::Down,
295            utf16_code_point: 0,
296            physical: Some(physical),
297        }
298    }
299
300    #[test]
301    fn edges_dedupe_while_held() {
302        let mut s = Staging::default();
303        s.handle_key(&key(PhysicalKey::KeyW));
304        s.handle_key(&key(PhysicalKey::KeyW));
305        assert_eq!(s.take_edges().len(), 1);
306    }
307
308    #[test]
309    fn focus_loss_clears_levels() {
310        let mut s = Staging::default();
311        s.handle_key(&key(PhysicalKey::KeyW));
312        s.set_window_focused(false);
313        assert!(s.held.is_empty());
314    }
315
316    #[test]
317    fn capture_stashes_physical_and_skips_edges() {
318        let mut s = Staging::default();
319        s.capture_armed = true;
320        s.handle_key(&key(PhysicalKey::KeyZ));
321        assert_eq!(s.capture_pending_physical, Some(PhysicalKey::KeyZ));
322        assert!(s.take_edges().is_empty());
323    }
324
325    #[test]
326    fn capture_click_stashes_button_not_click_edge() {
327        let mut s = Staging::default();
328        s.capture_armed = true;
329        s.pick_down(PointerButton::Primary);
330        assert_eq!(s.capture_pending_mouse, Some(true));
331        assert!(s.take_clicks().is_empty());
332        assert!(!s.lmb_held);
333    }
334}