use crate::dom::Style;
use crate::render::colorize::{ColorLevel, Kind, colorize};
use crate::render::grid::Grid;
pub fn render_background(
x: i32,
y: i32,
width: u16,
height: u16,
style: &Style,
grid: &mut Grid,
level: ColorLevel,
) {
let Some(bg) = style.background_color.as_deref() else {
return;
};
let has_border = style.border_style.is_some();
let left = i32::from(has_border && style.border_left != Some(false));
let right = i32::from(has_border && style.border_right != Some(false));
let top = i32::from(has_border && style.border_top != Some(false));
let bottom = i32::from(has_border && style.border_bottom != Some(false));
let content_width = width as i32 - left - right;
let content_height = height as i32 - top - bottom;
if content_width <= 0 || content_height <= 0 {
return;
}
let bg_line = colorize(
&" ".repeat(content_width as usize),
Some(bg),
Kind::Bg,
level,
);
for row in 0..content_height {
grid.write(x + left, y + top + row, &bg_line);
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::dom::{BorderStyle, Style};
use crate::render::border::render_border;
use crate::render::grid::{Clip, Grid};
#[test]
fn box_background_fills_content_rect() {
let style = Style {
background_color: Some("red".to_owned()),
..Style::default()
};
let mut g = Grid::new(2, 4);
render_background(0, 0, 4, 2, &style, &mut g, ColorLevel::Truecolor);
let out = g.get().0;
assert_eq!(out, "\x1b[41m \x1b[49m\n\x1b[41m \x1b[49m");
}
#[test]
fn box_background_inside_border() {
let style = Style {
border_style: Some(BorderStyle::Named("round".to_owned())),
background_color: Some("blue".to_owned()),
..Style::default()
};
let mut g = Grid::new(3, 5);
render_background(0, 0, 5, 3, &style, &mut g, ColorLevel::Truecolor);
render_border(0, 0, 5, 3, &style, &mut g, ColorLevel::Truecolor);
let out = g.get().0;
assert_eq!(out, "╭───╮\n│\x1b[44m \x1b[49m│\n╰───╯");
let row1 = out.lines().nth(1).unwrap();
assert!(
row1.contains("\x1b[44m"),
"interior fill carries blue bg open"
);
assert!(
row1.starts_with('│'),
"left border cell has no bg, just `│`"
);
assert!(row1.ends_with('│'), "right border cell has no bg, just `│`");
}
#[test]
fn box_background_zero_content_noop() {
let style = Style {
border_style: Some(BorderStyle::Named("single".to_owned())),
background_color: Some("red".to_owned()),
..Style::default()
};
let mut g = Grid::new(2, 2);
render_background(0, 0, 2, 2, &style, &mut g, ColorLevel::Truecolor);
let out = g.get().0;
assert_eq!(out, "\n");
assert!(
!out.contains('\x1b'),
"zero-content fill must emit no SGR bytes"
);
}
#[test]
fn box_background_fill_is_clipped() {
let style = Style {
background_color: Some("red".to_owned()),
..Style::default()
};
let mut g = Grid::new(4, 5);
g.push_clip(Clip {
x1: Some(0),
x2: Some(3),
y1: Some(0),
y2: Some(2),
});
render_background(0, 0, 5, 4, &style, &mut g, ColorLevel::Truecolor);
g.pop_clip();
let out = g.get().0;
assert_eq!(out, "\x1b[41m \x1b[49m\n\x1b[41m \x1b[49m\n\n");
assert_eq!(
out.trim_end_matches('\n'),
"\x1b[41m \x1b[49m\n\x1b[41m \x1b[49m"
);
for row in out.lines().filter(|r| !r.is_empty()) {
assert_eq!(
row, "\x1b[41m \x1b[49m",
"each surviving fill row is clipped to 3 cells"
);
}
}
#[test]
fn box_background_fill_partial_border_inset() {
let style = Style {
border_style: Some(BorderStyle::Named("single".to_owned())),
border_top: Some(false),
background_color: Some("blue".to_owned()),
..Style::default()
};
let mut g = Grid::new(4, 6);
render_background(0, 0, 6, 4, &style, &mut g, ColorLevel::Truecolor);
render_border(0, 0, 6, 4, &style, &mut g, ColorLevel::Truecolor);
let out = g.get().0;
assert_eq!(
out,
"│\x1b[44m \x1b[49m│\n│\x1b[44m \x1b[49m│\n│\x1b[44m \x1b[49m│\n└────┘"
);
let row0 = out.lines().next().unwrap();
assert!(
row0.contains("\x1b[44m"),
"row 0 carries the blue fill (borderTop=false → top inset 0)"
);
assert!(
row0.starts_with('│') && row0.ends_with('│'),
"row 0 has side borders but NO top edge"
);
}
}