Implement basic state switching, different lists
parent
373de985f8
commit
b0b0a4e058
90
src/main.rs
90
src/main.rs
|
@ -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,18 +487,38 @@ fn ui<B: Backend>(f: &mut Frame<B>, app: &mut App) {
|
|||
)
|
||||
.split(chunks[1]);
|
||||
|
||||
// Artist
|
||||
// Artists
|
||||
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))
|
||||
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_widget(artists_block, bottom_chunks[0]);
|
||||
|
||||
// Album
|
||||
let albums = Block::default().title("Album").borders(Borders::ALL);
|
||||
f.render_widget(albums, bottom_chunks[1]);
|
||||
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);
|
||||
|
||||
// Song
|
||||
let songs: Vec<ListItem> = app
|
||||
.songs
|
||||
|
@ -490,12 +529,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 {
|
||||
|
|
186
src/structs.rs
186
src/structs.rs
|
@ -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,
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue