1mod assemble;
48mod cell;
49mod cell_primitives;
50mod command;
51mod context;
52mod line_network;
53pub(crate) mod sizing;
54
55use std::any::Any;
56use std::fmt;
57
58use super::geometry::Size;
59
60pub use cell::{CanvasCell, CellContribution, Composition, PositionedCell};
61pub use context::CanvasContext;
62pub use line_network::{LineContinuations, LineGlyphs, LineNetwork};
63pub(crate) use sizing::CanvasRequirements;
64pub use sizing::CanvasSizing;
65
66pub(in crate::view) use assemble::compose_canvas;
67
68#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Hash)]
70pub struct Position {
71 pub x: i64,
72 pub y: i64,
73}
74
75impl Position {
76 pub const fn new(x: i64, y: i64) -> Self {
77 Self { x, y }
78 }
79}
80
81pub trait CanvasItem: fmt::Debug + Send + Sync + 'static {
83 fn draw(&self, context: &mut CanvasContext);
85}
86
87trait ErasedItem: fmt::Debug + Send + Sync {
88 fn draw(&self, context: &mut CanvasContext);
89 fn clone_box(&self) -> Box<dyn ErasedItem>;
90 fn equals(&self, other: &dyn ErasedItem) -> bool;
91 fn as_any(&self) -> &dyn Any;
92}
93
94impl<T> ErasedItem for T
95where
96 T: CanvasItem + Clone + PartialEq,
97{
98 fn draw(&self, context: &mut CanvasContext) {
99 CanvasItem::draw(self, context);
100 }
101
102 fn clone_box(&self) -> Box<dyn ErasedItem> {
103 Box::new(self.clone())
104 }
105
106 fn equals(&self, other: &dyn ErasedItem) -> bool {
107 other.as_any().downcast_ref::<T>() == Some(self)
108 }
109
110 fn as_any(&self) -> &dyn Any {
111 self
112 }
113}
114
115#[derive(Debug)]
116struct Item(Box<dyn ErasedItem>);
117
118impl Clone for Item {
119 fn clone(&self) -> Self {
120 Self(self.0.clone_box())
121 }
122}
123
124impl PartialEq for Item {
125 fn eq(&self, other: &Self) -> bool {
126 self.0.equals(&*other.0)
127 }
128}
129
130#[derive(Debug, Clone, Default, PartialEq)]
132pub struct Canvas {
133 sizing: CanvasSizing,
134 items: Vec<Item>,
135}
136
137impl Canvas {
138 pub const fn new() -> Self {
139 Self {
140 sizing: CanvasSizing::viewport(),
141 items: Vec::new(),
142 }
143 }
144
145 #[must_use]
151 pub fn sizing(mut self, sizing: CanvasSizing) -> Self {
152 self.sizing = sizing;
153 self
154 }
155
156 pub const fn width(mut self, width: usize) -> Self {
162 self.sizing.set_viewport_width(width);
163 self
164 }
165
166 pub const fn height(mut self, height: usize) -> Self {
172 self.sizing.set_viewport_height(height);
173 self
174 }
175
176 pub const fn extent(mut self, size: Size) -> Self {
182 self.sizing.set_viewport_extent(size);
183 self
184 }
185
186 pub fn item<T>(mut self, item: T) -> Self
188 where
189 T: CanvasItem + Clone + PartialEq,
190 {
191 self.items.push(Item(Box::new(item)));
192 self
193 }
194
195 pub fn items<T>(&self) -> impl Iterator<Item = &T>
202 where
203 T: CanvasItem + Clone + PartialEq,
204 {
205 self.items
206 .iter()
207 .filter_map(|item| item.0.as_any().downcast_ref::<T>())
208 }
209
210 pub(in crate::view) const fn explicit_width(&self) -> Option<usize> {
211 self.sizing.explicit_width()
212 }
213
214 pub(in crate::view) const fn explicit_height(&self) -> Option<usize> {
215 self.sizing.explicit_height()
216 }
217
218 pub(in crate::view) const fn uses_viewport_sizing(&self) -> bool {
219 self.sizing.is_viewport()
220 }
221
222 pub(in crate::view) fn width_requirements(&self) -> sizing::Requirements {
223 self.sizing.width_requirements()
224 }
225
226 pub(in crate::view) fn height_requirements(&self, width: usize) -> sizing::Requirements {
227 self.sizing.height_requirements(width)
228 }
229
230 pub(super) fn draw(&self, size: Size) -> CanvasContext {
231 let mut context = CanvasContext::new(size);
232 for item in &self.items {
233 item.0.draw(&mut context);
234 }
235 context
236 }
237}
238
239#[cfg(test)]
240mod tests {
241 use std::sync::{Arc, Mutex};
242
243 use super::sizing::{CanvasMeasure, CanvasRequirements};
244 use super::*;
245 use crate::{Align, Available, BlockStyle, Grapheme, View, measure, resolve};
246
247 #[derive(Debug, Clone, PartialEq, Eq)]
248 enum Event {
249 Width,
250 Height(usize),
251 Draw(Size),
252 }
253
254 #[derive(Debug, Clone)]
255 struct StagedMeasure {
256 width: CanvasRequirements,
257 height: CanvasRequirements,
258 events: Arc<Mutex<Vec<Event>>>,
259 }
260
261 impl PartialEq for StagedMeasure {
262 fn eq(&self, other: &Self) -> bool {
263 self.width == other.width && self.height == other.height
264 }
265 }
266
267 impl CanvasMeasure for StagedMeasure {
268 fn width_requirements(&self) -> CanvasRequirements {
269 self.events.lock().unwrap().push(Event::Width);
270 self.width
271 }
272
273 fn height_requirements(&self, width: usize) -> CanvasRequirements {
274 self.events.lock().unwrap().push(Event::Height(width));
275 self.height
276 }
277 }
278
279 #[derive(Debug, Clone)]
280 struct DrawPastHeight(Arc<Mutex<Vec<Event>>>);
281
282 impl PartialEq for DrawPastHeight {
283 fn eq(&self, _other: &Self) -> bool {
284 true
285 }
286 }
287
288 impl CanvasItem for DrawPastHeight {
289 fn draw(&self, context: &mut CanvasContext) {
290 self.0.lock().unwrap().push(Event::Draw(context.size()));
291 context.cells([PositionedCell::new(
292 Position::new(0, 2),
293 CellContribution::new().symbol(Grapheme::new("x")),
294 )]);
295 }
296 }
297
298 #[derive(Debug, Clone, PartialEq, Eq)]
299 struct Label(&'static str);
300
301 impl CanvasItem for Label {
302 fn draw(&self, _context: &mut CanvasContext) {}
303 }
304
305 #[derive(Debug, Clone, PartialEq, Eq)]
306 struct Marker;
307
308 impl CanvasItem for Marker {
309 fn draw(&self, _context: &mut CanvasContext) {}
310 }
311
312 #[test]
313 fn owned_items_can_be_inspected_by_their_concrete_type() {
314 let canvas = Canvas::new()
315 .item(Label("first"))
316 .item(Marker)
317 .item(Label("second"));
318
319 assert_eq!(
320 canvas.items::<Label>().collect::<Vec<_>>(),
321 [&Label("first"), &Label("second")]
322 );
323 assert_eq!(canvas.items::<Marker>().count(), 1);
324 }
325
326 #[test]
327 fn intrinsic_measurement_is_staged_once_before_drawing() {
328 let events = Arc::new(Mutex::new(Vec::new()));
329 let sizing = CanvasSizing::intrinsic(StagedMeasure {
330 width: CanvasRequirements::new(8, 3),
331 height: CanvasRequirements::new(4, 2),
332 events: Arc::clone(&events),
333 });
334 let view = View::canvas(
335 Canvas::new()
336 .sizing(sizing)
337 .item(DrawPastHeight(Arc::clone(&events))),
338 );
339
340 assert_eq!(measure(&view), Size::new(8, 4));
341 assert_eq!(
342 *events.lock().unwrap(),
343 [Event::Width, Event::Height(8)],
344 "measurement never invokes Canvas items"
345 );
346 events.lock().unwrap().clear();
347
348 let resolved = resolve(&view, Available::size(5, 2)).unwrap();
349
350 assert_eq!(resolved.size(), Size::new(5, 2));
351 assert_eq!(
352 *events.lock().unwrap(),
353 [Event::Width, Event::Height(5), Event::Draw(Size::new(5, 2))]
354 );
355 assert!(
356 resolved
357 .rows()
358 .iter()
359 .flatten()
360 .all(|cell| cell.symbol() != "x"),
361 "drawing beyond the selected height is cropped without reflow"
362 );
363 }
364
365 #[test]
366 fn intrinsic_equality_ignores_allocation_identity() {
367 let first = CanvasSizing::intrinsic(StagedMeasure {
368 width: CanvasRequirements::new(8, 3),
369 height: CanvasRequirements::new(4, 2),
370 events: Arc::new(Mutex::new(Vec::new())),
371 });
372 let same_value_in_another_allocation = CanvasSizing::intrinsic(StagedMeasure {
373 width: CanvasRequirements::new(8, 3),
374 height: CanvasRequirements::new(4, 2),
375 events: Arc::new(Mutex::new(Vec::new())),
376 });
377
378 assert_eq!(first, same_value_in_another_allocation);
379 }
380
381 #[test]
382 fn intrinsic_floors_win_before_the_final_safety_crop() {
383 let events = Arc::new(Mutex::new(Vec::new()));
384 let sizing = CanvasSizing::intrinsic(StagedMeasure {
385 width: CanvasRequirements::new(8, 3),
386 height: CanvasRequirements::new(4, 3),
387 events: Arc::clone(&events),
388 });
389 let view = View::canvas(
390 Canvas::new()
391 .sizing(sizing)
392 .item(DrawPastHeight(Arc::clone(&events))),
393 );
394
395 let resolved = resolve(&view, Available::size(1, 1)).unwrap();
396
397 assert_eq!(resolved.size(), Size::new(1, 1));
398 assert_eq!(
399 *events.lock().unwrap(),
400 [Event::Width, Event::Height(3), Event::Draw(Size::new(3, 3))]
401 );
402 }
403
404 #[test]
405 fn selected_width_height_floors_propagate_through_ancestor_claims() {
406 let first_events = Arc::new(Mutex::new(Vec::new()));
407 let second_events = Arc::new(Mutex::new(Vec::new()));
408 let intrinsic = |height, floor, events: &Arc<Mutex<Vec<Event>>>| {
409 View::canvas(
410 Canvas::new()
411 .sizing(CanvasSizing::intrinsic(StagedMeasure {
412 width: CanvasRequirements::new(1, 0),
413 height: CanvasRequirements::new(height, floor),
414 events: Arc::clone(events),
415 }))
416 .item(DrawPastHeight(Arc::clone(events))),
417 )
418 };
419 let view = View::column(
420 Align::Left,
421 [
422 View::block(BlockStyle::new(), intrinsic(10, 8, &first_events)),
423 intrinsic(10, 0, &second_events),
424 ],
425 );
426
427 let resolved = resolve(&view, Available::size(3, 10)).unwrap();
428
429 assert_eq!(resolved.size(), Size::new(3, 10));
430 assert_eq!(
431 first_events.lock().unwrap().last(),
432 Some(&Event::Draw(Size::new(1, 8)))
433 );
434 assert_eq!(
435 second_events.lock().unwrap().last(),
436 Some(&Event::Draw(Size::new(3, 2)))
437 );
438 }
439}