use std::fmt;
use std::path::PathBuf;
use std::time::Duration;
use crate::View;
use playr_core::audio::Mode;
#[derive(Debug, Clone, PartialEq)]
pub enum Action {
Quit,
Help,
CommandHelp,
ShowView(View),
NextView,
Cursor(i64),
CursorFirst,
CursorLast,
StartSearch,
Search(String),
ClearSearch,
StartCommand,
Activate,
Add,
Remove,
MoveTrack(i64),
ClearSelection,
StartSave,
SaveAs(String),
DeletePlaylist,
StartRename,
RenameTo(String),
PlayPlaylist(String),
Scan(PathBuf),
Rescan,
ShowRoots,
ForgetRoot(PathBuf),
Prune(Option<PathBuf>),
Open(Vec<PathBuf>),
TogglePause,
Next,
Prev,
Stop,
SeekBy(i64),
SeekTo(Duration),
VolumeBy(f32),
SetVolume(f32),
SpeedBy(i32),
SetSpeed(i32),
CycleMode(bool),
SetMode(Mode),
Mark,
MarkAt(Duration),
UndoMark,
ClearMarks,
NextMark,
PrevMark,
Slice(Slicing),
Zoom(Zoom),
Display(Option<crate::Display>),
Nudge(Nudge),
Snap(Option<bool>),
RangeIn,
RangeOut,
SetRange(Option<(Duration, Duration)>),
Loop(Option<bool>),
MoveCursor(Nudge),
SetCursor(Option<Duration>),
PickMark(bool),
MoveMark(Nudge),
MoveMarkTo(Duration),
SnapMark,
DeleteMark,
Audition,
PickEdge(crate::sampler::Edge),
MoveEdge(Nudge),
WriteSlices,
DiscardSlices,
Theme(crate::Theme),
Map {
view: Option<View>,
key: Key,
action: Option<Box<Action>>,
},
Unmap {
view: Option<View>,
key: Key,
},
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Zoom {
In,
Out,
All,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Nudge {
Columns(i64),
Percent(i64),
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub enum Slicing {
Region,
Marks,
Equal(usize),
Onsets(Option<f32>),
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum KeyCode {
Char(char),
F(u8),
Enter,
Esc,
Tab,
BackTab,
Backspace,
Delete,
Insert,
Up,
Down,
Left,
Right,
Home,
End,
PageUp,
PageDown,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
pub struct Modifiers {
pub ctrl: bool,
pub alt: bool,
pub shift: bool,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct Key {
code: KeyCode,
mods: Modifiers,
}
const KEY_NAMES: &[(&str, KeyCode)] = &[
("space", KeyCode::Char(' ')),
("enter", KeyCode::Enter),
("esc", KeyCode::Esc),
("tab", KeyCode::Tab),
("backtab", KeyCode::BackTab),
("backspace", KeyCode::Backspace),
("delete", KeyCode::Delete),
("insert", KeyCode::Insert),
("up", KeyCode::Up),
("down", KeyCode::Down),
("left", KeyCode::Left),
("right", KeyCode::Right),
("home", KeyCode::Home),
("end", KeyCode::End),
("pageup", KeyCode::PageUp),
("pagedown", KeyCode::PageDown),
];
const PREFIXES: [&str; 3] = ["ctrl-", "alt-", "shift-"];
impl Key {
pub fn new(code: KeyCode, mut mods: Modifiers) -> Self {
let code = match code {
KeyCode::Char(c) => {
mods.shift = false;
if mods.ctrl {
KeyCode::Char(c.to_ascii_lowercase())
} else {
code
}
}
KeyCode::BackTab => {
mods.shift = false;
code
}
_ => code,
};
Key { code, mods }
}
pub fn parse(text: &str) -> Result<Key, String> {
let mut rest = text;
let mut mods = Modifiers::default();
'prefixes: loop {
for prefix in PREFIXES {
if let Some(r) = rest.strip_prefix(prefix).filter(|r| !r.is_empty()) {
match prefix {
"ctrl-" => mods.ctrl = true,
"alt-" => mods.alt = true,
_ => mods.shift = true,
}
rest = r;
continue 'prefixes;
}
}
break;
}
let mut chars = rest.chars();
let code = match (chars.next(), chars.next()) {
(Some(c), None) => KeyCode::Char(c),
_ => {
let lower = rest.to_ascii_lowercase();
let f = lower
.strip_prefix('f')
.and_then(|n| n.parse::<u8>().ok())
.filter(|n| (1..=12).contains(n));
match (KEY_NAMES.iter().find(|(n, _)| *n == lower), f) {
(Some((_, code)), _) => *code,
(None, Some(n)) => KeyCode::F(n),
(None, None) => return Err(format!("not a key: {text}")),
}
}
};
Ok(Key::new(code, mods))
}
}
impl fmt::Display for Key {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let held = [self.mods.ctrl, self.mods.alt, self.mods.shift];
for (prefix, held) in PREFIXES.iter().zip(held) {
if held {
f.write_str(prefix)?;
}
}
if let Some((name, _)) = KEY_NAMES.iter().find(|(_, c)| *c == self.code) {
return f.write_str(name);
}
match self.code {
KeyCode::Char(c) => write!(f, "{c}"),
KeyCode::F(n) => write!(f, "f{n}"),
other => write!(f, "{other:?}"),
}
}
}
#[derive(Debug, Clone, PartialEq)]
pub struct Binding {
pub view: Option<View>,
pub key: Key,
pub action: Option<Action>,
}
#[derive(Debug, Clone, PartialEq)]
pub struct Keymap {
bindings: Vec<Binding>,
}
impl Default for Keymap {
fn default() -> Self {
crate::config::Config::default().keys
}
}
impl Keymap {
pub fn empty() -> Self {
Keymap {
bindings: Vec::new(),
}
}
pub fn lookup(&self, key: Key, view: View) -> Option<&Action> {
let find = |v| self.bindings.iter().find(|b| b.key == key && b.view == v);
find(Some(view)).or_else(|| find(None))?.action.as_ref()
}
pub fn bind(&mut self, view: Option<View>, key: Key, action: Option<Action>) {
let binding = Binding { view, key, action };
match self
.bindings
.iter_mut()
.find(|b| b.key == key && b.view == view)
{
Some(old) => *old = binding,
None => self.bindings.push(binding),
}
}
pub fn unbind(&mut self, view: Option<View>, key: Key) -> bool {
let before = self.bindings.len();
self.bindings.retain(|b| !(b.key == key && b.view == view));
self.bindings.len() < before
}
pub fn bindings(&self) -> &[Binding] {
&self.bindings
}
}