Compare commits

...

2 Commits

Author SHA1 Message Date
~erin ef19940418
Switch all lists over to StatefulLists 2023-03-22 21:46:38 -04:00
~erin b0b0a4e058
Implement basic state switching, different lists 2023-03-22 21:43:34 -04:00
2 changed files with 251 additions and 39 deletions

View File

@ -25,7 +25,7 @@ use paris::{error, info};
use serde::{Deserialize, Serialize};
mod structs;
use structs::{App, InputMode, StatefulList};
use structs::{App, InputMode, StatefulList, Blocks, SongList};
#[derive(Deserialize, Serialize)]
struct Config {
@ -174,18 +174,19 @@ fn run_app<B: Backend>(
let timeout = tick_rate
.checked_sub(last_tick.elapsed())
.unwrap_or_else(|| Duration::from_millis(10));
.unwrap_or_else(|| Duration::from_millis(1));
if crossterm::event::poll(timeout)? {
if let Event::Key(key) = event::read()? {
match app.input_mode {
InputMode::Normal => {
match key.code {
KeyCode::Char('q') => return Ok(()),
KeyCode::Char('h') => app.songs.unselect(),
KeyCode::Char('j') => app.songs.next(),
KeyCode::Char('k') => app.songs.previous(),
KeyCode::Char('i') => app.input_mode = InputMode::Editing,
KeyCode::Char('p') => app.mpd_client.toggle_pause().unwrap(),
KeyCode::Char('s') => app.mpd_client.stop().unwrap(),
KeyCode::Tab => app.switch_block(),
KeyCode::Char('l') => {
match app.songs.play() {
match app.songs.choose() {
Some(s) => {
match app.mpd_client.clear() {
Ok(..) => {},
@ -200,10 +201,28 @@ fn run_app<B: Backend>(
None => {},
}
},
KeyCode::Char('p') => app.mpd_client.toggle_pause().unwrap(),
KeyCode::Char('s') => app.mpd_client.stop().unwrap(),
KeyCode::Char('i') => app.input_mode = InputMode::Editing,
_ => {}
KeyCode::Char('h') => {
match app.selected_block {
Blocks::Songs => app.songs.unselect(),
Blocks::Artists => app.artists.unselect(),
Blocks::Albums => app.albums.unselect(),
}
},
KeyCode::Char('j') => {
match app.selected_block {
Blocks::Songs => app.songs.next(),
Blocks::Artists => app.artists.next(),
Blocks::Albums => app.albums.next(),
}
},
KeyCode::Char('k') => {
match app.selected_block {
Blocks::Songs => app.songs.previous(),
Blocks::Artists => app.artists.previous(),
Blocks::Albums => app.albums.previous(),
}
},
_ => {},
}
},
InputMode::Editing => {
@ -230,7 +249,7 @@ fn run_app<B: Backend>(
},
Err(e) => vec![("<no results>".to_string(), 0, None)],
};
app.songs = StatefulList::with_items(app.last_songs.clone());
app.songs = SongList::with_items(app.last_songs.clone());
app.input_mode = InputMode::Normal;
}
KeyCode::Char(c) => {
@ -252,7 +271,7 @@ fn run_app<B: Backend>(
},
Err(e) => vec![("<no results>".to_string(), 0, None)],
};
app.songs = StatefulList::with_items(app.last_songs.clone());
app.songs = SongList::with_items(app.last_songs.clone());
}
KeyCode::Backspace => {
app.input.pop();
@ -273,7 +292,7 @@ fn run_app<B: Backend>(
},
Err(e) => vec![("<no results>".to_string(), 0, None)],
};
app.songs = StatefulList::with_items(app.last_songs.clone());
app.songs = SongList::with_items(app.last_songs.clone());
}
_ => {}
}
@ -468,19 +487,47 @@ fn ui<B: Backend>(f: &mut Frame<B>, app: &mut App) {
)
.split(chunks[1]);
// Artist
let artists = vec![ListItem::new("a".to_string())];
let artist = List::new(artists)
.block(Block::default().title("Artist").borders(Borders::ALL))
.style(Style::default().fg(Color::White))
// Artists
let artists: Vec<ListItem> = app
.artists
.items
.iter()
.map(|i| {
let lines = vec![Spans::from(i.0.clone())];
ListItem::new(lines)
})
.collect();
let artists_block = List::new(artists)
.block(Block::default().title("Artists").borders(Borders::ALL)
.border_style(match app.selected_block {
Blocks::Artists => Style::default().fg(Color::Yellow),
_ => Style::default(),
}))
.highlight_style(Style::default().fg(Color::Yellow))
.highlight_symbol(">");
f.render_widget(artist, bottom_chunks[0]);
f.render_stateful_widget(artists_block, bottom_chunks[0], &mut app.artists.state);
// Album
let albums = Block::default().title("Album").borders(Borders::ALL);
f.render_widget(albums, bottom_chunks[1]);
// Song
// Albums
let albums: Vec<ListItem> = app
.albums
.items
.iter()
.map(|i| {
let lines = vec![Spans::from(i.0.clone())];
ListItem::new(lines)
})
.collect();
let albums_block = List::new(albums)
.block(Block::default().title("Albums").borders(Borders::ALL)
.border_style(match app.selected_block {
Blocks::Albums => Style::default().fg(Color::Yellow),
_ => Style::default(),
}))
.highlight_style(Style::default().fg(Color::Yellow))
.highlight_symbol(">");
f.render_stateful_widget(albums_block, bottom_chunks[1], &mut app.albums.state);
// Songs
let songs: Vec<ListItem> = app
.songs
.items
@ -490,12 +537,15 @@ fn ui<B: Backend>(f: &mut Frame<B>, app: &mut App) {
ListItem::new(lines)
})
.collect();
let song = List::new(songs)
.block(Block::default().title("Songs").borders(Borders::ALL))
.style(Style::default().fg(Color::White))
let songs_block = List::new(songs)
.block(Block::default().title("Songs").borders(Borders::ALL)
.border_style(match app.selected_block {
Blocks::Songs => Style::default().fg(Color::Yellow),
_ => Style::default(),
}))
.highlight_style(Style::default().fg(Color::Yellow))
.highlight_symbol(">");
f.render_stateful_widget(song, bottom_chunks[2], &mut app.songs.state);
f.render_stateful_widget(songs_block, bottom_chunks[2], &mut app.songs.state);
// Timeline
let percent = match mpd_status.time {

View File

@ -3,22 +3,39 @@ use tui::widgets::ListState;
use paris::error;
use std::process::exit;
pub struct StatefulList {
use mpd::song::Song;
pub trait StatefulList {
type Item;
type List;
type Selector;
fn with_items(items: Vec<Self::Item>) -> Self::List;
fn next(&mut self);
fn previous(&mut self);
fn choose(&mut self) -> Option<Self::Selector>;
fn unselect(&mut self);
}
pub struct SongList {
pub state: ListState,
pub items: Vec<(String, usize, Option<Song>)>,
}
use mpd::song::Song;
impl StatefulList for SongList {
type Item = (String, usize, Option<Song>);
type List = SongList;
type Selector = Song;
impl StatefulList {
pub fn with_items(items: Vec<(String, usize, Option<Song>)>) -> StatefulList {
StatefulList {
fn with_items(items: Vec<(String, usize, Option<Song>)>) -> SongList {
SongList {
state: ListState::default(),
items,
}
}
pub fn next(&mut self) {
fn next(&mut self) {
let i = match self.state.selected() {
Some(i) => {
if i >= self.items.len() - 1 {
@ -32,7 +49,7 @@ impl StatefulList {
self.state.select(Some(i));
}
pub fn previous(&mut self) {
fn previous(&mut self) {
let i = match self.state.selected() {
Some(i) => {
if i == 0 {
@ -46,7 +63,11 @@ impl StatefulList {
self.state.select(Some(i));
}
pub fn play(&mut self) -> Option<Song> {
fn unselect(&mut self) {
self.state.select(None);
}
fn choose(&mut self) -> Option<Song> {
let selected = match self.state.selected() {
Some(i) => i,
None => 0,
@ -55,11 +76,128 @@ impl StatefulList {
let songs = self.items[selected].2.clone();
return songs;
}
}
pub fn unselect(&mut self) {
pub struct ArtistList {
pub state: ListState,
pub items: Vec<(String, usize)>,
}
impl StatefulList for ArtistList {
type Item = (String, usize);
type List = ArtistList;
type Selector = String;
fn with_items(items: Vec<(String, usize)>) -> ArtistList {
ArtistList {
state: ListState::default(),
items,
}
}
fn next(&mut self) {
let i = match self.state.selected() {
Some(i) => {
if i >= self.items.len() - 1 {
0
} else {
i + 1
}
}
None => 0,
};
self.state.select(Some(i));
}
fn previous(&mut self) {
let i = match self.state.selected() {
Some(i) => {
if i == 0 {
self.items.len() - 1
} else {
i - 1
}
}
None => 0,
};
self.state.select(Some(i));
}
fn unselect(&mut self) {
self.state.select(None);
}
fn choose(&mut self) -> Option<String> {
let selected = match self.state.selected() {
Some(i) => i,
None => 0,
};
let artist = self.items[selected].1.clone();
return Some(artist.to_string());
}
}
pub struct AlbumList {
pub state: ListState,
pub items: Vec<(String, usize)>,
}
impl StatefulList for AlbumList {
type Item = (String, usize);
type List = AlbumList;
type Selector = String;
fn with_items(items: Vec<(String, usize)>) -> AlbumList {
AlbumList {
state: ListState::default(),
items,
}
}
fn next(&mut self) {
let i = match self.state.selected() {
Some(i) => {
if i >= self.items.len() - 1 {
0
} else {
i + 1
}
}
None => 0,
};
self.state.select(Some(i));
}
fn previous(&mut self) {
let i = match self.state.selected() {
Some(i) => {
if i == 0 {
self.items.len() - 1
} else {
i - 1
}
}
None => 0,
};
self.state.select(Some(i));
}
fn unselect(&mut self) {
self.state.select(None);
}
fn choose(&mut self) -> Option<String> {
let selected = match self.state.selected() {
Some(i) => i,
None => 0,
};
let album = self.items[selected].0.clone();
return Some(album);
}
}
/// This struct holds the current state of the app. In particular, it has the `items` field which is a wrapper
@ -69,15 +207,28 @@ impl StatefulList {
/// Check the event handling at the bottom to see how to change the state on incoming events.
/// Check the drawing logic for items on how to specify the highlighting style for selected items.
pub struct App {
pub songs: StatefulList,
pub songs: SongList,
pub last_songs: Vec<(String, usize, Option<Song>)>,
pub artists: ArtistList,
pub last_artists: Vec<(String, usize)>,
pub albums: AlbumList,
pub last_albums: Vec<(String, usize)>,
pub mpd_client: Client,
pub input: String,
pub input_mode: InputMode,
pub selected_block: Blocks,
pub messages: Vec<String>,
}
impl App {
pub fn switch_block(&mut self) {
let current_block = &self.selected_block;
self.selected_block = match current_block {
Blocks::Artists => Blocks::Albums,
Blocks::Albums => Blocks::Songs,
Blocks::Songs => Blocks::Artists,
};
}
pub fn new(port: u16, host: String) -> App {
let client = match Client::connect(format!("{}:{}", host, port)) {
Ok(conn) => conn,
@ -88,11 +239,16 @@ impl App {
};
App {
songs: StatefulList::with_items(vec![]),
songs: SongList::with_items(vec![]),
last_songs: vec![],
artists: ArtistList::with_items(vec![]),
last_artists: vec![],
albums: AlbumList::with_items(vec![]),
last_albums: vec![],
mpd_client: client,
input: String::new(),
input_mode: InputMode::Normal,
selected_block: Blocks::Songs,
messages: Vec::new(),
}
}
@ -102,3 +258,9 @@ pub enum InputMode {
Normal,
Editing,
}
pub enum Blocks {
Artists,
Albums,
Songs,
}