pyxel/
input.rs

1use std::collections::HashMap;
2
3use crate::keys::{
4    Key, KeyValue, GAMEPAD_KEY_INDEX_INTERVAL, GAMEPAD_KEY_START_INDEX, MOUSE_KEY_START_INDEX,
5    MOUSE_POS_X, MOUSE_POS_Y, MOUSE_WHEEL_X, MOUSE_WHEEL_Y,
6};
7use crate::pyxel::Pyxel;
8use crate::utils::f64_to_i32;
9
10#[derive(PartialEq)]
11enum KeyState {
12    Pressed,
13    Released,
14    PressedAndReleased,
15    ReleasedAndPressed,
16}
17
18pub struct Input {
19    mouse_visible: bool,
20    key_states: HashMap<Key, (u32, KeyState)>,
21    key_values: HashMap<Key, KeyValue>,
22}
23
24impl Input {
25    pub fn new() -> Self {
26        Self {
27            mouse_visible: false,
28            key_states: HashMap::new(),
29            key_values: HashMap::new(),
30        }
31    }
32}
33
34impl Pyxel {
35    pub fn btn(&mut self, key: Key) -> bool {
36        assert!(
37            !self.is_analog_key(key),
38            "btn is called with an analog key 0x{key:X}"
39        );
40
41        if let Some((frame_count, key_state)) = self.input.key_states.get(&key) {
42            if *key_state == KeyState::Pressed
43                || *key_state == KeyState::ReleasedAndPressed
44                || *frame_count == self.frame_count && *key_state == KeyState::PressedAndReleased
45            {
46                return true;
47            }
48        }
49        false
50    }
51
52    pub fn btnp(
53        &mut self,
54        key: Key,
55        hold_frame_count: Option<u32>,
56        repeat_frame_count: Option<u32>,
57    ) -> bool {
58        assert!(
59            !self.is_analog_key(key),
60            "btnp is called with an analog key 0x{key:X}"
61        );
62
63        if let Some((frame_count, key_state)) = self.input.key_states.get(&key) {
64            if *key_state == KeyState::Released {
65                return false;
66            }
67
68            if *frame_count == self.frame_count {
69                return true;
70            }
71
72            if *key_state == KeyState::PressedAndReleased {
73                return false;
74            }
75
76            let hold_frame_count = hold_frame_count.unwrap_or(0);
77            let repeat_frame_count = repeat_frame_count.unwrap_or(0);
78            if repeat_frame_count == 0 {
79                return false;
80            }
81
82            let elapsed_frames = self.frame_count as i32 - (*frame_count + hold_frame_count) as i32;
83            if elapsed_frames >= 0 && elapsed_frames % repeat_frame_count as i32 == 0 {
84                return true;
85            }
86        }
87        false
88    }
89
90    pub fn btnr(&mut self, key: Key) -> bool {
91        assert!(
92            !self.is_analog_key(key),
93            "btnr is called with an analog key 0x{key:X}"
94        );
95
96        if let Some((frame_count, key_state)) = self.input.key_states.get(&key) {
97            if *key_state == KeyState::Pressed {
98                return false;
99            }
100
101            if *frame_count == self.frame_count {
102                return true;
103            }
104        }
105        false
106    }
107
108    pub fn btnv(&mut self, key: Key) -> KeyValue {
109        assert!(
110            self.is_analog_key(key),
111            "btnv is called with a non-analog key 0x{key:X}"
112        );
113
114        self.input.key_values.get(&key).copied().unwrap_or(0)
115    }
116
117    pub fn mouse(&mut self, visible: bool) {
118        self.input.mouse_visible = visible;
119    }
120
121    pub fn warp_mouse(&mut self, x: f64, y: f64) {
122        let x = f64_to_i32(x);
123        let y = f64_to_i32(y);
124        self.input.key_values.insert(MOUSE_POS_X, x);
125        self.input.key_values.insert(MOUSE_POS_Y, y);
126        pyxel_platform::set_mouse_pos(
127            x * self.system.screen_scale as i32 + self.system.screen_x,
128            y * self.system.screen_scale as i32 + self.system.screen_y,
129        );
130    }
131
132    pub(crate) fn start_input_frame(&mut self) {
133        self.input.key_values.insert(MOUSE_WHEEL_X, 0);
134        self.input.key_values.insert(MOUSE_WHEEL_Y, 0);
135        self.mouse_wheel = 0;
136        self.input_keys.clear();
137        self.input_text.clear();
138        self.dropped_files.clear();
139    }
140
141    pub(crate) fn reset_key(&mut self, key: Key) {
142        self.input.key_states.remove(&key);
143    }
144
145    pub(crate) fn press_key(&mut self, key: Key) {
146        let mut key_state = KeyState::Pressed;
147        if let Some((last_frame_count, last_key_state)) = self.input.key_states.get(&key) {
148            if *last_frame_count == self.frame_count && *last_key_state != KeyState::Pressed {
149                key_state = KeyState::ReleasedAndPressed;
150            }
151        }
152
153        self.input
154            .key_states
155            .insert(key, (self.frame_count, key_state));
156        if key < MOUSE_KEY_START_INDEX {
157            self.input_keys.push(key);
158        }
159    }
160
161    pub(crate) fn release_key(&mut self, key: Key) {
162        let mut key_state = KeyState::Released;
163        if let Some((last_frame_count, last_key_state)) = self.input.key_states.get(&key) {
164            if *last_frame_count == self.frame_count && *last_key_state != KeyState::Released {
165                key_state = KeyState::PressedAndReleased;
166            }
167        }
168
169        self.input
170            .key_states
171            .insert(key, (self.frame_count, key_state));
172    }
173
174    pub(crate) fn change_key_value(&mut self, key: Key, value: KeyValue) {
175        let mut value = value;
176
177        match key {
178            MOUSE_POS_X => {
179                value = ((value - self.system.screen_x) as f64 / self.system.screen_scale) as i32;
180                self.mouse_x = value;
181            }
182            MOUSE_POS_Y => {
183                value = ((value - self.system.screen_y) as f64 / self.system.screen_scale) as i32;
184                self.mouse_y = value;
185            }
186            MOUSE_WHEEL_Y => {
187                self.mouse_wheel = value;
188            }
189            _ => {}
190        }
191
192        self.input.key_values.insert(key, value);
193    }
194
195    pub(crate) fn add_input_text(&mut self, text: &str) {
196        self.input_text += text;
197    }
198
199    pub(crate) fn add_dropped_file(&mut self, filename: &str) {
200        self.dropped_files.push(filename.to_string());
201    }
202
203    pub(crate) fn is_mouse_visible(&self) -> bool {
204        self.input.mouse_visible
205    }
206
207    fn is_analog_key(&self, key: Key) -> bool {
208        matches!(
209            key,
210            MOUSE_POS_X | MOUSE_POS_Y | MOUSE_WHEEL_X | MOUSE_WHEEL_Y
211        ) || (key >= GAMEPAD_KEY_START_INDEX && (key % GAMEPAD_KEY_INDEX_INTERVAL) < 6)
212    }
213}