use std::fmt::Write as _;
use monospace_core::{
Arm, Buffer, Cell, CellReader, Direction, Glyph, GlyphCatalog, Pos, Size, Stroke,
SymbolStyleName, Terminal, Window, render,
};
use std::num::NonZeroU16;
use crate::{Anchor, Delta, Diagram, Endpoint, Reference, Shape, ShapeId};
const WHAT: &str = concat!(
"A gallery: a chosen set of examples, one block each, for reading rather than for coverage. ",
"Each label lists the shapes in the order they were added, each one derived from its own ",
"Debug, so a label cannot disagree with the values it names; what was done between two ",
"drawings of one diagram is the second line, and it is named for the change rather than for ",
"the order, which 080 could make and a displacement does not. It is written by hand, because ",
"a diagram offers one query by an identity and no listing of the shapes it holds. Under each ",
"picture is the surface that picture came from: a count of the positions the diagram wrote, ",
"then a row for each of them, saying what that position renders, what each of its four arms ",
"holds, and the base stroke every Set arm is drawn in. A Set arm names the stroke; Unset and ",
"Closed are the model's own two words for the other two. A position left unwritten is a hole ",
"in the picture rather than a row here, and the count is how many there are. A literal or a ",
"symbol has no arms, so its arm columns are empty. A block moving means a figure or an order ",
"changed, and what moved is the thing to look at — the same acceptance a characterization ",
"gets, without its claim of covering a range.",
);
const COLUMNS: [&str; 7] = ["at", "glyph", "top", "right", "bottom", "left", "base"];
fn arm_text(arm: &Arm) -> String {
match arm {
Arm::Set(stroke) => stroke.0.clone(),
Arm::Closed => "Closed".to_string(),
Arm::Unset => "Unset".to_string(),
}
}
fn at(x: i16, y: i16) -> Pos {
Pos { x, y }
}
fn light() -> Stroke {
Stroke::from("light")
}
fn glyph(text: &str) -> Glyph {
Glyph::new(text).expect("one glyph")
}
fn id(ordinal: u16) -> ShapeId {
ShapeId::new(NonZeroU16::new(ordinal).expect("no gallery names zero"))
}
fn window(width: u16, height: u16) -> Size {
Size { width, height }
}
fn kind_of(shape: &Shape) -> &'static str {
match shape {
Shape::Box { .. } => "Box",
Shape::Line { .. } => "Line",
Shape::Connector { .. } => "Connector",
}
}
fn the_kinds() -> Vec<(&'static str, Shape)> {
vec![
(
kind_of(&box_shape(at(0, 0), None)),
box_shape(at(0, 0), None),
),
(kind_of(&line_shape(at(0, 0), 6)), line_shape(at(0, 0), 6)),
(
kind_of(&connector_shape(at(0, 0), at(6, 0))),
connector_shape(at(0, 0), at(6, 0)),
),
]
}
fn box_shape(at_: Pos, fill: Option<&str>) -> Shape {
Shape::Box {
at: at_,
size: window(6, 3),
stroke: light(),
fill: fill.map(glyph),
text: None,
}
}
fn line_shape(at_: Pos, len: u16) -> Shape {
Shape::Line {
at: at_,
len,
orientation: monospace_core::Orientation::Horizontal,
stroke: light(),
}
}
fn connector_shape(from_at: Pos, to_at: Pos) -> Shape {
Shape::Connector {
from: Endpoint::absolute(
from_at,
Direction::Right,
monospace_core::Terminal::Glyph {
glyph: glyph("◄")
},
),
to: Endpoint::absolute(
to_at,
Direction::Left,
monospace_core::Terminal::Glyph {
glyph: glyph("►")
},
),
stroke: light(),
}
}
fn small_box(at_: Pos, fill: Option<&str>) -> Shape {
Shape::Box {
at: at_,
size: window(4, 3),
stroke: light(),
fill: fill.map(glyph),
text: None,
}
}
fn catalog() -> GlyphCatalog {
GlyphCatalog::union([GlyphCatalog::light(), GlyphCatalog::solid()])
}
fn draw(size: Size, diagram: &Diagram) -> (String, String) {
let origin = at(0, 0);
let mut buffer = Buffer::new(Window { origin, size });
diagram.draw(&mut buffer);
let picture = render(&buffer, &catalog(), buffer.window());
(picture, surface(&buffer, size))
}
fn surface(buffer: &Buffer, size: Size) -> String {
let catalog = catalog();
let mut rows: Vec<[String; 7]> = Vec::new();
let total = size.width as usize * size.height as usize;
for y in 0..size.height.cast_signed() {
for x in 0..size.width.cast_signed() {
let cell = buffer.cell(at(x, y));
let (glyph, top, right, bottom, left, base) = match &cell {
None => continue,
Some(Cell::Literal(g)) => (
g.as_str().to_string(),
String::new(),
String::new(),
String::new(),
String::new(),
String::new(),
),
Some(Cell::Symbol(s)) => (
catalog
.symbol(s)
.map_or("?".to_string(), |g| g.as_str().to_string()),
String::new(),
String::new(),
String::new(),
String::new(),
String::new(),
),
Some(Cell::Strokes(s)) => (
catalog
.glyph(&s.key())
.map_or("?".to_string(), |g| g.as_str().to_string()),
arm_text(&s.top),
arm_text(&s.right),
arm_text(&s.bottom),
arm_text(&s.left),
s.base.0.clone(),
),
};
rows.push([format!("{x},{y}"), glyph, top, right, bottom, left, base]);
}
}
let widths: Vec<usize> = (0..COLUMNS.len())
.map(|column| {
rows.iter()
.map(|row| row[column].chars().count())
.chain(std::iter::once(COLUMNS[column].len()))
.max()
.unwrap_or(0)
})
.collect();
let line = |cells: &[String]| {
let padded: Vec<String> = cells
.iter()
.zip(&widths)
.map(|(cell, width)| format!("{cell:<width$}"))
.collect();
format!(" | {} |", padded.join(" | "))
};
let mut out = format!(" wrote {} of {total} positions\n", rows.len());
let header: Vec<String> = COLUMNS.iter().map(|name| (*name).to_string()).collect();
let _ = writeln!(out, "{}", line(&header));
let rule: Vec<String> = widths.iter().map(|width| "-".repeat(*width)).collect();
let _ = writeln!(out, " | {} |", rule.join(" | "));
for row in &rows {
let _ = writeln!(out, "{}", line(row));
}
out
}
fn block(shapes: &str, change: &str, size: Size, diagram: &Diagram) -> String {
let mut out = String::new();
let _ = writeln!(out, " shapes: {shapes}");
let _ = writeln!(out, " change: {change}");
let (picture, surface) = draw(size, diagram);
for line in picture.lines() {
let _ = writeln!(out, "{}", format!(" {line}").trim_end());
}
let _ = write!(out, "{surface}");
out
}
fn snap(name: &str, body: &str) {
let mut settings = insta::Settings::clone_current();
settings.set_snapshot_path(concat!(
env!("CARGO_MANIFEST_DIR"),
"/src/snapshots/gallery"
));
settings.set_description(WHAT);
settings.bind(|| insta::assert_snapshot!(name, body));
}
#[test]
fn the_three_kinds() {
let mut out = String::new();
for (kind, shape) in the_kinds() {
let label = format!("Shape::{kind} {shape:?}");
let mut diagram = Diagram::new();
diagram = diagram.add(shape).0;
let _ = writeln!(out, "{label}");
let (picture, surface) = draw(window(7, 3), &diagram);
for line in picture.lines() {
let _ = writeln!(out, "{}", format!(" {line}").trim_end());
}
let _ = write!(out, "{surface}");
let _ = writeln!(out);
}
snap("the_three_kinds", &out);
}
#[test]
fn the_order_decides_a_shared_cell() {
let labelled = "[small_box(0,0,fill=░), small_box(2,1,fill=▓)]";
let mut diagram = Diagram::new();
diagram = diagram.add(small_box(at(0, 0), Some("░"))).0;
diagram = diagram.add(small_box(at(2, 1), Some("▓"))).0;
let first = block(labelled, "as written", window(6, 4), &diagram);
diagram = diagram.forward(id(1));
let moved = block(
labelled,
"1 moved one place toward the front",
window(6, 4),
&diagram,
);
snap(
"the_order_decides_a_shared_cell",
&format!("{first}\n{moved}"),
);
}
#[test]
fn an_arm_terminal_composes_where_a_border_is() {
let labelled = "[small_box(0,0,no fill), arm_connector(3,1 -> 7,1)]";
let mut diagram = Diagram::new();
diagram = diagram.add(small_box(at(0, 0), None)).0;
diagram = diagram.add(arm_connector(at(3, 1), at(7, 1))).0;
snap(
"an_arm_terminal_composes_where_a_border_is",
&block(labelled, "as written", window(8, 3), &diagram),
);
}
fn arm_connector(from_at: Pos, to_at: Pos) -> Shape {
Shape::Connector {
from: Endpoint::absolute(from_at, Direction::Right, monospace_core::Terminal::Arm),
to: Endpoint::absolute(to_at, Direction::Left, monospace_core::Terminal::Arm),
stroke: light(),
}
}
fn hanging_connector(box_id: ShapeId) -> Shape {
Shape::Connector {
from: Endpoint::reference(
Reference::new(box_id, Anchor::Right, Delta { dx: 0, dy: 0 }, 0),
monospace_core::Terminal::Arm,
),
to: Endpoint::absolute(at(7, 1), Direction::Left, monospace_core::Terminal::Arm),
stroke: light(),
}
}
#[test]
fn an_endpoint_hangs_from_a_side_and_follows_it() {
let labelled =
"[small_box(0,0,no fill), arm_connector(from = Reference(1, Right, offset (0,0)) -> 7,1)]";
let diagram = Diagram::new();
let (mut diagram, box_id) = diagram.add(small_box(at(0, 0), None));
diagram = diagram.add(hanging_connector(box_id)).0;
let first = block(labelled, "as written", window(8, 3), &diagram);
let moved = diagram
.get(box_id)
.expect("the box is in the diagram")
.displaced_by(Delta { dx: 4, dy: 0 });
diagram = diagram.replace(box_id, moved);
let second = block(
labelled,
"the box displaced four cells right",
window(8, 3),
&diagram,
);
let from_as_written = Diagram::new();
let (from_as_written, second_box_id) = from_as_written.add(small_box(at(0, 0), None));
let (mut from_as_written, arrow_id) = from_as_written.add(hanging_connector(second_box_id));
let moved_arrow = from_as_written
.get(arrow_id)
.expect("the connector is in the diagram")
.displaced_by(Delta { dx: 0, dy: 2 });
from_as_written = from_as_written.replace(arrow_id, moved_arrow);
let third = block(
labelled,
"the connector displaced two cells down",
window(8, 4),
&from_as_written,
);
let with_the_box_taken_out = Diagram::new();
let (mut with_the_box_taken_out, the_removed) =
with_the_box_taken_out.add(small_box(at(0, 0), None));
with_the_box_taken_out = with_the_box_taken_out.add(hanging_connector(the_removed)).0;
with_the_box_taken_out = with_the_box_taken_out.remove(the_removed);
let fourth = block(
labelled,
"the box taken out",
window(8, 3),
&with_the_box_taken_out,
);
snap(
"an_endpoint_hangs_from_a_side_and_follows_it",
&format!("{first}\n{second}\n{third}\n{fourth}"),
);
}
fn symbol_connector(from_id: ShapeId, to_id: ShapeId, out: i16) -> Shape {
Shape::Connector {
from: Endpoint::reference(
Reference::new(from_id, Anchor::Right, Delta { dx: 0, dy: 0 }, 0),
Terminal::Arm,
),
to: Endpoint::reference(
Reference::new(to_id, Anchor::Left, Delta { dx: 0, dy: 0 }, out),
Terminal::Symbol {
style: SymbolStyleName::from("solid"),
},
),
stroke: light(),
}
}
#[test]
fn a_symbol_terminal_attached_to_a_side_stands_one_cell_outward_by_default() {
let labelled = "[small_box(0,0,no fill), small_box(7,0,no fill), symbol_connector(from = Reference(1, Right), to = Reference(2, Left))]";
let (diagram, box1_id) = Diagram::new().add(small_box(at(0, 0), None));
let (mut diagram, box2_id) = diagram.add(small_box(at(7, 0), None));
diagram = diagram.add(symbol_connector(box1_id, box2_id, 0)).0;
let first = block(labelled, "as written", window(15, 3), &diagram);
let moved = diagram
.get(box2_id)
.expect("the target box is in the diagram")
.displaced_by(Delta { dx: 4, dy: 0 });
diagram = diagram.replace(box2_id, moved);
let second = block(
labelled,
"the target box displaced four cells right",
window(15, 3),
&diagram,
);
let (diagram3, b1) = Diagram::new().add(small_box(at(0, 0), None));
let (mut diagram3, b2) = diagram3.add(small_box(at(7, 0), None));
diagram3 = diagram3.add(symbol_connector(b1, b2, 0)).0;
diagram3 = diagram3.remove(b2);
let third = block(
labelled,
"the target box taken out",
window(15, 3),
&diagram3,
);
let labelled_out1 = "[small_box(0,0,no fill), small_box(7,0,no fill), symbol_connector(from = Reference(1, Right), to = Reference(2, Left, out=1))]";
let (diagram4, b1) = Diagram::new().add(small_box(at(0, 0), None));
let (mut diagram4, b2) = diagram4.add(small_box(at(7, 0), None));
diagram4 = diagram4.add(symbol_connector(b1, b2, 1)).0;
let fourth = block(
labelled_out1,
"the connector with explicit out: 1",
window(15, 3),
&diagram4,
);
let labelled_out_neg1 = "[small_box(0,0,no fill), small_box(7,0,no fill), symbol_connector(from = Reference(1, Right), to = Reference(2, Left, out=-1))]";
let (diagram5, b1) = Diagram::new().add(small_box(at(0, 0), None));
let (mut diagram5, b2) = diagram5.add(small_box(at(7, 0), None));
diagram5 = diagram5.add(symbol_connector(b1, b2, -1)).0;
let fifth = block(
labelled_out_neg1,
"the connector with explicit out: -1",
window(15, 3),
&diagram5,
);
snap(
"a_symbol_terminal_attached_to_a_side_stands_one_cell_outward_by_default",
&format!("{first}\n{second}\n{third}\n{fourth}\n{fifth}"),
);
}