1use crate::cell::Side;
6use crate::shape::fragment::end::End;
7use crate::shape::fragment::segment::Segment;
8use crate::{Orientation, Pos, Shape, Stroke, Surface};
9
10#[derive(Debug)]
20pub struct Line {
21 pub at: Pos,
23 pub len: u16,
25 pub orientation: Orientation,
27 pub stroke: Stroke,
29}
30
31impl Line {
32 fn pos_at(&self, i: u16) -> Option<Pos> {
35 match self.orientation {
36 Orientation::Horizontal => self
37 .at
38 .x
39 .checked_add_unsigned(i)
40 .map(|x| Pos { x, y: self.at.y }),
41 Orientation::Vertical => self
42 .at
43 .y
44 .checked_add_unsigned(i)
45 .map(|y| Pos { x: self.at.x, y }),
46 }
47 }
48}
49
50impl Shape for Line {
51 fn draw(&self, surface: &mut dyn Surface) {
52 if self.len == 0 {
53 return;
54 }
55 let (forward, backward) = match self.orientation {
56 Orientation::Horizontal => (Side::Right, Side::Left),
57 Orientation::Vertical => (Side::Bottom, Side::Top),
58 };
59
60 if let Some(first) = self.pos_at(0) {
61 End {
62 at: first,
63 side: forward,
64 stroke: self.stroke.clone(),
65 }
66 .draw(surface);
67 }
68 if self.len >= 3
69 && let Some(from) = self.pos_at(1)
70 {
71 Segment {
72 from,
73 len: self.len - 2,
74 orientation: self.orientation,
75 stroke: self.stroke.clone(),
76 }
77 .draw(surface);
78 }
79 if self.len >= 2
80 && let Some(last) = self.pos_at(self.len - 1)
81 {
82 End {
83 at: last,
84 side: backward,
85 stroke: self.stroke.clone(),
86 }
87 .draw(surface);
88 }
89 }
90}
91
92#[cfg(test)]
93mod tests {
94 use super::Line;
95 use crate::shape::counting::CountingSurface;
96 use crate::{
97 Arm, Buffer, CellReader, FigureLayer, GlyphCatalog, Orientation, Pos, Shape, Size,
98 StampMode, Stroke, Window, render,
99 };
100
101 fn light() -> Stroke {
102 Stroke::from("light")
103 }
104
105 #[test]
108 fn a_horizontal_line_of_length_5_renders_and_stops_cleanly() {
109 let origin = Pos { x: 0, y: 0 };
110 let size = Size {
111 width: 6,
112 height: 1,
113 };
114 let mut buffer = Buffer::new(Window { origin, size });
115 Line {
116 at: origin,
117 len: 5,
118 orientation: Orientation::Horizontal,
119 stroke: light(),
120 }
121 .draw(&mut FigureLayer::new(&mut buffer, StampMode::Above));
122
123 assert_eq!(
124 render(&buffer, &GlyphCatalog::light(), buffer.window()),
125 "───── \n"
126 );
127 assert!(buffer.cell(Pos { x: 5, y: 0 }).is_none());
128 }
129
130 #[test]
133 fn a_vertical_line_of_length_4_renders_and_stops_cleanly() {
134 let origin = Pos { x: 0, y: 0 };
135 let size = Size {
136 width: 1,
137 height: 5,
138 };
139 let mut buffer = Buffer::new(Window { origin, size });
140 Line {
141 at: origin,
142 len: 4,
143 orientation: Orientation::Vertical,
144 stroke: light(),
145 }
146 .draw(&mut FigureLayer::new(&mut buffer, StampMode::Above));
147
148 assert_eq!(
149 render(&buffer, &GlyphCatalog::light(), buffer.window()),
150 "│\n│\n│\n│\n \n"
151 );
152 assert!(buffer.cell(Pos { x: 0, y: 4 }).is_none());
153 }
154
155 #[test]
160 fn a_horizontal_lines_first_cell_carries_only_its_forward_arm() {
161 let origin = Pos { x: 0, y: 0 };
162 let mut buffer = Buffer::new(Window {
163 origin,
164 size: Size {
165 width: 5,
166 height: 1,
167 },
168 });
169 Line {
170 at: origin,
171 len: 5,
172 orientation: Orientation::Horizontal,
173 stroke: light(),
174 }
175 .draw(&mut FigureLayer::new(&mut buffer, StampMode::Above));
176
177 let Some(crate::Cell::Strokes(cell)) = buffer.cell(origin) else {
178 panic!("expected a stroke cell at the line's first position");
179 };
180 assert_eq!(cell.right, Arm::Set(Stroke::from("light")));
181 assert_eq!(cell.top, Arm::Unset);
182 assert_eq!(cell.bottom, Arm::Unset);
183 assert_eq!(cell.left, Arm::Unset);
184 }
185
186 #[test]
189 fn a_line_of_length_2_is_two_ends_with_no_run_between_them() {
190 let origin = Pos { x: 0, y: 0 };
191 let size = Size {
192 width: 2,
193 height: 1,
194 };
195 let mut buffer = Buffer::new(Window { origin, size });
196 Line {
197 at: origin,
198 len: 2,
199 orientation: Orientation::Horizontal,
200 stroke: light(),
201 }
202 .draw(&mut FigureLayer::new(&mut buffer, StampMode::Above));
203
204 assert_eq!(
205 render(&buffer, &GlyphCatalog::light(), buffer.window()),
206 "──\n"
207 );
208 }
209
210 #[test]
213 fn a_line_of_length_1_writes_exactly_one_position() {
214 let origin = Pos { x: 0, y: 0 };
215 let mut buffer = Buffer::new(Window {
216 origin,
217 size: Size {
218 width: 1,
219 height: 1,
220 },
221 });
222 Line {
223 at: origin,
224 len: 1,
225 orientation: Orientation::Horizontal,
226 stroke: light(),
227 }
228 .draw(&mut FigureLayer::new(&mut buffer, StampMode::Above));
229
230 assert!(buffer.cell(origin).is_some());
231 }
232
233 #[test]
235 fn a_line_of_length_0_draws_nothing() {
236 let origin = Pos { x: 0, y: 0 };
237 let mut buffer = Buffer::new(Window {
238 origin,
239 size: Size {
240 width: 1,
241 height: 1,
242 },
243 });
244 Line {
245 at: origin,
246 len: 0,
247 orientation: Orientation::Horizontal,
248 stroke: light(),
249 }
250 .draw(&mut FigureLayer::new(&mut buffer, StampMode::Above));
251
252 assert!(buffer.cell(origin).is_none());
253 }
254
255 #[test]
258 fn no_line_above_writes_any_position_more_than_once() {
259 for len in [0, 1, 2, 3, 5] {
260 let mut surface = CountingSurface::default();
261 Line {
262 at: Pos { x: 0, y: 0 },
263 len,
264 orientation: Orientation::Horizontal,
265 stroke: light(),
266 }
267 .draw(&mut surface);
268
269 assert!(
270 surface.max_writes() <= 1,
271 "length {len} wrote a position more than once"
272 );
273 }
274 }
275
276 #[test]
282 fn two_lines_sharing_an_end_compose_into_the_corner_they_make() {
283 let origin = Pos { x: 0, y: 0 };
284 let size = Size {
285 width: 1,
286 height: 1,
287 };
288 let horizontal = || Line {
289 at: origin,
290 len: 3,
291 orientation: Orientation::Horizontal,
292 stroke: light(),
293 };
294 let vertical = || Line {
295 at: origin,
296 len: 3,
297 orientation: Orientation::Vertical,
298 stroke: light(),
299 };
300
301 for mode in [StampMode::Above, StampMode::Below] {
302 let mut horizontal_first = Buffer::new(Window { origin, size });
303 horizontal().draw(&mut FigureLayer::new(
304 &mut horizontal_first,
305 StampMode::Above,
306 ));
307 vertical().draw(&mut FigureLayer::new(&mut horizontal_first, mode));
308
309 let mut vertical_first = Buffer::new(Window { origin, size });
310 vertical().draw(&mut FigureLayer::new(&mut vertical_first, StampMode::Above));
311 horizontal().draw(&mut FigureLayer::new(&mut vertical_first, mode));
312
313 for (label, buffer) in [
314 ("horizontal first", &horizontal_first),
315 ("vertical first", &vertical_first),
316 ] {
317 assert_eq!(
318 render(buffer, &GlyphCatalog::light(), buffer.window()),
319 "┌\n",
320 "{label}, second stamp under {mode:?}"
321 );
322 }
323 }
324 }
325}