diff --git a/src/commands.rs b/src/commands.rs index 0d58040..e614a8a 100644 --- a/src/commands.rs +++ b/src/commands.rs @@ -2,39 +2,45 @@ use std::collections::HashMap; use std::sync::Arc; use cursive::event::{Event, Key}; +use cursive::views::ViewRef; use cursive::Cursive; -use playlists::{Playlist, Playlists}; +use playlists::Playlists; use queue::{Queue, RepeatSetting}; use spotify::Spotify; -use track::Track; -use ui; +use traits::ViewExt; use ui::layout::Layout; -use ui::listview::ListView; -use ui::search::SearchView; -type CommandResult = Result, String>; -type CommandCb = dyn Fn(&mut Cursive, Vec) -> CommandResult; +type CommandCb = dyn Fn(&mut Cursive, &[String]) -> Result, String>; + +#[derive(PartialEq)] +pub enum CommandResult { + Consumed(Option), + Ignored, +} pub struct CommandManager { - commands: HashMap>, + callbacks: HashMap>>, aliases: HashMap, } impl CommandManager { pub fn new() -> CommandManager { CommandManager { - commands: HashMap::new(), + callbacks: HashMap::new(), aliases: HashMap::new(), } } - pub fn register>(&mut self, name: S, aliases: Vec, cb: Box) { + 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()); } - self.commands.insert(name, cb); } pub fn register_all( @@ -43,272 +49,100 @@ impl CommandManager { queue: Arc, playlists: Arc, ) { - self.register( + 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", - vec!["q", "x"], - Box::new(move |s, _args| { + Some(Box::new(move |s, _args| { s.quit(); Ok(None) - }), + })), ); { let queue = queue.clone(); - self.register( + self.register_command( "stop", - Vec::new(), - Box::new(move |_s, _args| { + Some(Box::new(move |_s, _args| { queue.stop(); Ok(None) - }), + })), ); } { let queue = queue.clone(); - self.register( + self.register_command( "previous", - Vec::new(), - Box::new(move |_s, _args| { + Some(Box::new(move |_s, _args| { queue.previous(); Ok(None) - }), + })), ); } { let queue = queue.clone(); - self.register( + self.register_command( "next", - Vec::new(), - Box::new(move |_s, _args| { + Some(Box::new(move |_s, _args| { queue.next(true); Ok(None) - }), + })), ); } { let queue = queue.clone(); - self.register( + self.register_command( "clear", - Vec::new(), - Box::new(move |_s, _args| { + Some(Box::new(move |_s, _args| { queue.clear(); Ok(None) - }), + })), ); } { - let spotify = spotify.clone(); - self.register( - "search", - Vec::new(), - Box::new(move |s, args| { - s.call_on_id("main", |v: &mut Layout| { - v.set_view("search"); - }); - s.call_on_id("search", |v: &mut SearchView| { - if !args.is_empty() { - v.run_search(args.join(" "), spotify.clone()); - } - }); - Ok(None) - }), - ); - } - - { - self.register( + let playlists = playlists.clone(); + self.register_command( "playlists", - vec!["lists"], - Box::new(move |s, args| { + Some(Box::new(move |_s, args| { if let Some(arg) = args.get(0) { if arg == "update" { playlists.fetch_playlists(); playlists.save_cache(); } - } else { - s.call_on_id("main", |v: &mut Layout| { - v.set_view("playlists"); - }); } Ok(None) - }), - ); - } - - self.register( - "move", - Vec::new(), - Box::new(move |s, args| { - if args.is_empty() { - return Err("Missing direction (up, down, left, right)".to_string()); - } - - let dir = &args[0]; - - let amount: i32 = args - .get(1) - .unwrap_or(&"1".to_string()) - .parse() - .map_err(|e| format!("{:?}", e))?; - - if dir == "up" || dir == "down" { - let dir = if dir == "up" { -1 } else { 1 }; - s.call_on_id(ui::queue::LIST_ID, |v: &mut ListView| { - v.move_focus(dir * amount); - }); - s.call_on_id(ui::search::LIST_ID, |v: &mut ListView| { - v.move_focus(dir * amount); - }); - s.call_on_id(ui::playlists::LIST_ID, |v: &mut ListView| { - v.move_focus(dir * amount); - }); - s.on_event(Event::Refresh); - return Ok(None); - } - - if dir == "left" || dir == "right" { - return Ok(None); - } - - Err(format!("Unrecognized direction: {}", dir)) - }), - ); - - { - let queue = queue.clone(); - self.register( - "queue", - Vec::new(), - Box::new(move |s, args| { - if let Some(arg) = args.get(0) { - if arg != "selected" { - return Err("".into()); - } - } else { - s.call_on_id("main", |v: &mut Layout| { - v.set_view("queue"); - }); - return Ok(None); - } - - { - let queue = queue.clone(); - s.call_on_id(ui::search::LIST_ID, |v: &mut ListView| { - v.with_selected(Box::new(move |t| { - queue.append(t); - })); - }); - } - - { - let queue = queue.clone(); - s.call_on_id(ui::playlists::LIST_ID, |v: &mut ListView| { - v.with_selected(Box::new(move |pl| { - for track in pl.tracks.iter() { - queue.append(track); - } - })); - }); - } - - Ok(None) - }), + })), ); } { let queue = queue.clone(); - self.register( - "play", - vec!["pause", "toggle", "toggleplay", "toggleplayback"], - Box::new(move |s, args| { - if let Some(arg) = args.get(0) { - if arg != "selected" { - return Err("".into()); - } - } else { - queue.toggleplayback(); - return Ok(None); - } - - { - let queue = queue.clone(); - s.call_on_id(ui::queue::LIST_ID, |v: &mut ListView| { - queue.play(v.get_selected_index(), true); - }); - } - - { - let queue = queue.clone(); - s.call_on_id(ui::search::LIST_ID, |v: &mut ListView| { - v.with_selected(Box::new(move |t| { - let index = queue.append_next(vec![t]); - queue.play(index, true); - })); - }); - } - - { - let queue = queue.clone(); - s.call_on_id(ui::playlists::LIST_ID, |v: &mut ListView| { - v.with_selected(Box::new(move |pl| { - let index = queue.append_next(pl.tracks.iter().collect()); - queue.play(index, true); - })); - }); - } - + self.register_command( + "playpause", + Some(Box::new(move |_s, _args| { + queue.toggleplayback(); Ok(None) - }), + })), ); } { let queue = queue.clone(); - self.register( - "delete", - Vec::new(), - Box::new(move |s, args| { - if let Some(arg) = args.get(0) { - if arg != "selected" { - return Err("".into()); - } - } else { - return Err("".into()); - } - - { - let queue = queue.clone(); - s.call_on_id(ui::queue::LIST_ID, |v: &mut ListView| { - queue.remove(v.get_selected_index()); - }); - } - - { - if let Some(Some(dialog)) = s - .call_on_id("playlists", |v: &mut ui::playlists::PlaylistView| { - v.delete_dialog() - }) - { - s.add_layer(dialog); - } - } - - Ok(None) - }), - ); - } - - { - let queue = queue.clone(); - self.register( + self.register_command( "shuffle", - Vec::new(), - Box::new(move |_s, args| { + Some(Box::new(move |_s, args| { if let Some(arg) = args.get(0) { queue.set_shuffle(match arg.as_ref() { "on" => true, @@ -322,16 +156,15 @@ impl CommandManager { } Ok(None) - }), + })), ); } { let queue = queue.clone(); - self.register( + self.register_command( "repeat", - vec!["loop"], - Box::new(move |_s, args| { + 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, @@ -350,16 +183,15 @@ impl CommandManager { } Ok(None) - }), + })), ); } { let spotify = spotify.clone(); - self.register( + self.register_command( "seek", - Vec::new(), - Box::new(move |_s, args| { + Some(Box::new(move |_s, args| { if let Some(arg) = args.get(0) { match arg.chars().next().unwrap() { '+' | '-' => { @@ -372,7 +204,7 @@ impl CommandManager { } Ok(None) - }), + })), ); } } @@ -385,21 +217,36 @@ impl CommandManager { } } + 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 result = if let Some(cb) = self.commands.get(&self.handle_aliases(&components[0])) { - cb(s, components[1..].to_vec()) - } else { - Err("Unknown command.".to_string()) - }; + let cmd = self.handle_aliases(&components[0]); + let args = components[1..].to_vec(); - // TODO: handle non-error output as well - if let Err(e) = result { - s.call_on_id("main", |v: &mut Layout| { - v.set_error(e); - }); - } + 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>( @@ -431,24 +278,24 @@ impl CommandManager { let mut kb = HashMap::new(); kb.insert("q".into(), "quit".into()); - kb.insert("P".into(), "toggle".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 selected".into()); - kb.insert("Enter".into(), "play selected".into()); - kb.insert("d".into(), "delete selected".into()); - kb.insert("/".into(), "search".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(), "queue".into()); - kb.insert("F2".into(), "search".into()); - kb.insert("F3".into(), "playlists".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()); diff --git a/src/main.rs b/src/main.rs index 7df333b..db4b2ab 100644 --- a/src/main.rs +++ b/src/main.rs @@ -133,8 +133,6 @@ fn main() { #[cfg(feature = "mpris")] let mpris_manager = Arc::new(mpris::MprisManager::new(spotify.clone(), queue.clone())); - let search = ui::search::SearchView::new(spotify.clone(), queue.clone()); - let playlists = Arc::new(Playlists::new(&event_manager, &spotify)); { @@ -152,6 +150,14 @@ fn main() { }); } + let mut cmd_manager = CommandManager::new(); + cmd_manager.register_all(spotify.clone(), queue.clone(), playlists.clone()); + + let cmd_manager = Arc::new(cmd_manager); + CommandManager::register_keybindings(cmd_manager.clone(), &mut cursive, cfg.keybindings.clone()); + + let search = ui::search::SearchView::new(spotify.clone(), queue.clone()); + let playlistsview = ui::playlists::PlaylistView::new(&playlists, queue.clone()); let queueview = ui::queue::QueueView::new(queue.clone(), playlists.clone()); @@ -192,12 +198,6 @@ fn main() { cursive.add_fullscreen_layer(layout.with_id("main")); - let mut cmd_manager = CommandManager::new(); - cmd_manager.register_all(spotify.clone(), queue.clone(), playlists.clone()); - - let cmd_manager = Arc::new(cmd_manager); - CommandManager::register_keybindings(cmd_manager.clone(), &mut cursive, cfg.keybindings); - // cursive event loop while cursive.is_running() { cursive.step(); diff --git a/src/playlists.rs b/src/playlists.rs index 897ce3f..282eb04 100644 --- a/src/playlists.rs +++ b/src/playlists.rs @@ -48,6 +48,17 @@ impl ListItem for Playlist { fn display_right(&self) -> String { format!("{} tracks", self.tracks.len()) } + + fn play(&self, queue: Arc) { + let index = queue.append_next(self.tracks.iter().collect()); + queue.play(index, true); + } + + fn queue(&self, queue: Arc) { + for track in self.tracks.iter() { + queue.append(track); + } + } } impl Playlists { diff --git a/src/track.rs b/src/track.rs index 7520c84..ea4c4fc 100644 --- a/src/track.rs +++ b/src/track.rs @@ -91,4 +91,13 @@ impl ListItem for Track { fn display_right(&self) -> String { self.duration_str() } + + fn play(&self, queue: Arc) { + let index = queue.append_next(vec![self]); + queue.play(index, true); + } + + fn queue(&self, queue: Arc) { + queue.append(self); + } } diff --git a/src/traits.rs b/src/traits.rs index 3512552..767feaa 100644 --- a/src/traits.rs +++ b/src/traits.rs @@ -1,9 +1,48 @@ use std::sync::Arc; +use cursive::view::{View, ViewWrapper}; +use cursive::views::IdView; +use cursive::Cursive; + +use commands::CommandResult; use queue::Queue; pub trait ListItem { fn is_playing(&self, queue: Arc) -> bool; fn display_left(&self) -> String; fn display_right(&self) -> String; + fn play(&self, queue: Arc); + fn queue(&self, queue: Arc); +} + +pub trait ViewExt: View { + fn on_command(&mut self, + _s: &mut Cursive, + _cmd: &String, + _args: &[String] + ) -> Result { + Ok(CommandResult::Ignored) + } +} + +impl ViewExt for IdView { + fn on_command(&mut self, + s: &mut Cursive, + cmd: &String, + args: &[String] + ) -> Result { + self.with_view_mut(move |v| { + v.on_command(s, cmd, args) + }).unwrap() + } +} + +pub trait IntoBoxedViewExt { + fn as_boxed_view_ext(self) -> Box; +} + +impl IntoBoxedViewExt for V { + fn as_boxed_view_ext(self) -> Box { + Box::new(self) + } } diff --git a/src/ui/layout.rs b/src/ui/layout.rs index 53e699f..1c92808 100644 --- a/src/ui/layout.rs +++ b/src/ui/layout.rs @@ -9,14 +9,16 @@ use cursive::traits::View; use cursive::vec::Vec2; use cursive::view::{IntoBoxedView, Selector}; use cursive::views::EditView; -use cursive::Printer; +use cursive::{Cursive, Printer}; use unicode_width::UnicodeWidthStr; use events; +use traits::{ViewExt, IntoBoxedViewExt}; +use commands::CommandResult; struct Screen { title: String, - view: Box, + view: Box, } pub struct Layout { @@ -26,8 +28,8 @@ pub struct Layout { focus: Option, pub cmdline: EditView, cmdline_focus: bool, - error: Option, - error_time: Option, + result: Result, String>, + result_time: Option, screenchange: bool, last_size: Vec2, ev: events::EventManager, @@ -48,8 +50,8 @@ impl Layout { focus: None, cmdline: EditView::new().filler(" ").style(style), cmdline_focus: false, - error: None, - error_time: None, + result: Ok(None), + result_time: None, screenchange: true, last_size: Vec2::new(0, 0), ev: ev.clone(), @@ -64,18 +66,18 @@ impl Layout { } } - pub fn add_view, T: IntoBoxedView>(&mut self, id: S, view: T, title: &str) { + pub fn add_view, T: IntoBoxedViewExt>(&mut self, id: S, view: T, title: &str) { let s = id.into(); let screen = Screen { title: title.to_string(), - view: view.as_boxed_view(), + view: view.as_boxed_view_ext(), }; self.views.insert(s.clone(), screen); self.title = title.to_owned(); self.focus = Some(s); } - pub fn view, T: IntoBoxedView>(mut self, id: S, view: T, title: &str) -> Self { + pub fn view, T: IntoBoxedViewExt>(mut self, id: S, view: T, title: &str) -> Self { (&mut self).add_view(id, view, title); self } @@ -92,35 +94,35 @@ impl Layout { self.ev.trigger(); } - pub fn set_error>(&mut self, error: S) { - self.error = Some(error.into()); - self.error_time = Some(SystemTime::now()); + pub fn set_result(&mut self, result: Result, String>) { + self.result = result; + self.result_time = Some(SystemTime::now()); } pub fn clear_cmdline(&mut self) { self.cmdline.set_content(""); self.cmdline_focus = false; - self.error = None; - self.error_time = None; + self.result = Ok(None); + self.result_time = None; } - fn get_error(&self) -> Option { - if let Some(t) = self.error_time { + fn get_result(&self) -> Result, String> { + if let Some(t) = self.result_time { if t.elapsed().unwrap() > Duration::from_secs(5) { - return None; + return Ok(None); } } - self.error.clone() + self.result.clone() } } impl View for Layout { fn draw(&self, printer: &Printer<'_, '_>) { - let error = self.get_error(); + let result = self.get_result(); let cmdline_visible = self.cmdline.get_content().len() > 0; let mut cmdline_height = if cmdline_visible { 1 } else { 0 }; - if error.is_some() { + if result.as_ref().map(|o| o.is_some()).unwrap_or(true) { cmdline_height += 1; } @@ -143,17 +145,18 @@ impl View for Layout { self.statusbar .draw(&printer.offset((0, printer.size.y - 2 - cmdline_height))); - if let Some(e) = error { + if let Ok(Some(r)) = result { + printer.print_hline((0, printer.size.y - cmdline_height), printer.size.x, " "); + printer.print((0, printer.size.y - cmdline_height), &r); + } else if let Err(e) = result { let style = ColorStyle::new( ColorType::Color(*self.theme.palette.custom("error").unwrap()), ColorType::Color(*self.theme.palette.custom("error_bg").unwrap()), ); + printer.with_color(style, |printer| { printer.print_hline((0, printer.size.y - cmdline_height), printer.size.x, " "); - printer.print( - (0, printer.size.y - cmdline_height), - &format!("ERROR: {}", e), - ); + printer.print((0, printer.size.y - cmdline_height), &format!("ERROR: {}", e)); }); } @@ -169,11 +172,11 @@ impl View for Layout { fn on_event(&mut self, event: Event) -> EventResult { if let Event::Mouse { position, .. } = event { - let error = self.get_error(); + let result = self.get_result(); let cmdline_visible = self.cmdline.get_content().len() > 0; let mut cmdline_height = if cmdline_visible { 1 } else { 0 }; - if error.is_some() { + if result.as_ref().map(|o| o.is_some()).unwrap_or(true) { cmdline_height += 1; } @@ -243,3 +246,26 @@ impl View for Layout { } } } + +impl ViewExt for Layout { + fn on_command(&mut self, + s: &mut Cursive, + cmd: &String, + args: &[String] + ) -> Result { + if cmd == "focus" { + if let Some(view) = args.get(0) { + if self.views.keys().any(|k| k == view) { + self.set_view(view.clone()); + } + } + + Ok(CommandResult::Consumed(None)) + } else if let Some(ref id) = self.focus { + let screen = self.views.get_mut(id).unwrap(); + screen.view.on_command(s, cmd, args) + } else { + Ok(CommandResult::Ignored) + } + } +} diff --git a/src/ui/listview.rs b/src/ui/listview.rs index 968da92..a68a6f6 100644 --- a/src/ui/listview.rs +++ b/src/ui/listview.rs @@ -1,20 +1,21 @@ use std::cmp::{max, min}; -use std::sync::{Arc, RwLock, RwLockReadGuard}; +use std::sync::{Arc, RwLock}; use cursive::align::HAlign; use cursive::event::{Event, EventResult, MouseButton, MouseEvent}; use cursive::theme::{ColorStyle, ColorType, PaletteColor}; use cursive::traits::View; use cursive::view::ScrollBase; -use cursive::{Printer, Rect, Vec2}; +use cursive::{Cursive, Printer, Rect, Vec2}; use unicode_width::UnicodeWidthStr; use queue::Queue; -use traits::ListItem; +use traits::{ListItem, ViewExt}; +use commands::CommandResult; pub struct ListView { content: Arc>>, - last_content_length: usize, + last_content_len: usize, selected: usize, last_size: Vec2, scrollbar: ScrollBase, @@ -25,7 +26,7 @@ impl ListView { pub fn new(content: Arc>>, queue: Arc) -> Self { Self { content, - last_content_length: 0, + last_content_len: 0, selected: 0, last_size: Vec2::new(0, 0), scrollbar: ScrollBase::new(), @@ -33,13 +34,6 @@ impl ListView { } } - pub fn with_selected(&self, cb: Box ()>) { - match self.content.read().unwrap().get(self.selected) { - Some(x) => cb(x), - None => error!("listview: invalid item index: {})", self.selected), - } - } - pub fn get_selected_index(&self) -> usize { self.selected } @@ -54,12 +48,6 @@ impl ListView { let new = self.selected as i32 + delta; self.move_focus_to(max(new, 0) as usize); } - - pub fn content(&self) -> RwLockReadGuard> { - self.content - .read() - .expect("could not readlock listview content") - } } impl View for ListView { @@ -116,13 +104,13 @@ impl View for ListView { } fn layout(&mut self, size: Vec2) { - self.last_content_length = self.content.read().unwrap().len(); + self.last_content_len = self.content.read().unwrap().len(); self.last_size = size; - self.scrollbar.set_heights(size.y, self.last_content_length); + self.scrollbar.set_heights(size.y, self.last_content_len); } fn needs_relayout(&self) -> bool { - self.content.read().unwrap().len() != self.last_content_length + self.content.read().unwrap().len() != self.last_content_len } fn on_event(&mut self, e: Event) -> EventResult { @@ -184,3 +172,53 @@ impl View for ListView { } } } + +impl ViewExt for ListView { + fn on_command(&mut self, + _s: &mut Cursive, + cmd: &String, + args: &[String] + ) -> Result { + if cmd == "play" { + let content = self.content.read().unwrap(); + if let Some(item) = content.get(self.selected) { + item.play(self.queue.clone()); + } + return Ok(CommandResult::Consumed(None)); + } + + if cmd == "queue" { + let content = self.content.read().unwrap(); + if let Some(item) = content.get(self.selected) { + item.queue(self.queue.clone()); + } + return Ok(CommandResult::Consumed(None)); + } + + if cmd != "move" { + return Ok(CommandResult::Ignored); + } + + if let Some(dir) = args.get(0) { + let amount: i32 = args + .get(1) + .unwrap_or(&"1".to_string()) + .parse() + .map_err(|e| format!("{:?}", e))?; + + let len = self.content.read().unwrap().len(); + + if dir == "up" && self.selected > 0 { + self.move_focus(amount * -1); + return Ok(CommandResult::Consumed(None)); + } + + if dir == "down" && self.selected < len - 1 { + self.move_focus(amount); + return Ok(CommandResult::Consumed(None)); + } + } + + Ok(CommandResult::Ignored) + } +} diff --git a/src/ui/playlists.rs b/src/ui/playlists.rs index eafceac..3805873 100644 --- a/src/ui/playlists.rs +++ b/src/ui/playlists.rs @@ -5,8 +5,10 @@ use cursive::view::ViewWrapper; use cursive::views::{Dialog, IdView}; use cursive::Cursive; +use commands::CommandResult; use playlists::{Playlist, Playlists}; use queue::Queue; +use traits::ViewExt; use ui::listview::ListView; use ui::modal::Modal; @@ -52,3 +54,21 @@ impl PlaylistView { impl ViewWrapper for PlaylistView { wrap_impl!(self.list: IdView>); } + +impl ViewExt for PlaylistView { + fn on_command(&mut self, + s: &mut Cursive, + cmd: &String, + args: &[String] + ) -> Result { + if cmd == "delete" { + if let Some(dialog) = self.delete_dialog() { + s.add_layer(dialog); + } + return Ok(CommandResult::Consumed(None)); + } + + self.list.on_command(s, cmd, args) + + } +} diff --git a/src/ui/queue.rs b/src/ui/queue.rs index 9f17a59..09c0bdd 100644 --- a/src/ui/queue.rs +++ b/src/ui/queue.rs @@ -1,34 +1,34 @@ use cursive::event::{Callback, Event, EventResult}; use cursive::traits::{Boxable, Identifiable, View}; use cursive::view::ViewWrapper; -use cursive::views::{Dialog, EditView, IdView, ScrollView, SelectView}; +use cursive::views::{Dialog, EditView, ScrollView, SelectView}; use cursive::Cursive; use std::sync::Arc; +use commands::CommandResult; use playlists::Playlists; use queue::Queue; use track::Track; +use traits::ViewExt; use ui::listview::ListView; use ui::modal::Modal; pub struct QueueView { - list: IdView>, + list: ListView, playlists: Arc, + queue: Arc, } -pub const LIST_ID: &str = "queue_list"; impl QueueView { pub fn new(queue: Arc, playlists: Arc) -> QueueView { - let list = ListView::new(queue.queue.clone(), queue.clone()).with_id(LIST_ID); + let list = ListView::new(queue.queue.clone(), queue.clone()); - QueueView { list, playlists } + QueueView { list, playlists, queue } } - fn save_dialog_cb(s: &mut Cursive, playlists: Arc, id: Option) { - let tracks = s - .call_on_id(LIST_ID, |view: &mut ListView<_>| view.content().clone()) - .unwrap(); + fn save_dialog_cb(s: &mut Cursive, queue: Arc, playlists: Arc, id: Option) { + let tracks = queue.queue.read().unwrap().clone(); match id { Some(id) => { playlists.overwrite_playlist(&id, &tracks); @@ -53,7 +53,7 @@ impl QueueView { } } - fn save_dialog(playlists: Arc) -> Modal { + fn save_dialog(queue: Arc, playlists: Arc) -> Modal { let mut list_select: SelectView> = SelectView::new().autojump(); list_select.add_item("[Create new]", None); @@ -62,7 +62,7 @@ impl QueueView { } list_select.set_on_submit(move |s, selected| { - Self::save_dialog_cb(s, playlists.clone(), selected.clone()) + Self::save_dialog_cb(s, queue.clone(), playlists.clone(), selected.clone()) }); let dialog = Dialog::new() @@ -75,15 +75,16 @@ impl QueueView { } impl ViewWrapper for QueueView { - wrap_impl!(self.list: IdView>); + wrap_impl!(self.list: ListView); fn wrap_on_event(&mut self, ch: Event) -> EventResult { match ch { Event::Char('s') => { debug!("save list"); + let queue = self.queue.clone(); let playlists = self.playlists.clone(); let cb = move |s: &mut Cursive| { - let dialog = Self::save_dialog(playlists.clone()); + let dialog = Self::save_dialog(queue.clone(), playlists.clone()); s.add_layer(dialog) }; EventResult::Consumed(Some(Callback::from_fn(cb))) @@ -92,3 +93,29 @@ impl ViewWrapper for QueueView { } } } + +impl ViewExt for QueueView { + fn on_command(&mut self, + s: &mut Cursive, + cmd: &String, + args: &[String] + ) -> Result { + if cmd == "play" { + self.queue.play(self.list.get_selected_index(), true); + return Ok(CommandResult::Consumed(None)); + } + + if cmd == "queue" { + return Ok(CommandResult::Ignored); + } + + if cmd == "delete" { + self.queue.remove(self.list.get_selected_index()); + return Ok(CommandResult::Consumed(None)); + } + + self.with_view_mut(move |v| { + v.on_command(s, cmd, args) + }).unwrap() + } +} diff --git a/src/ui/search.rs b/src/ui/search.rs index 8502bd7..8eb7424 100644 --- a/src/ui/search.rs +++ b/src/ui/search.rs @@ -9,9 +9,11 @@ use cursive::{Cursive, Printer, Vec2}; use std::cell::RefCell; use std::sync::{Arc, Mutex, RwLock}; +use commands::CommandResult; use queue::Queue; use spotify::Spotify; use track::Track; +use traits::ViewExt; use ui::listview::ListView; pub struct SearchView { @@ -19,6 +21,7 @@ pub struct SearchView { edit: IdView, list: IdView>, edit_focused: bool, + spotify: Arc, } pub const LIST_ID: &str = "search_list"; @@ -31,7 +34,7 @@ impl SearchView { .on_submit(move |s, input| { if !input.is_empty() { s.call_on_id("search", |v: &mut SearchView| { - v.run_search(input, spotify.clone()); + v.run_search(input); v.focus_view(&Selector::Id(LIST_ID)).unwrap(); }); } @@ -44,10 +47,11 @@ impl SearchView { edit: searchfield, list, edit_focused: true, + spotify } } - pub fn run_search>(&mut self, query: S, spotify: Arc) { + pub fn run_search>(&mut self, query: S) { let query = query.into(); let q = query.clone(); self.edit @@ -55,7 +59,7 @@ impl SearchView { v.set_content(q); }); - if let Some(results) = spotify.search(&query, 50, 0) { + if let Some(results) = self.spotify.search(&query, 50, 0) { let tracks = results .tracks .items @@ -67,18 +71,6 @@ impl SearchView { self.edit_focused = false; } } - - fn list_index(&self) -> usize { - self.list.with_view(|v| v.get_selected_index()).unwrap_or(0) - } - - fn pass_event_focused(&mut self, event: Event) -> EventResult { - if self.edit_focused { - self.edit.on_event(event) - } else { - self.list.on_event(event) - } - } } impl View for SearchView { @@ -118,24 +110,46 @@ impl View for SearchView { } fn on_event(&mut self, event: Event) -> EventResult { - match event { - Event::Key(Key::Tab) => { - self.edit_focused = !self.edit_focused; - EventResult::Consumed(None) - } - Event::Key(Key::Esc) if self.edit_focused => { - self.edit_focused = false; - EventResult::Consumed(None) - } - Event::Key(Key::Down) if self.edit_focused => { - self.edit_focused = false; - EventResult::Consumed(None) - } - Event::Key(Key::Up) if (!self.edit_focused && self.list_index() == 0) => { - self.edit_focused = true; - EventResult::Consumed(None) - } - _ => self.pass_event_focused(event), + if self.edit_focused { + self.edit.on_event(event) + } else { + self.list.on_event(event) } } } + + +impl ViewExt for SearchView { + fn on_command(&mut self, + s: &mut Cursive, + cmd: &String, + args: &[String] + ) -> Result { + if cmd == "search" && !args.is_empty() { + self.run_search(args.join(" ")); + return Ok(CommandResult::Consumed(None)); + } + + let result = if !self.edit_focused { + self.list.on_command(s, cmd, args)? + } else { + CommandResult::Ignored + }; + + if result == CommandResult::Ignored && cmd == "move" { + if let Some(dir) = args.get(0) { + if dir == "up" && !self.edit_focused { + self.edit_focused = true; + return Ok(CommandResult::Consumed(None)); + } + + if dir == "down" && self.edit_focused { + self.edit_focused = false; + return Ok(CommandResult::Consumed(None)); + } + } + } + + Ok(result) + } +}