Skip to main content

urushi/view/canvas/
cell.rs

1use std::fmt;
2
3use unicode_segmentation::UnicodeSegmentation;
4
5use crate::{Grapheme, TextStyle};
6
7use super::Position;
8
9/// How one command combines its contributions with preceding commands.
10#[derive(Clone, Copy)]
11pub enum Composition {
12    Replace,
13    Overlay,
14    Custom(fn(&CanvasCell, &CellContribution) -> CanvasCell),
15}
16
17impl fmt::Debug for Composition {
18    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
19        match self {
20            Self::Replace => f.write_str("Replace"),
21            Self::Overlay => f.write_str("Overlay"),
22            Self::Custom(_) => f.write_str("Custom(..)"),
23        }
24    }
25}
26
27/// Complete content of one Canvas cell.
28#[derive(Debug, Clone, PartialEq, Eq)]
29pub struct CanvasCell {
30    symbol: String,
31    style: TextStyle,
32}
33
34impl CanvasCell {
35    /// Creates complete cell content.
36    ///
37    /// The symbol is validated at this boundary even in release builds.
38    pub fn new(symbol: &Grapheme, style: TextStyle) -> Self {
39        validate_symbol(symbol.as_str());
40        Self {
41            symbol: symbol.as_str().to_owned(),
42            style,
43        }
44    }
45
46    pub fn get_symbol(&self) -> &str {
47        &self.symbol
48    }
49
50    pub const fn get_style(&self) -> &TextStyle {
51        &self.style
52    }
53
54    /// Replaces the symbol, validating it even in release builds.
55    pub fn symbol(mut self, symbol: &Grapheme) -> Self {
56        validate_symbol(symbol.as_str());
57        self.symbol = symbol.as_str().to_owned();
58        self
59    }
60
61    pub fn style(mut self, style: TextStyle) -> Self {
62        self.style = style;
63        self
64    }
65}
66
67/// A sparse contribution; absent fields are transparent under [`Composition::Overlay`].
68#[derive(Debug, Clone, Default, PartialEq, Eq)]
69pub struct CellContribution {
70    symbol: Option<String>,
71    style: Option<TextStyle>,
72}
73
74impl CellContribution {
75    pub fn new() -> Self {
76        Self::default()
77    }
78
79    /// Contributes a symbol, validating it even in release builds.
80    pub fn symbol(mut self, symbol: &Grapheme) -> Self {
81        validate_symbol(symbol.as_str());
82        self.symbol = Some(symbol.as_str().to_owned());
83        self
84    }
85
86    pub fn style(mut self, style: TextStyle) -> Self {
87        self.style = Some(style);
88        self
89    }
90
91    pub fn get_symbol(&self) -> Option<&str> {
92        self.symbol.as_deref()
93    }
94
95    pub fn get_style(&self) -> Option<&TextStyle> {
96        self.style.as_ref()
97    }
98}
99
100/// Sparse positioned cell input.
101#[derive(Debug, Clone, PartialEq, Eq)]
102pub struct PositionedCell {
103    pub position: Position,
104    pub contribution: CellContribution,
105}
106
107impl PositionedCell {
108    pub const fn new(position: Position, contribution: CellContribution) -> Self {
109        Self {
110            position,
111            contribution,
112        }
113    }
114}
115
116pub(super) fn validate_symbol(symbol: &str) {
117    assert!(
118        !symbol.chars().any(char::is_control) && symbol.graphemes(true).count() == 1,
119        "a Canvas cell symbol must be exactly one printable grapheme: {symbol:?}"
120    );
121}
122
123pub(super) fn validate_cell_glyph(symbol: &str) {
124    validate_symbol(symbol);
125    assert_eq!(
126        Grapheme::new(symbol).width(),
127        1,
128        "a rasterized cell glyph must occupy exactly one terminal cell: {symbol:?}"
129    );
130}