mirror of
https://github.com/figsoda/mmtc
synced 2024-11-22 07:03:05 +00:00
option to configure layout
This commit is contained in:
parent
446a2edaa2
commit
cd57e56a94
5 changed files with 523 additions and 90 deletions
45
Cargo.lock
generated
45
Cargo.lock
generated
|
@ -12,6 +12,12 @@ version = "0.4.7"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4d25d88fd6b8041580a654f9d0c581a047baee2b3efee13275f2fc392fc75034"
|
||||
|
||||
[[package]]
|
||||
name = "base64"
|
||||
version = "0.12.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3441f0f7b02788e948e47f457ca01f1d7e6d92c693bc132c22b087d3141c03ff"
|
||||
|
||||
[[package]]
|
||||
name = "bitflags"
|
||||
version = "1.2.1"
|
||||
|
@ -78,9 +84,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "crossterm"
|
||||
version = "0.18.1"
|
||||
version = "0.18.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cef9149b29071d44c9fb98fd9c27fcf74405bbdb761889ad6a03f36be93b0b15"
|
||||
checksum = "4e86d73f2a0b407b5768d10a8c720cf5d2df49a9efc10ca09176d201ead4b7fb"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
"crossterm_winapi",
|
||||
|
@ -208,8 +214,10 @@ name = "mmtc"
|
|||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"crossterm 0.18.1",
|
||||
"crossterm 0.18.2",
|
||||
"expand",
|
||||
"ron",
|
||||
"serde",
|
||||
"tokio",
|
||||
"tui",
|
||||
]
|
||||
|
@ -313,12 +321,43 @@ version = "0.1.57"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "41cc0f7e4d5d4544e8861606a285bb08d3e70712ccc7d2b84d7c0ccfaf4b05ce"
|
||||
|
||||
[[package]]
|
||||
name = "ron"
|
||||
version = "0.6.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f8a58080b7bb83b2ea28c3b7a9a994fd5e310330b7c8ca5258d99b98128ecfe4"
|
||||
dependencies = [
|
||||
"base64",
|
||||
"bitflags",
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "scopeguard"
|
||||
version = "1.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd"
|
||||
|
||||
[[package]]
|
||||
name = "serde"
|
||||
version = "1.0.117"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b88fa983de7720629c9387e9f517353ed404164b1e482c970a90c1a4aaf7dc1a"
|
||||
dependencies = [
|
||||
"serde_derive",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_derive"
|
||||
version = "1.0.117"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cbd1ae72adb44aab48f325a02444a5fc079349a8d804c1fc922aed3f7454c74e"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "signal-hook"
|
||||
version = "0.1.16"
|
||||
|
|
|
@ -13,8 +13,10 @@ readme = "README.md"
|
|||
|
||||
[dependencies]
|
||||
anyhow = "1.0.33"
|
||||
crossterm = "0.18.1"
|
||||
crossterm = "0.18.2"
|
||||
expand = "0.1.2"
|
||||
ron = "0.6.2"
|
||||
serde = { version = "1.0.117", features = ["derive"] }
|
||||
tui = { version = "0.12.0", default-features = false, features = ["crossterm"] }
|
||||
|
||||
[dependencies.tokio]
|
||||
|
|
191
src/config.rs
Normal file
191
src/config.rs
Normal file
|
@ -0,0 +1,191 @@
|
|||
use serde::{
|
||||
de::{self, EnumAccess, SeqAccess, VariantAccess, Visitor},
|
||||
Deserialize, Deserializer,
|
||||
};
|
||||
|
||||
use std::{
|
||||
cmp::min,
|
||||
error,
|
||||
fmt::{self, Formatter},
|
||||
};
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
pub struct Config {
|
||||
pub layout: Widget,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
pub enum Widget {
|
||||
Rows(Vec<Constrained<Widget>>),
|
||||
Columns(Vec<Constrained<Widget>>),
|
||||
Textbox(Texts),
|
||||
Queue { columns: Vec<Constrained<Texts>> },
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
pub enum Constrained<T> {
|
||||
Free(T),
|
||||
Fixed(u16, T),
|
||||
Ratio(u32, T),
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum Texts {
|
||||
Empty,
|
||||
Plain(String),
|
||||
CurrentFile,
|
||||
CurrentTitle,
|
||||
CurrentArtist,
|
||||
CurrentAlbum,
|
||||
QueueFile,
|
||||
QueueTitle,
|
||||
QueueArtist,
|
||||
QueueAlbum,
|
||||
Parts(Vec<Texts>),
|
||||
If(Condition, Box<Texts>, Box<Texts>),
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
pub enum Condition {
|
||||
TitleExist,
|
||||
ArtistExist,
|
||||
AlbumExist,
|
||||
}
|
||||
|
||||
impl<'de> Deserialize<'de> for Texts {
|
||||
fn deserialize<D>(de: D) -> Result<Self, D::Error>
|
||||
where
|
||||
D: Deserializer<'de>,
|
||||
{
|
||||
struct TextsVisitor;
|
||||
impl<'de> Visitor<'de> for TextsVisitor {
|
||||
type Value = Texts;
|
||||
|
||||
fn expecting(&self, formatter: &mut Formatter) -> fmt::Result {
|
||||
formatter.write_str("enum Texts")
|
||||
}
|
||||
|
||||
fn visit_unit<E: error::Error>(self) -> Result<Self::Value, E> {
|
||||
Ok(Texts::Empty)
|
||||
}
|
||||
|
||||
fn visit_seq<A: SeqAccess<'de>>(self, mut sa: A) -> Result<Self::Value, A::Error> {
|
||||
let mut xs = Vec::with_capacity(min(sa.size_hint().unwrap_or(0), 4096));
|
||||
while let Some(x) = sa.next_element()? {
|
||||
xs.push(x);
|
||||
}
|
||||
Ok(Texts::Parts(xs))
|
||||
}
|
||||
|
||||
fn visit_enum<A: EnumAccess<'de>>(self, ea: A) -> Result<Self::Value, A::Error> {
|
||||
#[derive(Deserialize)]
|
||||
#[serde(field_identifier)]
|
||||
enum Variant {
|
||||
Plain,
|
||||
CurrentFile,
|
||||
CurrentTitle,
|
||||
CurrentArtist,
|
||||
CurrentAlbum,
|
||||
QueueFile,
|
||||
QueueTitle,
|
||||
QueueArtist,
|
||||
QueueAlbum,
|
||||
Parts,
|
||||
If,
|
||||
IfNot,
|
||||
}
|
||||
|
||||
struct IfVisitor;
|
||||
impl<'de> Visitor<'de> for IfVisitor {
|
||||
type Value = Texts;
|
||||
|
||||
fn expecting(&self, formatter: &mut Formatter) -> fmt::Result {
|
||||
formatter.write_str("IfNot variant")
|
||||
}
|
||||
|
||||
fn visit_seq<A: SeqAccess<'de>>(
|
||||
self,
|
||||
mut sa: A,
|
||||
) -> Result<Self::Value, A::Error> {
|
||||
Ok(Texts::If(
|
||||
sa.next_element()?
|
||||
.ok_or_else(|| de::Error::invalid_length(0, &self))?,
|
||||
sa.next_element()?.map_or_else(
|
||||
|| Err(de::Error::invalid_length(1, &self)),
|
||||
|x| Ok(Box::new(x)),
|
||||
)?,
|
||||
Box::new(sa.next_element()?.unwrap_or(Texts::Empty)),
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
struct IfNotVisitor;
|
||||
impl<'de> Visitor<'de> for IfNotVisitor {
|
||||
type Value = Texts;
|
||||
|
||||
fn expecting(&self, formatter: &mut Formatter) -> fmt::Result {
|
||||
formatter.write_str("IfNot variant")
|
||||
}
|
||||
|
||||
fn visit_seq<A: SeqAccess<'de>>(
|
||||
self,
|
||||
mut sa: A,
|
||||
) -> Result<Self::Value, A::Error> {
|
||||
let cond = sa
|
||||
.next_element()?
|
||||
.ok_or_else(|| de::Error::invalid_length(0, &self))?;
|
||||
let no = sa.next_element()?.map_or_else(
|
||||
|| Err(de::Error::invalid_length(1, &self)),
|
||||
|x| Ok(Box::new(x)),
|
||||
)?;
|
||||
let yes = Box::new(sa.next_element()?.unwrap_or(Texts::Empty));
|
||||
Ok(Texts::If(cond, yes, no))
|
||||
}
|
||||
}
|
||||
|
||||
let (variant, va) = ea.variant()?;
|
||||
|
||||
macro_rules! unit_variant {
|
||||
($v:ident) => {{
|
||||
va.unit_variant()?;
|
||||
Ok(Texts::$v)
|
||||
}};
|
||||
}
|
||||
|
||||
match variant {
|
||||
Variant::Plain => Ok(Texts::Plain(va.newtype_variant()?)),
|
||||
Variant::CurrentFile => unit_variant!(CurrentFile),
|
||||
Variant::CurrentTitle => unit_variant!(CurrentTitle),
|
||||
Variant::CurrentArtist => unit_variant!(CurrentArtist),
|
||||
Variant::CurrentAlbum => unit_variant!(CurrentAlbum),
|
||||
Variant::QueueFile => unit_variant!(QueueFile),
|
||||
Variant::QueueTitle => unit_variant!(QueueTitle),
|
||||
Variant::QueueArtist => unit_variant!(QueueArtist),
|
||||
Variant::QueueAlbum => unit_variant!(QueueAlbum),
|
||||
Variant::Parts => Ok(Texts::Parts(va.newtype_variant()?)),
|
||||
Variant::If => va.tuple_variant(3, IfVisitor),
|
||||
Variant::IfNot => va.tuple_variant(3, IfNotVisitor),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
de.deserialize_enum(
|
||||
"Texts",
|
||||
&[
|
||||
"Plain",
|
||||
"CurrentFile",
|
||||
"CurrentTitle",
|
||||
"CurrentArtist",
|
||||
"CurrentAlbum",
|
||||
"QueueFile",
|
||||
"QueueTitle",
|
||||
"QueueArtist",
|
||||
"QueueAlbum",
|
||||
"Parts",
|
||||
"If",
|
||||
"IfNot",
|
||||
],
|
||||
TextsVisitor,
|
||||
)
|
||||
}
|
||||
}
|
279
src/layout.rs
Normal file
279
src/layout.rs
Normal file
|
@ -0,0 +1,279 @@
|
|||
use tui::{
|
||||
backend::Backend,
|
||||
layout::{Constraint, Direction, Layout, Rect},
|
||||
text::{Span, Spans},
|
||||
widgets::{List, ListItem, Paragraph},
|
||||
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,
|
||||
) {
|
||||
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 {
|
||||
match x {
|
||||
Constrained::Free(w) => {
|
||||
ws.push(w);
|
||||
cs.push(Constraint::Min(0));
|
||||
}
|
||||
Constrained::Fixed(n, w) => {
|
||||
ws.push(w);
|
||||
cs.push(Constraint::Length(*n));
|
||||
}
|
||||
Constrained::Ratio(n, w) => {
|
||||
ws.push(w);
|
||||
cs.push(Constraint::Ratio(*n, denom));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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()) {
|
||||
render(frame, chunk, w, queue, status);
|
||||
}
|
||||
}
|
||||
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 {
|
||||
match x {
|
||||
Constrained::Free(w) => {
|
||||
ws.push(w);
|
||||
cs.push(Constraint::Min(1));
|
||||
}
|
||||
Constrained::Fixed(n, w) => {
|
||||
ws.push(w);
|
||||
cs.push(Constraint::Length(*n));
|
||||
}
|
||||
Constrained::Ratio(n, w) => {
|
||||
ws.push(w);
|
||||
cs.push(Constraint::Ratio(*n, denom));
|
||||
}
|
||||
}
|
||||
}
|
||||
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()) {
|
||||
render(frame, chunk, w, queue, status);
|
||||
}
|
||||
}
|
||||
Widget::Textbox(xss) => {
|
||||
let mut spans = Vec::new();
|
||||
flatten(
|
||||
&mut spans,
|
||||
&xss,
|
||||
if let Some(Song { pos, .. }) = status.song {
|
||||
queue.get(pos)
|
||||
} else {
|
||||
None
|
||||
},
|
||||
None,
|
||||
);
|
||||
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 len = columns.capacity();
|
||||
let current_track = if let Some(Song { pos, .. }) = status.song {
|
||||
queue.get(pos)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
for column in columns {
|
||||
match column {
|
||||
Constrained::Free(xs) => {
|
||||
let mut items = Vec::with_capacity(len);
|
||||
for x in queue {
|
||||
let mut spans = Vec::new();
|
||||
flatten(&mut spans, xs, current_track, Some(x));
|
||||
items.push(ListItem::new(Spans::from(spans)));
|
||||
}
|
||||
ws.push(List::new(items));
|
||||
cs.push(Constraint::Min(1));
|
||||
}
|
||||
Constrained::Fixed(n, xs) => {
|
||||
let mut items = Vec::with_capacity(len);
|
||||
for x in queue {
|
||||
let mut spans = Vec::new();
|
||||
flatten(&mut spans, xs, current_track, Some(x));
|
||||
items.push(ListItem::new(Spans::from(spans)));
|
||||
}
|
||||
ws.push(List::new(items));
|
||||
cs.push(Constraint::Length(*n));
|
||||
}
|
||||
Constrained::Ratio(n, xs) => {
|
||||
let mut items = Vec::with_capacity(len);
|
||||
for x in queue {
|
||||
let mut spans = Vec::new();
|
||||
flatten(&mut spans, xs, current_track, Some(x));
|
||||
items.push(ListItem::new(Spans::from(spans)));
|
||||
}
|
||||
ws.push(List::new(items));
|
||||
cs.push(Constraint::Ratio(*n, denom));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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()) {
|
||||
frame.render_widget(w, chunk);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn flatten(
|
||||
spans: &mut Vec<Span>,
|
||||
xs: &Texts,
|
||||
current_track: Option<&Track>,
|
||||
queue_track: Option<&Track>,
|
||||
) {
|
||||
match xs {
|
||||
Texts::Empty => (),
|
||||
Texts::Plain(x) => spans.push(Span::raw(x.clone())),
|
||||
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::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 {
|
||||
flatten(spans, xs, current_track, queue_track);
|
||||
}
|
||||
}
|
||||
Texts::If(cond, box yes, box no) => {
|
||||
let xs = if match cond {
|
||||
Condition::TitleExist => {
|
||||
matches!(current_track, Some(Track { title: Some(_), .. }))
|
||||
}
|
||||
Condition::ArtistExist => matches!(
|
||||
current_track,
|
||||
Some(Track {
|
||||
artist: Some(_), ..
|
||||
}),
|
||||
),
|
||||
Condition::AlbumExist => matches!(queue_track, Some(Track { album: Some(_), .. })),
|
||||
} {
|
||||
yes
|
||||
} else {
|
||||
no
|
||||
};
|
||||
flatten(spans, xs, current_track, queue_track);
|
||||
}
|
||||
}
|
||||
}
|
94
src/main.rs
94
src/main.rs
|
@ -1,7 +1,10 @@
|
|||
#![feature(async_closure)]
|
||||
#![feature(box_patterns)]
|
||||
#![forbid(unsafe_code)]
|
||||
|
||||
mod config;
|
||||
mod fail;
|
||||
mod layout;
|
||||
mod mpd;
|
||||
|
||||
use anyhow::{Context, Result};
|
||||
|
@ -14,12 +17,7 @@ use tokio::{
|
|||
sync::Mutex,
|
||||
time::{sleep_until, Duration, Instant},
|
||||
};
|
||||
use tui::{
|
||||
backend::CrosstermBackend,
|
||||
layout::{Constraint, Direction, Layout, Rect},
|
||||
widgets::{List, ListItem, Paragraph},
|
||||
Terminal,
|
||||
};
|
||||
use tui::{backend::CrosstermBackend, Terminal};
|
||||
|
||||
use std::{
|
||||
io::{stdout, Write},
|
||||
|
@ -28,7 +26,7 @@ use std::{
|
|||
sync::Arc,
|
||||
};
|
||||
|
||||
use crate::mpd::{Song, Status, Track};
|
||||
use crate::config::Config;
|
||||
|
||||
fn cleanup() -> Result<()> {
|
||||
disable_raw_mode().context("Failed to clean up terminal")?;
|
||||
|
@ -53,16 +51,9 @@ async fn main() -> Result<()> {
|
|||
}
|
||||
|
||||
async fn run() -> Result<()> {
|
||||
let cfg: Config = ron::from_str(&std::fs::read_to_string("mmtc.ron").unwrap()).unwrap();
|
||||
let addr = SocketAddr::new(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), 6600);
|
||||
|
||||
let queue_layout = Layout::default()
|
||||
.direction(Direction::Horizontal)
|
||||
.constraints(vec![
|
||||
Constraint::Ratio(1, 3),
|
||||
Constraint::Ratio(1, 3),
|
||||
Constraint::Ratio(1, 3),
|
||||
]);
|
||||
|
||||
let mut idle_cl = mpd::init(addr).await.with_context(fail::connect(addr))?;
|
||||
let mut status_cl = mpd::init(addr).await.with_context(fail::connect(addr))?;
|
||||
|
||||
|
@ -106,79 +97,10 @@ async fn run() -> Result<()> {
|
|||
let deadline = Instant::now() + Duration::from_secs_f32(1.0 / 30.0);
|
||||
|
||||
let queue = &*queue.lock().await;
|
||||
let status = (*status.lock().await).clone();
|
||||
let status = &*status.lock().await;
|
||||
|
||||
term.draw(|frame| {
|
||||
let len = queue.len();
|
||||
let mut titles = Vec::with_capacity(len);
|
||||
let mut artists = Vec::with_capacity(len);
|
||||
let mut albums = Vec::with_capacity(len);
|
||||
|
||||
for Track {
|
||||
title,
|
||||
artist,
|
||||
album,
|
||||
..
|
||||
} in queue
|
||||
{
|
||||
titles.push(ListItem::new(title.clone().unwrap_or_default()));
|
||||
artists.push(ListItem::new(artist.clone().unwrap_or_default()));
|
||||
albums.push(ListItem::new(album.clone().unwrap_or_default()));
|
||||
}
|
||||
|
||||
let Rect {
|
||||
x,
|
||||
y,
|
||||
width,
|
||||
height,
|
||||
} = frame.size();
|
||||
let chunks = queue_layout.split(Rect {
|
||||
x,
|
||||
y,
|
||||
width,
|
||||
height: y + height - 1,
|
||||
});
|
||||
frame.render_widget(List::new(titles), chunks[0]);
|
||||
frame.render_widget(List::new(artists), chunks[1]);
|
||||
frame.render_widget(List::new(albums), chunks[2]);
|
||||
|
||||
if let Status {
|
||||
song: Some(Song { pos, elapsed }),
|
||||
..
|
||||
} = status
|
||||
{
|
||||
if let Some(Track {
|
||||
file,
|
||||
artist,
|
||||
album,
|
||||
title,
|
||||
time,
|
||||
}) = queue.get(pos)
|
||||
{
|
||||
frame.render_widget(
|
||||
Paragraph::new(format!(
|
||||
"[{:02}:{:02}/{:02}:{:02}] {}",
|
||||
elapsed / 60,
|
||||
elapsed % 60,
|
||||
time / 60,
|
||||
time % 60,
|
||||
match (title, artist, album) {
|
||||
(Some(title), Some(artist), Some(album)) =>
|
||||
format!("{} - {} - {}", title, artist, album),
|
||||
(Some(title), Some(artist), _) => format!("{} - {}", title, artist),
|
||||
(Some(title), ..) => title.clone(),
|
||||
_ => file.clone(),
|
||||
}
|
||||
)),
|
||||
Rect {
|
||||
x,
|
||||
y: y + height - 1,
|
||||
width,
|
||||
height: 1,
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
layout::render(frame, frame.size(), &cfg.layout, queue, status);
|
||||
})
|
||||
.context("Failed to draw to terminal")?;
|
||||
|
||||
|
|
Loading…
Reference in a new issue