naviagte through queue via j/k/down/up

This commit is contained in:
figsoda 2020-10-31 15:19:45 -04:00
parent cab3baad34
commit 06db49324e
5 changed files with 93 additions and 20 deletions

View file

@ -7,7 +7,10 @@ Config(
])), ])),
Min(0, Queue( Min(0, Queue(
columns: [ columns: [
Ratio(5, QueueTitle), Ratio(5, Parts([
If(Selected, Text("> ")),
QueueTitle,
])),
Ratio(4, QueueArtist), Ratio(4, QueueArtist),
Ratio(4, QueueAlbum), Ratio(4, QueueAlbum),
], ],

View file

@ -11,6 +11,8 @@ use std::{
#[derive(Deserialize)] #[derive(Deserialize)]
pub struct Config { pub struct Config {
#[serde(default)]
pub cycle: bool,
#[serde(default = "ups_default")] #[serde(default = "ups_default")]
pub ups: f64, pub ups: f64,
pub layout: Widget, pub layout: Widget,
@ -65,6 +67,7 @@ pub enum Condition {
TitleExist, TitleExist,
ArtistExist, ArtistExist,
AlbumExist, AlbumExist,
Selected,
Not(Box<Condition>), Not(Box<Condition>),
And(Box<Condition>, Box<Condition>), And(Box<Condition>, Box<Condition>),
Or(Box<Condition>, Box<Condition>), Or(Box<Condition>, Box<Condition>),

View file

@ -2,7 +2,7 @@ use tui::{
backend::Backend, backend::Backend,
layout::{Constraint, Direction, Layout, Rect}, layout::{Constraint, Direction, Layout, Rect},
text::{Span, Spans}, text::{Span, Spans},
widgets::{List, ListItem, Paragraph}, widgets::{List, ListItem, ListState, Paragraph},
Frame, Frame,
}; };
@ -17,6 +17,8 @@ pub fn render(
widget: &Widget, widget: &Widget,
queue: &Vec<Track>, queue: &Vec<Track>,
status: &Status, status: &Status,
selected: usize,
liststate: &ListState,
) { ) {
match widget { match widget {
Widget::Rows(xs) => { Widget::Rows(xs) => {
@ -51,7 +53,7 @@ pub fn render(
let mut ws = ws.into_iter(); let mut ws = ws.into_iter();
while let (Some(chunk), Some(w)) = (chunks.next(), ws.next()) { while let (Some(chunk), Some(w)) = (chunks.next(), ws.next()) {
render(frame, chunk, w, queue, status); render(frame, chunk, w, queue, status, selected, liststate);
} }
} }
Widget::Columns(xs) => { Widget::Columns(xs) => {
@ -86,7 +88,7 @@ pub fn render(
let mut ws = ws.into_iter(); let mut ws = ws.into_iter();
while let (Some(chunk), Some(w)) = (chunks.next(), ws.next()) { while let (Some(chunk), Some(w)) = (chunks.next(), ws.next()) {
render(frame, chunk, w, queue, status); render(frame, chunk, w, queue, status, selected, liststate);
} }
} }
Widget::Textbox(xss) => { Widget::Textbox(xss) => {
@ -96,7 +98,7 @@ pub fn render(
} else { } else {
None None
}; };
flatten(&mut spans, &xss, status, current_track, None); flatten(&mut spans, &xss, status, current_track, None, false);
frame.render_widget(Paragraph::new(Spans::from(spans)), size); frame.render_widget(Paragraph::new(Spans::from(spans)), size);
} }
Widget::Queue { columns } => { Widget::Queue { columns } => {
@ -125,10 +127,18 @@ pub fn render(
Constrained::Min(n, w) => (w, Constraint::Min(*n)), Constrained::Min(n, w) => (w, Constraint::Min(*n)),
Constrained::Ratio(n, xs) => (xs, Constraint::Ratio(*n, denom)), Constrained::Ratio(n, xs) => (xs, Constraint::Ratio(*n, denom)),
}; };
let mut items = Vec::with_capacity(len); let mut items = Vec::with_capacity(len);
for x in queue { for i in 0 .. queue.len() - 1 {
let mut spans = Vec::new(); let mut spans = Vec::new();
flatten(&mut spans, xs, status, current_track, Some(x)); flatten(
&mut spans,
xs,
status,
current_track,
Some(&queue[i]),
i == selected,
);
items.push(ListItem::new(Spans::from(spans))); items.push(ListItem::new(Spans::from(spans)));
} }
ws.push(List::new(items)); ws.push(List::new(items));
@ -143,7 +153,7 @@ pub fn render(
let mut ws = ws.into_iter(); let mut ws = ws.into_iter();
while let (Some(chunk), Some(w)) = (chunks.next(), ws.next()) { while let (Some(chunk), Some(w)) = (chunks.next(), ws.next()) {
frame.render_widget(w, chunk); frame.render_stateful_widget(w, chunk, &mut liststate.clone());
} }
} }
} }
@ -155,6 +165,7 @@ fn flatten(
status: &Status, status: &Status,
current_track: Option<&Track>, current_track: Option<&Track>,
queue_track: Option<&Track>, queue_track: Option<&Track>,
selected: bool,
) { ) {
match xs { match xs {
Texts::Empty => (), Texts::Empty => (),
@ -240,21 +251,26 @@ fn flatten(
} }
Texts::Parts(xss) => { Texts::Parts(xss) => {
for xs in xss { for xs in xss {
flatten(spans, xs, status, current_track, queue_track); flatten(spans, xs, status, current_track, queue_track, selected);
} }
} }
Texts::If(cond, box yes, box no) => { Texts::If(cond, box yes, box no) => {
let xs = if eval_cond(cond, status, current_track) { let xs = if eval_cond(cond, status, current_track, selected) {
yes yes
} else { } else {
no no
}; };
flatten(spans, xs, status, current_track, queue_track); flatten(spans, xs, status, current_track, queue_track, selected);
} }
} }
} }
fn eval_cond(cond: &Condition, status: &Status, current_track: Option<&Track>) -> bool { fn eval_cond(
cond: &Condition,
status: &Status,
current_track: Option<&Track>,
selected: bool,
) -> bool {
match cond { match cond {
Condition::Playing => current_track.is_some(), Condition::Playing => current_track.is_some(),
Condition::Repeat => status.repeat, Condition::Repeat => status.repeat,
@ -270,15 +286,19 @@ fn eval_cond(cond: &Condition, status: &Status, current_track: Option<&Track>) -
}), }),
), ),
Condition::AlbumExist => matches!(current_track, Some(Track { album: Some(_), .. })), Condition::AlbumExist => matches!(current_track, Some(Track { album: Some(_), .. })),
Condition::Not(box x) => !eval_cond(x, status, current_track), Condition::Selected => selected,
Condition::Not(box x) => !eval_cond(x, status, current_track, selected),
Condition::And(box x, box y) => { Condition::And(box x, box y) => {
eval_cond(x, status, current_track) && eval_cond(y, status, current_track) eval_cond(x, status, current_track, selected)
&& eval_cond(y, status, current_track, selected)
} }
Condition::Or(box x, box y) => { Condition::Or(box x, box y) => {
eval_cond(x, status, current_track) || eval_cond(y, status, current_track) eval_cond(x, status, current_track, selected)
|| eval_cond(y, status, current_track, selected)
} }
Condition::Xor(box x, box y) => { Condition::Xor(box x, box y) => {
eval_cond(x, status, current_track) ^ eval_cond(y, status, current_track) eval_cond(x, status, current_track, selected)
^ eval_cond(y, status, current_track, selected)
} }
} }
} }

View file

@ -16,7 +16,7 @@ use tokio::{
sync::mpsc, sync::mpsc,
time::{sleep_until, Duration, Instant}, time::{sleep_until, Duration, Instant},
}; };
use tui::{backend::CrosstermBackend, Terminal}; use tui::{backend::CrosstermBackend, widgets::ListState, Terminal};
use std::{ use std::{
io::{stdout, Write}, io::{stdout, Write},
@ -47,6 +47,8 @@ enum Command {
UpdateFrame, UpdateFrame,
UpdateQueue(Vec<Track>), UpdateQueue(Vec<Track>),
UpdateStatus(Status), UpdateStatus(Status),
Down,
Up,
} }
#[tokio::main] #[tokio::main]
@ -68,6 +70,9 @@ async fn run() -> Result<()> {
let mut queue = mpd::queue(&mut idle_cl).await?; let mut queue = mpd::queue(&mut idle_cl).await?;
let mut status = mpd::status(&mut status_cl).await?; let mut status = mpd::status(&mut status_cl).await?;
let mut selected = status.song.map_or(0, |song| song.pos);
let mut liststate = ListState::default();
liststate.select(Some(selected));
let update_interval = Duration::from_secs_f64(1.0 / cfg.ups); let update_interval = Duration::from_secs_f64(1.0 / cfg.ups);
@ -123,7 +128,13 @@ async fn run() -> Result<()> {
match ev { match ev {
Event::Key(KeyEvent { code, .. }) => match code { Event::Key(KeyEvent { code, .. }) => match code {
KeyCode::Char('q') | KeyCode::Esc => { KeyCode::Char('q') | KeyCode::Esc => {
tx.send(Command::Quit).await.unwrap_or_else(die) tx.send(Command::Quit).await.unwrap_or_else(die);
}
KeyCode::Char('j') | KeyCode::Down => {
tx.send(Command::Down).await.unwrap_or_else(die);
}
KeyCode::Char('k') | KeyCode::Up => {
tx.send(Command::Up).await.unwrap_or_else(die);
} }
_ => (), _ => (),
}, },
@ -138,7 +149,15 @@ async fn run() -> Result<()> {
Command::Quit => break, Command::Quit => break,
Command::UpdateFrame => term Command::UpdateFrame => term
.draw(|frame| { .draw(|frame| {
layout::render(frame, frame.size(), &cfg.layout, &queue, &status); layout::render(
frame,
frame.size(),
&cfg.layout,
&queue,
&status,
selected,
&liststate,
);
}) })
.context("Failed to draw to terminal")?, .context("Failed to draw to terminal")?,
Command::UpdateQueue(new_queue) => { Command::UpdateQueue(new_queue) => {
@ -149,6 +168,34 @@ async fn run() -> Result<()> {
status = new_status; status = new_status;
tx.send(Command::UpdateFrame).await?; tx.send(Command::UpdateFrame).await?;
} }
Command::Down => {
let len = queue.len();
if selected >= len {
selected = status.song.map_or(0, |song| song.pos);
} else if selected == len - 1 {
if cfg.cycle {
selected = 0
}
} else {
selected += 1
}
liststate.select(Some(selected));
tx.send(Command::UpdateFrame).await?;
}
Command::Up => {
let len = queue.len();
if selected >= len {
selected = status.song.map_or(0, |song| song.pos);
} else if selected == 0 {
if cfg.cycle {
selected = len - 1
}
} else {
selected -= 1
}
liststate.select(Some(selected));
tx.send(Command::UpdateFrame).await?;
}
} }
} }

View file

@ -20,7 +20,7 @@ pub struct Status {
pub song: Option<Song>, pub song: Option<Song>,
} }
#[derive(Debug)] #[derive(Copy, Clone, Debug)]
pub struct Song { pub struct Song {
pub pos: usize, pub pos: usize,
pub elapsed: u16, pub elapsed: u16,