Use "alternate screen" for Pride month animation

This removes the need to clear screen at each frame, which fixes the
screen flickering.
This commit is contained in:
Teoh Han Hui 2024-07-30 21:23:40 +08:00
parent 00369355e6
commit 13e29325b9
No known key found for this signature in database
GPG key ID: D43E2BABAF97DCAE
4 changed files with 121 additions and 34 deletions

60
Cargo.lock generated
View file

@ -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"

View file

@ -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 }

View file

@ -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"] }

View file

@ -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::<u8>::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(())
}