style: improve structure of clock code

This commit is contained in:
talwat 2025-12-17 10:31:30 +01:00
parent 11a6debcc4
commit ea24b7d8b3
7 changed files with 60 additions and 59 deletions

View File

@ -35,11 +35,11 @@ pub struct Args {
#[clap(long, short)] #[clap(long, short)]
minimalist: bool, minimalist: bool,
/// Exclude borders in UI. /// Exclude window borders.
#[clap(long, short)] #[clap(long, short)]
borderless: bool, borderless: bool,
/// Include a small clock in the UI. /// Include a clock.
#[clap(long, short)] #[clap(long, short)]
clock: bool, clock: bool,

View File

@ -8,50 +8,50 @@
#[cfg(test)] #[cfg(test)]
mod components { mod components {
use crate::ui; use crate::ui::interface;
use std::time::Duration; use std::time::Duration;
#[test] #[test]
fn format_duration_works() { fn format_duration_works() {
let d = Duration::from_secs(62); let d = Duration::from_secs(62);
assert_eq!(ui::components::format_duration(&d), "01:02"); assert_eq!(interface::components::format_duration(&d), "01:02");
} }
#[test] #[test]
fn format_duration_zero() { fn format_duration_zero() {
let d = Duration::from_secs(0); let d = Duration::from_secs(0);
assert_eq!(ui::components::format_duration(&d), "00:00"); assert_eq!(interface::components::format_duration(&d), "00:00");
} }
#[test] #[test]
fn format_duration_hours_wrap() { fn format_duration_hours_wrap() {
let d = Duration::from_secs(3661); // 1:01:01 let d = Duration::from_secs(3661); // 1:01:01
assert_eq!(ui::components::format_duration(&d), "61:01"); assert_eq!(interface::components::format_duration(&d), "61:01");
} }
#[test] #[test]
fn audio_bar_contains_percentage() { fn audio_bar_contains_percentage() {
let s = ui::components::audio_bar(10, 0.5, "50%"); let s = interface::components::audio_bar(10, 0.5, "50%");
assert!(s.contains("50%")); assert!(s.contains("50%"));
assert!(s.starts_with(" volume:")); assert!(s.starts_with(" volume:"));
} }
#[test] #[test]
fn audio_bar_muted_volume() { fn audio_bar_muted_volume() {
let s = ui::components::audio_bar(8, 0.0, "0%"); let s = interface::components::audio_bar(8, 0.0, "0%");
assert!(s.contains("0%")); assert!(s.contains("0%"));
} }
#[test] #[test]
fn audio_bar_full_volume() { fn audio_bar_full_volume() {
let s = ui::components::audio_bar(10, 1.0, "100%"); let s = interface::components::audio_bar(10, 1.0, "100%");
assert!(s.contains("100%")); assert!(s.contains("100%"));
} }
#[test] #[test]
fn controls_has_items() { fn controls_has_items() {
let s = ui::components::controls(30); let s = interface::components::controls(30);
assert!(s.contains("[s]")); assert!(s.contains("[s]"));
assert!(s.contains("[p]")); assert!(s.contains("[p]"));
assert!(s.contains("[q]")); assert!(s.contains("[q]"));
@ -60,7 +60,7 @@ mod components {
#[cfg(test)] #[cfg(test)]
mod window { mod window {
use crate::ui::window::Window; use crate::ui::interface::Window;
#[test] #[test]
fn new_border_strings() { fn new_border_strings() {

View File

@ -1,21 +1,16 @@
use std::sync::Arc; use std::sync::Arc;
use crate::{ use crate::{player::Current, ui, Args};
player::Current,
ui::{self, interface::Clock, window::Window},
Args,
};
use tokio::{ use tokio::{
sync::{broadcast, mpsc::Sender}, sync::{broadcast, mpsc::Sender},
task::JoinHandle, task::JoinHandle,
time::Instant, time::Instant,
}; };
pub mod components;
pub mod environment; pub mod environment;
pub use environment::Environment; pub use environment::Environment;
pub mod input; pub mod input;
pub mod interface; pub mod interface;
pub mod window;
#[cfg(feature = "mpris")] #[cfg(feature = "mpris")]
pub mod mpris; pub mod mpris;
@ -169,8 +164,8 @@ impl Handle {
params: interface::Params, params: interface::Params,
) -> Result<()> { ) -> Result<()> {
let mut interval = tokio::time::interval(params.delta); let mut interval = tokio::time::interval(params.delta);
let mut window = Window::new(state.width, params.borderless); let mut window = interface::Window::new(state.width, params.borderless);
let mut clock = params.clock.then(|| Clock::new(&mut window)); let mut clock = params.clock.then(|| interface::Clock::new(&mut window));
loop { loop {
if let Ok(message) = rx.try_recv() { if let Ok(message) = rx.try_recv() {
@ -182,9 +177,7 @@ impl Handle {
} }
} }
if let Some(x) = clock.as_mut() { clock.as_mut().map(|x| x.update(&mut window));
x.update(&mut window)
}
interface::draw(&mut state, &mut window, params)?; interface::draw(&mut state, &mut window, params)?;
interval.tick().await; interval.tick().await;
} }

View File

@ -1,39 +1,12 @@
use crate::{ use crate::{ui, Args};
ui::{self, components, window::Window},
Args,
};
use std::{env, time::Duration}; use std::{env, time::Duration};
use tokio::time::Instant;
/// An extremely simple clock to be used alongside the [`Window`]. pub mod clock;
pub struct Clock(Instant); pub mod components;
pub mod window;
impl Clock { pub use clock::Clock;
/// Small shorthand for getting the local time now, and formatting it. pub use window::Window;
#[inline]
fn now() -> chrono::format::DelayedFormat<chrono::format::StrftimeItems<'static>> {
chrono::Local::now().format("%H:%M:%S")
}
/// Checks if the last update was long enough ago, and if so,
/// updates the displayed clock.
///
/// This is to avoid constant calls to [`chrono::Local::now`], which
/// is somewhat expensive because of timezones.
pub fn update(&mut self, window: &mut Window) {
if self.0.elapsed().as_millis() >= 500 {
window.display(Self::now(), 8);
self.0 = Instant::now();
}
}
/// Simply creates a new clock, and renders it's initial state to the window top.
pub fn new(window: &mut Window) -> Self {
window.display(Self::now(), 8);
Self(Instant::now())
}
}
/// UI-specific parameters and options. /// UI-specific parameters and options.
#[derive(Copy, Clone, Debug, Default)] #[derive(Copy, Clone, Debug, Default)]
@ -47,7 +20,7 @@ pub struct Params {
/// Whether the visual part of the UI should be enabled. /// Whether the visual part of the UI should be enabled.
/// This only applies if the MPRIS feature is enabled. /// This only applies if the MPRIS feature is enabled.
pub enabled: bool, pub enabled: bool,
/// Whether to include the clock on the top bar. /// Whether to include the clock on the top bar.
pub clock: bool, pub clock: bool,

33
src/ui/interface/clock.rs Normal file
View File

@ -0,0 +1,33 @@
use tokio::time::Instant;
use super::window::Window;
/// An extremely simple clock to be used alongside the [`Window`].
pub struct Clock(Instant);
impl Clock {
/// Small shorthand for getting the local time now, and formatting it.
#[inline]
fn now() -> chrono::format::DelayedFormat<chrono::format::StrftimeItems<'static>> {
chrono::Local::now().format("%H:%M:%S")
}
/// Checks if the last update was long enough ago, and if so,
/// updates the displayed clock.
///
/// This is to avoid constant calls to [`chrono::Local::now`], which
/// is somewhat expensive because of timezones.
pub fn update(&mut self, window: &mut Window) {
if self.0.elapsed().as_millis() >= 200 {
window.display(Self::now(), 8);
self.0 = Instant::now();
}
}
/// Simply creates a new clock, and renders it's initial state to the top of the window.
pub fn new(window: &mut Window) -> Self {
window.display(Self::now(), 8);
Self(Instant::now())
}
}

View File

@ -11,6 +11,8 @@ use crossterm::{
use std::fmt::Write as _; use std::fmt::Write as _;
use unicode_segmentation::UnicodeSegmentation as _; use unicode_segmentation::UnicodeSegmentation as _;
use crate::ui;
/// Represents an abstraction for drawing the actual lowfi window itself. /// Represents an abstraction for drawing the actual lowfi window itself.
/// ///
/// The main purpose of this struct is just to add the fancy border, /// The main purpose of this struct is just to add the fancy border,
@ -72,7 +74,7 @@ impl Window {
content: Vec<String>, content: Vec<String>,
space: bool, space: bool,
testing: bool, testing: bool,
) -> super::Result<(String, u16)> { ) -> ui::Result<(String, u16)> {
let linefeed = if testing { "\n" } else { "\r\n" }; let linefeed = if testing { "\n" } else { "\r\n" };
let len: u16 = content.len().try_into()?; let len: u16 = content.len().try_into()?;
@ -110,7 +112,7 @@ impl Window {
} }
/// Actually draws the window, with each element in `content` being on a new line. /// Actually draws the window, with each element in `content` being on a new line.
pub fn draw(&mut self, content: Vec<String>, space: bool) -> super::Result<()> { pub fn draw(&mut self, content: Vec<String>, space: bool) -> ui::Result<()> {
let (rendered, height) = self.render(content, space, false)?; let (rendered, height) = self.render(content, space, false)?;
crossterm::execute!( crossterm::execute!(