Skip to main content

pite_script/
input.rs

1// SPDX-License-Identifier: MIT OR Apache-2.0
2use std::collections::HashSet;
3use std::sync::{LazyLock, RwLock};
4
5static INPUT: LazyLock<RwLock<InputState>> = LazyLock::new(|| RwLock::new(InputState::default()));
6
7#[derive(Debug, Default)]
8pub struct InputState {
9    held: HashSet<String>,
10    pressed: HashSet<String>,
11    released: HashSet<String>,
12    mouse: (f64, f64),
13}
14
15pub fn input_begin_frame() {
16    let mut state = INPUT.write().unwrap();
17    state.pressed.clear();
18    state.released.clear();
19}
20
21/// Drop all key state (held + edges). The editor calls this on play/stop
22/// transitions so a key held across the transition never sticks.
23pub fn input_clear() {
24    let mut state = INPUT.write().unwrap();
25    state.held.clear();
26    state.pressed.clear();
27    state.released.clear();
28}
29
30pub fn input_set_key(name: &str, held: bool) {
31    let mut state = INPUT.write().unwrap();
32    if held && state.held.insert(name.to_string()) {
33        state.pressed.insert(name.to_string());
34    } else if !held && state.held.remove(name) {
35        state.released.insert(name.to_string());
36    }
37}
38
39pub fn input_set_mouse(x: f64, y: f64) {
40    INPUT.write().unwrap().mouse = (x, y);
41}
42
43pub fn input_held(key: &str) -> bool {
44    INPUT.read().unwrap().held.contains(key)
45}
46
47pub fn input_pressed(key: &str) -> bool {
48    INPUT.read().unwrap().pressed.contains(key)
49}
50
51pub fn input_released(key: &str) -> bool {
52    INPUT.read().unwrap().released.contains(key)
53}
54
55pub fn input_mouse() -> (f64, f64) {
56    INPUT.read().unwrap().mouse
57}
58
59#[cfg(test)]
60mod tests {
61    use super::*;
62
63    #[test]
64    fn key_edges_last_one_frame() {
65        let _guard = crate::host::HOST_SERIAL.lock().unwrap();
66        input_begin_frame();
67        input_set_key("T-KeyEdgesLastOneFrame", false);
68        input_begin_frame();
69        assert!(!input_held("T-KeyEdgesLastOneFrame"));
70        input_set_key("T-KeyEdgesLastOneFrame", true);
71        assert!(input_held("T-KeyEdgesLastOneFrame"));
72        assert!(input_pressed("T-KeyEdgesLastOneFrame"));
73        input_begin_frame();
74        assert!(input_held("T-KeyEdgesLastOneFrame"));
75        assert!(!input_pressed("T-KeyEdgesLastOneFrame"));
76        input_set_key("T-KeyEdgesLastOneFrame", false);
77        assert!(!input_held("T-KeyEdgesLastOneFrame"));
78        assert!(input_released("T-KeyEdgesLastOneFrame"));
79        input_begin_frame();
80        assert!(!input_released("T-KeyEdgesLastOneFrame"));
81    }
82
83    #[test]
84    fn clear_drops_held_and_edges() {
85        let _guard = crate::host::HOST_SERIAL.lock().unwrap();
86        input_begin_frame();
87        input_set_key("T-ClearDropsState", true);
88        assert!(input_held("T-ClearDropsState"));
89        assert!(input_pressed("T-ClearDropsState"));
90        input_clear();
91        assert!(!input_held("T-ClearDropsState"));
92        assert!(!input_pressed("T-ClearDropsState"));
93        assert!(!input_released("T-ClearDropsState"));
94        // Releasing after a clear must not resurrect a release edge.
95        input_set_key("T-ClearDropsState", false);
96        assert!(!input_released("T-ClearDropsState"));
97        input_begin_frame();
98    }
99}