diff --git a/src/main.rs b/src/main.rs index 5710ebc..72bcd11 100644 --- a/src/main.rs +++ b/src/main.rs @@ -3,16 +3,84 @@ use crossterm::{ execute, terminal::{disable_raw_mode, enable_raw_mode, EnterAlternateScreen, LeaveAlternateScreen}, }; -use std::{error::Error, io}; +use std::{error::Error, io, time::{Duration, Instant}}; use tui::{ backend::{Backend, CrosstermBackend}, layout::{Alignment, Constraint, Direction, Layout}, style::{Color, Modifier, Style}, - text::Span, - widgets::{Block, BorderType, Borders}, + text::{Span, Spans}, + widgets::{Block, BorderType, Borders, List, ListItem, ListState, Gauge}, Frame, Terminal, }; +struct StatefulList { + state: ListState, + items: Vec, +} + +impl StatefulList { + fn with_items(items: Vec) -> StatefulList { + StatefulList { + 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); + } +} + +/// This struct holds the current state of the app. In particular, it has the `items` field which is a wrapper +/// around `ListState`. Keeping track of the items state let us render the associated widget with its state +/// and have access to features such as natural scrolling. +/// +/// 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. +struct App<'a> { + items: StatefulList<(&'a str, usize)>, +} + +impl<'a> App<'a> { + fn new() -> App<'a> { + App { + items: StatefulList::with_items(vec![ + ("Item0", 1), + ("Item1", 2), + ("Item2", 3), + ]), + } + } +} + fn main() -> Result<(), Box> { // setup terminal enable_raw_mode()?; @@ -22,7 +90,8 @@ fn main() -> Result<(), Box> { let mut terminal = Terminal::new(backend)?; // create app and run it - let res = run_app(&mut terminal); + let app = App::new(); + let res = run_app(&mut terminal, app); // restore terminal disable_raw_mode()?; @@ -40,19 +109,24 @@ fn main() -> Result<(), Box> { Ok(()) } -fn run_app(terminal: &mut Terminal) -> io::Result<()> { +fn run_app(terminal: &mut Terminal, mut app: App) -> io::Result<()> { loop { - terminal.draw(ui)?; + terminal.draw(|f| ui(f, &mut app))?; - if let Event::Key(key) = event::read()? { - if let KeyCode::Char('q') = key.code { - return Ok(()); + if let Event::Key(key) = event::read()? { + match key.code { + KeyCode::Char('q') => return Ok(()), + KeyCode::Char('h') => app.items.unselect(), + KeyCode::Char('j') => app.items.next(), + KeyCode::Char('k') => app.items.previous(), + _ => {} + } } - } } } -fn ui(f: &mut Frame) { + +fn ui(f: &mut Frame, app: &mut App) { // Wrapping block for a group // Just draw the block and the group on the same area and build the group // with at least a margin of 1 @@ -68,8 +142,8 @@ fn ui(f: &mut Frame) { let chunks = Layout::default() .direction(Direction::Vertical) - .margin(4) - .constraints([Constraint::Percentage(30), Constraint::Percentage(69), Constraint::Percentage(1)].as_ref()) + .margin(2) + .constraints([Constraint::Percentage(30), Constraint::Percentage(68), Constraint::Percentage(2)].as_ref()) .split(f.size()); // Top two inner blocks @@ -107,8 +181,23 @@ fn ui(f: &mut Frame) { .split(chunks[1]); // Artist - let artists = Block::default().title("Artist").borders(Borders::ALL); - f.render_widget(artists, bottom_chunks[0]); + let artists: Vec = app + .items + .items + .iter() + .map(|i| { + let mut lines = vec![Spans::from(i.0)]; + ListItem::new(lines) + }) + .collect(); + + let artist = List::new(artists) + .block(Block::default().title("Artist").borders(Borders::ALL)) + .style(Style::default().fg(Color::White)) + .highlight_style(Style::default().fg(Color::Yellow)) + .highlight_symbol(">"); + f.render_stateful_widget(artist, bottom_chunks[0], &mut app.items.state); + // Album let albums = Block::default().title("Album").borders(Borders::ALL); f.render_widget(albums, bottom_chunks[1]); @@ -117,7 +206,10 @@ fn ui(f: &mut Frame) { f.render_widget(songs, bottom_chunks[2]); // Timeline - let timeline = Block::default(); + let timeline = Gauge::default() + .block(Block::default().borders(Borders::NONE)) + .gauge_style(Style::default().fg(Color::Red)) + .percent(50); f.render_widget(timeline, chunks[2]); }