mirror of
https://github.com/figsoda/mmtc
synced 2025-02-16 21:38:38 +00:00
naviagte through queue via j/k/down/up
This commit is contained in:
parent
cab3baad34
commit
06db49324e
5 changed files with 93 additions and 20 deletions
5
mmtc.ron
5
mmtc.ron
|
@ -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),
|
||||||
],
|
],
|
||||||
|
|
|
@ -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>),
|
||||||
|
|
|
@ -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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
53
src/main.rs
53
src/main.rs
|
@ -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?;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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,
|
||||||
|
|
Loading…
Add table
Reference in a new issue