mirror of
https://github.com/figsoda/mmtc
synced 2024-11-22 07:03:05 +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(
|
||||
columns: [
|
||||
Ratio(5, QueueTitle),
|
||||
Ratio(5, Parts([
|
||||
If(Selected, Text("> ")),
|
||||
QueueTitle,
|
||||
])),
|
||||
Ratio(4, QueueArtist),
|
||||
Ratio(4, QueueAlbum),
|
||||
],
|
||||
|
|
|
@ -11,6 +11,8 @@ use std::{
|
|||
|
||||
#[derive(Deserialize)]
|
||||
pub struct Config {
|
||||
#[serde(default)]
|
||||
pub cycle: bool,
|
||||
#[serde(default = "ups_default")]
|
||||
pub ups: f64,
|
||||
pub layout: Widget,
|
||||
|
@ -65,6 +67,7 @@ pub enum Condition {
|
|||
TitleExist,
|
||||
ArtistExist,
|
||||
AlbumExist,
|
||||
Selected,
|
||||
Not(Box<Condition>),
|
||||
And(Box<Condition>, Box<Condition>),
|
||||
Or(Box<Condition>, Box<Condition>),
|
||||
|
|
|
@ -2,7 +2,7 @@ use tui::{
|
|||
backend::Backend,
|
||||
layout::{Constraint, Direction, Layout, Rect},
|
||||
text::{Span, Spans},
|
||||
widgets::{List, ListItem, Paragraph},
|
||||
widgets::{List, ListItem, ListState, Paragraph},
|
||||
Frame,
|
||||
};
|
||||
|
||||
|
@ -17,6 +17,8 @@ pub fn render(
|
|||
widget: &Widget,
|
||||
queue: &Vec<Track>,
|
||||
status: &Status,
|
||||
selected: usize,
|
||||
liststate: &ListState,
|
||||
) {
|
||||
match widget {
|
||||
Widget::Rows(xs) => {
|
||||
|
@ -51,7 +53,7 @@ pub fn render(
|
|||
let mut ws = ws.into_iter();
|
||||
|
||||
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) => {
|
||||
|
@ -86,7 +88,7 @@ pub fn render(
|
|||
let mut ws = ws.into_iter();
|
||||
|
||||
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) => {
|
||||
|
@ -96,7 +98,7 @@ pub fn render(
|
|||
} else {
|
||||
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);
|
||||
}
|
||||
Widget::Queue { columns } => {
|
||||
|
@ -125,10 +127,18 @@ pub fn render(
|
|||
Constrained::Min(n, w) => (w, Constraint::Min(*n)),
|
||||
Constrained::Ratio(n, xs) => (xs, Constraint::Ratio(*n, denom)),
|
||||
};
|
||||
|
||||
let mut items = Vec::with_capacity(len);
|
||||
for x in queue {
|
||||
for i in 0 .. queue.len() - 1 {
|
||||
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)));
|
||||
}
|
||||
ws.push(List::new(items));
|
||||
|
@ -143,7 +153,7 @@ pub fn render(
|
|||
let mut ws = ws.into_iter();
|
||||
|
||||
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,
|
||||
current_track: Option<&Track>,
|
||||
queue_track: Option<&Track>,
|
||||
selected: bool,
|
||||
) {
|
||||
match xs {
|
||||
Texts::Empty => (),
|
||||
|
@ -240,21 +251,26 @@ fn flatten(
|
|||
}
|
||||
Texts::Parts(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) => {
|
||||
let xs = if eval_cond(cond, status, current_track) {
|
||||
let xs = if eval_cond(cond, status, current_track, selected) {
|
||||
yes
|
||||
} else {
|
||||
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 {
|
||||
Condition::Playing => current_track.is_some(),
|
||||
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::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) => {
|
||||
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) => {
|
||||
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) => {
|
||||
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,
|
||||
time::{sleep_until, Duration, Instant},
|
||||
};
|
||||
use tui::{backend::CrosstermBackend, Terminal};
|
||||
use tui::{backend::CrosstermBackend, widgets::ListState, Terminal};
|
||||
|
||||
use std::{
|
||||
io::{stdout, Write},
|
||||
|
@ -47,6 +47,8 @@ enum Command {
|
|||
UpdateFrame,
|
||||
UpdateQueue(Vec<Track>),
|
||||
UpdateStatus(Status),
|
||||
Down,
|
||||
Up,
|
||||
}
|
||||
|
||||
#[tokio::main]
|
||||
|
@ -68,6 +70,9 @@ async fn run() -> Result<()> {
|
|||
|
||||
let mut queue = mpd::queue(&mut idle_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);
|
||||
|
||||
|
@ -123,7 +128,13 @@ async fn run() -> Result<()> {
|
|||
match ev {
|
||||
Event::Key(KeyEvent { code, .. }) => match code {
|
||||
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::UpdateFrame => term
|
||||
.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")?,
|
||||
Command::UpdateQueue(new_queue) => {
|
||||
|
@ -149,6 +168,34 @@ async fn run() -> Result<()> {
|
|||
status = new_status;
|
||||
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>,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
#[derive(Copy, Clone, Debug)]
|
||||
pub struct Song {
|
||||
pub pos: usize,
|
||||
pub elapsed: u16,
|
||||
|
|
Loading…
Reference in a new issue