shuyi_ui/input/frame.rs
1//! [`FrameInput`]: one frame's input as a screen sees it. Generic over the
2//! game's [`ActionSet`] (ticket 1106), and [`frame_seconds`], which makes
3//! a frame time a platform reports safe to add up.
4//!
5//! In tests, a screen sees only the actions it declares
6//! ([`Screen::actions`](crate::screen::Screen::actions), ADR-0008 §3):
7//! [`declared`](FrameInput::declared) filters a frame's input down to
8//! them, and asking whether an action it didn't declare is held fails the
9//! test.
10
11use crate::prelude::*;
12use shuyi_assets::keymap::{ActionSet, Button, Chord};
13
14/// `dt` as seconds that can pass: itself when finite and at least 0, else
15/// 0 (a NaN, an infinity or a negative frame time is a platform's
16/// nonsense, and no time passes).
17pub fn frame_seconds(dt: f32) -> f32 {
18 if dt.is_finite() { dt.max(0.0) } else { 0.0 }
19}
20
21/// One frame's input as a screen sees it.
22#[derive(Debug, Clone, PartialEq)]
23pub struct FrameInput<A: ActionSet> {
24 /// Actions this frame, in order: key presses, then key repeats. A fixed
25 /// step reads [`presses`](Self::presses) instead, which have no repeats.
26 pub actions: Vec<A>,
27 /// Seconds since the previous frame: finite and at least 0
28 /// ([`frame_seconds`]).
29 dt: f32,
30 /// Actions whose key is currently held down.
31 held: Vec<A>,
32 /// The chords pressed this frame, in order.
33 pressed: Vec<Chord>,
34 /// The characters typed this frame, in order.
35 text: Vec<char>,
36 /// Whether a controller button went down this frame.
37 pad: bool,
38 /// The controller buttons that went down this frame, in order.
39 buttons_down: Vec<Button>,
40 /// The controller buttons that went up this frame, in order.
41 buttons_up: Vec<Button>,
42 /// The fixed simulation steps due this frame.
43 ticks: u32,
44 /// The actions pressed this frame, in order, without the key repeats.
45 presses: Vec<A>,
46 /// The actions let go this frame, in order.
47 released: Vec<A>,
48 /// Whether the game came back from a suspend just before this frame.
49 resumed: bool,
50 /// The screen this input was [filtered](Self::declared) for, and the
51 /// actions it declares: asking about any other is a bug in the
52 /// screen's declaration.
53 declared: Option<(&'static str, Vec<A>)>,
54}
55
56impl<A: ActionSet> FrameInput<A> {
57 /// Input for one frame; `held` lists the actions whose keys are down.
58 /// No [`pressed_chords`](Self::pressed_chords). A `dt` that is not
59 /// finite or is negative counts as 0 ([`frame_seconds`]). Each of
60 /// `actions` is a press ([`presses`](Self::presses)), and one that
61 /// isn't held is a tap, let go too ([`released`](Self::released)),
62 /// until [`with_presses`](Self::with_presses) says otherwise.
63 pub fn new(actions: Vec<A>, dt: f32, held: Vec<A>) -> Self {
64 let mut released: Vec<A> = Vec::new();
65 for &action in &actions {
66 if !held.contains(&action) && !released.contains(&action) {
67 released.push(action);
68 }
69 }
70 Self {
71 presses: actions.clone(),
72 released,
73 actions,
74 dt: frame_seconds(dt),
75 held,
76 pressed: Vec::new(),
77 text: Vec::new(),
78 pad: false,
79 buttons_down: Vec::new(),
80 buttons_up: Vec::new(),
81 ticks: 0,
82 resumed: false,
83 declared: None,
84 }
85 }
86
87 /// This input as `screen` sees it when it declares that it reacts to
88 /// `declared` alone ([`Screen::actions`](crate::screen::Screen::actions)):
89 /// only those actions, with the actions held back (in order, as often
90 /// as they came). Asking the result whether an action outside
91 /// `declared` is [held](Self::is_held) panics: the screen reads an
92 /// action it didn't declare. Its [`presses`](Self::presses),
93 /// [`released`](Self::released) and [`held`](Self::held) actions are
94 /// `declared`'s alone. Keys, buttons and text stay as they were.
95 pub fn declared(&self, screen: &'static str, declared: &[A]) -> (Self, Vec<A>) {
96 let (actions, missed) = self.actions.iter().partition(|a| declared.contains(a));
97 let only = |list: &[A]| {
98 list.iter()
99 .copied()
100 .filter(|a| declared.contains(a))
101 .collect()
102 };
103 let input = Self {
104 actions,
105 presses: only(&self.presses),
106 released: only(&self.released),
107 held: only(&self.held),
108 declared: Some((screen, declared.to_vec())),
109 ..self.clone()
110 };
111 (input, missed)
112 }
113
114 /// The same input with the actions `presses`, pressed this frame in
115 /// order without the key repeats, and `released`, let go this frame in
116 /// order, as [`InputState`](super::InputState) gives them.
117 #[must_use]
118 pub fn with_presses(mut self, presses: Vec<A>, released: Vec<A>) -> Self {
119 self.presses = presses;
120 self.released = released;
121 self
122 }
123
124 /// The actions pressed this frame, in order, without the key repeats
125 /// that [`actions`](Self::actions) has after them: what a fixed step
126 /// reads. A tick stage reads them through its
127 /// [`TickDriver`](crate::tick::TickDriver), which gives each to one
128 /// step.
129 pub fn presses(&self) -> &[A] {
130 &self.presses
131 }
132
133 /// The actions let go this frame, in order: each when no key or button
134 /// holds it any more.
135 pub fn released(&self) -> &[A] {
136 &self.released
137 }
138
139 /// The actions whose key or button is down at the end of this frame, in
140 /// the game's order ([`ActionSet::ALL`]).
141 pub fn held(&self) -> &[A] {
142 &self.held
143 }
144
145 /// The same input with whether the game came back from a suspend just
146 /// before this frame.
147 #[must_use]
148 pub fn with_resumed(mut self, resumed: bool) -> Self {
149 self.resumed = resumed;
150 self
151 }
152
153 /// Whether the game came back from a suspend just before this frame
154 /// (the platform's `SystemEvent::Resume`): a tick stage forgets the
155 /// presses it kept from before ([`TickDriver`](crate::tick::TickDriver)
156 /// does).
157 pub fn resumed(&self) -> bool {
158 self.resumed
159 }
160
161 /// The same input with the controller buttons that went `down` and
162 /// `up` this frame (and so [`pad_pressed`](Self::pad_pressed) if any
163 /// went down).
164 #[must_use]
165 pub fn with_buttons(mut self, down: Vec<Button>, up: Vec<Button>) -> Self {
166 self.pad = !down.is_empty();
167 self.buttons_down = down;
168 self.buttons_up = up;
169 self
170 }
171
172 /// Seconds since the previous frame: always finite and at least 0, so
173 /// a screen adds it up as it is. It is for what only looks: rules count
174 /// [`ticks`](Self::ticks) or turns, with `shuyi_core::timer`.
175 pub fn dt(&self) -> f32 {
176 self.dt
177 }
178
179 /// The same input with `ticks` fixed simulation steps due.
180 #[must_use]
181 pub fn with_ticks(mut self, ticks: u32) -> Self {
182 self.ticks = ticks;
183 self
184 }
185
186 /// How many fixed simulation steps are due this frame (ADR-0003 §2: 60
187 /// a second, at most 4 a frame): a stage with a tick simulation steps
188 /// it this many times. The platform's `App` counts them from its clock
189 /// (`shuyi_platform::tick`); 0 while the game is suspended, in an
190 /// update with no time passing ([`Game::act`](crate::Game::act)) and
191 /// in a frame no `App` ran. Rules time things in these steps, with
192 /// `shuyi_core::timer`, and never with [`dt`](Self::dt).
193 pub fn ticks(&self) -> u32 {
194 self.ticks
195 }
196
197 /// The controller buttons that went down this frame (bound or not; by
198 /// binding position, stick directions included), for the Key bindings
199 /// screen, which captures the button for a slot. Anything else reacts
200 /// to [`actions`](Self::actions).
201 pub fn pressed_buttons(&self) -> &[Button] {
202 &self.buttons_down
203 }
204
205 /// The controller buttons that went up this frame, as
206 /// [`pressed_buttons`](Self::pressed_buttons).
207 pub fn released_buttons(&self) -> &[Button] {
208 &self.buttons_up
209 }
210
211 /// The same input with whether a controller button went down this
212 /// frame.
213 #[must_use]
214 pub fn with_pad(mut self, pad: bool) -> Self {
215 self.pad = pad;
216 self
217 }
218
219 /// Whether this frame's input came from a controller (a button went
220 /// down, or a [button chord](super::ButtonChord)'s button gave its own
221 /// action as it was let go): the player is on a controller, so a
222 /// screen that needs text shows a letter grid instead of asking them to
223 /// type. A game sets it with [`with_pad`](Self::with_pad);
224 /// [`with_buttons`](Self::with_buttons) sets it for a button that
225 /// went down.
226 pub fn pad_pressed(&self) -> bool {
227 self.pad
228 }
229
230 /// The same input with the chords `pressed` and the characters `text`
231 /// typed this frame.
232 #[must_use]
233 pub fn with_typing(mut self, pressed: Vec<Chord>, text: Vec<char>) -> Self {
234 self.pressed = pressed;
235 self.text = text;
236 self
237 }
238
239 /// The chords pressed this frame (bound or not; presses only, no
240 /// repeats), for the few screens that read keys themselves: a text
241 /// box's fixed keys ([`text_key`](super::fixed::text_key)) and the Key bindings
242 /// screen, which captures the key for a slot.
243 /// Anything else reacts to [`actions`](Self::actions).
244 pub fn pressed_chords(&self) -> &[Chord] {
245 &self.pressed
246 }
247
248 /// The characters typed this frame (printable only), for text boxes.
249 pub fn text(&self) -> &[char] {
250 &self.text
251 }
252
253 /// Whether a key bound to `action` is held (e.g. hold Confirm to
254 /// fast-forward).
255 ///
256 /// # Panics
257 ///
258 /// On input [`declared`](Self::declared) for a screen that doesn't
259 /// declare `action`.
260 pub fn is_held(&self, action: A) -> bool {
261 if let Some((screen, declared)) = &self.declared {
262 assert!(
263 declared.contains(&action),
264 "screen \"{screen}\" asks whether {action:?} is held, but doesn't declare it \
265 (Screen::actions)"
266 );
267 }
268 self.held.contains(&action)
269 }
270}