Skip to main content

urushi/view/canvas/
context.rs

1use crate::{Grapheme, TextStyle, View};
2
3use super::cell_primitives::CellPath;
4use super::command::{CellsCommand, RecordedCommand, TextCommand, ViewCommand};
5use super::line_network::LineNetwork;
6use super::{Composition, Position, PositionedCell};
7use crate::view::geometry::Size;
8
9/// Frame-scoped command recorder passed to [`super::CanvasItem::draw`].
10#[derive(Debug)]
11pub struct CanvasContext {
12    size: Size,
13    commands: Vec<RecordedCommand>,
14}
15
16impl CanvasContext {
17    pub(super) fn new(size: Size) -> Self {
18        Self {
19            size,
20            commands: Vec::new(),
21        }
22    }
23
24    pub const fn size(&self) -> Size {
25        self.size
26    }
27
28    pub const fn bounds(&self) -> (Position, Size) {
29        (Position::new(0, 0), self.size)
30    }
31
32    /// Records a View using [`Composition::Replace`].
33    ///
34    /// Supply a finite allocation on every axis whose result depends on
35    /// [`crate::Length::Fill`]. Canvas bounds are not an implicit allocation.
36    pub fn view(
37        &mut self,
38        origin: Position,
39        view: View,
40        width: Option<usize>,
41        height: Option<usize>,
42    ) {
43        self.view_with(origin, view, width, height, Composition::Replace);
44    }
45
46    /// Records a View with an explicit composition rule.
47    ///
48    /// Allocation requirements are the same as for [`Self::view`].
49    pub fn view_with(
50        &mut self,
51        origin: Position,
52        view: View,
53        width: Option<usize>,
54        height: Option<usize>,
55        composition: Composition,
56    ) {
57        self.record(
58            ViewCommand {
59                origin,
60                view,
61                allocation: (width, height),
62            },
63            composition,
64        );
65    }
66
67    /// Records plain text using [`Composition::Overlay`].
68    ///
69    /// As with [`View::text`], `text` must not contain terminal controls.
70    pub fn text(&mut self, origin: Position, text: impl Into<String>, style: TextStyle) {
71        self.text_with(origin, text, style, Composition::Overlay);
72    }
73
74    /// Records plain text with an explicit composition rule.
75    ///
76    /// The plain-text contract is the same as for [`Self::text`].
77    pub fn text_with(
78        &mut self,
79        origin: Position,
80        text: impl Into<String>,
81        style: TextStyle,
82        composition: Composition,
83    ) {
84        self.record(
85            TextCommand {
86                origin,
87                text: text.into(),
88                style,
89            },
90            composition,
91        );
92    }
93
94    /// Draws a marker along one cell-space segment using [`Composition::Overlay`].
95    ///
96    /// The segment may be horizontal, vertical, or diagonal. Intersecting
97    /// lines remain ordinary marker cells; use [`LineNetwork`] when crossings
98    /// must be derived from connectivity.
99    ///
100    /// # Panics
101    ///
102    /// Panics if `marker` is not exactly one printable, one-cell grapheme.
103    pub fn line(&mut self, from: Position, to: Position, marker: &Grapheme, style: TextStyle) {
104        self.line_with(from, to, marker, style, Composition::Overlay);
105    }
106
107    /// Draws a marker along one cell-space segment with explicit composition.
108    ///
109    /// # Panics
110    ///
111    /// Panics if `marker` is not exactly one printable, one-cell grapheme.
112    pub fn line_with(
113        &mut self,
114        from: Position,
115        to: Position,
116        marker: &Grapheme,
117        style: TextStyle,
118        composition: Composition,
119    ) {
120        self.record(CellPath::line(from, to, marker, style), composition);
121    }
122
123    /// Draws connected marker segments using [`Composition::Overlay`].
124    ///
125    /// # Panics
126    ///
127    /// Panics if `marker` is not exactly one printable, one-cell grapheme.
128    pub fn polyline(
129        &mut self,
130        points: impl IntoIterator<Item = Position>,
131        marker: &Grapheme,
132        style: TextStyle,
133    ) {
134        self.polyline_with(points, marker, style, Composition::Overlay);
135    }
136
137    /// Draws connected marker segments with explicit composition.
138    ///
139    /// # Panics
140    ///
141    /// Panics if `marker` is not exactly one printable, one-cell grapheme.
142    pub fn polyline_with(
143        &mut self,
144        points: impl IntoIterator<Item = Position>,
145        marker: &Grapheme,
146        style: TextStyle,
147        composition: Composition,
148    ) {
149        self.record(CellPath::polyline(points, marker, style), composition);
150    }
151
152    /// Draws a cell-aligned marker rectangle using [`Composition::Overlay`].
153    ///
154    /// # Panics
155    ///
156    /// Panics if `marker` is not exactly one printable, one-cell grapheme.
157    pub fn rectangle(
158        &mut self,
159        origin: Position,
160        width: usize,
161        height: usize,
162        marker: &Grapheme,
163        style: TextStyle,
164    ) {
165        self.rectangle_with(origin, width, height, marker, style, Composition::Overlay);
166    }
167
168    /// Draws a cell-aligned marker rectangle with explicit composition.
169    ///
170    /// # Panics
171    ///
172    /// Panics if `marker` is not exactly one printable, one-cell grapheme.
173    pub fn rectangle_with(
174        &mut self,
175        origin: Position,
176        width: usize,
177        height: usize,
178        marker: &Grapheme,
179        style: TextStyle,
180        composition: Composition,
181    ) {
182        self.record(
183            CellPath::rectangle(origin, width, height, marker, style),
184            composition,
185        );
186    }
187
188    /// Draws horizontal and vertical segments as one connected network.
189    ///
190    /// Intersections between segments in this network are rasterized as
191    /// corners, tees, and crossings. The resulting cells use
192    /// [`Composition::Overlay`], like other cell-producing commands.
193    pub fn line_network(&mut self, network: LineNetwork) {
194        self.line_network_with(network, Composition::Overlay);
195    }
196
197    /// Draws one connected line network with an explicit composition rule.
198    ///
199    /// The rule combines this network's rasterized glyphs with earlier Canvas
200    /// cells. It does not merge this value's connections with another command.
201    pub fn line_network_with(&mut self, network: LineNetwork, composition: Composition) {
202        self.record(network, composition);
203    }
204
205    /// Records sparse cell contributions using [`Composition::Overlay`].
206    pub fn cells(&mut self, cells: impl IntoIterator<Item = PositionedCell>) {
207        self.cells_with(cells, Composition::Overlay);
208    }
209
210    /// Records sparse cell contributions with an explicit composition rule.
211    pub fn cells_with(
212        &mut self,
213        cells: impl IntoIterator<Item = PositionedCell>,
214        composition: Composition,
215    ) {
216        self.record(CellsCommand(cells.into_iter().collect()), composition);
217    }
218
219    fn record(
220        &mut self,
221        command: impl super::command::CanvasCommand + 'static,
222        composition: Composition,
223    ) {
224        self.commands
225            .push(RecordedCommand::new(command, composition));
226    }
227
228    pub(super) fn into_commands(self) -> Vec<RecordedCommand> {
229        self.commands
230    }
231}