mirror of
https://github.com/talwat/lowfi
synced 2026-04-28 04:43:26 +00:00
157 lines
4.2 KiB
Rust
157 lines
4.2 KiB
Rust
//! Responsible for the actual graphical interface of lowfi.
|
|
|
|
use crate::{
|
|
ui::{self, State},
|
|
Args,
|
|
};
|
|
use std::{io::stdout, time::Duration};
|
|
|
|
pub mod clock;
|
|
pub mod components;
|
|
pub mod titlebar;
|
|
pub mod window;
|
|
|
|
pub use clock::Clock;
|
|
pub use titlebar::TitleBar;
|
|
pub use window::Window;
|
|
|
|
/// UI-specific parameters and options.
|
|
#[derive(Copy, Clone, Debug)]
|
|
pub struct Params {
|
|
/// Whether to include borders.
|
|
pub borderless: bool,
|
|
|
|
/// Whether to include the bottom control bar.
|
|
pub minimalist: bool,
|
|
|
|
/// Whether the visual part of the UI should be enabled.
|
|
/// This only applies if the MPRIS feature is enabled.
|
|
pub enabled: bool,
|
|
|
|
/// Whether to include the clock on the top bar.
|
|
pub clock: bool,
|
|
|
|
/// The full inner width of the terminal window.
|
|
pub(crate) width: usize,
|
|
|
|
/// The total delta between frames, which takes into account
|
|
/// the time it takes to actually render each frame.
|
|
///
|
|
/// Derived from the FPS.
|
|
pub delta: Duration,
|
|
}
|
|
|
|
impl Default for Params {
|
|
fn default() -> Self {
|
|
Self {
|
|
borderless: false,
|
|
minimalist: false,
|
|
enabled: true,
|
|
clock: false,
|
|
width: 27,
|
|
delta: Duration::from_secs_f32(1.0 / 12.0),
|
|
}
|
|
}
|
|
}
|
|
|
|
impl TryFrom<&Args> for Params {
|
|
type Error = ui::Error;
|
|
|
|
fn try_from(args: &Args) -> ui::Result<Self> {
|
|
let delta = 1.0 / f32::from(args.fps);
|
|
let delta = Duration::from_secs_f32(delta);
|
|
|
|
let disabled = crate::env("LOWFI_DISABLE_UI");
|
|
if disabled && !cfg!(feature = "mpris") {
|
|
return Err(ui::Error::RejectedDisable);
|
|
}
|
|
|
|
Ok(Self {
|
|
delta,
|
|
enabled: !disabled,
|
|
clock: args.clock,
|
|
width: 21 + args.width.min(32) * 2,
|
|
minimalist: args.minimalist,
|
|
borderless: args.borderless,
|
|
})
|
|
}
|
|
}
|
|
|
|
/// All of the state related to the interface itself,
|
|
/// which is displayed each frame to the standard output.
|
|
pub struct Interface {
|
|
/// The [`Window`] to render to.
|
|
pub(crate) window: Window,
|
|
|
|
/// The interval to wait between frames.
|
|
interval: tokio::time::Interval,
|
|
|
|
/// The visual clock, which is [`None`] if it has
|
|
/// been disabled by the [`Params`].
|
|
clock: Option<Clock>,
|
|
|
|
/// The interface parameters that control smaller
|
|
/// aesthetic features and options.
|
|
params: Params,
|
|
}
|
|
|
|
impl Default for Interface {
|
|
#[inline]
|
|
fn default() -> Self {
|
|
Self::new(Params::default()).unwrap()
|
|
}
|
|
}
|
|
|
|
impl Interface {
|
|
/// Creates a new interface.
|
|
pub fn new(params: Params) -> ui::Result<Self> {
|
|
let mut window = Window::new(params.width, params.borderless, false, true);
|
|
|
|
Ok(Self {
|
|
clock: params.clock.then(|| Clock::new(&mut window)),
|
|
interval: tokio::time::interval(params.delta),
|
|
window,
|
|
params,
|
|
})
|
|
}
|
|
|
|
/// Creates a full "menu" from the [`ui::State`], which can be
|
|
/// easily put into a window for display.
|
|
///
|
|
/// The menu really is just a [`Vec`] of the different components,
|
|
/// with padding already added.
|
|
pub(crate) fn menu(&self, state: &State) -> Vec<String> {
|
|
let action = components::action(state, self.params.width);
|
|
|
|
let middle = if state.volume_timer.is_some() {
|
|
let volume = state.sink.volume();
|
|
let percentage = format!("{}%", (volume * 100.0).round().abs());
|
|
|
|
components::audio_bar(self.params.width - 17, volume, &percentage)
|
|
} else {
|
|
components::progress_bar(state, self.params.width - 16)
|
|
};
|
|
|
|
let controls = components::controls(self.params.width);
|
|
if self.params.minimalist {
|
|
vec![action, middle]
|
|
} else {
|
|
vec![action, middle, controls]
|
|
}
|
|
}
|
|
|
|
/// Draws the terminal. This will also wait for the specified
|
|
/// delta to pass before completing.
|
|
pub async fn draw(&mut self, state: &State) -> super::Result<()> {
|
|
if let Some(x) = self.clock.as_mut() {
|
|
x.update(&mut self.window);
|
|
}
|
|
|
|
let menu = self.menu(state);
|
|
self.window.draw(stdout().lock(), menu)?;
|
|
self.interval.tick().await;
|
|
|
|
Ok(())
|
|
}
|
|
}
|