Implement browsing for albums, artists, playlists

This commit is contained in:
KoffeinFlummi
2019-04-23 01:40:00 +02:00
parent d750e5a46f
commit eed218d0c3
15 changed files with 449 additions and 46 deletions

74
src/ui/album.rs Normal file
View File

@@ -0,0 +1,74 @@
use std::sync::{Arc, RwLock};
use cursive::view::ViewWrapper;
use cursive::Cursive;
use album::Album;
use artist::Artist;
use commands::CommandResult;
use library::Library;
use queue::Queue;
use traits::ViewExt;
use ui::listview::ListView;
use ui::tabview::TabView;
pub struct AlbumView {
album: Album,
tabs: TabView,
}
impl AlbumView {
pub fn new(queue: Arc<Queue>, library: Arc<Library>, album: &Album) -> Self {
let mut album = album.clone();
album.load_tracks(queue.get_spotify());
let tracks = if let Some(t) = album.tracks.as_ref() {
t.clone()
} else {
Vec::new()
};
let artists = album.artist_ids
.iter()
.zip(album.artists.iter())
.map(|(id, name)| Artist::new(id.clone(), name.clone()))
.collect();
let tabs = TabView::new()
.tab(
"tracks",
"Tracks",
ListView::new(Arc::new(RwLock::new(tracks)), queue.clone(), library.clone()),
)
.tab(
"artists",
"Artists",
ListView::new(Arc::new(RwLock::new(artists)), queue.clone(), library.clone()),
);
Self {
album,
tabs
}
}
}
impl ViewWrapper for AlbumView {
wrap_impl!(self.tabs: TabView);
}
impl ViewExt for AlbumView {
fn title(&self) -> String {
format!("{} ({})", self.album.title, self.album.year)
}
fn on_command(
&mut self,
s: &mut Cursive,
cmd: &str,
args: &[String],
) -> Result<CommandResult, String> {
self.tabs.on_command(s, cmd, args)
}
}

112
src/ui/artist.rs Normal file
View File

@@ -0,0 +1,112 @@
use std::sync::{Arc, RwLock};
use std::thread;
use cursive::view::ViewWrapper;
use cursive::Cursive;
use artist::Artist;
use commands::CommandResult;
use library::Library;
use queue::Queue;
use track::Track;
use traits::ViewExt;
use ui::listview::ListView;
use ui::tabview::TabView;
pub struct ArtistView {
artist: Artist,
tabs: TabView,
}
impl ArtistView {
pub fn new(queue: Arc<Queue>, library: Arc<Library>, artist: &Artist) -> Self {
let mut artist = artist.clone();
let spotify = queue.get_spotify();
artist.load_albums(spotify.clone());
let albums = if let Some(a) = artist.albums.as_ref() {
a.clone()
} else {
Vec::new()
};
let top_tracks: Arc<RwLock<Vec<Track>>> = Arc::new(RwLock::new(Vec::new()));
{
let top_tracks = top_tracks.clone();
let spotify = spotify.clone();
let id = artist.id.clone();
thread::spawn(move || {
if let Some(tracks) = spotify.artist_top_tracks(id) {
top_tracks.write().unwrap().extend(tracks);
}
});
}
let related: Arc<RwLock<Vec<Artist>>> = Arc::new(RwLock::new(Vec::new()));
{
let related = related.clone();
let spotify = spotify.clone();
let id = artist.id.clone();
thread::spawn(move || {
if let Some(artists) = spotify.artist_related_artists(id) {
related.write().unwrap().extend(artists);
}
});
}
let mut tabs = TabView::new();
if let Some(tracks) = artist.tracks.as_ref() {
let tracks = tracks.clone();
tabs.add_tab(
"tracks",
"Saved Tracks",
ListView::new(Arc::new(RwLock::new(tracks)), queue.clone(), library.clone()),
);
}
tabs.add_tab(
"top_tracks",
"Top 10",
ListView::new(top_tracks.clone(), queue.clone(), library.clone()),
);
tabs.add_tab(
"albums",
"Albums",
ListView::new(Arc::new(RwLock::new(albums)), queue.clone(), library.clone()),
);
tabs.add_tab(
"related",
"Related Artists",
ListView::new(related.clone(), queue.clone(), library.clone()),
);
Self {
artist,
tabs
}
}
}
impl ViewWrapper for ArtistView {
wrap_impl!(self.tabs: TabView);
}
impl ViewExt for ArtistView {
fn title(&self) -> String {
self.artist.name.clone()
}
fn on_command(
&mut self,
s: &mut Cursive,
cmd: &str,
args: &[String],
) -> Result<CommandResult, String> {
self.tabs.on_command(s, cmd, args)
}
}

View File

@@ -23,6 +23,7 @@ struct Screen {
pub struct Layout {
views: HashMap<String, Screen>,
stack: Vec<Screen>,
statusbar: Box<dyn View>,
focus: Option<String>,
pub cmdline: EditView,
@@ -44,6 +45,7 @@ impl Layout {
Layout {
views: HashMap::new(),
stack: Vec::new(),
statusbar: status.as_boxed_view(),
focus: None,
cmdline: EditView::new().filler(" ").style(style),
@@ -64,10 +66,10 @@ impl Layout {
}
}
pub fn add_view<S: Into<String>, T: IntoBoxedViewExt>(&mut self, id: S, view: T, title: &str) {
pub fn add_view<S: Into<String>, T: IntoBoxedViewExt>(&mut self, id: S, view: T, title: S) {
let s = id.into();
let screen = Screen {
title: title.to_string(),
title: title.into(),
view: view.as_boxed_view_ext(),
};
self.views.insert(s.clone(), screen);
@@ -78,7 +80,7 @@ impl Layout {
mut self,
id: S,
view: T,
title: &str,
title: S,
) -> Self {
(&mut self).add_view(id, view, title);
self
@@ -89,6 +91,7 @@ impl Layout {
self.focus = Some(s);
self.cmdline_focus = false;
self.screenchange = true;
self.stack.clear();
// trigger a redraw
self.ev.trigger();
@@ -114,6 +117,44 @@ impl Layout {
}
self.result.clone()
}
pub fn push_view(&mut self, view: Box<dyn ViewExt>) {
let title = view.title();
let screen = Screen {
title,
view
};
self.stack.push(screen);
}
pub fn pop_view(&mut self) {
self.stack.pop();
}
fn get_current_screen(&self) -> Option<&Screen> {
if self.stack.len() > 0 {
return self.stack.last();
}
if let Some(id) = self.focus.as_ref() {
self.views.get(id)
} else {
None
}
}
fn get_current_screen_mut(&mut self) -> Option<&mut Screen> {
if self.stack.len() > 0 {
return self.stack.last_mut();
}
if let Some(id) = self.focus.as_ref() {
self.views.get_mut(id)
} else {
None
}
}
}
impl View for Layout {
@@ -126,13 +167,15 @@ impl View for Layout {
cmdline_height += 1;
}
if let Some(ref id) = self.focus {
let screen = &self.views[id];
if let Some(screen) = self.get_current_screen() {
// screen title
printer.with_color(ColorStyle::title_primary(), |printer| {
let offset = HAlign::Center.get_offset(screen.title.width(), printer.size.x);
printer.print((offset, 0), &screen.title);
if self.stack.len() > 0 {
printer.print((1, 0), "<");
}
});
// screen content
@@ -202,8 +245,7 @@ impl View for Layout {
return self.cmdline.on_event(event);
}
if let Some(ref id) = self.focus {
let screen = self.views.get_mut(id).unwrap();
if let Some(screen) = self.get_current_screen_mut() {
screen.view.on_event(event)
} else {
EventResult::Ignored
@@ -217,22 +259,20 @@ impl View for Layout {
self.cmdline.layout(Vec2::new(size.x, 1));
if let Some(ref id) = self.focus {
let screen = self.views.get_mut(id).unwrap();
if let Some(screen) = self.get_current_screen_mut() {
screen.view.layout(Vec2::new(size.x, size.y - 3));
}
// the focus view has changed, let the views know so they can redraw
// their items
if self.screenchange {
debug!("layout: new screen selected: {}", &id);
self.screenchange = false;
}
// the focus view has changed, let the views know so they can redraw
// their items
if self.screenchange {
debug!("layout: new screen selected: {:?}", self.focus);
self.screenchange = false;
}
}
fn call_on_any<'a>(&mut self, s: &Selector, c: AnyCb<'a>) {
if let Some(ref id) = self.focus {
let screen = self.views.get_mut(id).unwrap();
if let Some(screen) = self.get_current_screen_mut() {
screen.view.call_on_any(s, c);
}
}
@@ -242,8 +282,7 @@ impl View for Layout {
return self.cmdline.take_focus(source);
}
if let Some(ref id) = self.focus {
let screen = self.views.get_mut(id).unwrap();
if let Some(screen) = self.get_current_screen_mut() {
screen.view.take_focus(source)
} else {
false
@@ -268,8 +307,10 @@ impl ViewExt for Layout {
}
Ok(CommandResult::Consumed(None))
} else if let Some(ref id) = self.focus {
let screen = self.views.get_mut(id).unwrap();
} else if cmd == "back" {
self.pop_view();
Ok(CommandResult::Consumed(None))
} else if let Some(screen) = self.get_current_screen_mut() {
screen.view.on_command(s, cmd, args)
} else {
Ok(CommandResult::Ignored)

View File

@@ -13,7 +13,9 @@ use commands::CommandResult;
use library::Library;
use queue::Queue;
use track::Track;
use traits::{ListItem, ViewExt};
use traits::{IntoBoxedViewExt, ListItem, ViewExt};
use ui::album::AlbumView;
use ui::artist::ArtistView;
pub type Paginator<I> = Box<Fn(Arc<RwLock<Vec<I>>>) + Send + Sync>;
pub struct Pagination<I: ListItem> {
@@ -336,6 +338,42 @@ impl<I: ListItem + Clone> ViewExt for ListView<I> {
}
}
if cmd == "open" {
let mut content = self.content.write().unwrap();
if let Some(item) = content.get_mut(self.selected) {
let queue = self.queue.clone();
let library = self.library.clone();
if let Some(view) = item.open(queue, library) {
return Ok(CommandResult::View(view));
}
}
}
if cmd == "goto" {
let mut content = self.content.write().unwrap();
if let Some(item) = content.get_mut(self.selected) {
let queue = self.queue.clone();
let library = self.library.clone();
let arg = args.get(0).map(|s| s.clone()).unwrap_or_default();
if arg == "album" {
if let Some(album) = item.album(queue.clone()) {
let view = AlbumView::new(queue, library, &album)
.as_boxed_view_ext();
return Ok(CommandResult::View(view));
}
}
if arg == "artist" {
if let Some(artist) = item.artist() {
let view = ArtistView::new(queue, library, &artist)
.as_boxed_view_ext();
return Ok(CommandResult::View(view));
}
}
}
}
Ok(CommandResult::Ignored)
}
}

View File

@@ -1,7 +1,10 @@
pub mod album;
pub mod artist;
pub mod layout;
pub mod library;
pub mod listview;
pub mod modal;
pub mod playlist;
pub mod playlists;
pub mod queue;
pub mod search;

48
src/ui/playlist.rs Normal file
View File

@@ -0,0 +1,48 @@
use std::sync::{Arc, RwLock};
use cursive::view::ViewWrapper;
use cursive::Cursive;
use commands::CommandResult;
use library::Library;
use playlist::Playlist;
use queue::Queue;
use track::Track;
use traits::ViewExt;
use ui::listview::ListView;
pub struct PlaylistView {
playlist: Playlist,
list: ListView<Track>,
}
impl PlaylistView {
pub fn new(queue: Arc<Queue>, library: Arc<Library>, playlist: &Playlist) -> Self {
let playlist = playlist.clone();
let list = ListView::new(Arc::new(RwLock::new(playlist.tracks.clone())), queue, library);
Self {
playlist,
list
}
}
}
impl ViewWrapper for PlaylistView {
wrap_impl!(self.list: ListView<Track>);
}
impl ViewExt for PlaylistView {
fn title(&self) -> String {
self.playlist.name.clone()
}
fn on_command(
&mut self,
s: &mut Cursive,
cmd: &str,
args: &[String],
) -> Result<CommandResult, String> {
self.list.on_command(s, cmd, args)
}
}

View File

@@ -463,16 +463,18 @@ impl ViewExt for SearchView {
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 let CommandResult::Ignored = result {
if 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));
if dir == "down" && self.edit_focused {
self.edit_focused = false;
return Ok(CommandResult::Consumed(None));
}
}
}
}