monospace_core/shape.rs
1//! What a shape draws into, and what a shape is. See _Shapes_ in
2//! [`docs/model.md`](../../../docs/model.md).
3
4use crate::{Cell, Pos, ShapeId, StampMode, StampSink};
5
6mod box_shape;
7mod connector;
8pub(crate) mod fragment;
9mod line;
10mod route;
11mod text;
12
13#[cfg(test)]
14mod gallery;
15
16pub use box_shape::BoxShape;
17pub use connector::{Connector, Endpoint, Terminal};
18pub use line::Line;
19pub use text::{Text, TextShape};
20
21/// One write operation and no reader. What a shape draws into.
22///
23/// A fragment cannot inspect what lies beneath it or what a sibling has already written, because
24/// `Surface` gives it nothing to read with — see _Complete and fragment_ in
25/// [`docs/model.md`](../../../docs/model.md).
26pub trait Surface {
27 /// Writes `cell` at the absolute position `at`.
28 fn stamp(&mut self, at: Pos, cell: Cell);
29}
30
31/// A sink, a stamp mode and **who is stamping**, bound together at construction.
32///
33/// The caller's choice between the two stamp modes is taken here, once: no shape or fragment
34/// this crate defines ever names a [`StampMode`] itself, since a shape has no opinion about how
35/// it composes with the figures it draws alongside — see _Shapes_ in
36/// [`docs/model.md`](../../../docs/model.md). The same holds for the identity: **ownership is per
37/// shape, not per cell**, so it rides here rather than on [`Surface::stamp`], and a shape is never
38/// told which identity it draws under. `FigureLayer` is the only [`Surface`] this crate ships.
39///
40/// A layer bound by [`new`](Self::new) is bound to nobody and owns nothing, which is what the
41/// core's own figures ask for — they are values that draw and nothing more.
42pub struct FigureLayer<'a> {
43 sink: &'a mut dyn StampSink,
44 mode: StampMode,
45 owner: Option<ShapeId>,
46}
47
48impl<'a> FigureLayer<'a> {
49 /// Binds `sink` to `mode` for every stamp drawn through this layer, under no identity.
50 #[must_use]
51 pub fn new(sink: &'a mut dyn StampSink, mode: StampMode) -> Self {
52 Self {
53 sink,
54 mode,
55 owner: None,
56 }
57 }
58
59 /// Binds `sink` to `mode` and to `owner` for every stamp drawn through this layer.
60 ///
61 /// One layer per figure rather than one per stamp, which is what keeps the identity off
62 /// [`Surface::stamp`]: a figure with an identity parameter is a figure that can be drawn under
63 /// two, or under none, and the caller above is the only thing that knows which.
64 ///
65 /// The identity is taken **by value**, which is what leaves this struct with a single lifetime:
66 /// an identity is two bytes and `Copy`, so a layer holds one without borrowing it, and nothing
67 /// downstream of this constructor has to keep a shape's identity alive for the length of a
68 /// drawing.
69 #[must_use]
70 pub fn stamped_by(sink: &'a mut dyn StampSink, mode: StampMode, owner: ShapeId) -> Self {
71 Self {
72 sink,
73 mode,
74 owner: Some(owner),
75 }
76 }
77}
78
79impl Surface for FigureLayer<'_> {
80 fn stamp(&mut self, at: Pos, cell: Cell) {
81 self.sink.stamp(at, cell, self.mode, self.owner);
82 }
83}
84
85/// A value describing a figure.
86///
87/// It draws, and answers nothing else about itself — everything that read an extent of its own was
88/// a test or a restatement of "no position is written twice", so a shape has the one operation and
89/// no question about itself. See _Shapes_ in [`docs/model.md`](../../../docs/model.md). A shape is
90/// constructed where it is used, drawn, and discarded: it has no mutable state and no lifecycle,
91/// so drawing the same shape twice produces the same writes.
92pub trait Shape {
93 /// Draws this shape's cells into `surface`.
94 fn draw(&self, surface: &mut dyn Surface);
95}
96
97/// A [`Surface`] that counts writes per position, so a test can observe that no shape writes
98/// any position more than once in one drawing. Second only to [`FigureLayer`] as an implementation of
99/// this trait, and it exists only in tests: a shape's own report of what it wrote would be a claim
100/// rather than an observation, so a second implementation is what checks the claim.
101#[cfg(test)]
102pub(crate) mod counting {
103 use std::collections::HashMap;
104
105 use super::{Cell, Pos, Surface};
106
107 /// See the module's own documentation.
108 #[derive(Default)]
109 pub(crate) struct CountingSurface {
110 counts: HashMap<(i16, i16), u16>,
111 }
112
113 impl CountingSurface {
114 /// The largest number of times any single position has been stamped so far.
115 pub(crate) fn max_writes(&self) -> u16 {
116 self.counts.values().copied().max().unwrap_or(0)
117 }
118 }
119
120 impl Surface for CountingSurface {
121 fn stamp(&mut self, at: Pos, _cell: Cell) {
122 *self.counts.entry((at.x, at.y)).or_default() += 1;
123 }
124 }
125}