use tui_vfx_types::{Cell, Color, Grid, Rect};
use crate::types::ShadowConfig;
pub struct HalfBlockRenderer;
#[allow(dead_code)]
const RIGHT_HALF: char = '▐';
const LOWER_HALF: char = '▄';
const LEFT_HALF: char = '▌';
const UPPER_HALF: char = '▀';
const LEFT_THREE_QUARTERS: char = '▊';
#[allow(dead_code)]
const THREE_QUARTER_BLOCK: char = '▙';
const QUADRANT_LL_LR_UR: char = '▟';
impl HalfBlockRenderer {
pub fn render<G: Grid>(grid: &mut G, element_rect: Rect, config: &ShadowConfig, progress: f64) {
let shadow_color = config.color_at_progress(progress);
if shadow_color.a == 0 {
return;
}
let surface = Color::TRANSPARENT;
let rect_x = element_rect.x as i32;
let rect_y = element_rect.y as i32;
let rect_w = element_rect.width as i32;
let rect_h = element_rect.height as i32;
let ox = config.offset_x as i32;
let oy = config.offset_y as i32;
let edges = config.edges;
if edges.has_right() && ox > 0 {
Self::render_right_edge(
grid,
rect_x,
rect_y,
rect_w,
rect_h,
ox,
oy,
shadow_color,
surface,
config.soft_edges,
);
}
if edges.has_bottom() && oy > 0 {
Self::render_bottom_edge(
grid,
rect_x,
rect_y,
rect_w,
rect_h,
ox,
oy,
shadow_color,
surface,
config.soft_edges,
);
}
if edges.has_left() && ox < 0 {
Self::render_left_edge(
grid,
rect_x,
rect_y,
rect_w,
rect_h,
ox,
oy,
shadow_color,
surface,
config.soft_edges,
);
}
if edges.has_top() && oy < 0 {
Self::render_top_edge(
grid,
rect_x,
rect_y,
rect_w,
rect_h,
ox,
oy,
shadow_color,
surface,
config.soft_edges,
);
}
if edges.has_right() && edges.has_bottom() && ox > 0 && oy > 0 {
Self::render_corner(
grid,
rect_x,
rect_y,
rect_w,
rect_h,
ox,
oy,
shadow_color,
surface,
config.soft_edges,
);
}
}
#[allow(clippy::too_many_arguments)]
fn render_right_edge<G: Grid>(
grid: &mut G,
rect_x: i32,
rect_y: i32,
rect_w: i32,
rect_h: i32,
ox: i32,
oy: i32,
shadow: Color,
surface: Color,
soft: bool,
) {
let start_x = (rect_x + rect_w).max(0) as usize;
let end_x = (rect_x + rect_w + ox).max(0) as usize;
let start_y = (rect_y + oy.max(0)).max(0) as usize;
let end_y = (rect_y + rect_h + oy.min(0)).max(0) as usize;
for y in start_y..end_y {
for x in start_x..end_x {
if grid.in_bounds(x, y) {
let cell = if soft && x == start_x {
shadow_cell(
Cell::new(LEFT_THREE_QUARTERS)
.with_fg(surface)
.with_bg(shadow),
)
} else if soft && x == start_x + 1 {
shadow_cell(Cell::new(LEFT_HALF).with_fg(shadow).with_bg(surface))
} else {
shadow_cell(Cell::new(' ').with_bg(shadow))
};
grid.set(x, y, cell);
}
}
}
}
#[allow(clippy::too_many_arguments)]
fn render_bottom_edge<G: Grid>(
grid: &mut G,
rect_x: i32,
rect_y: i32,
rect_w: i32,
rect_h: i32,
ox: i32,
oy: i32,
shadow: Color,
surface: Color,
soft: bool,
) {
let start_x = (rect_x + ox.max(0)).max(0) as usize;
let end_x = (rect_x + rect_w + ox.min(0)).max(0) as usize;
let start_y = (rect_y + rect_h).max(0) as usize;
let end_y = (rect_y + rect_h + oy).max(0) as usize;
for y in start_y..end_y {
for x in start_x..end_x {
if grid.in_bounds(x, y) {
let cell = if soft && y == start_y {
shadow_cell(Cell::new(LOWER_HALF).with_fg(shadow).with_bg(surface))
} else {
shadow_cell(Cell::new(' ').with_bg(shadow))
};
grid.set(x, y, cell);
}
}
}
}
#[allow(clippy::too_many_arguments)]
fn render_left_edge<G: Grid>(
grid: &mut G,
rect_x: i32,
rect_y: i32,
_rect_w: i32,
rect_h: i32,
ox: i32,
oy: i32,
shadow: Color,
surface: Color,
soft: bool,
) {
let start_x = (rect_x + ox).max(0) as usize;
let end_x = rect_x.max(0) as usize;
let start_y = (rect_y + oy.max(0)).max(0) as usize;
let end_y = (rect_y + rect_h + oy.min(0)).max(0) as usize;
for y in start_y..end_y {
for x in start_x..end_x {
if grid.in_bounds(x, y) {
let cell = if soft && x == end_x.saturating_sub(1) {
shadow_cell(Cell::new(LEFT_HALF).with_fg(shadow).with_bg(surface))
} else {
shadow_cell(Cell::new(' ').with_bg(shadow))
};
grid.set(x, y, cell);
}
}
}
}
#[allow(clippy::too_many_arguments)]
fn render_top_edge<G: Grid>(
grid: &mut G,
rect_x: i32,
rect_y: i32,
rect_w: i32,
_rect_h: i32,
ox: i32,
oy: i32,
shadow: Color,
surface: Color,
soft: bool,
) {
let start_x = (rect_x + ox.max(0)).max(0) as usize;
let end_x = (rect_x + rect_w + ox.min(0)).max(0) as usize;
let start_y = (rect_y + oy).max(0) as usize;
let end_y = rect_y.max(0) as usize;
for y in start_y..end_y {
for x in start_x..end_x {
if grid.in_bounds(x, y) {
let cell = if soft && y == end_y.saturating_sub(1) {
shadow_cell(Cell::new(UPPER_HALF).with_fg(shadow).with_bg(surface))
} else {
shadow_cell(Cell::new(' ').with_bg(shadow))
};
grid.set(x, y, cell);
}
}
}
}
#[allow(clippy::too_many_arguments)]
fn render_corner<G: Grid>(
grid: &mut G,
rect_x: i32,
rect_y: i32,
rect_w: i32,
rect_h: i32,
ox: i32,
oy: i32,
shadow: Color,
surface: Color,
soft: bool,
) {
let corner_x = (rect_x + rect_w).max(0) as usize;
let corner_y = (rect_y + rect_h).max(0) as usize;
for dy in 0..oy as usize {
for dx in 0..ox as usize {
let x = corner_x + dx;
let y = corner_y + dy;
if grid.in_bounds(x, y) {
let cell = if soft && dx == 0 && dy == 0 {
shadow_cell(
Cell::new(QUADRANT_LL_LR_UR)
.with_fg(shadow)
.with_bg(surface),
)
} else if soft && dx == 0 {
shadow_cell(
Cell::new(LEFT_THREE_QUARTERS)
.with_fg(surface)
.with_bg(shadow),
)
} else if soft && dx == 1 && dy == 0 {
shadow_cell(Cell::new(LEFT_HALF).with_fg(shadow).with_bg(surface))
} else if soft && dx == 1 {
shadow_cell(Cell::new(LEFT_HALF).with_fg(shadow).with_bg(surface))
} else {
shadow_cell(Cell::new(' ').with_bg(shadow))
};
grid.set(x, y, cell);
}
}
}
}
}
#[inline]
fn shadow_cell(cell: Cell) -> Cell {
cell.with_mod_alpha(Some(255))
}
#[cfg(test)]
mod tests {
use super::*;
use crate::types::ShadowEdges;
use tui_vfx_types::OwnedGrid;
#[test]
fn test_render_basic_shadow() {
let mut grid = OwnedGrid::new(20, 10);
let rect = Rect::new(5, 2, 8, 4);
let config = ShadowConfig::new(Color::BLACK.with_alpha(128))
.with_offset(2, 1) .with_edges(ShadowEdges::BOTTOM_RIGHT);
HalfBlockRenderer::render(&mut grid, rect, &config, 1.0);
let cell = grid.get(13, 3).unwrap();
assert_eq!(cell.ch, LEFT_THREE_QUARTERS);
assert_ne!(cell.bg, Color::TRANSPARENT);
let cell = grid.get(14, 3).unwrap();
assert_eq!(cell.ch, LEFT_HALF);
assert_ne!(cell.fg, Color::TRANSPARENT); }
#[test]
fn test_zero_progress_renders_nothing() {
let mut grid = OwnedGrid::new(20, 10);
let rect = Rect::new(5, 2, 8, 4);
let config = ShadowConfig::new(Color::BLACK);
HalfBlockRenderer::render(&mut grid, rect, &config, 0.0);
for y in 0..10 {
for x in 0..20 {
let cell = grid.get(x, y).unwrap();
assert_eq!(cell.bg, Color::TRANSPARENT);
}
}
}
}