Implement browsing for albums, artists, playlists
This commit is contained in:
12
src/album.rs
12
src/album.rs
@@ -4,11 +4,13 @@ use std::sync::Arc;
|
||||
use chrono::{DateTime, Utc};
|
||||
use rspotify::spotify::model::album::{FullAlbum, SavedAlbum, SimplifiedAlbum};
|
||||
|
||||
use artist::Artist;
|
||||
use library::Library;
|
||||
use queue::Queue;
|
||||
use spotify::Spotify;
|
||||
use track::Track;
|
||||
use traits::ListItem;
|
||||
use traits::{IntoBoxedViewExt, ListItem, ViewExt};
|
||||
use ui::album::AlbumView;
|
||||
|
||||
#[derive(Clone, Deserialize, Serialize)]
|
||||
pub struct Album {
|
||||
@@ -168,4 +170,12 @@ impl ListItem for Album {
|
||||
library.save_album(self);
|
||||
}
|
||||
}
|
||||
|
||||
fn open(&self, queue: Arc<Queue>, library: Arc<Library>) -> Option<Box<dyn ViewExt>> {
|
||||
Some(AlbumView::new(queue, library, self).as_boxed_view_ext())
|
||||
}
|
||||
|
||||
fn artist(&self) -> Option<Artist> {
|
||||
Some(Artist::new(self.artist_ids[0].clone(), self.artists[0].clone()))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,7 +8,8 @@ use library::Library;
|
||||
use queue::Queue;
|
||||
use spotify::Spotify;
|
||||
use track::Track;
|
||||
use traits::ListItem;
|
||||
use traits::{IntoBoxedViewExt, ListItem, ViewExt};
|
||||
use ui::artist::ArtistView;
|
||||
|
||||
#[derive(Clone, Deserialize, Serialize)]
|
||||
pub struct Artist {
|
||||
@@ -21,7 +22,18 @@ pub struct Artist {
|
||||
}
|
||||
|
||||
impl Artist {
|
||||
fn load_albums(&mut self, spotify: Arc<Spotify>) {
|
||||
pub fn new(id: String, name: String) -> Self {
|
||||
Self {
|
||||
id,
|
||||
name,
|
||||
url: "".into(),
|
||||
albums: None,
|
||||
tracks: None,
|
||||
is_followed: false,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn load_albums(&mut self, spotify: Arc<Spotify>) {
|
||||
if let Some(albums) = self.albums.as_mut() {
|
||||
for album in albums {
|
||||
album.load_tracks(spotify.clone());
|
||||
@@ -168,4 +180,8 @@ impl ListItem for Artist {
|
||||
library.follow_artist(self);
|
||||
}
|
||||
}
|
||||
|
||||
fn open(&self, queue: Arc<Queue>, library: Arc<Library>) -> Option<Box<dyn ViewExt>> {
|
||||
Some(ArtistView::new(queue, library, self).as_boxed_view_ext())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,9 +13,9 @@ use ui::layout::Layout;
|
||||
|
||||
type CommandCb = dyn Fn(&mut Cursive, &[String]) -> Result<Option<String>, String>;
|
||||
|
||||
#[derive(PartialEq)]
|
||||
pub enum CommandResult {
|
||||
Consumed(Option<String>),
|
||||
View(Box<dyn ViewExt>),
|
||||
Ignored,
|
||||
}
|
||||
|
||||
@@ -60,6 +60,9 @@ impl CommandManager {
|
||||
self.register_command("queue", None);
|
||||
self.register_command("save", None);
|
||||
self.register_command("delete", None);
|
||||
self.register_command("back", None);
|
||||
self.register_command("open", None);
|
||||
self.register_command("goto", None);
|
||||
|
||||
self.register_command(
|
||||
"quit",
|
||||
@@ -231,6 +234,12 @@ impl CommandManager {
|
||||
|
||||
if let CommandResult::Consumed(output) = local {
|
||||
Ok(output)
|
||||
} else if let CommandResult::View(view) = local {
|
||||
s.call_on_id("main", move |v: &mut Layout| {
|
||||
v.push_view(view);
|
||||
});
|
||||
|
||||
Ok(None)
|
||||
} else if let Some(callback) = self.callbacks.get(cmd) {
|
||||
callback.as_ref().map(|cb| cb(s, args)).unwrap_or(Ok(None))
|
||||
} else {
|
||||
@@ -306,6 +315,11 @@ impl CommandManager {
|
||||
kb.insert("F1".into(), "focus queue".into());
|
||||
kb.insert("F2".into(), "focus search".into());
|
||||
kb.insert("F3".into(), "focus library".into());
|
||||
kb.insert("Backspace".into(), "back".into());
|
||||
|
||||
kb.insert("o".into(), "open".into());
|
||||
kb.insert("a".into(), "goto album".into());
|
||||
kb.insert("A".into(), "goto artist".into());
|
||||
|
||||
kb.insert("Up".into(), "move up".into());
|
||||
kb.insert("Down".into(), "move down".into());
|
||||
|
||||
@@ -370,14 +370,9 @@ impl Library {
|
||||
let mut artists = self.artists.write().unwrap();
|
||||
|
||||
if !artists.iter().any(|a| &a.id == id) {
|
||||
artists.push(Artist {
|
||||
id: id.clone(),
|
||||
name: name.clone(),
|
||||
url: "".into(),
|
||||
albums: None,
|
||||
tracks: Some(Vec::new()),
|
||||
is_followed: false,
|
||||
});
|
||||
let mut artist = Artist::new(id.clone(), name.clone());
|
||||
artist.tracks = Some(Vec::new());
|
||||
artists.push(artist);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -4,7 +4,8 @@ use std::sync::Arc;
|
||||
use library::Library;
|
||||
use queue::Queue;
|
||||
use track::Track;
|
||||
use traits::ListItem;
|
||||
use traits::{IntoBoxedViewExt, ListItem, ViewExt};
|
||||
use ui::playlist::PlaylistView;
|
||||
|
||||
#[derive(Clone, Debug, Deserialize, Serialize)]
|
||||
pub struct Playlist {
|
||||
@@ -63,4 +64,8 @@ impl ListItem for Playlist {
|
||||
library.follow_playlist(self);
|
||||
}
|
||||
}
|
||||
|
||||
fn open(&self, queue: Arc<Queue>, library: Arc<Library>) -> Option<Box<dyn ViewExt>> {
|
||||
Some(PlaylistView::new(queue, library, self).as_boxed_view_ext())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -37,6 +37,7 @@ use std::sync::RwLock;
|
||||
use std::thread;
|
||||
use std::time::{Duration, SystemTime};
|
||||
|
||||
use artist::Artist;
|
||||
use config;
|
||||
use events::{Event, EventManager};
|
||||
use track::Track;
|
||||
@@ -571,6 +572,16 @@ impl Spotify {
|
||||
self.api_with_retry(|api| api.user_playlist_follow_playlist(&owner_id, &id, true))
|
||||
}
|
||||
|
||||
pub fn artist_top_tracks(&self, id: String) -> Option<Vec<Track>> {
|
||||
self.api_with_retry(|api| api.artist_top_tracks(&id, None))
|
||||
.map(|ft| ft.tracks.iter().map(|t| t.into()).collect())
|
||||
}
|
||||
|
||||
pub fn artist_related_artists(&self, id: String) -> Option<Vec<Artist>> {
|
||||
self.api_with_retry(|api| api.artist_related_artists(&id))
|
||||
.map(|fa| fa.artists.iter().map(|a| a.into()).collect())
|
||||
}
|
||||
|
||||
pub fn load(&self, track: &Track) {
|
||||
info!("loading track: {:?}", track);
|
||||
self.channel
|
||||
|
||||
21
src/track.rs
21
src/track.rs
@@ -5,9 +5,11 @@ use chrono::{DateTime, Utc};
|
||||
use rspotify::spotify::model::album::FullAlbum;
|
||||
use rspotify::spotify::model::track::{FullTrack, SavedTrack, SimplifiedTrack};
|
||||
|
||||
use album::Album;
|
||||
use artist::Artist;
|
||||
use library::Library;
|
||||
use queue::Queue;
|
||||
use traits::ListItem;
|
||||
use traits::{ListItem, ViewExt};
|
||||
|
||||
#[derive(Clone, Deserialize, Serialize)]
|
||||
pub struct Track {
|
||||
@@ -19,6 +21,7 @@ pub struct Track {
|
||||
pub artists: Vec<String>,
|
||||
pub artist_ids: Vec<String>,
|
||||
pub album: String,
|
||||
pub album_id: String,
|
||||
pub album_artists: Vec<String>,
|
||||
pub cover_url: String,
|
||||
pub url: String,
|
||||
@@ -57,6 +60,7 @@ impl Track {
|
||||
artists,
|
||||
artist_ids,
|
||||
album: album.name.clone(),
|
||||
album_id: album.id.clone(),
|
||||
album_artists,
|
||||
cover_url,
|
||||
url: track.uri.clone(),
|
||||
@@ -104,6 +108,7 @@ impl From<&FullTrack> for Track {
|
||||
artists,
|
||||
artist_ids,
|
||||
album: track.album.name.clone(),
|
||||
album_id: track.album.id.clone(),
|
||||
album_artists,
|
||||
cover_url,
|
||||
url: track.uri.clone(),
|
||||
@@ -177,4 +182,18 @@ impl ListItem for Track {
|
||||
library.save_tracks(vec![self], true);
|
||||
}
|
||||
}
|
||||
|
||||
fn open(&self, _queue: Arc<Queue>, _library: Arc<Library>) -> Option<Box<dyn ViewExt>> {
|
||||
None
|
||||
}
|
||||
|
||||
fn album(&self, queue: Arc<Queue>) -> Option<Album> {
|
||||
let spotify = queue.get_spotify();
|
||||
|
||||
spotify.album(&self.album_id).map(|ref fa| fa.into())
|
||||
}
|
||||
|
||||
fn artist(&self) -> Option<Artist> {
|
||||
Some(Artist::new(self.artist_ids[0].clone(), self.artists[0].clone()))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,6 +4,8 @@ use cursive::view::{View, ViewWrapper};
|
||||
use cursive::views::IdView;
|
||||
use cursive::Cursive;
|
||||
|
||||
use album::Album;
|
||||
use artist::Artist;
|
||||
use commands::CommandResult;
|
||||
use library::Library;
|
||||
use queue::Queue;
|
||||
@@ -15,9 +17,22 @@ pub trait ListItem: Sync + Send + 'static {
|
||||
fn play(&mut self, queue: Arc<Queue>);
|
||||
fn queue(&mut self, queue: Arc<Queue>);
|
||||
fn toggle_saved(&mut self, library: Arc<Library>);
|
||||
fn open(&self, queue: Arc<Queue>, library: Arc<Library>) -> Option<Box<dyn ViewExt>>;
|
||||
|
||||
fn album(&self, _queue: Arc<Queue>) -> Option<Album> {
|
||||
None
|
||||
}
|
||||
|
||||
fn artist(&self) -> Option<Artist> {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
pub trait ViewExt: View {
|
||||
fn title(&self) -> String {
|
||||
"".into()
|
||||
}
|
||||
|
||||
fn on_command(
|
||||
&mut self,
|
||||
_s: &mut Cursive,
|
||||
|
||||
74
src/ui/album.rs
Normal file
74
src/ui/album.rs
Normal 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
112
src/ui/artist.rs
Normal 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)
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
48
src/ui/playlist.rs
Normal 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)
|
||||
}
|
||||
}
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user