diff --git a/config/src/shortcuts/gesture.rs b/config/src/shortcuts/gesture.rs new file mode 100644 index 0000000..44baf51 --- /dev/null +++ b/config/src/shortcuts/gesture.rs @@ -0,0 +1,201 @@ +use std::str::FromStr; + +// SPDX-License-Identifier: MPL-2.0 +use serde::{Deserialize, Serialize}; + +/// Description of a gesture that can be handled by the compositor +#[serde_with::serde_as] +#[derive(Clone, Debug, Deserialize, PartialEq, Eq, Serialize, Hash)] +#[serde(deny_unknown_fields)] +pub struct Gesture { + /// How many fingers are held down + pub fingers: i32, + pub direction: Direction, + // A custom description for a custom binding + #[serde(default, skip_serializing_if = "Option::is_none")] + pub description: Option, +} + +/// Describes a direction, either absolute or relative +#[serde_with::serde_as] +#[derive(Clone, Debug, Deserialize, PartialEq, Eq, Serialize, Hash)] +#[serde(deny_unknown_fields)] +pub enum Direction { + Relative(RelativeDirection), + Absolute(AbsoluteDirection), +} + +impl ToString for Direction { + fn to_string(&self) -> String { + match self { + Direction::Absolute(abs) => match abs { + AbsoluteDirection::AbsoluteUp => "AbsoluteUp".to_string(), + AbsoluteDirection::AbsoluteDown => "AbsoluteDown".to_string(), + AbsoluteDirection::AbsoluteLeft => "AbsoluteLeft".to_string(), + AbsoluteDirection::AbsoluteRight => "AbsoluteRight".to_string(), + }, + Direction::Relative(rel) => match rel { + RelativeDirection::RelativeForward => "RelativeForward".to_string(), + RelativeDirection::RelativeBackward => "RelativeBackward".to_string(), + RelativeDirection::RelativeLeft => "RelativeLeft".to_string(), + RelativeDirection::RelativeRight => "RelativeRight".to_string(), + }, + } + } +} + +impl FromStr for Direction { + type Err = String; + + fn from_str(s: &str) -> Result { + match s { + // Absolute directions + "AbsoluteUp" => Ok(Direction::Absolute(AbsoluteDirection::AbsoluteUp)), + "AbsoluteDown" => Ok(Direction::Absolute(AbsoluteDirection::AbsoluteDown)), + "AbsoluteLeft" => Ok(Direction::Absolute(AbsoluteDirection::AbsoluteLeft)), + "AbsoluteRight" => Ok(Direction::Absolute(AbsoluteDirection::AbsoluteRight)), + // Relative directions + "RelativeForward" => Ok(Direction::Relative(RelativeDirection::RelativeForward)), + "RelativeBackward" => Ok(Direction::Relative(RelativeDirection::RelativeBackward)), + "RelativeLeft" => Ok(Direction::Relative(RelativeDirection::RelativeLeft)), + "RelativeRight" => Ok(Direction::Relative(RelativeDirection::RelativeRight)), + _ => Err(format!("Invalid direction string")) + } + } +} + +/// Describes a relative direction (typically relative to the workspace direction) +#[serde_with::serde_as] +#[derive(Clone, Debug, Deserialize, PartialEq, Eq, Serialize, Hash)] +#[serde(deny_unknown_fields)] +pub enum RelativeDirection { + RelativeForward, + RelativeBackward, + RelativeLeft, + RelativeRight, +} + +/// Describes an absolute direction (i.e. not relative to workspace direction) +#[serde_with::serde_as] +#[derive(Clone, Debug, Deserialize, PartialEq, Eq, Serialize, Hash)] +#[serde(deny_unknown_fields)] +pub enum AbsoluteDirection { + AbsoluteUp, + AbsoluteDown, + AbsoluteLeft, + AbsoluteRight, +} + +impl Gesture { + /// Creates a new gesture from a number of fingers and a direction + pub fn new(fingers: impl Into, direction: impl Into) -> Gesture { + Gesture { + fingers: fingers.into(), + direction: direction.into(), + description: None, + } + } + + /// Returns true if the direction is absolute + pub fn is_absolute(&self) -> bool { + matches!(self.direction, Direction::Absolute(_)) + } + + /// Append the binding to an existing string + pub fn to_string_in_place(&self, string: &mut String) { + string.push_str(&format!( + "{} Finger {}", + self.fingers, + self.direction.to_string() + )); + } +} + +impl ToString for Gesture { + fn to_string(&self) -> String { + let mut string = String::new(); + self.to_string_in_place(&mut string); + string + } +} + + +impl FromStr for Gesture { + type Err = String; + + fn from_str(value: &str) -> Result { + let mut value_iter = value.split("+"); + let n = match value_iter.next() { + Some(val) => val, + None => { + return Err(format!("no value for the number of fingers")); + }, + }; + let fingers = match i32::from_str(n) { + Ok(a) => a, + Err(_) => { + return Err(format!("could not parse number of fingers")); + }, + }; + + let n2 = match value_iter.next() { + Some(val) => val, + None => { + return Err(format!("could not parse direction")); + }, + }; + + let direction = match Direction::from_str(n2) { + Ok(dir) => dir, + Err(e) => { + return Err(e); + }, + }; + + if let Some(n3) = value_iter.next() { + return Err(format!("Extra data {} not expected", n3)); + } + + return Ok(Self { + fingers, + direction, + description: None, + }); + } +} + + +#[cfg(test)] +mod tests { + use crate::shortcuts::gesture::{AbsoluteDirection, Direction, RelativeDirection}; + + use super::Gesture; + use std::str::FromStr; + + #[test] + fn binding_from_str() { + assert_eq!( + Gesture::from_str("3+RelativeLeft"), + Ok(Gesture::new( + 3, + Direction::Relative(RelativeDirection::RelativeLeft) + )) + ); + + assert_eq!( + Gesture::from_str("5+AbsoluteUp"), + Ok(Gesture::new( + 5, + Direction::Absolute(AbsoluteDirection::AbsoluteUp) + )) + ); + + assert_ne!( + Gesture::from_str("4+AbsoluteLeft+More+Info"), + Ok(Gesture::new( + 4, + Direction::Absolute(AbsoluteDirection::AbsoluteLeft) + )) + ); + } +} diff --git a/config/src/shortcuts/mod.rs b/config/src/shortcuts/mod.rs index f069855..7a04a76 100644 --- a/config/src/shortcuts/mod.rs +++ b/config/src/shortcuts/mod.rs @@ -8,7 +8,9 @@ pub mod modifier; pub use modifier::{Modifier, Modifiers, ModifiersDef}; mod binding; +mod gesture; pub use binding::Binding; +pub use gesture::Gesture; pub mod sym; @@ -80,6 +82,8 @@ pub fn system_actions(context: &cosmic_config::Config) -> SystemActions { pub struct Config { pub defaults: Shortcuts, pub custom: Shortcuts, + pub default_gestures: Gestures, + pub custom_gestures: Gestures, pub system_actions: SystemActions, } @@ -97,6 +101,18 @@ impl Config { .shortcut_for_action(action) .or_else(|| self.defaults.shortcut_for_action(action)) } + + pub fn gestures(&self) -> impl Iterator { + self.custom_gestures + .iter() + .chain(self.default_gestures.iter()) + } + + pub fn gesture_for_action(&self, action: &Action) -> Option { + self.custom_gestures + .gesture_for_action(action) + .or_else(|| self.default_gestures.gesture_for_action(action)) + } } /// A map of defined key [Binding]s and their triggerable [Action]s @@ -172,3 +188,49 @@ pub enum State { Pressed, Released, } + +/// A map of defined [Gesture]s and their triggerable [Action]s +#[derive(Clone, Debug, Default, PartialEq, Deserialize, Serialize)] +#[serde(transparent)] +pub struct Gestures(pub HashMap); + +impl Gestures { + pub fn insert_default_gesture( + &mut self, + fingers: i32, + direction: gesture::Direction, + action: Action, + ) { + if !self.0.values().any(|a| a == &action) { + let pattern = Gesture { + description: None, + fingers, + direction, + }; + if !self.0.contains_key(&pattern) { + self.0.insert(pattern, action.clone()); + } + } + } + + pub fn iter(&self) -> impl Iterator { + self.0.iter() + } + + pub fn iter_mut(&mut self) -> impl Iterator { + self.0.iter_mut() + } + + pub fn gesture_for_action(&self, action: &Action) -> Option { + self.gestures(action) + .next() // take the first one + .map(|gesture| gesture.to_string()) + } + + pub fn gestures<'a>(&'a self, action: &'a Action) -> impl Iterator { + self.0 + .iter() + .filter(move |(_, a)| *a == action) + .map(|(b, _)| b) + } +}