use crate::base::{Rect, Rgba};
use crate::render::cell::Cell;
use crate::render::layer::CellShader;
use super::{cell_hash, STABLE, TRANSPARENT_CELL};
#[derive(Copy, Clone, Debug)]
pub struct ScanlineFade {
pub duration: f32,
pub rows: i32,
}
impl CellShader for ScanlineFade {
fn shade(&self, _x: i32, y: i32, t: f32, cell: Cell) -> Cell {
if self.duration <= 0.0 {
return cell; }
let progress = (t / self.duration).clamp(0.0, 1.0);
let line = progress * self.rows as f32;
let coverage = (line - y as f32).clamp(0.0, 1.0);
if coverage >= 1.0 {
return cell;
}
if coverage <= 0.0 {
return TRANSPARENT_CELL;
}
let mut c = cell;
c.bg = c.bg.with_alpha((c.bg.a as f32 * coverage).round() as u8);
if coverage < 0.5 {
c.glyph = crate::render::cell::Glyph::EMPTY;
c.fg = Rgba::TRANSPARENT;
c.ul = Rgba::TRANSPARENT;
c.link = 0;
}
c
}
fn changed_region(&self, t0: f32, t1: f32, bounds: Rect) -> Option<Rect> {
if self.duration <= 0.0 {
return STABLE; }
let line = |t: f32| (t / self.duration).clamp(0.0, 1.0) * self.rows as f32;
let (l0, l1) = (line(t0), line(t1));
if l0 == l1 {
return STABLE; }
let (lo, hi) = (l0.min(l1), l0.max(l1));
let y0 = (lo - 1.0).floor() as i32 + 1; let y1 = hi.ceil() as i32 - 1; Some(Rect::new(bounds.x, y0, bounds.w, (y1 - y0 + 1).max(0)))
}
}
#[derive(Copy, Clone, Debug)]
pub struct GradientReveal {
pub duration: f32,
pub dir: (f32, f32),
pub travel: f32,
pub softness: f32,
}
impl CellShader for GradientReveal {
fn shade(&self, x: i32, y: i32, t: f32, cell: Cell) -> Cell {
if self.duration <= 0.0 {
return cell;
}
let len = (self.dir.0 * self.dir.0 + self.dir.1 * self.dir.1).sqrt();
if len < 1e-6 {
return cell;
}
let (dx, dy) = (self.dir.0 / len, self.dir.1 / len);
let progress = (t / self.duration).clamp(0.0, 1.0);
let soft = self.softness.max(0.0);
let front = -soft + progress * (self.travel + 2.0 * soft);
let p = x as f32 * dx + y as f32 * dy;
let coverage = ((front - p) / soft.max(1e-6)).clamp(0.0, 1.0);
if coverage >= 1.0 {
return cell;
}
if coverage <= 0.0 {
return TRANSPARENT_CELL;
}
let mut c = cell;
c.bg = c.bg.with_alpha((c.bg.a as f32 * coverage).round() as u8);
if coverage < 0.5 {
c.glyph = crate::render::cell::Glyph::EMPTY;
c.fg = Rgba::TRANSPARENT;
c.ul = Rgba::TRANSPARENT;
c.link = 0;
}
c
}
fn changed_region(&self, t0: f32, t1: f32, bounds: Rect) -> Option<Rect> {
if self.duration <= 0.0 {
return STABLE; }
let len = (self.dir.0 * self.dir.0 + self.dir.1 * self.dir.1).sqrt();
if len < 1e-6 {
return STABLE;
}
let soft = self.softness.max(0.0);
let front = |t: f32| {
let progress = (t / self.duration).clamp(0.0, 1.0);
-soft + progress * (self.travel + 2.0 * soft)
};
let (f0, f1) = (front(t0), front(t1));
if f0 == f1 {
return STABLE; }
let (dx, dy) = (self.dir.0 / len, self.dir.1 / len);
let lo = f0.min(f1) - soft.max(1e-6);
let hi = f0.max(f1);
if dy == 0.0 {
let (a, b) = if dx > 0.0 { (lo, hi) } else { (-hi, -lo) };
let x0 = a.floor() as i32;
let x1 = b.ceil() as i32;
return Some(Rect::new(x0, bounds.y, (x1 - x0 + 1).max(0), bounds.h));
}
if dx == 0.0 {
let (a, b) = if dy > 0.0 { (lo, hi) } else { (-hi, -lo) };
let y0 = a.floor() as i32;
let y1 = b.ceil() as i32;
return Some(Rect::new(bounds.x, y0, bounds.w, (y1 - y0 + 1).max(0)));
}
None
}
}
#[derive(Copy, Clone, Debug)]
pub struct Dissolve {
pub duration: f32,
pub seed: u32,
}
impl CellShader for Dissolve {
fn shade(&self, x: i32, y: i32, t: f32, cell: Cell) -> Cell {
if self.duration <= 0.0 {
return cell; }
let progress = (t / self.duration).clamp(0.0, 1.0);
if cell_hash(x, y, self.seed) < progress {
cell
} else {
TRANSPARENT_CELL
}
}
fn changed_region(&self, t0: f32, t1: f32, _bounds: Rect) -> Option<Rect> {
if self.duration <= 0.0 {
return STABLE;
}
let p = |t: f32| (t / self.duration).clamp(0.0, 1.0);
if p(t0) == p(t1) {
STABLE
} else {
None
}
}
}