mirror of
https://github.com/talwat/lowfi
synced 2025-05-10 22:32:19 +00:00
138 lines
4.1 KiB
Rust
138 lines
4.1 KiB
Rust
//! Various different individual components that
|
|
//! appear in lowfi's UI, like the progress bar.
|
|
|
|
use std::{
|
|
ops::Deref as _,
|
|
sync::{atomic::Ordering, Arc},
|
|
time::Duration,
|
|
};
|
|
|
|
use crossterm::style::Stylize as _;
|
|
use unicode_segmentation::UnicodeSegmentation as _;
|
|
|
|
use crate::{player::Player, tracks::Info};
|
|
|
|
/// Small helper function to format durations.
|
|
pub fn format_duration(duration: &Duration) -> String {
|
|
let seconds = duration.as_secs() % 60;
|
|
let minutes = duration.as_secs() / 60;
|
|
|
|
format!("{minutes:02}:{seconds:02}")
|
|
}
|
|
|
|
/// Creates the progress bar, as well as all the padding needed.
|
|
pub fn progress_bar(player: &Player, current: Option<&Arc<Info>>, width: usize) -> String {
|
|
let mut duration = Duration::new(0, 0);
|
|
let elapsed = if current.is_some() {
|
|
player.sink.get_pos()
|
|
} else {
|
|
Duration::new(0, 0)
|
|
};
|
|
|
|
let mut filled = 0;
|
|
if let Some(current) = current {
|
|
if let Some(x) = current.duration {
|
|
duration = x;
|
|
|
|
let elapsed = elapsed.as_secs() as f32 / duration.as_secs() as f32;
|
|
filled = (elapsed * width as f32).round() as usize;
|
|
}
|
|
};
|
|
|
|
format!(
|
|
" [{}{}] {}/{} ",
|
|
"/".repeat(filled),
|
|
" ".repeat(width.saturating_sub(filled)),
|
|
format_duration(&elapsed),
|
|
format_duration(&duration),
|
|
)
|
|
}
|
|
|
|
/// Creates the audio bar, as well as all the padding needed.
|
|
pub fn audio_bar(volume: f32, percentage: &str, width: usize) -> String {
|
|
let audio = (volume * width as f32).round() as usize;
|
|
|
|
format!(
|
|
" volume: [{}{}] {}{} ",
|
|
"/".repeat(audio),
|
|
" ".repeat(width.saturating_sub(audio)),
|
|
" ".repeat(4usize.saturating_sub(percentage.len())),
|
|
percentage,
|
|
)
|
|
}
|
|
|
|
/// This represents the main "action" bars state.
|
|
enum ActionBar {
|
|
/// When the app is currently displaying "paused".
|
|
Paused(Info),
|
|
|
|
/// When the app is currently displaying "playing".
|
|
Playing(Info),
|
|
|
|
/// When the app is currently displaying "loading".
|
|
Loading,
|
|
}
|
|
|
|
impl ActionBar {
|
|
/// Formats the action bar to be displayed.
|
|
/// The second value is the character length of the result.
|
|
fn format(&self, star: bool) -> (String, usize) {
|
|
let (word, subject) = match self {
|
|
Self::Playing(x) => ("playing", Some((x.display_name.clone(), x.width))),
|
|
Self::Paused(x) => ("paused", Some((x.display_name.clone(), x.width))),
|
|
Self::Loading => ("loading", None),
|
|
};
|
|
|
|
subject.map_or_else(
|
|
|| (word.to_owned(), word.len()),
|
|
|(subject, len)| {
|
|
(
|
|
format!("{} {}{}", word, if star { "*" } else { "" }, subject.bold()),
|
|
word.len() + 1 + len + if star { 1 } else { 0 },
|
|
)
|
|
},
|
|
)
|
|
}
|
|
}
|
|
|
|
/// Creates the top/action bar, which has the name of the track and it's status.
|
|
/// This also creates all the needed padding.
|
|
pub fn action(player: &Player, current: Option<&Arc<Info>>, width: usize) -> String {
|
|
let (main, len) = current
|
|
.map_or(ActionBar::Loading, |info| {
|
|
let info = info.deref().clone();
|
|
|
|
if player.sink.is_paused() {
|
|
ActionBar::Paused(info)
|
|
} else {
|
|
ActionBar::Playing(info)
|
|
}
|
|
})
|
|
.format(player.bookmarked.load(Ordering::Relaxed));
|
|
|
|
if len > width {
|
|
let chopped: String = main.graphemes(true).take(width + 1).collect();
|
|
|
|
format!("{chopped}...")
|
|
} else {
|
|
format!("{}{}", main, " ".repeat(width - len))
|
|
}
|
|
}
|
|
|
|
/// Creates the bottom controls bar, and also spaces it properly.
|
|
pub fn controls(width: usize) -> String {
|
|
let controls = [["[s]", "kip"], ["[p]", "ause"], ["[q]", "uit"]];
|
|
|
|
let len: usize = controls.concat().iter().map(|x| x.len()).sum();
|
|
let controls = controls.map(|x| format!("{}{}", x[0].bold(), x[1]));
|
|
|
|
let mut controls = controls.join(&" ".repeat((width - len) / (controls.len() - 1)));
|
|
// This is needed because changing the above line
|
|
// only works for when the width is even
|
|
controls.push_str(match width % 2 {
|
|
0 => " ",
|
|
_ => "",
|
|
});
|
|
controls
|
|
}
|