use std::collections::HashMap; use std::sync::Arc; use cursive::event::{Event, Key}; use cursive::views::ViewRef; use cursive::Cursive; use playlists::Playlists; use queue::{Queue, RepeatSetting}; use spotify::Spotify; use traits::ViewExt; use ui::layout::Layout; type CommandCb = dyn Fn(&mut Cursive, &[String]) -> Result, String>; #[derive(PartialEq)] pub enum CommandResult { Consumed(Option), Ignored, } pub struct CommandManager { callbacks: HashMap>>, aliases: HashMap, } impl CommandManager { pub fn new() -> CommandManager { CommandManager { callbacks: HashMap::new(), aliases: HashMap::new(), } } pub fn register_command>(&mut self, name: S, cb: Option>) { self.callbacks.insert(name.into(), cb); } pub fn register_aliases>(&mut self, name: S, aliases: Vec) { let name = name.into(); for a in aliases { self.aliases.insert(a.into(), name.clone()); } } pub fn register_all( &mut self, spotify: Arc, queue: Arc, playlists: Arc, ) { self.register_aliases("quit", vec!["q", "x"]); self.register_aliases("playpause", vec!["pause", "toggleplay", "toggleplayback"]); self.register_aliases("repeat", vec!["loop"]); self.register_command("search", None); self.register_command("move", None); self.register_command("play", None); self.register_command("queue", None); self.register_command("delete", None); self.register_command( "quit", Some(Box::new(move |s, _args| { s.quit(); Ok(None) })), ); { let queue = queue.clone(); self.register_command( "stop", Some(Box::new(move |_s, _args| { queue.stop(); Ok(None) })), ); } { let queue = queue.clone(); self.register_command( "previous", Some(Box::new(move |_s, _args| { queue.previous(); Ok(None) })), ); } { let queue = queue.clone(); self.register_command( "next", Some(Box::new(move |_s, _args| { queue.next(true); Ok(None) })), ); } { let queue = queue.clone(); self.register_command( "clear", Some(Box::new(move |_s, _args| { queue.clear(); Ok(None) })), ); } { let playlists = playlists.clone(); self.register_command( "playlists", Some(Box::new(move |_s, args| { if let Some(arg) = args.get(0) { if arg == "update" { playlists.fetch_playlists(); playlists.save_cache(); } } Ok(None) })), ); } { let queue = queue.clone(); self.register_command( "playpause", Some(Box::new(move |_s, _args| { queue.toggleplayback(); Ok(None) })), ); } { let queue = queue.clone(); self.register_command( "shuffle", Some(Box::new(move |_s, args| { if let Some(arg) = args.get(0) { queue.set_shuffle(match arg.as_ref() { "on" => true, "off" => false, _ => { return Err("Unknown shuffle setting.".to_string()); } }); } else { queue.set_shuffle(!queue.get_shuffle()); } Ok(None) })), ); } { let queue = queue.clone(); self.register_command( "repeat", Some(Box::new(move |_s, args| { if let Some(arg) = args.get(0) { queue.set_repeat(match arg.as_ref() { "list" | "playlist" | "queue" => RepeatSetting::RepeatPlaylist, "track" | "once" => RepeatSetting::RepeatTrack, "none" | "off" => RepeatSetting::None, _ => { return Err("Unknown loop setting.".to_string()); } }); } else { queue.set_repeat(match queue.get_repeat() { RepeatSetting::None => RepeatSetting::RepeatPlaylist, RepeatSetting::RepeatPlaylist => RepeatSetting::RepeatTrack, RepeatSetting::RepeatTrack => RepeatSetting::None, }); } Ok(None) })), ); } { let spotify = spotify.clone(); self.register_command( "seek", Some(Box::new(move |_s, args| { if let Some(arg) = args.get(0) { match arg.chars().next().unwrap() { '+' | '-' => { spotify.seek_relative(arg.parse::().unwrap_or(0)); } _ => { spotify.seek(arg.parse::().unwrap_or(0)); } } } Ok(None) })), ); } } fn handle_aliases(&self, name: &str) -> String { if let Some(s) = self.aliases.get(name) { self.handle_aliases(s) } else { name.to_string() } } fn handle_callbacks(&self, s: &mut Cursive, cmd: &String, args: &[String]) -> Result, String> { let local = { let mut main: ViewRef = s.find_id("main").unwrap(); main.on_command(s, cmd, args)? }; if let CommandResult::Consumed(output) = local { Ok(output) } else if let Some(callback) = self.callbacks.get(cmd) { callback.as_ref() .map(|cb| cb(s, args)) .unwrap_or(Ok(None)) } else { Err("Unknown command.".to_string()) } } pub fn handle(&self, s: &mut Cursive, cmd: String) { let components: Vec = cmd.trim().split(' ').map(|s| s.to_string()).collect(); let cmd = self.handle_aliases(&components[0]); let args = components[1..].to_vec(); let result = self.handle_callbacks(s, &cmd, &args); s.call_on_id("main", |v: &mut Layout| { v.set_result(result); }); s.on_event(Event::Refresh); } pub fn register_keybinding, S: Into>( this: Arc, cursive: &mut Cursive, event: E, command: S, ) { let cmd = command.into(); cursive.add_global_callback(event, move |s| { this.handle(s, cmd.clone()); }); } pub fn register_keybindings<'a>( this: Arc, cursive: &'a mut Cursive, keybindings: Option>, ) { let mut kb = Self::default_keybindings(); kb.extend(keybindings.unwrap_or_default()); for (k, v) in kb { Self::register_keybinding(this.clone(), cursive, Self::parse_keybinding(k), v); } } fn default_keybindings() -> HashMap { let mut kb = HashMap::new(); kb.insert("q".into(), "quit".into()); kb.insert("P".into(), "toggleplay".into()); kb.insert("R".into(), "playlists update".into()); kb.insert("S".into(), "stop".into()); kb.insert("<".into(), "previous".into()); kb.insert(">".into(), "next".into()); kb.insert("c".into(), "clear".into()); kb.insert(" ".into(), "queue".into()); kb.insert("Enter".into(), "play".into()); kb.insert("d".into(), "delete".into()); kb.insert("/".into(), "focus search".into()); kb.insert(".".into(), "seek +500".into()); kb.insert(",".into(), "seek -500".into()); kb.insert("r".into(), "repeat".into()); kb.insert("z".into(), "shuffle".into()); kb.insert("F1".into(), "focus queue".into()); kb.insert("F2".into(), "focus search".into()); kb.insert("F3".into(), "focus playlists".into()); kb.insert("Up".into(), "move up".into()); kb.insert("Down".into(), "move down".into()); kb.insert("Left".into(), "move left".into()); kb.insert("Right".into(), "move right".into()); kb.insert("PageUp".into(), "move up 5".into()); kb.insert("PageDown".into(), "move down 5".into()); kb.insert("k".into(), "move up".into()); kb.insert("j".into(), "move down".into()); kb.insert("h".into(), "move left".into()); kb.insert("l".into(), "move right".into()); kb } fn parse_keybinding(kb: String) -> cursive::event::Event { match kb.as_ref() { "Enter" => Event::Key(Key::Enter), "Tab" => Event::Key(Key::Tab), "Backspace" => Event::Key(Key::Backspace), "Esc" => Event::Key(Key::Esc), "Left" => Event::Key(Key::Left), "Right" => Event::Key(Key::Right), "Up" => Event::Key(Key::Up), "Down" => Event::Key(Key::Down), "Ins" => Event::Key(Key::Ins), "Del" => Event::Key(Key::Del), "Home" => Event::Key(Key::Home), "End" => Event::Key(Key::End), "PageUp" => Event::Key(Key::PageUp), "PageDown" => Event::Key(Key::PageDown), "PauseBreak" => Event::Key(Key::PauseBreak), "NumpadCenter" => Event::Key(Key::NumpadCenter), "F0" => Event::Key(Key::F0), "F1" => Event::Key(Key::F1), "F2" => Event::Key(Key::F2), "F3" => Event::Key(Key::F3), "F4" => Event::Key(Key::F4), "F5" => Event::Key(Key::F5), "F6" => Event::Key(Key::F6), "F7" => Event::Key(Key::F7), "F8" => Event::Key(Key::F8), "F9" => Event::Key(Key::F9), "F10" => Event::Key(Key::F10), "F11" => Event::Key(Key::F11), "F12" => Event::Key(Key::F12), s => Event::Char(s.chars().next().unwrap()), } } }