Skip to main content

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}