mmtc/src/layout.rs

305 lines
9.8 KiB
Rust
Raw Normal View History

2020-10-30 12:20:37 -04:00
use tui::{
backend::Backend,
layout::{Constraint, Direction, Layout, Rect},
text::{Span, Spans},
2020-10-31 15:19:45 -04:00
widgets::{List, ListItem, ListState, Paragraph},
2020-10-30 12:20:37 -04:00
Frame,
};
use crate::{
config::{Condition, Constrained, Texts, Widget},
mpd::{Song, Status, Track},
};
pub fn render(
frame: &mut Frame<impl Backend>,
size: Rect,
widget: &Widget,
queue: &Vec<Track>,
status: &Status,
2020-10-31 15:19:45 -04:00
selected: usize,
liststate: &ListState,
2020-10-30 12:20:37 -04:00
) {
match widget {
Widget::Rows(xs) => {
let len = xs.capacity();
let mut ws = Vec::with_capacity(len);
let mut cs = Vec::with_capacity(len);
let denom = xs.iter().fold(0, |n, x| {
if let Constrained::Ratio(m, _) = x {
n + m
} else {
n
}
});
for x in xs {
2020-10-30 17:41:00 -04:00
let (w, constraint) = match x {
Constrained::Fixed(n, w) => (w, Constraint::Length(*n)),
Constrained::Max(n, w) => (w, Constraint::Max(*n)),
Constrained::Min(n, w) => (w, Constraint::Min(*n)),
2020-10-30 17:41:00 -04:00
Constrained::Ratio(n, w) => (w, Constraint::Ratio(*n, denom)),
};
ws.push(w);
cs.push(constraint);
2020-10-30 12:20:37 -04:00
}
let layout = Layout::default()
.direction(Direction::Vertical)
.constraints(cs);
let mut chunks = layout.split(size).into_iter();
let mut ws = ws.into_iter();
while let (Some(chunk), Some(w)) = (chunks.next(), ws.next()) {
2020-10-31 15:19:45 -04:00
render(frame, chunk, w, queue, status, selected, liststate);
2020-10-30 12:20:37 -04:00
}
}
Widget::Columns(xs) => {
let len = xs.capacity();
let mut ws = Vec::with_capacity(len);
let mut cs = Vec::with_capacity(len);
let denom = xs.iter().fold(0, |n, x| {
if let Constrained::Ratio(m, _) = x {
n + m
} else {
n
}
});
for x in xs {
2020-10-30 17:41:00 -04:00
let (w, constraint) = match x {
Constrained::Fixed(n, w) => (w, Constraint::Length(*n)),
Constrained::Max(n, w) => (w, Constraint::Max(*n)),
Constrained::Min(n, w) => (w, Constraint::Min(*n)),
2020-10-30 17:41:00 -04:00
Constrained::Ratio(n, w) => (w, Constraint::Ratio(*n, denom)),
};
ws.push(w);
cs.push(constraint);
2020-10-30 12:20:37 -04:00
}
2020-10-30 17:41:00 -04:00
2020-10-30 12:20:37 -04:00
let layout = Layout::default()
.direction(Direction::Horizontal)
.constraints(cs);
let mut chunks = layout.split(size).into_iter();
let mut ws = ws.into_iter();
while let (Some(chunk), Some(w)) = (chunks.next(), ws.next()) {
2020-10-31 15:19:45 -04:00
render(frame, chunk, w, queue, status, selected, liststate);
2020-10-30 12:20:37 -04:00
}
}
Widget::Textbox(xss) => {
let mut spans = Vec::new();
let current_track = if let Some(Song { pos, .. }) = status.song {
queue.get(pos)
} else {
None
};
2020-10-31 15:19:45 -04:00
flatten(&mut spans, &xss, status, current_track, None, false);
2020-10-30 12:20:37 -04:00
frame.render_widget(Paragraph::new(Spans::from(spans)), size);
}
Widget::Queue { columns } => {
let len = columns.capacity();
let mut ws = Vec::with_capacity(len);
let mut cs = Vec::with_capacity(len);
let denom = columns.iter().fold(0, |n, x| {
if let Constrained::Ratio(m, _) = x {
n + m
} else {
n
}
});
let current_track = if let Some(Song { pos, .. }) = status.song {
queue.get(pos)
2020-10-30 12:20:37 -04:00
} else {
None
2020-10-30 12:20:37 -04:00
};
2020-10-30 17:41:00 -04:00
2020-10-30 12:20:37 -04:00
for column in columns {
2020-10-30 17:41:00 -04:00
let (xs, constraint) = match column {
Constrained::Fixed(n, w) => (w, Constraint::Length(*n)),
Constrained::Max(n, w) => (w, Constraint::Max(*n)),
Constrained::Min(n, w) => (w, Constraint::Min(*n)),
2020-10-30 17:41:00 -04:00
Constrained::Ratio(n, xs) => (xs, Constraint::Ratio(*n, denom)),
};
2020-10-31 15:19:45 -04:00
2020-10-30 17:41:00 -04:00
let mut items = Vec::with_capacity(len);
2020-10-31 15:19:45 -04:00
for i in 0 .. queue.len() - 1 {
2020-10-30 17:41:00 -04:00
let mut spans = Vec::new();
2020-10-31 15:19:45 -04:00
flatten(
&mut spans,
xs,
status,
current_track,
Some(&queue[i]),
i == selected,
);
2020-10-30 17:41:00 -04:00
items.push(ListItem::new(Spans::from(spans)));
2020-10-30 12:20:37 -04:00
}
2020-10-30 17:41:00 -04:00
ws.push(List::new(items));
cs.push(constraint);
2020-10-30 12:20:37 -04:00
}
let layout = Layout::default()
.direction(Direction::Horizontal)
.constraints(cs);
let mut chunks = layout.split(size).into_iter();
let mut ws = ws.into_iter();
while let (Some(chunk), Some(w)) = (chunks.next(), ws.next()) {
2020-10-31 15:19:45 -04:00
frame.render_stateful_widget(w, chunk, &mut liststate.clone());
2020-10-30 12:20:37 -04:00
}
}
}
}
fn flatten(
spans: &mut Vec<Span>,
xs: &Texts,
status: &Status,
2020-10-30 12:20:37 -04:00
current_track: Option<&Track>,
queue_track: Option<&Track>,
2020-10-31 15:19:45 -04:00
selected: bool,
2020-10-30 12:20:37 -04:00
) {
match xs {
Texts::Empty => (),
2020-10-30 17:09:22 -04:00
Texts::Text(x) => spans.push(Span::raw(x.clone())),
Texts::CurrentElapsed => {
if let Some(Song { elapsed, .. }) = status.song {
spans.push(Span::raw(format!(
"{:02}:{:02}",
elapsed / 60,
elapsed % 60
)))
}
}
Texts::CurrentDuration => {
if let Some(Track { time, .. }) = current_track {
spans.push(Span::raw(format!("{:02}:{:02}", time / 60, time % 60,)))
}
}
2020-10-30 12:20:37 -04:00
Texts::CurrentFile => {
if let Some(Track { file, .. }) = current_track {
spans.push(Span::raw(file.clone()));
}
}
Texts::CurrentTitle => {
if let Some(Track {
title: Some(title), ..
}) = current_track
{
spans.push(Span::raw(title.clone()));
}
}
Texts::CurrentArtist => {
if let Some(Track {
artist: Some(artist),
..
}) = current_track
{
spans.push(Span::raw(artist.clone()));
}
}
Texts::CurrentAlbum => {
if let Some(Track {
album: Some(album), ..
}) = current_track
{
spans.push(Span::raw(album.clone()));
}
}
Texts::QueueDuration => {
if let Some(Track { time, .. }) = queue_track {
spans.push(Span::raw(format!("{:02}:{:02}", time / 60, time % 60,)))
}
}
2020-10-30 12:20:37 -04:00
Texts::QueueFile => {
if let Some(Track { file, .. }) = current_track {
spans.push(Span::raw(file.clone()));
}
}
Texts::QueueTitle => {
if let Some(Track {
title: Some(title), ..
}) = queue_track
{
spans.push(Span::raw(title.clone()));
}
}
Texts::QueueArtist => {
if let Some(Track {
artist: Some(artist),
..
}) = queue_track
{
spans.push(Span::raw(artist.clone()));
}
}
Texts::QueueAlbum => {
if let Some(Track {
album: Some(album), ..
}) = queue_track
{
spans.push(Span::raw(album.clone()));
}
}
Texts::Parts(xss) => {
for xs in xss {
2020-10-31 15:19:45 -04:00
flatten(spans, xs, status, current_track, queue_track, selected);
2020-10-30 12:20:37 -04:00
}
}
Texts::If(cond, box yes, box no) => {
2020-10-31 15:19:45 -04:00
let xs = if eval_cond(cond, status, current_track, selected) {
2020-10-30 12:20:37 -04:00
yes
} else {
no
};
2020-10-31 15:19:45 -04:00
flatten(spans, xs, status, current_track, queue_track, selected);
2020-10-30 12:20:37 -04:00
}
}
}
2020-10-31 15:19:45 -04:00
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,
Condition::Random => status.random,
Condition::Single => status.single == Some(true),
Condition::Oneshot => status.single == None,
Condition::Consume => status.consume,
Condition::TitleExist => matches!(current_track, Some(Track { title: Some(_), .. })),
Condition::ArtistExist => matches!(
current_track,
Some(Track {
artist: Some(_), ..
}),
),
Condition::AlbumExist => matches!(current_track, Some(Track { album: Some(_), .. })),
2020-10-31 15:19:45 -04:00
Condition::Selected => selected,
Condition::Not(box x) => !eval_cond(x, status, current_track, selected),
Condition::And(box x, box y) => {
2020-10-31 15:19:45 -04:00
eval_cond(x, status, current_track, selected)
&& eval_cond(y, status, current_track, selected)
}
Condition::Or(box x, box y) => {
2020-10-31 15:19:45 -04:00
eval_cond(x, status, current_track, selected)
|| eval_cond(y, status, current_track, selected)
}
Condition::Xor(box x, box y) => {
2020-10-31 15:19:45 -04:00
eval_cond(x, status, current_track, selected)
^ eval_cond(y, status, current_track, selected)
}
}
}