Add tabview, add playlists to search
This commit is contained in:
@@ -108,7 +108,7 @@ impl Playlists {
|
||||
}
|
||||
}
|
||||
|
||||
fn process_playlist(list: &SimplifiedPlaylist, spotify: &Spotify) -> Playlist {
|
||||
pub fn process_playlist(list: &SimplifiedPlaylist, spotify: &Spotify) -> Playlist {
|
||||
debug!("got list: {}", list.name);
|
||||
let id = list.id.clone();
|
||||
|
||||
|
||||
@@ -15,7 +15,7 @@ use rspotify::spotify::client::ApiError;
|
||||
use rspotify::spotify::client::Spotify as SpotifyAPI;
|
||||
use rspotify::spotify::model::page::Page;
|
||||
use rspotify::spotify::model::playlist::{PlaylistTrack, SimplifiedPlaylist};
|
||||
use rspotify::spotify::model::search::SearchTracks;
|
||||
use rspotify::spotify::model::search::{SearchTracks, SearchPlaylists};
|
||||
|
||||
use failure::Error;
|
||||
|
||||
@@ -428,10 +428,14 @@ impl Spotify {
|
||||
result.map(|r| r.id)
|
||||
}
|
||||
|
||||
pub fn search(&self, query: &str, limit: u32, offset: u32) -> Option<SearchTracks> {
|
||||
pub fn search_track(&self, query: &str, limit: u32, offset: u32) -> Option<SearchTracks> {
|
||||
self.api_with_retry(|api| api.search_track(query, limit, offset, None))
|
||||
}
|
||||
|
||||
pub fn search_playlist(&self, query: &str, limit: u32, offset: u32) -> Option<SearchPlaylists> {
|
||||
self.api_with_retry(|api| api.search_playlist(query, limit, offset, None))
|
||||
}
|
||||
|
||||
pub fn current_user_playlist(
|
||||
&self,
|
||||
limit: u32,
|
||||
|
||||
@@ -5,3 +5,4 @@ pub mod playlists;
|
||||
pub mod queue;
|
||||
pub mod search;
|
||||
pub mod statusbar;
|
||||
pub mod tabview;
|
||||
|
||||
@@ -10,16 +10,19 @@ use std::cell::RefCell;
|
||||
use std::sync::{Arc, Mutex, RwLock};
|
||||
|
||||
use commands::CommandResult;
|
||||
use playlists::{Playlist, Playlists};
|
||||
use queue::Queue;
|
||||
use spotify::Spotify;
|
||||
use track::Track;
|
||||
use traits::ViewExt;
|
||||
use ui::listview::ListView;
|
||||
use ui::tabview::TabView;
|
||||
|
||||
pub struct SearchView {
|
||||
results: Arc<RwLock<Vec<Track>>>,
|
||||
results_tracks: Arc<RwLock<Vec<Track>>>,
|
||||
results_playlists: Arc<RwLock<Vec<Playlist>>>,
|
||||
edit: IdView<EditView>,
|
||||
list: IdView<ListView<Track>>,
|
||||
list: IdView<TabView>,
|
||||
edit_focused: bool,
|
||||
spotify: Arc<Spotify>,
|
||||
}
|
||||
@@ -28,7 +31,8 @@ pub const LIST_ID: &str = "search_list";
|
||||
pub const EDIT_ID: &str = "search_edit";
|
||||
impl SearchView {
|
||||
pub fn new(spotify: Arc<Spotify>, queue: Arc<Queue>) -> SearchView {
|
||||
let results = Arc::new(RwLock::new(Vec::new()));
|
||||
let results_tracks = Arc::new(RwLock::new(Vec::new()));
|
||||
let results_playlists = Arc::new(RwLock::new(Vec::new()));
|
||||
|
||||
let searchfield = EditView::new()
|
||||
.on_submit(move |s, input| {
|
||||
@@ -40,14 +44,18 @@ impl SearchView {
|
||||
}
|
||||
})
|
||||
.with_id(EDIT_ID);
|
||||
let list = ListView::new(results.clone(), queue).with_id(LIST_ID);
|
||||
|
||||
let tabs = TabView::new()
|
||||
.tab("tracks", "Tracks", ListView::new(results_tracks.clone(), queue.clone()))
|
||||
.tab("playlists", "Playlists", ListView::new(results_playlists.clone(), queue.clone()));
|
||||
|
||||
SearchView {
|
||||
results,
|
||||
results_tracks,
|
||||
results_playlists,
|
||||
edit: searchfield,
|
||||
list,
|
||||
list: tabs.with_id(LIST_ID),
|
||||
edit_focused: true,
|
||||
spotify,
|
||||
spotify
|
||||
}
|
||||
}
|
||||
|
||||
@@ -66,17 +74,29 @@ impl SearchView {
|
||||
v.set_content(q);
|
||||
});
|
||||
|
||||
if let Some(results) = self.spotify.search(&query, 50, 0) {
|
||||
if let Some(results) = self.spotify.search_track(&query, 50, 0) {
|
||||
let tracks = results
|
||||
.tracks
|
||||
.items
|
||||
.iter()
|
||||
.map(|ft| Track::new(ft))
|
||||
.collect();
|
||||
let mut r = self.results.write().unwrap();
|
||||
let mut r = self.results_tracks.write().unwrap();
|
||||
*r = tracks;
|
||||
self.edit_focused = false;
|
||||
}
|
||||
|
||||
if let Some(results) = self.spotify.search_playlist(&query, 50, 0) {
|
||||
let pls = results
|
||||
.playlists
|
||||
.items
|
||||
.iter()
|
||||
.map(|sp| Playlists::process_playlist(sp, &&self.spotify))
|
||||
.collect();
|
||||
let mut r = self.results_playlists.write().unwrap();
|
||||
*r = pls;
|
||||
self.edit_focused = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
139
src/ui/tabview.rs
Normal file
139
src/ui/tabview.rs
Normal file
@@ -0,0 +1,139 @@
|
||||
use std::cmp::{max, min};
|
||||
use std::collections::HashMap;
|
||||
|
||||
use cursive::align::HAlign;
|
||||
use cursive::theme::{ColorStyle, ColorType, PaletteColor};
|
||||
use cursive::traits::View;
|
||||
use cursive::{Cursive, Printer, Vec2};
|
||||
use unicode_width::UnicodeWidthStr;
|
||||
|
||||
use commands::CommandResult;
|
||||
use traits::{ViewExt, IntoBoxedViewExt};
|
||||
|
||||
pub struct Tab {
|
||||
title: String,
|
||||
view: Box<dyn ViewExt>,
|
||||
}
|
||||
|
||||
pub struct TabView {
|
||||
tabs: Vec<Tab>,
|
||||
ids: HashMap<String, usize>,
|
||||
selected: usize
|
||||
}
|
||||
|
||||
impl TabView {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
tabs: Vec::new(),
|
||||
ids: HashMap::new(),
|
||||
selected: 0
|
||||
}
|
||||
}
|
||||
|
||||
pub fn add_tab<S: Into<String>, V: IntoBoxedViewExt>(&mut self, id: S, title: S, view: V) {
|
||||
let tab = Tab {
|
||||
title: title.into(),
|
||||
view: view.as_boxed_view_ext()
|
||||
};
|
||||
self.tabs.push(tab);
|
||||
self.ids.insert(id.into(), self.tabs.len() - 1);
|
||||
}
|
||||
|
||||
pub fn tab<S: Into<String>, V: IntoBoxedViewExt>(mut self, id: S, title: S, view: V) -> Self {
|
||||
self.add_tab(id, title, view);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn move_focus_to(&mut self, target: usize) {
|
||||
let len = self.tabs.len().saturating_sub(1);
|
||||
self.selected = min(target, len);
|
||||
}
|
||||
|
||||
pub fn move_focus(&mut self, delta: i32) {
|
||||
let new = self.selected as i32 + delta;
|
||||
self.move_focus_to(max(new, 0) as usize);
|
||||
}
|
||||
}
|
||||
|
||||
impl View for TabView {
|
||||
fn draw(&self, printer: &Printer<'_, '_>) {
|
||||
if self.tabs.len() == 0 {
|
||||
return;
|
||||
}
|
||||
|
||||
let tabwidth = printer.size.x / self.tabs.len();
|
||||
for (i, tab) in self.tabs.iter().enumerate() {
|
||||
let style = if self.selected == i {
|
||||
ColorStyle::new(
|
||||
ColorType::Palette(PaletteColor::Tertiary),
|
||||
ColorType::Palette(PaletteColor::Highlight),
|
||||
)
|
||||
} else {
|
||||
ColorStyle::primary()
|
||||
};
|
||||
|
||||
let mut width = tabwidth;
|
||||
if i == self.tabs.len() {
|
||||
width += printer.size.x % self.tabs.len();
|
||||
}
|
||||
|
||||
let offset = HAlign::Center.get_offset(tab.title.width(), width);
|
||||
|
||||
printer.with_color(style, |printer| {
|
||||
printer.print_hline((i * tabwidth, 0), width, " ");
|
||||
printer.print((i * tabwidth + offset, 0), &tab.title);
|
||||
});
|
||||
}
|
||||
|
||||
if let Some(tab) = self.tabs.get(self.selected) {
|
||||
let printer = printer
|
||||
.offset((0, 1))
|
||||
.cropped((printer.size.x, printer.size.y - 1));
|
||||
|
||||
tab.view.draw(&printer);
|
||||
}
|
||||
}
|
||||
|
||||
fn layout(&mut self, size: Vec2) {
|
||||
if let Some(tab) = self.tabs.get_mut(self.selected) {
|
||||
tab.view.layout(Vec2::new(size.x, size.y - 1));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl ViewExt for TabView {
|
||||
fn on_command(
|
||||
&mut self,
|
||||
s: &mut Cursive,
|
||||
cmd: &str,
|
||||
args: &[String],
|
||||
) -> Result<CommandResult, String> {
|
||||
if cmd == "move" {
|
||||
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.tabs.len();
|
||||
|
||||
if dir == "left" && self.selected > 0 {
|
||||
self.move_focus(-amount);
|
||||
return Ok(CommandResult::Consumed(None));
|
||||
}
|
||||
|
||||
if dir == "right" && self.selected < len - 1 {
|
||||
self.move_focus(amount);
|
||||
return Ok(CommandResult::Consumed(None));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(tab) = self.tabs.get_mut(self.selected) {
|
||||
tab.view.on_command(s, cmd, args)
|
||||
} else {
|
||||
Ok(CommandResult::Ignored)
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user