gooey-rs 0.12.1

Tile-based UI library with audio support
Documentation
use std;
use derive_more::{From, TryInto};
use parse_display::{Display, FromStr};
use strum::{EnumCount, FromRepr};
use crate::interface::controller::controls;
use super::Modifiers;

pub mod keycode;
pub use self::keycode::Keycode;

#[derive(Clone, Copy, Debug, Eq)]
pub struct Button {
  pub variant   : Variant,
  pub modifiers : Modifiers
}

#[derive(Clone, Copy, Debug, Eq, Ord, PartialEq, PartialOrd)]
pub enum State {
  Pressed,
  Released
}

/// Identifies which button was pressed.
///
/// NOTE: the types contained in this newtype wrapper should have unique
/// serializations, this is so we can omit the tag when giving bindings in
/// configuration files (see `control::button::Binding`).
///
/// `serde_plain` is unable to parse untagged enums, so we use the
/// `parse_display::FromStr` derive instead.
// TODO: joystick buttons ?
#[derive(Clone, Copy, Debug, Eq, Ord, PartialEq, PartialOrd, Display, FromStr, From,
  TryInto)]
#[display("{0:?}")]  // an "untagged" serialization format for FromStr
pub enum Variant {
  Keycode (Keycode),
  Mouse   (Mouse)
}

/// A mouse button
#[derive(Clone, Copy, Debug, Eq, Ord, PartialEq, PartialOrd, EnumCount, FromRepr,
  FromStr)]
pub enum Mouse {
  Mouse1, Mouse2, Mouse3, Mouse4, Mouse5
}

/// Binds an input button to a button control and implements custom serialization format
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct Binding <E : controls::Id <controls::Button> = controls::Nil>
  (pub Button, pub controls::button::Control <E>, pub std::marker::PhantomData <E>);

impl <E : controls::Id <controls::Button>> Binding <E> {
  pub fn new (button : Button, control : controls::button::Control <E>) -> Self {
    Binding (button, control, Default::default())
  }
}
impl <E : controls::Id <controls::Button>>
  From <controls::button::Binding <E>> for Binding <E>
{
  fn from (controls::button::Binding (control, input, _) : controls::button::Binding <E>)
    -> Self
  {
    Binding::new (input, control)
  }
}
impl <E : controls::Id <controls::Button>>
  From <Binding <E>> for (Button, controls::button::Control <E>)
{
  fn from (Binding (input, control, _) : Binding <E>) -> Self {
    (input, control)
  }
}
impl <E : controls::Id <controls::Button>>
  From <(Button, controls::button::Control <E>)> for Binding <E>
{
  fn from ((input, control) : (Button, controls::button::Control <E>)) -> Self {
    Binding::new (input, control)
  }
}

impl Button {
  pub const fn with_modifiers (self, modifiers : Modifiers) -> Self {
    Button { modifiers, .. self }
  }
}

impl PartialEq for Button {
  fn eq (&self, other : &Self) -> bool {
    if self.variant != other.variant {
      false
    } else if self.modifiers.contains (Modifiers::ANY)
      || other.modifiers.contains (Modifiers::ANY)
    {
      true
    } else {
      self.modifiers == other.modifiers
    }
  }
}

// NOTE: we need to define partial ord to use custom partial eq comparison
#[expect(clippy::non_canonical_partial_ord_impl)]
impl PartialOrd for Button {
  fn partial_cmp (&self, other : &Self) -> Option <std::cmp::Ordering> {
    let out = if self == other {
      std::cmp::Ordering::Equal
    } else if self.variant > other.variant {
      std::cmp::Ordering::Greater
    } else if self.variant < other.variant {
      std::cmp::Ordering::Less
    } else if self.modifiers > other.modifiers {
      std::cmp::Ordering::Greater
    } else if self.modifiers < other.modifiers {
      std::cmp::Ordering::Less
    } else {
      unreachable!()
    };
    Some (out)
  }
}

// NOTE: we need to define ord to use custom partial ord comparison
impl Ord for Button {
  fn cmp (&self, other : &Self) -> std::cmp::Ordering {
    self.partial_cmp (other).unwrap()
  }
}

impl <K : Into <Variant>> From <K> for Button {
  fn from (k : K) -> Self {
    Button {
      variant:   k.into(),
      modifiers: Modifiers::empty()
    }
  }
}

//
// private
//

struct ButtonVisitor;

impl serde::Serialize for Button {
  fn serialize <S : serde::Serializer> (&self, serializer : S)
    -> Result <S::Ok, S::Error>
  {
    serializer.serialize_str (&format!("{}{}",
      self.variant, self.modifiers.to_string().to_uppercase()))
  }
}

impl serde::de::Visitor <'_> for ButtonVisitor {
  type Value = Button;
  fn expecting (&self, formatter : &mut std::fmt::Formatter) -> std::fmt::Result {
    formatter.write_str ("A control: button pair")
  }
  fn visit_str <ERR : serde::de::Error> (self, v : &str) -> Result <Self::Value, ERR> {
    use std::str::FromStr;
    let mut iter = v.split (':').next().unwrap().split ('+');
    let s        = iter.next().unwrap();
    let variant  = Variant::from_str (s).map_err (|err|{
      log::error!(err:?, string=s; "error parsing button");
      panic!("error parsing button string \"{s}\": {err}")
    }).unwrap();
    let mut modifiers = Modifiers::empty();
    for modifier in iter {
      match modifier.to_lowercase().as_str() {
        "shift" => modifiers |= Modifiers::SHIFT,
        "ctrl"  => modifiers |= Modifiers::CTRL,
        "alt"   => modifiers |= Modifiers::ALT,
        "super" => modifiers |= Modifiers::SUPER,
        "any"   => modifiers |= Modifiers::ANY,
        _ => return Err (
          ERR::custom (format!("Unrecognized modifier: {modifier:?}")))
      }
    }
    Ok (Button { variant, modifiers })
  }
}

impl <'de> serde::Deserialize <'de> for Button {
  fn deserialize <D : serde::Deserializer <'de>> (deserializer : D)
    -> Result <Self, D::Error>
  {
    deserializer.deserialize_str (ButtonVisitor)
  }
}

struct BindingVisitor <E : controls::Id <controls::Button> = controls::Nil> (
  std::marker::PhantomData <E>);

impl <E : controls::Id <controls::Button>> serde::Serialize for Binding <E> {
  fn serialize <S : serde::Serializer> (&self, serializer : S)
    -> Result <S::Ok, S::Error>
  {
    let input   = serde_plain::to_string (&self.0).unwrap();
    let control = serde_plain::to_string (&self.1).unwrap();
    serializer.serialize_str (&format!("{input}: {control}"))
  }
}

impl <E : controls::Id <controls::Button>>
  serde::de::Visitor <'_> for BindingVisitor <E>
{
  type Value = Binding <E>;
  fn expecting (&self, formatter : &mut std::fmt::Formatter) -> std::fmt::Result {
    formatter.write_str ("A button: control pair")
  }
  fn visit_str <ERR : serde::de::Error> (self, v : &str) -> Result <Self::Value, ERR> {
    let input = {
      let s = v.split (':').next().unwrap();
      serde_plain::from_str::<Button> (s).unwrap()
    };
    let control : controls::button::Control <E> = {
      let s = v.split (' ').nth (1).unwrap();
      serde_plain::from_str::<controls::button::Control <E>> (s).unwrap()
    };
    Ok (Binding::new (input, control))
  }
}

impl <'de, E : controls::Id <controls::Button>>
  serde::Deserialize <'de> for Binding <E>
{
  fn deserialize <D : serde::Deserializer <'de>> (deserializer : D)
    -> Result <Self, D::Error>
  {
    deserializer.deserialize_str (BindingVisitor (Default::default()))
  }
}

#[cfg(test)]
mod test {
  use strum;
  use crate::interface::controller::controls;

  use super::*;

  #[test]
  fn variant_unique_serialization() {
    use strum::EnumCount;
    for i in 0..Keycode::COUNT {
      use std::str::FromStr;
      let variant = Variant::from (Keycode::from_repr (i).unwrap());
      let s = variant.to_string();
      assert_eq!(variant, Variant::from_str (&s).unwrap());
    }

    for i in 0..Mouse::COUNT {
      use std::str::FromStr;
      let variant = Variant::from (Mouse::from_repr (i).unwrap());
      let s = variant.to_string();
      assert_eq!(variant, Variant::from_str (&s).unwrap());
    }
  }

  #[test]
  fn button_equality() {
    let a = Button { modifiers: Modifiers::ANY, .. Button::from (Keycode::A) };
    let b = Button { modifiers: Modifiers::SHIFT, .. Button::from (Keycode::A) };
    let c = Button { modifiers: Modifiers::ANY, .. Button::from (Keycode::C) };
    let d = Button::from (Keycode::C);
    assert_eq!(a, b);
    assert_eq!(&a, &b);
    assert_ne!(a, c);
    assert_eq!(c, d);
    assert_eq!(&c, &d);
    let buttons = [(a, controls::Button (0))];
    assert_eq!(a.cmp (&b), std::cmp::Ordering::Equal);
    let _ = buttons.binary_search_by_key (&b, |(button, _)| *button).unwrap();
  }
}