Skip to main content

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}