diff --git a/RELEASES.md b/RELEASES.md index 7fe4dac1..fc37da31 100644 --- a/RELEASES.md +++ b/RELEASES.md @@ -11,6 +11,13 @@ ### Usability - Implemented `Eq` for `Timing` and `InputMap`. +- `UserInput` is now generic over the maximum size of inputs for a `Chord` (the default is still `8`). + - If an iterator with too many inputs is passed to `UserInput::chord`, the rest of the inputs are now ignored instead of panicing. + - If you want to react to this case, the new `UserInput::try_make_chord` function returns a `Result` instead. + +### Breaking changes + +- `UserInput::chord` does not panic anymore if you provide an iterator with more inputs than the maximum allowed number. Instead, the rest of the elements are silently ignored. ## Version 0.5 diff --git a/src/user_input.rs b/src/user_input.rs index 61ccbecb..afb22c6b 100644 --- a/src/user_input.rs +++ b/src/user_input.rs @@ -11,32 +11,41 @@ use crate::{ buttonlike::{MouseMotionDirection, MouseWheelDirection}, }; +/// An [`UserInput::Chord`] could not be created because there were too many input elements +#[derive(Clone, Copy, PartialEq, Eq, Debug)] +pub struct ChordCreationError; + /// Some combination of user input, which may cross [`Input`]-mode boundaries /// /// Suitable for use in an [`InputMap`](crate::input_map::InputMap) #[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)] -pub enum UserInput { +pub enum UserInput { /// A single button Single(InputKind), /// A combination of buttons, pressed simultaneously /// - /// Up to 8 (!!) buttons can be chorded together at once. + /// The number of buttons you can chord together is configurable via the `CHORD_MAX_SIZE` generic (8 by default). /// Chords are considered to belong to all of the [InputMode]s of their constituent buttons. - Chord(PetitSet), + Chord(PetitSet), /// A virtual DPad that you can get an [`AxisPair`] from VirtualDPad(VirtualDPad), } -impl UserInput { +impl UserInput { /// Creates a [`UserInput::Chord`] from an iterator of [`InputKind`]s /// /// If `inputs` has a length of 1, a [`UserInput::Single`] variant will be returned instead. + /// + /// If `inputs` contains more than `CHORD_MAX_SIZE` elements, the rest of the elements are ignored. + /// If you want to react to this case with a [`Result`], use [`UserInput::try_make_chord`] instead. pub fn chord(inputs: impl IntoIterator>) -> Self { // We can't just check the length unless we add an ExactSizeIterator bound :( - let mut length: u8 = 0; + let mut length: usize = 0; + + let mut set: PetitSet = PetitSet::default(); - let mut set: PetitSet = PetitSet::default(); - for button in inputs { + // If the iterator contains more than the maximum number of elements, ignore the rest + for button in inputs.into_iter().take(CHORD_MAX_SIZE) { length += 1; set.insert(button.into()); } @@ -47,6 +56,38 @@ impl UserInput { } } + /// Creates a [`UserInput::Chord`] from an iterator of [`InputKind`]s + /// + /// If `inputs` has a length of 1, a [`UserInput::Single`] variant will be returned instead. + /// + /// If `inputs` contains more than `CHORD_MAX_SIZE` elements, a [`ChordCreationError`] is returned. + /// If you want to silently ignore the rest of the elements, use the [`UserInput::chord`] function instead. + pub fn try_make_chord( + inputs: impl IntoIterator>, + ) -> Result { + // We can't just check the length unless we add an ExactSizeIterator bound :( + let mut length: usize = 0; + + let mut set: PetitSet = PetitSet::default(); + + // If the iterator contains more than the maximum number of elements, ignore the rest + for button in inputs.into_iter().take(CHORD_MAX_SIZE) { + length += 1; + + if length >= CHORD_MAX_SIZE { + return Err(ChordCreationError); + } + set.insert(button.into()); + } + + let user_input = match length { + 1 => UserInput::Single(set.into_iter().next().unwrap()), + _ => UserInput::Chord(set), + }; + + Ok(user_input) + } + /// The number of logical inputs that make up the [`UserInput`]. /// /// - A [`Single`][UserInput::Single] input returns 1