Skip to content

Commit

Permalink
feat: Gesture configuration in shortcuts config
Browse files Browse the repository at this point in the history
This commit adds a Gesture abstraction, which can define gestures by the
number of fingers used, and the direction the gesture is performed in.

Oftentimes, we want the gesture to be either absolute (i.e. ignoring
context such as workspace direction), or relative (if workspaces
are horizontal, forward would be right, backward would be left. if
workspaces are vertical, forward would be up, backward would be down).

The abstraction is modeled after the Binding type, and the resulting
Gestures type is similar to the Shortcuts type.

This is the first step in implementing configurable touchpad gestures
(pop-os/cosmic-comp#396)

Signed-off-by: Ryan Brue <[email protected]>
  • Loading branch information
ryanabx committed Dec 20, 2024
1 parent 9d9ad8e commit bb6cd68
Show file tree
Hide file tree
Showing 2 changed files with 263 additions and 0 deletions.
201 changes: 201 additions & 0 deletions config/src/shortcuts/gesture.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,201 @@
// SPDX-License-Identifier: MPL-2.0
use std::str::FromStr;

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<String>,
}

/// 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<Self, Self::Err> {
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<i32>, direction: impl Into<Direction>) -> 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<Self, Self::Err> {
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)
))
);
}
}
62 changes: 62 additions & 0 deletions config/src/shortcuts/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down Expand Up @@ -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,
}

Expand All @@ -97,6 +101,18 @@ impl Config {
.shortcut_for_action(action)
.or_else(|| self.defaults.shortcut_for_action(action))
}

pub fn gestures(&self) -> impl Iterator<Item = (&Gesture, &Action)> {
self.custom_gestures
.iter()
.chain(self.default_gestures.iter())
}

pub fn gesture_for_action(&self, action: &Action) -> Option<String> {
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
Expand Down Expand Up @@ -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<Gesture, Action>);

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<Item = (&Gesture, &Action)> {
self.0.iter()
}

pub fn iter_mut(&mut self) -> impl Iterator<Item = (&Gesture, &mut Action)> {
self.0.iter_mut()
}

pub fn gesture_for_action(&self, action: &Action) -> Option<String> {
self.gestures(action)
.next() // take the first one
.map(|gesture| gesture.to_string())
}

pub fn gestures<'a>(&'a self, action: &'a Action) -> impl Iterator<Item = &'a Gesture> {
self.0
.iter()
.filter(move |(_, a)| *a == action)
.map(|(b, _)| b)
}
}

0 comments on commit bb6cd68

Please sign in to comment.