use super::{BorderCell, Buffer};
use crate::render::Cell;
use crate::render::compose::{alpha_blend, canvas_bg, canvas_fg};
#[derive(Clone, Copy, PartialEq, Eq)]
enum LayerGlyph {
None,
New,
Same,
Spacer,
}
fn shows_glyph(cell: &Cell) -> bool {
match cell.raw_symbol() {
None => false,
Some("") => true,
Some(s) => !s.chars().all(char::is_whitespace),
}
}
fn visible_symbol(cell: &Cell) -> bool {
cell.raw_symbol()
.is_some_and(|s| !s.is_empty() && !s.chars().all(char::is_whitespace))
}
fn adds_border(layer: &BorderCell, backdrop: &BorderCell, layer_q: u8, backdrop_q: u8) -> bool {
layer
.iter()
.zip(backdrop)
.any(|(l, b)| (l.winner.is_some() && l.winner != b.winner) || (l.killed && !b.killed))
|| layer_q & !backdrop_q != 0
}
impl Buffer {
pub(crate) fn composite_group(&mut self, layer: &Buffer, alpha: f32) {
if alpha <= 0.0 {
return;
}
let region = self.area.intersection(layer.area);
if region.is_empty() {
return;
}
for y in region.y..region.bottom() {
let mut placed_wide = false;
for x in region.x..region.right() {
let (Some(i), Some(j)) = (self.index_of(x, y), layer.index_of(x, y)) else {
continue;
};
let next_blank = self
.index_of(x.saturating_add(1), y)
.is_none_or(|k| self.backdrop_blank(k));
placed_wide = self.composite_cell(layer, i, j, alpha, placed_wide, next_blank);
}
self.repair_wide_pairs(y, region.x.saturating_sub(1), region.right());
}
}
fn backdrop_blank(&self, i: usize) -> bool {
!shows_glyph(&self.content[i])
&& !self.border_dirs[i].iter().any(|d| d.is_visible())
&& self.half_block_quads[i] == 0
}
fn composite_cell(
&mut self,
layer: &Buffer,
i: usize,
j: usize,
alpha: f32,
after_wide: bool,
next_blank: bool,
) -> bool {
let before = &self.content[i];
let after = &layer.content[j];
let glyph = if after.is_spacer() && after_wide {
LayerGlyph::Spacer
} else if !visible_symbol(after) || after == before {
LayerGlyph::None
} else if after.raw_symbol() == before.raw_symbol() {
LayerGlyph::Same
} else {
LayerGlyph::New
};
let blank = self.backdrop_blank(i);
let wide = glyph == LayerGlyph::New && after.cell_width() == 2;
let takes = alpha >= 0.5 || (blank && (!wide || next_blank));
let text_wins = match glyph {
LayerGlyph::None => false,
LayerGlyph::New => takes,
LayerGlyph::Same | LayerGlyph::Spacer => true,
};
let border_wins = adds_border(
&layer.border_dirs[j],
&self.border_dirs[i],
layer.half_block_quads[j],
self.half_block_quads[i],
) && (alpha >= 0.5 || blank);
let backdrop_bg = canvas_bg(before.bg);
let bg_changed = after.bg != before.bg;
let layer_bg = canvas_bg(after.bg);
let mut out = before.clone();
if bg_changed {
out.bg = alpha_blend(layer_bg, alpha, backdrop_bg);
}
match glyph {
LayerGlyph::New | LayerGlyph::Spacer if text_wins => {
match after.raw_symbol() {
Some(s) => out.set_symbol(s),
None => out.set_blank(),
};
out.modifier = after.modifier;
out.diff = after.diff;
out.link = after.link.clone();
out.fg = alpha_blend(canvas_fg(after.fg), alpha, backdrop_bg);
}
LayerGlyph::Same => {
out.modifier = after.modifier;
out.diff = after.diff;
out.link = after.link.clone();
if after.fg != before.fg {
out.fg = alpha_blend(canvas_fg(after.fg), alpha, canvas_fg(before.fg));
}
}
_ => {
if bg_changed && shows_glyph(before) {
out.fg = alpha_blend(layer_bg, alpha, canvas_fg(before.fg));
}
}
}
self.content[i] = out;
if text_wins || border_wins {
let backdrop = self.border_dirs[i];
let mut state = layer.border_dirs[j];
for (l, b) in state.iter_mut().zip(backdrop) {
if l.winner != b.winner
&& let Some(w) = &mut l.winner
{
w.fg = alpha_blend(canvas_fg(w.fg), alpha, backdrop_bg);
}
}
self.border_dirs[i] = state;
self.half_block_quads[i] = layer.half_block_quads[j];
} else if bg_changed {
for d in &mut self.border_dirs[i] {
if let Some(w) = &mut d.winner {
w.fg = alpha_blend(layer_bg, alpha, canvas_fg(w.fg));
}
}
}
wide && text_wins
}
fn repair_wide_pairs(&mut self, y: u16, from: u16, to: u16) {
let right = self.area.right();
for x in from.max(self.area.x)..=to.min(right.saturating_sub(1)) {
let Some(i) = self.index_of(x, y) else {
continue;
};
let orphan = if self.content[i].is_spacer() {
x == self.area.x || self.content[i - 1].cell_width() != 2
} else if self.content[i].cell_width() == 2 {
x + 1 >= right || !self.content[i + 1].is_spacer()
} else {
false
};
if orphan {
self.content[i].set_symbol(" ");
}
}
}
}