1use alloc::vec::Vec;
10
11use shuyi_assets::keymap::{CrankDef, StickDef};
12use shuyi_ui::RawInputEvent;
13use shuyi_ui::input::Button;
14use shuyi_ui::input::crank::{Crank, STEPS_PER_TURN};
15use shuyi_ui::input::pad::{PadKind, PadState, Pads};
16
17pub const BUTTONS: [(u32, Button); 6] = [
20 (0x01, Button::DpadLeft),
21 (0x02, Button::DpadRight),
22 (0x04, Button::DpadUp),
23 (0x08, Button::DpadDown),
24 (0x20, Button::East),
25 (0x10, Button::West),
26];
27
28pub fn menu_button(slot: u8) -> Option<Button> {
30 Button::system_menu(slot)
31}
32
33pub fn menu_press(slot: u8) -> Option<[RawInputEvent; 2]> {
36 let button = menu_button(slot)?;
37 Some([
38 RawInputEvent::PadDown(button, PadKind::Playdate),
39 RawInputEvent::PadUp(button),
40 ])
41}
42
43const PAD: usize = 0;
45
46fn state(bits: u32) -> PadState {
48 PadState {
49 buttons: BUTTONS
50 .iter()
51 .filter(|&&(bit, _)| bits & bit != 0)
52 .map(|&(_, button)| button)
53 .collect(),
54 ..PadState::default()
55 }
56}
57
58const DEGREES_PER_TURN: f32 = 360.0;
60
61#[derive(Debug, Clone, Copy, Default, PartialEq)]
65struct Steps {
66 carry: f32,
67}
68
69impl Steps {
70 #[allow(clippy::cast_possible_truncation, clippy::cast_precision_loss)]
73 fn of(&mut self, degrees: f32) -> i32 {
74 if !degrees.is_finite() {
75 return 0;
76 }
77 let per_degree = STEPS_PER_TURN as f32 / DEGREES_PER_TURN;
78 let exact = degrees * per_degree + self.carry;
79 let steps = exact as i32;
81 self.carry = exact - steps as f32;
82 steps
83 }
84}
85
86#[derive(Debug, Clone)]
88pub struct PadInput {
89 pads: Pads,
90 crank: Crank,
91 steps: Steps,
92}
93
94impl PadInput {
95 pub fn new(crank: CrankDef) -> Self {
97 Self {
98 pads: Pads::new(StickDef::default()),
100 crank: Crank::new(crank),
101 steps: Steps::default(),
102 }
103 }
104
105 pub fn poll(
112 &mut self,
113 current: u32,
114 pushed: u32,
115 turned: f32,
116 docked: bool,
117 ) -> Vec<RawInputEvent> {
118 let held = state(current | pushed);
119 let mut events =
120 RawInputEvent::from_pads(&mut self.pads, &[(PAD, PadKind::Playdate, held)]);
121 if docked {
122 self.steps = Steps::default();
123 }
124 let steps = self.steps.of(turned);
125 events.extend(RawInputEvent::from_crank_steps(
126 &mut self.crank,
127 steps,
128 docked,
129 PadKind::Playdate,
130 ));
131 events
132 }
133
134 pub fn set_crank(&mut self, crank: CrankDef) {
136 self.crank = Crank::new(crank);
137 }
138}
139
140#[cfg(test)]
141mod tests {
142 use alloc::vec;
143
144 use shuyi_assets::target::TargetProfile;
145
146 use super::*;
147
148 const LEFT: u32 = 1;
149 const UP: u32 = 4;
150 const B: u32 = 16;
151 const A: u32 = 32;
152
153 fn pad() -> PadInput {
154 PadInput::new(CrankDef::default())
155 }
156
157 fn down(button: Button) -> RawInputEvent {
158 RawInputEvent::PadDown(button, PadKind::Playdate)
159 }
160
161 fn up(button: Button) -> RawInputEvent {
162 RawInputEvent::PadUp(button)
163 }
164
165 fn notches(button: Button, times: usize) -> Vec<RawInputEvent> {
167 (0..times)
168 .flat_map(|_| [down(button), up(button)])
169 .collect()
170 }
171
172 #[test]
173 fn a_is_east_b_is_west_and_the_d_pad_is_the_d_pad() {
174 let mut p = pad();
175 assert_eq!(p.poll(A, A, 0.0, true), [down(Button::East)]);
176 assert_eq!(
177 p.poll(B | UP | LEFT, B | UP | LEFT, 0.0, true),
178 [
179 up(Button::East),
180 down(Button::West),
181 down(Button::DpadUp),
182 down(Button::DpadLeft),
183 ]
184 );
185 assert_eq!(state(0b11_1111).buttons.iter().count(), 6);
186 assert_eq!(state(1 << 6), PadState::default(), "no other bits");
187 }
188
189 #[test]
192 fn every_button_is_one_the_profile_lists() {
193 let profile = TargetProfile::from_source(
194 "targets/playdate.ron",
195 "playdate",
196 include_str!("../../../targets/playdate.ron"),
197 )
198 .unwrap_or_else(|e| panic!("{e:?}"));
199 for (_, button) in BUTTONS {
200 assert!(
201 profile.input.buttons.contains(&button),
202 "{button:?} isn't in {:?}",
203 profile.input.buttons
204 );
205 }
206 }
207
208 #[test]
209 fn a_held_button_is_one_press_until_it_is_let_go() {
210 let mut p = pad();
211 assert_eq!(p.poll(A, A, 0.0, true), [down(Button::East)]);
212 assert_eq!(p.poll(A, 0, 0.0, true), []);
213 assert_eq!(p.poll(0, 0, 0.0, true), [up(Button::East)]);
214 assert_eq!(p.poll(0, 0, 0.0, true), []);
215 }
216
217 #[test]
218 fn a_tap_between_two_frames_is_not_lost() {
219 let mut p = pad();
220 assert_eq!(p.poll(0, B, 0.0, true), [down(Button::West)]);
221 assert_eq!(p.poll(0, 0, 0.0, true), [up(Button::West)]);
222 }
223
224 #[test]
226 fn a_crank_turn_of_90_degrees_gives_two_notches_forward() {
227 let mut p = pad();
228 assert_eq!(p.poll(0, 0, 90.0, false), notches(Button::CrankForward, 2));
229 assert_eq!(p.poll(0, 0, -45.0, false), notches(Button::CrankBack, 1));
230 }
231
232 #[test]
234 fn a_slow_turn_gets_there_in_the_same_notches() {
235 let mut p = pad();
236 let mut events = vec![];
237 for _ in 0..450 {
238 events.extend(p.poll(0, 0, 0.2, false));
239 }
240 assert_eq!(events, notches(Button::CrankForward, 2));
241 }
242
243 #[test]
244 fn a_docked_crank_gives_nothing_and_forgets_a_part_notch() {
245 let mut p = pad();
246 assert_eq!(p.poll(0, 0, 44.0, false), []);
247 assert_eq!(p.poll(0, 0, 90.0, true), []);
248 assert_eq!(p.poll(0, 0, 44.0, false), []);
249 }
250
251 #[test]
252 fn the_keymap_sets_the_notch() {
253 let mut p = pad();
254 p.set_crank(CrankDef { notch_degrees: 90 });
255 assert_eq!(p.poll(0, 0, 90.0, false), notches(Button::CrankForward, 1));
256 }
257
258 #[test]
259 fn a_broken_reading_is_no_turn() {
260 let mut p = pad();
261 assert_eq!(p.poll(0, 0, f32::NAN, false), []);
262 assert_eq!(p.poll(0, 0, f32::INFINITY, false), []);
263 assert_eq!(p.poll(0, 0, 45.0, false), notches(Button::CrankForward, 1));
264 }
265}