diff --git a/Cargo.lock b/Cargo.lock index 0877323f..2d4d00ad 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -106,6 +106,17 @@ version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0b6a852b24ab71dffc585bcb46eaf7959d175cb865a7152e35b348d1b2960422" +[[package]] +name = "crossterm" +version = "0.27.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f476fe445d41c9e991fd07515a6f463074b782242ccf4a5b7b1d1012e70824df" +dependencies = [ + "bitflags", + "libc", + "parking_lot", +] + [[package]] name = "deranged" version = "0.3.11" @@ -224,6 +235,7 @@ dependencies = [ "anstream", "anyhow", "bpaf", + "crossterm", "deranged", "directories", "enable-ansi-support", @@ -319,6 +331,16 @@ version = "0.4.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "78b3ae25bc7c8c38cec158d1f2757ee79e9b3740fbc7ccf0e59e4b08d793fa89" +[[package]] +name = "lock_api" +version = "0.4.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "07af8b9cdd281b7915f413fa73f29ebd5d55d0d3f0155584dade1ff18cea1b17" +dependencies = [ + "autocfg", + "scopeguard", +] + [[package]] name = "log" version = "0.4.21" @@ -432,6 +454,29 @@ dependencies = [ "syn", ] +[[package]] +name = "parking_lot" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1bf18183cf54e8d6059647fc3063646a1801cf30896933ec2311622cc4b9a27" +dependencies = [ + "lock_api", + "parking_lot_core", +] + +[[package]] +name = "parking_lot_core" +version = "0.9.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e401f977ab385c9e4e3ab30627d6f26d00e2c73eef317493c4ec6d468726cf8" +dependencies = [ + "cfg-if", + "libc", + "redox_syscall", + "smallvec", + "windows-targets 0.52.5", +] + [[package]] name = "pin-project-lite" version = "0.2.14" @@ -462,6 +507,15 @@ dependencies = [ "proc-macro2", ] +[[package]] +name = "redox_syscall" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2a908a6e00f1fdd0dfd9c0eb08ce85126f6d8bbda50017e74bc4a4b7d4a926a4" +dependencies = [ + "bitflags", +] + [[package]] name = "redox_users" version = "0.4.5" @@ -536,6 +590,12 @@ dependencies = [ "winapi-util", ] +[[package]] +name = "scopeguard" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" + [[package]] name = "serde" version = "1.0.203" diff --git a/Cargo.toml b/Cargo.toml index 2575109b..5f64b75a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -17,6 +17,7 @@ ansi_colours = { version = "1.2.2", default-features = false } anstream = { version = "0.6.14", default-features = false } anyhow = { version = "1.0.86", default-features = false } bpaf = { version = "0.9.12", default-features = false } +crossterm = { version = "0.27.0", default-features = false } deranged = { version = "0.3.11", default-features = false } directories = { version = "5.0.1", default-features = false } enable-ansi-support = { version = "0.2.1", default-features = false } diff --git a/crates/hyfetch/Cargo.toml b/crates/hyfetch/Cargo.toml index 5cfa63af..c892aba8 100644 --- a/crates/hyfetch/Cargo.toml +++ b/crates/hyfetch/Cargo.toml @@ -15,6 +15,7 @@ ansi_colours = { workspace = true, features = [] } anstream = { workspace = true, features = [], optional = true } anyhow = { workspace = true, features = ["std"] } bpaf = { workspace = true, features = [] } +crossterm = { workspace = true, features = [] } deranged = { workspace = true, features = ["serde", "std"] } directories = { workspace = true, features = [] } enterpolation = { workspace = true, features = ["bspline", "std"] } diff --git a/crates/hyfetch/src/pride_month.rs b/crates/hyfetch/src/pride_month.rs index 7ee2efe4..20766f1f 100644 --- a/crates/hyfetch/src/pride_month.rs +++ b/crates/hyfetch/src/pride_month.rs @@ -1,3 +1,4 @@ +use std::fmt::Write as _; use std::io::{self, Write as _}; use std::num::{NonZeroU16, NonZeroUsize, Wrapping}; use std::sync::atomic::{AtomicBool, Ordering}; @@ -6,12 +7,16 @@ use std::time::Duration; use std::{cmp, thread}; use anyhow::{anyhow, Context as _, Result}; +use crossterm::execute; +use crossterm::terminal::{ + BeginSynchronizedUpdate, EndSynchronizedUpdate, EnterAlternateScreen, LeaveAlternateScreen, +}; use palette::blend::Blend as _; use palette::{LinSrgba, Srgb, WithAlpha as _}; use strum::VariantArray as _; use terminal_size::{terminal_size, Height, Width}; -use crate::color_util::{clear_screen, color, printc, ForegroundBackground, ToAnsiString as _}; +use crate::color_util::{color, ForegroundBackground, ToAnsiString as _}; use crate::neofetch_util::ascii_size; use crate::presets::Preset; use crate::types::AnsiMode; @@ -143,19 +148,25 @@ pub fn start_animation(color_mode: AnsiMode) -> Result<()> { let black = LinSrgba::new(0.0, 0.0, 0.0, 0.5); let draw_frame = |frame: usize| -> Result<()> { + execute!(io::stdout(), BeginSynchronizedUpdate) + .context("failed to begin synchronized update")?; + let mut buf = String::new(); // Loop over the height for y in 0..h.get() { // Print the starting color - buf.push_str( - &colors[frame + write!( + buf, + "{bg}{fg}", + bg = colors[frame .wrapping_add(y.into()) .div_euclid(block_width.get().into()) .rem_euclid(colors.len())] .to_ansi_string(color_mode, ForegroundBackground::Background), - ); - buf.push_str(&fg.to_ansi_string(color_mode, ForegroundBackground::Foreground)); + fg = fg.to_ansi_string(color_mode, ForegroundBackground::Foreground) + ) + .unwrap(); // Loop over the width for x in 0..w.get() { @@ -204,48 +215,68 @@ pub fn start_animation(color_mode: AnsiMode) -> Result<()> { { let c: LinSrgba = c.with_alpha(1.0).into_linear(); let c = Srgb::::from_linear(c.overlay(black).without_alpha()); - buf.push_str( - &c.to_ansi_string(color_mode, ForegroundBackground::Background), - ); + write!( + buf, + "{bg}", + bg = c.to_ansi_string(color_mode, ForegroundBackground::Background), + ) + .unwrap(); } else { - buf.push_str( - &c.to_ansi_string(color_mode, ForegroundBackground::Background), - ); + write!( + buf, + "{bg}", + bg = c.to_ansi_string(color_mode, ForegroundBackground::Background), + ) + .unwrap(); } } // If text should be printed, print text if y_text && text_start_x <= x && x < text_end_x { - buf.push( - text_lines[usize::from(y.checked_sub(text_start_y).unwrap())] + write!( + buf, + "{text_char}", + text_char = text_lines[usize::from(y.checked_sub(text_start_y).unwrap())] .chars() .nth(usize::from(x.checked_sub(text_start_x).unwrap())) .unwrap(), - ); + ) + .unwrap(); } else if y == notice_y && notice_start_x <= x && x < notice_end_x { - buf.push( - NOTICE + write!( + buf, + "{notice_char}", + notice_char = NOTICE .chars() .nth(usize::from(x.checked_sub(notice_start_x).unwrap())) .unwrap(), - ); + ) + .unwrap(); } else { - buf.push(' '); + write!(buf, " ").unwrap(); } } // New line if it isn't the last line if y != h.get().checked_sub(1).unwrap() { - buf.push_str( - &color("&r\n", color_mode) - .expect("line separator should not contain invalid color codes"), - ); + writeln!( + buf, + "{reset}", + reset = color("&r", color_mode).expect("reset should be valid"), + ) + .unwrap(); } } - write!(io::stdout(), "{buf}") - .and_then(|_| io::stdout().flush()) - .context("failed to write to stdout")?; + { + let mut stdout = io::stdout().lock(); + write!(stdout, "{buf}") + .and_then(|_| stdout.flush()) + .context("failed to write to stdout")?; + } + + execute!(io::stdout(), EndSynchronizedUpdate) + .context("failed to end synchronized update")?; Ok(()) }; @@ -278,25 +309,19 @@ pub fn start_animation(color_mode: AnsiMode) -> Result<()> { const SPEED: u8 = 2; let frame_delay = Duration::from_secs_f32(1.0 / 25.0); - loop { - // Clear the screen - clear_screen(None, color_mode, false).context("failed to clear screen")?; + execute!(io::stdout(), EnterAlternateScreen).context("failed to enter alternate screen")?; + loop { draw_frame(frame.0)?; frame += usize::from(SPEED); thread::sleep(frame_delay); - // TODO: handle Ctrl+C so that we can clear the screen; but we don't have a nice - // way to unregister the signal handler after that :'( - // See https://github.com/Detegr/rust-ctrlc/issues/106 if key_pressed.load(Ordering::Acquire) { break; } } - // Clear the screen - printc("&r", color_mode).context("failed to reset terminal style")?; - clear_screen(None, color_mode, false).context("failed to clear screen")?; + execute!(io::stdout(), LeaveAlternateScreen).context("failed to leave alternate screen")?; Ok(()) }