feat: improve UI flexibility if resizing is implemented

This commit is contained in:
Tal 2024-10-03 18:05:22 +02:00
parent 9741d4b0d5
commit b85d8c0be2
5 changed files with 56 additions and 59 deletions

View File

@ -46,6 +46,6 @@ async fn main() -> eyre::Result<()> {
} => scrape::scrape(extension, include_full).await,
}
} else {
play::play(cli.alternate).await
play::play(cli).await
}
}

View File

@ -6,17 +6,18 @@ use tokio::{sync::mpsc, task};
use crate::player::Player;
use crate::player::{ui, Messages};
use crate::Args;
/// Initializes the audio server, and then safely stops
/// it when the frontend quits.
pub async fn play(alternate: bool) -> eyre::Result<()> {
pub async fn play(args: Args) -> eyre::Result<()> {
let (tx, rx) = mpsc::channel(8);
let player = Arc::new(Player::new().await?);
let audio = task::spawn(Player::play(Arc::clone(&player), tx.clone(), rx));
tx.send(Messages::Init).await?;
ui::start(Arc::clone(&player), tx.clone(), alternate).await?;
ui::start(Arc::clone(&player), tx.clone(), args).await?;
audio.abort();
player.sink.stop();

View File

@ -218,7 +218,9 @@ impl Player {
}
}
Messages::ChangeVolume(change) => {
player.sink.set_volume((player.sink.volume() + change).clamp(0.0, 1.0));
player
.sink
.set_volume((player.sink.volume() + change).clamp(0.0, 1.0));
}
}
}

View File

@ -9,6 +9,8 @@ use std::{
time::Duration,
};
use crate::Args;
use super::Player;
use crossterm::{
cursor::{Hide, MoveTo, MoveToColumn, MoveUp, Show},
@ -16,6 +18,7 @@ use crossterm::{
style::{Print, Stylize},
terminal::{self, Clear, ClearType, EnterAlternateScreen, LeaveAlternateScreen},
};
use lazy_static::lazy_static;
use tokio::{
sync::mpsc::Sender,
task::{self},
@ -29,53 +32,51 @@ mod components;
/// The total width of the UI.
const WIDTH: usize = 27;
/// The width of the progress bar, not including the borders (`[` and `]`) or padding.
const PROGRESS_WIDTH: usize = WIDTH - 16;
/// The width of the audio bar, again not including borders or padding.
const AUDIO_WIDTH: usize = WIDTH - 17;
/// Self explanitory.
const FPS: usize = 12;
/// How long the audio bar will be visible for when audio is adjusted.
/// This is in frames.
const AUDIO_BAR_DURATION: usize = 9;
const AUDIO_BAR_DURATION: usize = 10;
/// How long to wait in between frames.
/// This is fairly arbitrary, but an ideal value should be enough to feel
/// snappy but not require too many resources.
const FRAME_DELTA: f32 = 1.0 / FPS as f32;
lazy_static! {
/// The volume timer, which controls how long the volume display should
/// show up and when it should disappear.
static ref VOLUME_TIMER: AtomicUsize = AtomicUsize::new(0);
}
/// The code for the interface itself.
///
/// `volume_timer` is a bit strange, but it tracks how long the `volume` bar
/// has been displayed for, so that it's only displayed for a certain amount of frames.
async fn interface(player: Arc<Player>, volume_timer: Arc<AtomicUsize>) -> eyre::Result<()> {
async fn interface(player: Arc<Player>) -> eyre::Result<()> {
loop {
let action = components::action(&player);
let action = components::action(&player, WIDTH);
let timer = VOLUME_TIMER.load(Ordering::Relaxed);
let volume = player.sink.volume();
let percentage = format!("{}%", (volume * 100.0).round().abs());
let timer = volume_timer.load(Ordering::Relaxed);
let middle = match timer {
0 => components::progress_bar(&player),
_ => components::audio_bar(&player),
0 => components::progress_bar(&player, WIDTH - 16),
_ => components::audio_bar(volume, &percentage, WIDTH - 17),
};
if timer > 0 && timer <= AUDIO_BAR_DURATION {
volume_timer.fetch_add(1, Ordering::Relaxed);
VOLUME_TIMER.fetch_add(1, Ordering::Relaxed);
} else if timer > AUDIO_BAR_DURATION {
volume_timer.store(0, Ordering::Relaxed);
VOLUME_TIMER.store(0, Ordering::Relaxed);
}
let controls = [
format!("{}kip", "[s]".bold()),
format!("{}ause", "[p]".bold()),
format!("{}uit", "[q]".bold()),
];
let controls = components::controls(WIDTH);
// Formats the menu properly
let menu = [action, middle, controls.join(" ")]
.map(|x| format!("{}\r\n", x.reset()).to_string());
let menu = [action, middle, controls].map(|x| format!("{}\r\n", x.reset()).to_string());
crossterm::execute!(
stdout(),
@ -96,22 +97,16 @@ async fn interface(player: Arc<Player>, volume_timer: Arc<AtomicUsize>) -> eyre:
///
/// `alternate` controls whether to use [EnterAlternateScreen] in order to hide
/// previous terminal history.
pub async fn start(
queue: Arc<Player>,
sender: Sender<Messages>,
alternate: bool,
) -> eyre::Result<()> {
pub async fn start(queue: Arc<Player>, sender: Sender<Messages>, args: Args) -> eyre::Result<()> {
crossterm::execute!(stdout(), Hide)?;
terminal::enable_raw_mode()?;
if alternate {
if args.alternate {
crossterm::execute!(stdout(), EnterAlternateScreen, MoveTo(0, 0))?;
}
let volume_timer = Arc::new(AtomicUsize::new(0));
task::spawn(interface(Arc::clone(&queue), volume_timer.clone()));
task::spawn(interface(Arc::clone(&queue)));
loop {
let event::Event::Key(event) = event::read()? else {
@ -145,16 +140,16 @@ pub async fn start(
_ => continue,
};
// If it's modifying the volume, then we'll set the `volume_timer` to 1
// If it's modifying the volume, then we'll set the `VOLUME_TIMER` to 1
// so that the ui thread will know that it should show the audio bar.
if let Messages::ChangeVolume(_) = messages {
volume_timer.store(1, Ordering::Relaxed);
VOLUME_TIMER.store(1, Ordering::Relaxed);
}
sender.send(messages).await?;
}
if alternate {
if args.alternate {
crossterm::execute!(stdout(), LeaveAlternateScreen)?;
}

View File

@ -2,15 +2,7 @@ use std::{sync::Arc, time::Duration};
use crossterm::style::Stylize;
use crate::{
player::{
ui::{AUDIO_WIDTH, PROGRESS_WIDTH},
Player,
},
tracks::TrackInfo,
};
use super::WIDTH;
use crate::{player::Player, tracks::TrackInfo};
/// Small helper function to format durations.
pub fn format_duration(duration: &Duration) -> String {
@ -21,7 +13,7 @@ pub fn format_duration(duration: &Duration) -> String {
}
/// Creates the progress bar, as well as all the padding needed.
pub fn progress_bar(player: &Arc<Player>) -> String {
pub fn progress_bar(player: &Arc<Player>, width: usize) -> String {
let mut duration = Duration::new(0, 0);
let elapsed = player.sink.get_pos();
@ -31,30 +23,27 @@ pub fn progress_bar(player: &Arc<Player>) -> String {
duration = x;
let elapsed = elapsed.as_secs() as f32 / duration.as_secs() as f32;
filled = (elapsed * PROGRESS_WIDTH as f32).round() as usize;
filled = (elapsed * width as f32).round() as usize;
}
};
format!(
" [{}{}] {}/{} ",
"/".repeat(filled),
" ".repeat(PROGRESS_WIDTH.saturating_sub(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(player: &Arc<Player>) -> String {
let volume = player.sink.volume();
let audio = (player.sink.volume() * AUDIO_WIDTH as f32).round() as usize;
let percentage = format!("{}%", (volume * 100.0).round().abs());
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(AUDIO_WIDTH.saturating_sub(audio)),
" ".repeat(width.saturating_sub(audio)),
" ".repeat(4usize.saturating_sub(percentage.len())),
percentage,
)
@ -91,7 +80,7 @@ impl ActionBar {
/// 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: &Arc<Player>) -> String {
pub fn action(player: &Arc<Player>, width: usize) -> String {
let (main, len) = player
.current
.load()
@ -106,9 +95,19 @@ pub fn action(player: &Arc<Player>) -> String {
})
.format();
if len > WIDTH {
format!("{}...", &main[..=WIDTH])
if len > width {
format!("{}...", &main[..=width])
} else {
format!("{}{}", main, " ".repeat(WIDTH - len))
format!("{}{}", main, " ".repeat(width - len))
}
}
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]));
controls.join(&" ".repeat((width - len) / (controls.len() - 1)))
}