Skip to main content

pfx_input/
action.rs

1use std::collections::BTreeMap;
2use std::fmt;
3
4use serde::{Deserialize, Serialize};
5
6use crate::device::{Axis, Button, Control, Key, MouseButton, Sign, Stick, Wheel};
7
8#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
9pub enum ActionKind {
10    Digital,
11    Axis,
12    Stick,
13}
14
15#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
16pub struct Repeat {
17    pub delay: u32,
18    pub interval: u32,
19}
20
21#[derive(Clone, Debug, PartialEq)]
22pub struct ActionSpec {
23    pub name: String,
24    pub kind: ActionKind,
25    pub context: String,
26    pub dead_zone: f32,
27    pub repeat: Option<Repeat>,
28    pub keyboard: Vec<Binding>,
29    pub gamepad: Vec<Binding>,
30}
31
32impl ActionSpec {
33    fn new(name: &str, kind: ActionKind, dead_zone: f32) -> Self {
34        Self {
35            name: name.to_owned(),
36            kind,
37            context: String::new(),
38            dead_zone,
39            repeat: None,
40            keyboard: Vec::new(),
41            gamepad: Vec::new(),
42        }
43    }
44
45    pub fn digital(name: &str) -> Self {
46        Self::new(name, ActionKind::Digital, 0.0)
47    }
48
49    pub fn axis(name: &str) -> Self {
50        Self::new(name, ActionKind::Axis, 0.2)
51    }
52
53    pub fn stick(name: &str) -> Self {
54        Self::new(name, ActionKind::Stick, 0.2)
55    }
56
57    pub fn context(mut self, context: &str) -> Self {
58        context.clone_into(&mut self.context);
59        self
60    }
61
62    pub fn dead_zone(mut self, dead_zone: f32) -> Self {
63        self.dead_zone = dead_zone.clamp(0.0, 0.95);
64        self
65    }
66
67    pub fn repeat(mut self, delay: u32, interval: u32) -> Self {
68        self.repeat = Some(Repeat {
69            delay: delay.max(1),
70            interval: interval.max(1),
71        });
72        self
73    }
74
75    pub fn key(mut self, binding: Binding) -> Self {
76        self.keyboard.push(binding);
77        self
78    }
79
80    pub fn pad(mut self, binding: Binding) -> Self {
81        self.gamepad.push(binding);
82        self
83    }
84}
85
86#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
87pub struct ActionId(pub u16);
88
89#[derive(Clone, Debug, Default, PartialEq)]
90pub struct Actions {
91    specs: Vec<ActionSpec>,
92    names: BTreeMap<String, ActionId>,
93}
94
95#[derive(Clone, Debug, PartialEq, Eq)]
96pub enum ActionsError {
97    Duplicate(String),
98    TooMany,
99}
100
101impl fmt::Display for ActionsError {
102    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
103        match self {
104            ActionsError::Duplicate(name) => write!(f, "action {name:?} is named twice"),
105            ActionsError::TooMany => write!(f, "more than 65535 actions"),
106        }
107    }
108}
109
110impl std::error::Error for ActionsError {}
111
112impl Actions {
113    pub fn new(specs: Vec<ActionSpec>) -> Result<Self, ActionsError> {
114        if specs.len() > usize::from(u16::MAX) {
115            return Err(ActionsError::TooMany);
116        }
117        let mut names = BTreeMap::new();
118        for (index, spec) in specs.iter().enumerate() {
119            if names
120                .insert(spec.name.clone(), ActionId(index as u16))
121                .is_some()
122            {
123                return Err(ActionsError::Duplicate(spec.name.clone()));
124            }
125        }
126        Ok(Self { specs, names })
127    }
128
129    pub fn id(&self, name: &str) -> Option<ActionId> {
130        self.names.get(name).copied()
131    }
132
133    pub fn spec(&self, id: ActionId) -> &ActionSpec {
134        &self.specs[usize::from(id.0)]
135    }
136
137    pub fn len(&self) -> usize {
138        self.specs.len()
139    }
140
141    pub fn is_empty(&self) -> bool {
142        self.specs.is_empty()
143    }
144
145    pub fn iter(&self) -> impl Iterator<Item = (ActionId, &ActionSpec)> {
146        self.specs
147            .iter()
148            .enumerate()
149            .map(|(index, spec)| (ActionId(index as u16), spec))
150    }
151}
152
153#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
154pub enum Source {
155    Key(Key),
156    Mouse(MouseButton),
157    Wheel(Wheel),
158    Button(Button),
159    Axis(Axis, Sign),
160}
161
162impl Source {
163    pub fn is_keyboard(self) -> bool {
164        matches!(self, Source::Key(_))
165    }
166
167    pub fn is_mouse(self) -> bool {
168        matches!(self, Source::Mouse(_) | Source::Wheel(_))
169    }
170
171    pub fn is_gamepad(self) -> bool {
172        matches!(self, Source::Button(_) | Source::Axis(..))
173    }
174
175    pub fn control(self) -> Control {
176        match self {
177            Source::Key(key) => Control::Key(key),
178            Source::Mouse(button) => Control::Mouse(button),
179            Source::Wheel(wheel) => Control::Wheel(wheel),
180            Source::Button(button) => Control::Button(button),
181            Source::Axis(axis, _) => Control::Stick(axis.stick()),
182        }
183    }
184}
185
186#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
187pub enum Binding {
188    Press(Source),
189    Axis {
190        negative: Source,
191        positive: Source,
192    },
193    PadAxis(Axis),
194    Stick {
195        up: Source,
196        down: Source,
197        left: Source,
198        right: Source,
199    },
200    PadStick(Stick),
201}
202
203impl Binding {
204    pub fn key(key: Key) -> Self {
205        Binding::Press(Source::Key(key))
206    }
207
208    pub fn mouse(button: MouseButton) -> Self {
209        Binding::Press(Source::Mouse(button))
210    }
211
212    pub fn button(button: Button) -> Self {
213        Binding::Press(Source::Button(button))
214    }
215
216    pub fn keys(negative: Key, positive: Key) -> Self {
217        Binding::Axis {
218            negative: Source::Key(negative),
219            positive: Source::Key(positive),
220        }
221    }
222
223    pub fn buttons(negative: Button, positive: Button) -> Self {
224        Binding::Axis {
225            negative: Source::Button(negative),
226            positive: Source::Button(positive),
227        }
228    }
229
230    pub fn key_stick(up: Key, down: Key, left: Key, right: Key) -> Self {
231        Binding::Stick {
232            up: Source::Key(up),
233            down: Source::Key(down),
234            left: Source::Key(left),
235            right: Source::Key(right),
236        }
237    }
238
239    pub fn dpad() -> Self {
240        Binding::Stick {
241            up: Source::Button(Button::DPadUp),
242            down: Source::Button(Button::DPadDown),
243            left: Source::Button(Button::DPadLeft),
244            right: Source::Button(Button::DPadRight),
245        }
246    }
247
248    pub fn sources(&self) -> Vec<Source> {
249        match *self {
250            Binding::Press(source) => vec![source],
251            Binding::Axis { negative, positive } => vec![negative, positive],
252            Binding::PadAxis(axis) => vec![
253                Source::Axis(axis, Sign::Negative),
254                Source::Axis(axis, Sign::Positive),
255            ],
256            Binding::Stick {
257                up,
258                down,
259                left,
260                right,
261            } => vec![up, down, left, right],
262            Binding::PadStick(stick) => {
263                let (x, y) = stick.axes();
264                vec![
265                    Source::Axis(y, Sign::Positive),
266                    Source::Axis(y, Sign::Negative),
267                    Source::Axis(x, Sign::Negative),
268                    Source::Axis(x, Sign::Positive),
269                ]
270            }
271        }
272    }
273
274    pub fn part_count(&self) -> usize {
275        match self {
276            Binding::Press(_) => 1,
277            Binding::Axis { .. } => 2,
278            Binding::Stick { .. } => 4,
279            Binding::PadAxis(_) | Binding::PadStick(_) => 0,
280        }
281    }
282
283    fn part(&self, part: usize) -> Option<Source> {
284        if part >= self.part_count() {
285            return None;
286        }
287        self.sources().get(part).copied()
288    }
289
290    fn with_part(&self, part: usize, source: Source) -> Option<Binding> {
291        let mut binding = *self;
292        match &mut binding {
293            Binding::Press(slot) if part == 0 => *slot = source,
294            Binding::Axis { negative, .. } if part == 0 => *negative = source,
295            Binding::Axis { positive, .. } if part == 1 => *positive = source,
296            Binding::Stick { up, .. } if part == 0 => *up = source,
297            Binding::Stick { down, .. } if part == 1 => *down = source,
298            Binding::Stick { left, .. } if part == 2 => *left = source,
299            Binding::Stick { right, .. } if part == 3 => *right = source,
300            _ => return None,
301        }
302        Some(binding)
303    }
304
305    pub fn controls(&self) -> Vec<Control> {
306        match *self {
307            Binding::PadAxis(axis) => vec![Control::Stick(axis.stick())],
308            Binding::PadStick(stick) => vec![Control::Stick(stick)],
309            Binding::Stick {
310                up: Source::Button(Button::DPadUp),
311                down: Source::Button(Button::DPadDown),
312                left: Source::Button(Button::DPadLeft),
313                right: Source::Button(Button::DPadRight),
314            } => vec![Control::DPad],
315            _ => {
316                let mut controls = Vec::new();
317                for source in self.sources() {
318                    let control = source.control();
319                    if !controls.contains(&control) {
320                        controls.push(control);
321                    }
322                }
323                controls
324            }
325        }
326    }
327}
328
329#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
330pub enum Set {
331    Keyboard,
332    Gamepad,
333}
334
335#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
336pub struct Slot {
337    pub set: Set,
338    pub index: usize,
339    pub part: usize,
340}
341
342impl Slot {
343    pub fn keyboard(index: usize, part: usize) -> Self {
344        Self {
345            set: Set::Keyboard,
346            index,
347            part,
348        }
349    }
350}
351
352#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
353pub enum OnConflict {
354    Refuse,
355    Swap,
356    Replace,
357}
358
359#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
360pub struct Conflict {
361    pub action: String,
362    pub slot: Slot,
363    pub source: Source,
364}
365
366#[derive(Clone, Debug, PartialEq, Eq)]
367pub enum RebindError {
368    UnknownAction(String),
369    NoSlot(Slot),
370    WrongDevice(Source),
371    Conflicts(Vec<Conflict>),
372}
373
374impl fmt::Display for RebindError {
375    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
376        match self {
377            RebindError::UnknownAction(name) => write!(f, "no action named {name:?}"),
378            RebindError::NoSlot(slot) => write!(f, "no binding at {slot:?}"),
379            RebindError::WrongDevice(source) => {
380                write!(f, "{source:?} is not a keyboard or mouse control")
381            }
382            RebindError::Conflicts(conflicts) => {
383                write!(f, "already bound to ")?;
384                for (index, conflict) in conflicts.iter().enumerate() {
385                    if index > 0 {
386                        write!(f, ", ")?;
387                    }
388                    write!(f, "{}", conflict.action)?;
389                }
390                Ok(())
391            }
392        }
393    }
394}
395
396impl std::error::Error for RebindError {}
397
398#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
399pub struct BindingMap {
400    pub keyboard: BTreeMap<String, Vec<Binding>>,
401    pub gamepad: BTreeMap<String, Vec<Binding>>,
402}
403
404impl BindingMap {
405    pub fn defaults(actions: &Actions) -> Self {
406        let mut map = Self::default();
407        for (_, spec) in actions.iter() {
408            map.keyboard
409                .insert(spec.name.clone(), spec.keyboard.clone());
410            map.gamepad.insert(spec.name.clone(), spec.gamepad.clone());
411        }
412        map
413    }
414
415    pub fn fit(&mut self, actions: &Actions) {
416        self.keyboard.retain(|name, _| actions.id(name).is_some());
417        self.gamepad.retain(|name, _| actions.id(name).is_some());
418        for (_, spec) in actions.iter() {
419            self.keyboard
420                .entry(spec.name.clone())
421                .or_insert_with(|| spec.keyboard.clone());
422            self.gamepad
423                .entry(spec.name.clone())
424                .or_insert_with(|| spec.gamepad.clone());
425        }
426    }
427
428    pub fn reset(&mut self, actions: &Actions, name: &str) -> Result<(), RebindError> {
429        let id = actions
430            .id(name)
431            .ok_or_else(|| RebindError::UnknownAction(name.to_owned()))?;
432        let spec = actions.spec(id);
433        self.keyboard
434            .insert(spec.name.clone(), spec.keyboard.clone());
435        self.gamepad.insert(spec.name.clone(), spec.gamepad.clone());
436        Ok(())
437    }
438
439    pub fn set(&self, set: Set) -> &BTreeMap<String, Vec<Binding>> {
440        match set {
441            Set::Keyboard => &self.keyboard,
442            Set::Gamepad => &self.gamepad,
443        }
444    }
445
446    fn set_mut(&mut self, set: Set) -> &mut BTreeMap<String, Vec<Binding>> {
447        match set {
448            Set::Keyboard => &mut self.keyboard,
449            Set::Gamepad => &mut self.gamepad,
450        }
451    }
452
453    pub fn bindings(&self, set: Set, name: &str) -> &[Binding] {
454        self.set(set).get(name).map_or(&[], Vec::as_slice)
455    }
456
457    pub fn source_at(&self, name: &str, slot: Slot) -> Option<Source> {
458        self.bindings(slot.set, name)
459            .get(slot.index)?
460            .part(slot.part)
461    }
462
463    pub fn conflicts(
464        &self,
465        actions: &Actions,
466        name: &str,
467        slot: Slot,
468        source: Source,
469    ) -> Vec<Conflict> {
470        let Some(id) = actions.id(name) else {
471            return Vec::new();
472        };
473        let context = &actions.spec(id).context;
474        let mut found = Vec::new();
475        for (other_name, bindings) in self.set(slot.set) {
476            let Some(other) = actions.id(other_name) else {
477                continue;
478            };
479            if &actions.spec(other).context != context {
480                continue;
481            }
482            for (index, binding) in bindings.iter().enumerate() {
483                for part in 0..binding.part_count() {
484                    let at = Slot {
485                        set: slot.set,
486                        index,
487                        part,
488                    };
489                    if other_name == name && at == slot {
490                        continue;
491                    }
492                    if binding.part(part) == Some(source) {
493                        found.push(Conflict {
494                            action: other_name.clone(),
495                            slot: at,
496                            source,
497                        });
498                    }
499                }
500            }
501        }
502        found
503    }
504
505    pub fn rebind_key(
506        &mut self,
507        actions: &Actions,
508        name: &str,
509        slot: Slot,
510        source: Source,
511        on_conflict: OnConflict,
512    ) -> Result<Vec<Conflict>, RebindError> {
513        if slot.set != Set::Keyboard || !(source.is_keyboard() || source.is_mouse()) {
514            return Err(RebindError::WrongDevice(source));
515        }
516        self.rebind(actions, name, slot, source, on_conflict)
517    }
518
519    pub fn rebind(
520        &mut self,
521        actions: &Actions,
522        name: &str,
523        slot: Slot,
524        source: Source,
525        on_conflict: OnConflict,
526    ) -> Result<Vec<Conflict>, RebindError> {
527        if actions.id(name).is_none() {
528            return Err(RebindError::UnknownAction(name.to_owned()));
529        }
530        let previous = self
531            .source_at(name, slot)
532            .ok_or(RebindError::NoSlot(slot))?;
533        let conflicts = self.conflicts(actions, name, slot, source);
534        let mut slot = slot;
535        if !conflicts.is_empty() {
536            match on_conflict {
537                OnConflict::Refuse => return Err(RebindError::Conflicts(conflicts)),
538                OnConflict::Swap => {
539                    for conflict in &conflicts {
540                        self.write(&conflict.action, conflict.slot, previous);
541                    }
542                }
543                OnConflict::Replace => {
544                    let mut dropped: Vec<(&str, usize)> = Vec::new();
545                    for conflict in conflicts.iter().rev() {
546                        if conflict.action == name && conflict.slot.index == slot.index {
547                            self.write(name, conflict.slot, previous);
548                            continue;
549                        }
550                        if dropped.contains(&(conflict.action.as_str(), conflict.slot.index)) {
551                            continue;
552                        }
553                        dropped.push((conflict.action.as_str(), conflict.slot.index));
554                        self.drop_binding(&conflict.action, conflict.slot);
555                        if conflict.action == name && conflict.slot.index < slot.index {
556                            slot.index -= 1;
557                        }
558                    }
559                }
560            }
561        }
562        self.write(name, slot, source);
563        Ok(conflicts)
564    }
565
566    fn write(&mut self, name: &str, slot: Slot, source: Source) {
567        if let Some(binding) = self
568            .set_mut(slot.set)
569            .get_mut(name)
570            .and_then(|bindings| bindings.get_mut(slot.index))
571            && let Some(next) = binding.with_part(slot.part, source)
572        {
573            *binding = next;
574        }
575    }
576
577    fn drop_binding(&mut self, name: &str, slot: Slot) {
578        if let Some(bindings) = self.set_mut(slot.set).get_mut(name)
579            && slot.index < bindings.len()
580        {
581            bindings.remove(slot.index);
582        }
583    }
584
585    pub fn add(&mut self, name: &str, set: Set, binding: Binding) {
586        self.set_mut(set)
587            .entry(name.to_owned())
588            .or_default()
589            .push(binding);
590    }
591
592    pub fn remove(&mut self, name: &str, set: Set, index: usize) -> Option<Binding> {
593        let bindings = self.set_mut(set).get_mut(name)?;
594        (index < bindings.len()).then(|| bindings.remove(index))
595    }
596}