shuyi_ui/input/keymap.rs
1//! [`Keymap`]: the lookup from key chords and controller buttons to a
2//! game's actions, plus the key-repeat timings (game ADR-0015, game ADR-0031,
3//! game ADR-0034). Generic over the game's [`ActionSet`] (ticket 1106).
4
5use crate::prelude::*;
6use alloc::collections::BTreeMap;
7use shuyi_assets::keymap::KeymapDef;
8use shuyi_assets::keymap::{
9 ActionSet, Button, ButtonChord, Chord, ChordDef, Key, Layout, RepeatDef, UiAction,
10};
11
12use super::pad::{self, PadKind};
13use super::state::Device;
14
15/// Lookup from chord, and from controller button or
16/// [button chord](ButtonChord), to actions, plus repeat and chord timings.
17/// A chord or button is on at most one action of each
18/// [group](ActionSet::Group) (game ADR-0068), so one press can give an action of
19/// each group: the screen on top reacts to the ones it uses.
20#[derive(Debug, Clone, PartialEq, Eq)]
21pub struct Keymap<A: ActionSet> {
22 /// Every chord that does something, fixed keys included, with its
23 /// actions in group order.
24 bindings: BTreeMap<Chord, Vec<A>>,
25 /// Each action's own chords (not the fixed keys), in the order given.
26 chords: BTreeMap<A, Vec<Chord>>,
27 /// Every controller button or button chord that does something, with
28 /// its actions in group order.
29 pad: BTreeMap<ButtonChord, Vec<A>>,
30 /// Each action's buttons and button chords, in the order given.
31 buttons: BTreeMap<A, Vec<ButtonChord>>,
32 repeat: RepeatDef,
33 chord: ChordDef,
34}
35
36/// How long a controller button that is in a [button chord](ButtonChord)
37/// holds back its own actions, so that pressing its partner can fire the
38/// chord instead (ticket 1034). See [`Keymap::chord_wait`].
39#[derive(Debug, Clone, Copy, PartialEq, Eq)]
40pub enum ChordWait {
41 /// Not at all: it acts on press, like any button. A button in no chord,
42 /// or one whose own action repeats (the cursor never lags). A chord
43 /// with it fires only when it is pressed while its partner waits.
44 None,
45 /// For the chord window or until it is let go, whichever is first: its
46 /// partner may come just after it.
47 Window,
48 /// Until it is let go: a partner that acts on press may come any time
49 /// while it is held.
50 Release,
51}
52
53impl<A: ActionSet> Keymap<A> {
54 /// A keymap with `bindings` plus the game's
55 /// [fixed keys](ActionSet::fixed_keys). A chord listed twice for one
56 /// group keeps its last action of that group;
57 /// [reserved](Chord::is_reserved) chords (plain `Escape` and `Delete`)
58 /// are left out.
59 pub fn new(bindings: impl IntoIterator<Item = (Chord, A)>, repeat: RepeatDef) -> Self {
60 let mut lookup = BTreeMap::new();
61 let mut chords: BTreeMap<A, Vec<Chord>> = BTreeMap::new();
62 for (chord, action) in bindings {
63 if chord.is_reserved() {
64 continue;
65 }
66 if let Some(old) = put(&mut lookup, chord, action)
67 && let Some(list) = chords.get_mut(&old)
68 {
69 list.retain(|&c| c != chord);
70 }
71 chords.entry(action).or_default().push(chord);
72 }
73 for &(chord, action) in A::fixed_keys() {
74 put(&mut lookup, chord, action);
75 }
76 Self {
77 bindings: lookup,
78 chords,
79 pad: BTreeMap::new(),
80 buttons: BTreeMap::new(),
81 repeat,
82 chord: ChordDef::default(),
83 }
84 }
85
86 /// This keymap with `pad` as its controller buttons and button chords,
87 /// replacing any it had. A button or chord listed twice for one group
88 /// keeps its last action of that group.
89 #[must_use]
90 pub fn with_pad<B: Into<ButtonChord>>(mut self, pad: impl IntoIterator<Item = (B, A)>) -> Self {
91 self.pad.clear();
92 self.buttons.clear();
93 for (button, action) in pad {
94 let button = button.into();
95 if let Some(old) = put(&mut self.pad, button, action)
96 && let Some(list) = self.buttons.get_mut(&old)
97 {
98 list.retain(|&b| b != button);
99 }
100 self.buttons.entry(action).or_default().push(button);
101 }
102 self
103 }
104
105 /// This keymap with `chord` as its button chords' timing.
106 #[must_use]
107 pub fn with_chord(mut self, chord: ChordDef) -> Self {
108 self.chord = chord;
109 self
110 }
111
112 /// This keymap with the default controller buttons from a validated
113 /// keymap definition (the same in every layout and the layout picker)
114 /// and its chord timing.
115 #[must_use]
116 pub fn with_default_pad(self, def: &KeymapDef<A>) -> Self {
117 let pad = def.pad.iter();
118 self.with_pad(pad.flat_map(|(&a, buttons)| buttons.iter().map(move |&b| (b, a))))
119 .with_chord(def.chord)
120 }
121
122 /// `layout`'s default bindings from a validated keymap definition (no
123 /// player changes; for those see
124 /// [`PlayerKeys`](super::bindings::PlayerKeys)), with its repeat
125 /// timings and the default controller buttons. A definition without
126 /// that layout gives a keymap with only the fixed keys and the buttons.
127 pub fn for_layout(def: &KeymapDef<A>, layout: Layout) -> Self {
128 let keys = def.layouts.get(&layout).into_iter().flatten();
129 let bindings = keys.flat_map(|(&a, chords)| chords.iter().map(move |&c| (c, a)));
130 Self::new(bindings, def.repeat).with_default_pad(def)
131 }
132
133 /// The keys that work before any layout is chosen, from the keymap
134 /// definition's `layout_picker` section, so the layout picker can be
135 /// used whichever hand the player types with. Actions it doesn't list
136 /// have no key. The controller buttons are the default ones, as in
137 /// every layout.
138 pub fn layout_picker(def: &KeymapDef<A>) -> Self {
139 let bindings = def.layout_picker.iter().map(|(&c, &a)| (c, a));
140 Self::new(bindings, def.repeat).with_default_pad(def)
141 }
142
143 /// The actions bound to `chord`, one at most of each group, in group
144 /// order. `Shift+h` and `h` are distinct: there is no fallback from a
145 /// shifted chord to the plain one.
146 pub fn actions(&self, chord: Chord) -> &[A] {
147 self.bindings.get(&chord).map_or(&[], Vec::as_slice)
148 }
149
150 /// The first of [`actions`](Self::actions), if any: the only one for a
151 /// chord of a single group.
152 pub fn action(&self, chord: Chord) -> Option<A> {
153 self.actions(chord).first().copied()
154 }
155
156 /// The actions bound to the controller button `button` on its own, as
157 /// [`actions`](Self::actions).
158 pub fn pad_actions(&self, button: Button) -> &[A] {
159 self.chord_actions(ButtonChord::single(button))
160 }
161
162 /// The first of [`pad_actions`](Self::pad_actions), if any.
163 pub fn pad_action(&self, button: Button) -> Option<A> {
164 self.pad_actions(button).first().copied()
165 }
166
167 /// The actions bound to `chord` (a button chord, or one button), as
168 /// [`actions`](Self::actions).
169 pub fn chord_actions(&self, chord: ButtonChord) -> &[A] {
170 self.pad.get(&chord).map_or(&[], Vec::as_slice)
171 }
172
173 /// Every controller button and button chord bound to `action`, in the
174 /// order they were given (file order for the keymap file).
175 pub fn buttons_for(&self, action: A) -> Vec<ButtonChord> {
176 self.buttons.get(&action).cloned().unwrap_or_default()
177 }
178
179 /// Key-repeat timings.
180 pub fn repeat(&self) -> RepeatDef {
181 self.repeat
182 }
183
184 /// Button chords' timing.
185 pub fn chord(&self) -> ChordDef {
186 self.chord
187 }
188
189 /// How long `button` holds back its own actions when pressed, so that
190 /// a [button chord](ButtonChord) with it can fire instead (ticket
191 /// 1034). A button in no chord, or whose own action
192 /// [repeats](ActionSet::is_repeatable) (the D-pad on the cursor), acts
193 /// on press. Otherwise it waits until it is let go if a partner of its
194 /// acts on press (`B` in `B+←`: hold it as long as you like before `←`)
195 /// or it has no action of its own; else for the chord window or its
196 /// release, whichever is first (`A` in `A+B`). A pad with no chords
197 /// acts on press throughout, exactly as before chords.
198 pub fn chord_wait(&self, button: Button) -> ChordWait {
199 let repeats = |b| self.pad_actions(b).iter().any(|a| a.is_repeatable());
200 let mut partners = self.pad.keys().filter_map(|c| c.partner(button)).peekable();
201 if partners.peek().is_none() || repeats(button) {
202 ChordWait::None
203 } else if self.pad_actions(button).is_empty() || partners.any(repeats) {
204 ChordWait::Release
205 } else {
206 ChordWait::Window
207 }
208 }
209
210 /// Every chord bound to `action` (its key slots), in the order they
211 /// were given: slot order for the player's bindings, file order for
212 /// the keymap file. Leaves out the fixed keys (see
213 /// [`fixed_chords_for`](Self::fixed_chords_for)).
214 pub fn chords_for(&self, action: A) -> Vec<Chord> {
215 self.chords.get(&action).cloned().unwrap_or_default()
216 }
217
218 /// The [fixed chords](ActionSet::fixed_keys) that also trigger
219 /// `action` in every keymap and can't be rebound.
220 pub fn fixed_chords_for(action: A) -> Vec<Chord> {
221 A::fixed_keys()
222 .iter()
223 .filter(|&&(_, a)| a == action)
224 .map(|&(c, _)| c)
225 .collect()
226 }
227
228 /// What moves the cursor, for help text: `arrows` (the language's word
229 /// for them) when the primary chords of the actions the game takes for
230 /// [up, left, down and right](ActionSet::ui) are the plain arrow keys,
231 /// otherwise those chords in up-left-down-right order (`wasd`). `None`
232 /// if any of them is unbound, or the game has no such action. Lives
233 /// here, not with the help text, because it names keys.
234 pub fn cursor_keys_name(&self, arrows_name: &str) -> Option<String> {
235 let order = [
236 (UiAction::Up, Key::Up),
237 (UiAction::Left, Key::Left),
238 (UiAction::Down, Key::Down),
239 (UiAction::Right, Key::Right),
240 ];
241 let chords = order
242 .iter()
243 .map(|&(ui, _)| self.primary(A::for_ui(ui)?))
244 .collect::<Option<Vec<_>>>()?;
245 let arrows = chords
246 .iter()
247 .zip(order)
248 .all(|(chord, (_, arrow))| *chord == Chord::plain(arrow));
249 Some(if arrows {
250 arrows_name.to_owned()
251 } else {
252 chords.iter().map(ToString::to_string).collect()
253 })
254 }
255
256 /// What moves the cursor on a pad of `kind`, for help text: the whole
257 /// sets of direction buttons the four cursor actions are on, joined
258 /// with `/` (`D-pad/L-stick` with this game's defaults); if they are on
259 /// no whole set, each one's [`primary_button`](Self::primary_button)'s
260 /// name in up-left-down-right order. `None` if any cursor action has no
261 /// button.
262 pub fn cursor_buttons_name(&self, kind: PadKind) -> Option<String> {
263 let mut bound: [Vec<Button>; 4] = Default::default();
264 let order = [
265 UiAction::Up,
266 UiAction::Down,
267 UiAction::Left,
268 UiAction::Right,
269 ];
270 for (buttons, ui) in bound.iter_mut().zip(order) {
271 let all = self.buttons_for(A::for_ui(ui)?);
272 *buttons = all.iter().filter_map(|c| c.single_button()).collect();
273 }
274 pad::cursor_buttons_name(kind, &bound)
275 }
276
277 /// The button (or button chord) help text names for `action`: the
278 /// first of [`buttons_for`](Self::buttons_for), or `None` if it has
279 /// none.
280 pub fn primary_button(&self, action: A) -> Option<ButtonChord> {
281 self.buttons_for(action).into_iter().next()
282 }
283
284 /// The chord help text names for `action`: the first of
285 /// [`chords_for`](Self::chords_for), or `None` if it has none.
286 pub fn primary(&self, action: A) -> Option<Chord> {
287 self.chords_for(action).into_iter().next()
288 }
289
290 /// Whether `action` has a key (keyboard) or a button (pad) for `device`.
291 pub fn has_input(&self, action: A, device: Device) -> bool {
292 match device {
293 Device::Keyboard => self.primary(action).is_some(),
294 Device::Pad(_) => self.primary_button(action).is_some(),
295 }
296 }
297}
298
299/// Binds `item` to `action` in `lookup`, replacing (and returning) the
300/// action of the same group it had; its actions stay in group order.
301fn put<T: Copy + Ord, A: ActionSet>(
302 lookup: &mut BTreeMap<T, Vec<A>>,
303 item: T,
304 action: A,
305) -> Option<A> {
306 let actions = lookup.entry(item).or_default();
307 let old = actions
308 .iter()
309 .position(|a| a.group() == action.group())
310 .map(|i| actions.remove(i));
311 actions.push(action);
312 actions.sort_by_key(|a| a.group());
313 old
314}