use crate::{Cell, Footprint, Pos, ShapeId, StampMode, StampSink};
pub struct FootprintSink<'a> {
sink: &'a mut dyn StampSink,
footprint: Option<Footprint>,
}
impl<'a> FootprintSink<'a> {
#[must_use]
pub fn new(sink: &'a mut dyn StampSink) -> Self {
Self {
sink,
footprint: None,
}
}
#[must_use]
pub fn footprint(&self) -> Option<Footprint> {
self.footprint
}
}
impl StampSink for FootprintSink<'_> {
fn stamp(&mut self, at: Pos, cell: Cell, mode: StampMode, owner: Option<ShapeId>) {
self.footprint = Some(match self.footprint {
None => Footprint { min: at, max: at },
Some(footprint) => footprint.including(at),
});
self.sink.stamp(at, cell, mode, owner);
}
}
#[cfg(test)]
mod tests {
use std::num::NonZeroU16;
use super::FootprintSink;
use crate::{
Arm, Buffer, Cell, CellReader, Footprint, OwnerReader, Pos, ShapeId, Size, StampMode,
StampSink, Stroke, StrokeCell, Window,
};
#[derive(Default)]
struct Recording {
stamps: Vec<(Pos, StampMode, Option<ShapeId>)>,
}
impl StampSink for Recording {
fn stamp(&mut self, at: Pos, _cell: Cell, mode: StampMode, owner: Option<ShapeId>) {
self.stamps.push((at, mode, owner));
}
}
fn light() -> Stroke {
Stroke::from("light")
}
fn id(ordinal: u16) -> ShapeId {
ShapeId::new(NonZeroU16::new(ordinal).expect("no test asks for zero"))
}
fn a_buffer(origin: Pos, width: u16, height: u16) -> Buffer {
Buffer::new(Window {
origin,
size: Size { width, height },
})
}
fn decided() -> Cell {
Cell::from(StrokeCell {
base: light(),
top: Arm::Set(light()),
right: Arm::Set(light()),
bottom: Arm::Set(light()),
left: Arm::Set(light()),
})
}
fn horizontal_run() -> Cell {
Cell::from(StrokeCell {
base: light(),
top: Arm::Unset,
right: Arm::Set(light()),
bottom: Arm::Unset,
left: Arm::Set(light()),
})
}
fn deciding_nothing() -> Cell {
Cell::from(StrokeCell {
base: light(),
top: Arm::Unset,
right: Arm::Unset,
bottom: Arm::Unset,
left: Arm::Unset,
})
}
#[test]
fn an_empty_drawing_has_no_footprint_and_the_first_stamp_is_one_cell() {
let mut recording = Recording::default();
let mut sink = FootprintSink::new(&mut recording);
assert_eq!(
sink.footprint(),
None,
"a drawing that stamped nothing reached somewhere"
);
let at = Pos { x: 2, y: 1 };
sink.stamp(at, decided(), StampMode::Above, None);
assert_eq!(sink.footprint(), Some(Footprint { min: at, max: at }));
}
#[test]
fn the_footprint_is_the_union_of_every_stamp() {
let mut recording = Recording::default();
let mut sink = FootprintSink::new(&mut recording);
sink.stamp(Pos { x: 3, y: 4 }, decided(), StampMode::Above, None);
sink.stamp(Pos { x: -1, y: 2 }, decided(), StampMode::Above, None);
assert_eq!(
sink.footprint(),
Some(Footprint {
min: Pos { x: -1, y: 2 },
max: Pos { x: 3, y: 4 },
})
);
}
#[test]
fn a_stamp_outside_the_window_writes_no_cell_and_still_counts() {
let mut buffer = a_buffer(Pos { x: 0, y: 0 }, 1, 1);
let far = Pos { x: -5, y: -7 };
let footprint = {
let mut sink = FootprintSink::new(&mut buffer);
sink.stamp(far, decided(), StampMode::Above, Some(id(1)));
sink.footprint()
};
assert!(
buffer.cell(far).is_none(),
"the window wrote a cell it does not hold"
);
assert_eq!(footprint, Some(Footprint { min: far, max: far }));
}
#[test]
fn a_stamp_that_decides_no_side_still_counts() {
let mut buffer = a_buffer(Pos { x: 0, y: 0 }, 3, 1);
let at = Pos { x: 0, y: 0 };
let footprint = {
let mut sink = FootprintSink::new(&mut buffer);
sink.stamp(at, deciding_nothing(), StampMode::Above, Some(id(1)));
sink.stamp(
Pos { x: 2, y: 0 },
horizontal_run(),
StampMode::Above,
Some(id(2)),
);
sink.footprint()
};
assert_eq!(
buffer.owner(Pos { x: 0, y: 0 }),
None,
"a cell deciding no side owns nothing"
);
assert_eq!(
footprint,
Some(Footprint {
min: Pos { x: 0, y: 0 },
max: Pos { x: 2, y: 0 },
})
);
}
#[test]
fn a_stamp_below_a_decided_cell_changes_nothing_and_still_counts() {
let mut buffer = a_buffer(Pos { x: 0, y: 0 }, 1, 1);
let at = Pos { x: 0, y: 0 };
buffer.stamp(at, decided(), StampMode::Above, Some(id(1)));
let before = buffer.cell(at).cloned();
let footprint = {
let mut sink = FootprintSink::new(&mut buffer);
sink.stamp(at, deciding_nothing(), StampMode::Below, Some(id(2)));
sink.footprint()
};
assert_eq!(buffer.cell(at).cloned(), before, "the cell changed");
assert_eq!(footprint, Some(Footprint { min: at, max: at }));
}
#[test]
fn every_stamp_reaches_the_sink_it_wraps() {
let mut recording = Recording::default();
let mut sink = FootprintSink::new(&mut recording);
sink.stamp(Pos { x: 1, y: 2 }, decided(), StampMode::Above, Some(id(3)));
sink.stamp(Pos { x: 4, y: 5 }, decided(), StampMode::Below, None);
assert_eq!(
recording.stamps,
vec![
(Pos { x: 1, y: 2 }, StampMode::Above, Some(id(3))),
(Pos { x: 4, y: 5 }, StampMode::Below, None),
]
);
}
}