#![forbid(unsafe_code)]
#[cfg(feature = "canvas")]
use crate::canvas::Painter;
use crate::visual_fx::{BackdropFx, FxContext};
use ftui_render::cell::PackedRgba;
pub struct QuakeConsoleFx {
drop_progress: f32,
texture: Vec<PackedRgba>,
texture_size: (u16, u16),
}
impl QuakeConsoleFx {
pub fn new() -> Self {
Self {
drop_progress: 1.0,
texture: Vec::new(),
texture_size: (0, 0),
}
}
pub fn set_progress(&mut self, progress: f32) {
self.drop_progress = progress.clamp(0.0, 1.0);
}
fn generate_texture(
&mut self,
width: u16,
height: u16,
base_color: PackedRgba,
highlight_color: PackedRgba,
) {
let w = width as usize;
let h = height as usize;
let len = w * h;
self.texture.resize(len, PackedRgba::default());
self.texture_size = (width, height);
let mut rng = 0xCAFEBABE_u32;
let mut rand = || {
rng = rng.wrapping_mul(1664525).wrapping_add(1013904223);
(rng >> 16) as f32 / 65536.0
};
for y in 0..h {
for x in 0..w {
let nx = x as f32 * 0.1;
let ny = y as f32 * 0.2;
let v = (nx.sin() + ny.cos() + rand() * 0.5).abs().clamp(0.0, 1.0);
let pixel = highlight_color.with_opacity(v * 0.3).over(base_color);
self.texture[y * w + x] = pixel;
}
}
}
#[cfg(feature = "canvas")]
pub fn render_painter(&mut self, painter: &mut Painter, theme: &crate::visual_fx::ThemeInputs) {
let (w, h) = painter.size();
let width = w as usize;
let height = h as usize;
let drop_height = (height as f32 * self.drop_progress).round() as usize;
if self.texture_size != (w, h) {
self.generate_texture(w, h, theme.bg_base, theme.bg_surface);
}
for y in 0..drop_height {
let row_start = y * width;
for x in 0..width {
let color = self.texture[row_start + x];
painter.point_colored(x as i32, y as i32, color);
}
}
if drop_height < height && drop_height > 0 {
let y = (drop_height - 1) as i32;
let color = theme.accent_primary;
painter.line_colored(0, y, (width - 1) as i32, y, Some(color));
}
}
}
impl BackdropFx for QuakeConsoleFx {
fn name(&self) -> &'static str {
"quake-console"
}
fn resize(&mut self, _width: u16, _height: u16) {}
fn render(&mut self, ctx: FxContext<'_>, out: &mut [PackedRgba]) {
if ctx.is_empty() {
return;
}
let width = ctx.width as usize;
let height = ctx.height as usize;
let drop_height = (height as f32 * self.drop_progress).round() as usize;
if self.texture_size != (ctx.width, ctx.height) {
self.generate_texture(
ctx.width,
ctx.height,
ctx.theme.bg_base,
ctx.theme.bg_surface,
);
}
for y in 0..drop_height {
let row_start = y * width;
let src_row = &self.texture[row_start..row_start + width];
let dst_row = &mut out[row_start..row_start + width];
dst_row.copy_from_slice(src_row);
}
if drop_height < height && drop_height > 0 {
let y = drop_height - 1;
for x in 0..width {
let idx = y * width + x;
out[idx] = ctx.theme.accent_primary;
}
}
}
}
impl Default for QuakeConsoleFx {
fn default() -> Self {
Self::new()
}
}