Skip to content

Commit 2f23faa

Browse files
authored
added mouse keybinds & keybind modifier (#91) (#92)
* action-secondary * keybind modifier * fix keybind modifier
1 parent 8e7dcee commit 2f23faa

File tree

7 files changed

+150
-63
lines changed

7 files changed

+150
-63
lines changed

src/config/defaults.rs

+3
Original file line numberDiff line numberDiff line change
@@ -98,6 +98,7 @@ pub fn layout() -> Vec<FumWidget> {
9898
text: "󰒮".to_string(),
9999
direction: Direction::default(),
100100
action: Some(Action::Prev),
101+
action_secondary: None,
101102
exec: None,
102103
bold: false,
103104
bg: None,
@@ -108,6 +109,7 @@ pub fn layout() -> Vec<FumWidget> {
108109
text: "$status-icon".to_string(),
109110
direction: Direction::default(),
110111
action: Some(Action::PlayPause),
112+
action_secondary: None,
111113
exec: None,
112114
bold: false,
113115
bg: None,
@@ -118,6 +120,7 @@ pub fn layout() -> Vec<FumWidget> {
118120
text: "󰒭".to_string(),
119121
direction: Direction::default(),
120122
action: Some(Action::Next),
123+
action_secondary: None,
121124
exec: None,
122125
bold: false,
123126
bg: None,

src/config/keybind.rs

+39-5
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
use crossterm::event::KeyCode;
1+
use crossterm::event::{KeyCode, KeyModifiers};
22
use serde::{de, Deserialize};
33

44
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
@@ -20,7 +20,8 @@ pub enum Keybind {
2020
Caps,
2121
F(u8),
2222
Char(char),
23-
Many(Vec<Keybind>)
23+
Many(Vec<Keybind>),
24+
WithModifier(KeyModifiers, Box<Keybind>)
2425
}
2526

2627
impl<'de> Deserialize<'de> for Keybind {
@@ -60,11 +61,11 @@ impl Keybind {
6061
Keybind::BackTab => KeyCode::BackTab,
6162
Keybind::Delete => KeyCode::Delete,
6263
Keybind::Insert => KeyCode::Insert,
63-
Keybind::Esc => KeyCode::Esc,
64-
Keybind::Caps => KeyCode::CapsLock,
64+
Keybind::Esc => KeyCode::Esc, Keybind::Caps => KeyCode::CapsLock,
6565
Keybind::F(u8) => KeyCode::F(*u8),
6666
Keybind::Char(char) => KeyCode::Char(*char),
67-
Keybind::Many(_) => unreachable!()
67+
Keybind::Many(_) => unreachable!(),
68+
Keybind::WithModifier(_, _) => unreachable!()
6869
}
6970
}
7071

@@ -88,18 +89,51 @@ impl Keybind {
8889
"insert" => Ok(Keybind::Insert),
8990
"caps" => Ok(Keybind::Caps),
9091
"esc" => Ok(Keybind::Esc),
92+
93+
// Fn keys.
9194
k if k.starts_with('f') => {
9295
match k[1..].parse::<u8>() {
9396
Ok(fn_num) => Ok(Keybind::F(fn_num)),
9497
Err(_) => Err(de::Error::custom("Invalid fn key format"))
9598
}
9699
},
100+
101+
// Individual key.
97102
k if k.len() == 1 => {
98103
match k.chars().next() {
99104
Some(char) => Ok(Keybind::Char(char)),
100105
None => Err(de::Error::custom(format!("Invalid keyboard key: {k}")))
101106
}
102107
},
108+
109+
k if k.contains("+") => {
110+
let split = k.split('+');
111+
let len = split.to_owned().count();
112+
113+
let modifier_keys = split.to_owned().take(len.saturating_sub(1)).collect::<Vec<&str>>();
114+
let key = split.to_owned().last().unwrap();
115+
116+
let mut modifiers = KeyModifiers::NONE;
117+
118+
for modifier in modifier_keys {
119+
let modifier = match modifier {
120+
"shift" => KeyModifiers::SHIFT,
121+
"ctrl" => KeyModifiers::CONTROL,
122+
"alt" => KeyModifiers::ALT,
123+
"super" => KeyModifiers::SUPER,
124+
"hyper" => KeyModifiers::HYPER,
125+
"meta" => KeyModifiers::META,
126+
_ => return Err(de::Error::custom(format!("Unknown key modifier: {modifier}")))
127+
};
128+
129+
modifiers = modifiers | modifier;
130+
}
131+
132+
let keybind = Keybind::parse_keybind(key)?;
133+
134+
Ok(Keybind::WithModifier(modifiers, Box::new(keybind)))
135+
},
136+
103137
_ => Err(de::Error::custom(format!("Unknown keybind: {keybind}")))
104138
}
105139
}

src/fum.rs

+90-53
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,21 @@ impl<'a> Fum<'a> {
8989
match keybind {
9090
Keybind::Many(keybinds) => {
9191
for keybind in keybinds {
92+
if let Keybind::WithModifier(modifiers, keybind) = keybind {
93+
if key.modifiers.intersects(*modifiers) {
94+
if key.code == keybind.into_keycode() {
95+
Action::run(action, self)?;
96+
}
97+
}
98+
} else {
99+
if key.code == keybind.into_keycode() {
100+
Action::run(action, self)?;
101+
}
102+
}
103+
}
104+
},
105+
Keybind::WithModifier(modifiers, keybind) => {
106+
if key.modifiers.intersects(*modifiers) {
92107
if key.code == keybind.into_keycode() {
93108
Action::run(action, self)?;
94109
}
@@ -102,70 +117,92 @@ impl<'a> Fum<'a> {
102117
}
103118
}
104119
},
105-
Event::Mouse(mouse) if mouse.kind == MouseEventKind::Down(MouseButton::Left) => {
106-
if let Some((action, exec)) = self.ui.click(mouse.column, mouse.row, &self.state.buttons) {
107-
let action = action.to_owned();
108-
let exec = exec.to_owned();
120+
Event::Mouse(mouse) => {
121+
match mouse.kind {
122+
// Button click mouse left.
123+
MouseEventKind::Down(MouseButton::Left) => {
124+
if let Some((action, _, exec)) = self.ui.click(mouse.column, mouse.row, &self.state.buttons) {
125+
let action = action.to_owned();
126+
let exec = exec.to_owned();
127+
128+
if let Some(action) = action {
129+
Action::run(&action, self)?;
130+
}
109131

110-
if let Some(action) = action {
111-
Action::run(&action, self)?;
112-
}
132+
if let Some(exec) = exec {
133+
let parts: Vec<&str> = exec.split_whitespace().collect();
134+
if let Some(command) = parts.get(0) {
135+
let _ = Command::new(command) // Ignore result
136+
.args(&parts[1..])
137+
.stdout(Stdio::null())
138+
.stderr(Stdio::null())
139+
.spawn();
140+
}
141+
}
142+
}
143+
},
144+
145+
// Button click mouse right.
146+
MouseEventKind::Down(MouseButton::Right) => {
147+
if let Some((_, action_secondary, _)) = self.ui.click(mouse.column, mouse.row, &self.state.buttons) {
148+
let action_secondary = action_secondary.to_owned();
113149

114-
if let Some(exec) = exec {
115-
let parts: Vec<&str> = exec.split_whitespace().collect();
116-
if let Some(command) = parts.get(0) {
117-
let _ = Command::new(command) // Ignore result
118-
.args(&parts[1..])
119-
.stdout(Stdio::null())
120-
.stderr(Stdio::null())
121-
.spawn();
150+
if let Some(action_secondary) = action_secondary {
151+
Action::run(&action_secondary, self)?;
152+
}
122153
}
123-
}
124-
}
125-
},
126-
Event::Mouse(mouse) if mouse.kind == MouseEventKind::Drag(MouseButton::Left) => {
127-
if !self.dragging && self.start_drag.is_none() {
128-
self.dragging = true;
129-
self.start_drag = Some(Position::new(mouse.column, mouse.row));
130-
}
154+
},
131155

132-
if self.dragging {
133-
self.current_drag = Some(Position::new(mouse.column, mouse.row));
134-
135-
if let Some(start_drag) = &self.start_drag {
136-
if let Some(current_drag) = &self.current_drag {
137-
if let Some((rect, direction, widget)) = self.ui.drag(start_drag, &self.state.sliders) {
138-
let value: f64 = match direction {
139-
Direction::Horizontal => ((current_drag.x as f64 - rect.x as f64) / rect.width as f64).clamp(0.0, 1.0),
140-
Direction::Vertical => (1.0 - ((current_drag.y as f64 - rect.y as f64) / rect.height as f64)).clamp(0.0, 1.0)
141-
};
142-
143-
match widget {
144-
SliderSource::Progress => {
145-
let position = value * self.state.meta.length.as_secs() as f64;
146-
if position >= self.state.meta.length.as_secs() as f64 {
147-
self.dragging = false;
148-
self.start_drag = None;
149-
self.current_drag = None;
150-
}
156+
// Mouse left drag.
157+
MouseEventKind::Drag(MouseButton::Left) => {
158+
if !self.dragging && self.start_drag.is_none() {
159+
self.dragging = true;
160+
self.start_drag = Some(Position::new(mouse.column, mouse.row));
161+
}
151162

152-
Action::run(&Action::Position(position as u64), self)?;
153-
},
154-
SliderSource::Volume => {
155-
let volume = value * 100.0;
156-
Action::run(&Action::Volume(VolumeType::Set(volume)), self)?;
163+
if self.dragging {
164+
self.current_drag = Some(Position::new(mouse.column, mouse.row));
165+
166+
if let Some(start_drag) = &self.start_drag {
167+
if let Some(current_drag) = &self.current_drag {
168+
if let Some((rect, direction, widget)) = self.ui.drag(start_drag, &self.state.sliders) {
169+
let value: f64 = match direction {
170+
Direction::Horizontal => ((current_drag.x as f64 - rect.x as f64) / rect.width as f64).clamp(0.0, 1.0),
171+
Direction::Vertical => (1.0 - ((current_drag.y as f64 - rect.y as f64) / rect.height as f64)).clamp(0.0, 1.0)
172+
};
173+
174+
match widget {
175+
SliderSource::Progress => {
176+
let position = value * self.state.meta.length.as_secs() as f64;
177+
if position >= self.state.meta.length.as_secs() as f64 {
178+
self.dragging = false;
179+
self.start_drag = None;
180+
self.current_drag = None;
181+
}
182+
183+
Action::run(&Action::Position(position as u64), self)?;
184+
},
185+
SliderSource::Volume => {
186+
let volume = value * 100.0;
187+
Action::run(&Action::Volume(VolumeType::Set(volume)), self)?;
188+
}
189+
}
157190
}
158191
}
159192
}
160193
}
161-
}
194+
},
195+
196+
// Mouse left release.
197+
MouseEventKind::Up(MouseButton::Left) => {
198+
self.dragging = false;
199+
self.start_drag = None;
200+
self.current_drag = None;
201+
},
202+
203+
_ => {}
162204
}
163205
},
164-
Event::Mouse(mouse) if mouse.kind == MouseEventKind::Up(MouseButton::Left) => {
165-
self.dragging = false;
166-
self.start_drag = None;
167-
self.current_drag = None;
168-
}
169206
Event::Resize(_, _) => {
170207
self.redraw = true;
171208
}

src/state.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ use crate::{action::Action, meta::Meta, widget::{Direction, SliderSource}};
77
pub struct FumState {
88
pub meta: Meta,
99
pub cover_art_ascii: String,
10-
pub buttons: HashMap<String, (Rect, Option<Action>, Option<String>)>,
10+
pub buttons: HashMap<String, (Rect, Option<Action>, Option<Action>, Option<String>)>,
1111
pub sliders: HashMap<String, (Rect, Direction, SliderSource)>,
1212
pub vars: HashMap<String, String>,
1313
pub parent_direction: Direction,

src/ui.rs

+8-2
Original file line numberDiff line numberDiff line change
@@ -15,11 +15,17 @@ impl<'a> Ui<'a> {
1515
}
1616
}
1717

18-
pub fn click(&self, x: u16, y: u16, buttons: &'a HashMap<String, (Rect, Option<Action>, Option<String>)>) -> Option<(&'a Option<Action>, &'a Option<String>)> {
19-
for (_, (rect, action, exec)) in buttons.iter() {
18+
pub fn click(
19+
&self,
20+
x: u16,
21+
y: u16,
22+
buttons: &'a HashMap<String, (Rect, Option<Action>, Option<Action>, Option<String>)>
23+
) -> Option<(&'a Option<Action>, &'a Option<Action>, &'a Option<String>)> {
24+
for (_, (rect, action, action_secondary, exec)) in buttons.iter() {
2025
if rect.contains(Position::new(x, y)) {
2126
return Some((
2227
action,
28+
action_secondary,
2329
exec
2430
))
2531
}

src/widget/button.rs

+7-2
Original file line numberDiff line numberDiff line change
@@ -5,12 +5,17 @@ use crate::{get_bold, get_color, state::FumState, text::replace_text};
55
use super::FumWidget;
66

77
pub fn render(widget: &FumWidget, area: Rect, buf: &mut Buffer, state: &mut FumState) {
8-
if let FumWidget::Button { id, text, action, exec, bold, bg, fg, .. } = widget {
8+
if let FumWidget::Button { id, text, action, action_secondary, exec, bold, bg, fg, .. } = widget {
99
let text = replace_text(text, state).to_string();
1010

1111
state.buttons.insert(
1212
id.to_string(),
13-
(area.clone(), action.to_owned(), exec.to_owned())
13+
(
14+
area.clone(),
15+
action.to_owned(),
16+
action_secondary.to_owned(),
17+
exec.to_owned()
18+
)
1419
);
1520

1621
let (bg, fg) = get_color!(bg, fg, &state.parent_bg, &state.parent_fg);

src/widget/widget.rs

+2
Original file line numberDiff line numberDiff line change
@@ -171,6 +171,8 @@ pub enum FumWidget {
171171
id: String,
172172
text: String,
173173
action: Option<Action>,
174+
#[serde(rename = "action-secondary")]
175+
action_secondary: Option<Action>,
174176
exec: Option<String>,
175177
#[serde(default = "Direction::default")]
176178
direction: Direction,

0 commit comments

Comments
 (0)