diff --git a/Cargo.lock b/Cargo.lock index 3dc4e08..e829e72 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -24,6 +24,15 @@ dependencies = [ "pkg-config", ] +[[package]] +name = "android_system_properties" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311" +dependencies = [ + "libc", +] + [[package]] name = "anstream" version = "0.6.21" @@ -312,6 +321,17 @@ version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724" +[[package]] +name = "chrono" +version = "0.4.42" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "145052bdd345b87320e369255277e3fb5152762ad123a901ef5c262dd38fe8d2" +dependencies = [ + "iana-time-zone", + "num-traits", + "windows-link", +] + [[package]] name = "clap" version = "4.5.53" @@ -1032,6 +1052,30 @@ dependencies = [ "tracing", ] +[[package]] +name = "iana-time-zone" +version = "0.1.64" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "33e57f83510bb73707521ebaffa789ec8caf86f9657cad665b092b581d40e9fb" +dependencies = [ + "android_system_properties", + "core-foundation-sys", + "iana-time-zone-haiku", + "js-sys", + "log", + "wasm-bindgen", + "windows-core 0.62.2", +] + +[[package]] +name = "iana-time-zone-haiku" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f" +dependencies = [ + "cc", +] + [[package]] name = "icu_collections" version = "2.1.1" @@ -1284,6 +1328,7 @@ version = "2.0.0-dev" dependencies = [ "arc-swap", "bytes", + "chrono", "clap", "crossterm", "dirs", @@ -2993,7 +3038,7 @@ version = "0.54.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9252e5725dbed82865af151df558e754e4a3c2c30818359eb17465f1346a1b49" dependencies = [ - "windows-core", + "windows-core 0.54.0", "windows-targets 0.52.6", ] @@ -3003,10 +3048,45 @@ version = "0.54.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "12661b9c89351d684a50a8a643ce5f608e20243b9fb84687800163429f161d65" dependencies = [ - "windows-result", + "windows-result 0.1.2", "windows-targets 0.52.6", ] +[[package]] +name = "windows-core" +version = "0.62.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8e83a14d34d0623b51dce9581199302a221863196a1dde71a7663a4c2be9deb" +dependencies = [ + "windows-implement", + "windows-interface", + "windows-link", + "windows-result 0.4.1", + "windows-strings", +] + +[[package]] +name = "windows-implement" +version = "0.60.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "053e2e040ab57b9dc951b72c264860db7eb3b0200ba345b4e4c3b14f67855ddf" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "windows-interface" +version = "0.59.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f316c4a2570ba26bbec722032c4099d8c8bc095efccdc15688708623367e358" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "windows-link" version = "0.2.1" @@ -3022,6 +3102,24 @@ dependencies = [ "windows-targets 0.52.6", ] +[[package]] +name = "windows-result" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7781fa89eaf60850ac3d2da7af8e5242a5ea78d1a11c49bf2910bb5a73853eb5" +dependencies = [ + "windows-link", +] + +[[package]] +name = "windows-strings" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7837d08f69c77cf6b07689544538e017c1bfcf57e34b4c0ff58e6c2cd3b37091" +dependencies = [ + "windows-link", +] + [[package]] name = "windows-sys" version = "0.45.0" diff --git a/Cargo.toml b/Cargo.toml index 9d06d3e..aa24bdb 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -42,6 +42,7 @@ arc-swap = "1.7.1" # Data reqwest = { version = "0.12.9", features = ["stream", "http2", "default-tls"], default-features = false } +chrono = { version = "0.4.42", features = ["clock"], default-features = false } bytes = "1.9.0" # I/O diff --git a/src/main.rs b/src/main.rs index 2a50596..4cccc4e 100644 --- a/src/main.rs +++ b/src/main.rs @@ -41,6 +41,10 @@ pub struct Args { #[clap(long, short)] borderless: bool, + /// Include a small clock in the UI. + #[clap(long, short)] + clock: bool, + /// Start lowfi paused. #[clap(long, short)] paused: bool, diff --git a/src/ui.rs b/src/ui.rs index 675d921..98b6235 100644 --- a/src/ui.rs +++ b/src/ui.rs @@ -2,7 +2,7 @@ use std::sync::Arc; use crate::{ player::Current, - ui::{self, window::Window}, + ui::{self, interface::Clock, window::Window}, Args, }; use tokio::{ @@ -170,6 +170,7 @@ impl Handle { ) -> Result<()> { let mut interval = tokio::time::interval(params.delta); let mut window = Window::new(state.width, params.borderless); + let mut clock = params.clock.then(|| Clock::new(&mut window)); loop { if let Ok(message) = rx.try_recv() { @@ -181,6 +182,7 @@ impl Handle { } } + clock.as_mut().map(|x| x.update(&mut window)); interface::draw(&mut state, &mut window, params)?; interval.tick().await; } diff --git a/src/ui/interface.rs b/src/ui/interface.rs index 4e7bdef..26588b0 100644 --- a/src/ui/interface.rs +++ b/src/ui/interface.rs @@ -1,15 +1,48 @@ use std::{env, time::Duration}; +use tokio::time::Instant; + use crate::{ ui::{self, components, window::Window}, Args, }; +/// 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::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_secs() >= 1 { + 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()) + } +} + #[derive(Copy, Clone, Debug, Default)] pub struct Params { pub borderless: bool, pub minimalist: bool, pub enabled: bool, + pub clock: bool, pub delta: Duration, } @@ -28,6 +61,7 @@ impl TryFrom<&Args> for Params { Ok(Self { delta, enabled: !disabled, + clock: args.clock, minimalist: args.minimalist, borderless: args.borderless, }) @@ -46,7 +80,7 @@ pub(crate) fn menu(state: &mut ui::State, params: Params) -> Vec { Some(timer) => { let volume = state.sink.volume(); let percentage = format!("{}%", (volume * 100.0).round().abs()); - if timer.elapsed() > Duration::from_secs(1) { + if timer.elapsed() > Duration::from_millis(500) { state.timer = None; } diff --git a/src/ui/window.rs b/src/ui/window.rs index 746249c..2413e33 100644 --- a/src/ui/window.rs +++ b/src/ui/window.rs @@ -1,4 +1,7 @@ -use std::io::{stdout, Stdout}; +use std::{ + fmt::Display, + io::{stdout, Stdout}, +}; use crossterm::{ cursor::{MoveToColumn, MoveUp}, @@ -17,7 +20,7 @@ pub struct Window { borderless: bool, /// The top & bottom borders, which are here since they can be - /// prerendered, as they don't change from window to window. + /// prerendered, as they don't change every single draw. /// /// If the option to not include borders is set, these will just be empty [String]s. pub(crate) borders: [String; 2], @@ -51,6 +54,12 @@ impl Window { } } + /// Adds text to the top of the window. + pub fn display(&mut self, display: impl Display, len: usize) { + let new = format!("┌─ {} {}─┐", display, "─".repeat(self.width - len - 2)); + self.borders[0] = new; + } + /// Renders the window itself, but doesn't actually draw it. /// /// `testing` just determines whether to add special features