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}