Skip to main content

widgetkit_render/
canvas.rs

1use crate::model::{
2    ClearCommand, ClipCommand, ClipPrimitive, CommandList, Fill, FillCommand, FillShape,
3    ImageCommand, ImageSource, Paint, RenderCommand, RenderFrame, StateCommand, StrokeCommand,
4    StrokeShape, TextCommand, Transform, TransformCommand,
5};
6use crate::text::measure_text;
7use crate::{Stroke, TextMetrics, TextStyle};
8use widgetkit_core::{Color, Point, Rect, Size};
9
10#[derive(Debug)]
11pub struct Canvas {
12    size: Size,
13    commands: CommandList,
14}
15
16/// Experimental command sink used by `Canvas::experimental_raw`.
17///
18/// This type is exposed through `widgetkit_render::unstable` only and may change in any `0.x`
19/// release.
20pub struct RawCanvas<'a> {
21    commands: &'a mut CommandList,
22}
23
24impl Canvas {
25    pub fn new(size: Size) -> Self {
26        Self {
27            size,
28            commands: CommandList::new(),
29        }
30    }
31
32    pub fn size(&self) -> Size {
33        self.size
34    }
35
36    pub fn measure_text(&self, text: impl AsRef<str>, style: &TextStyle) -> TextMetrics {
37        measure_text(text.as_ref(), style)
38    }
39
40    pub fn clear(&mut self, color: Color) {
41        self.raw().clear(color);
42    }
43
44    pub fn rect(&mut self, rect: Rect, color: Color) {
45        self.raw().fill_rect(rect, color);
46    }
47
48    pub fn round_rect(&mut self, rect: Rect, radius: f32, color: Color) {
49        self.raw().fill_round_rect(rect, radius, color);
50    }
51
52    pub fn line(&mut self, start: Point, end: Point, stroke: Stroke, color: Color) {
53        self.raw().stroke_line(start, end, stroke, color);
54    }
55
56    pub fn circle(&mut self, center: Point, radius: f32, color: Color) {
57        self.raw().fill_circle(center, radius, color);
58    }
59
60    pub fn ellipse(&mut self, center: Point, radius_x: f32, radius_y: f32, color: Color) {
61        self.raw().fill_ellipse(center, radius_x, radius_y, color);
62    }
63
64    pub fn text(
65        &mut self,
66        position: Point,
67        text: impl Into<String>,
68        style: TextStyle,
69        color: Color,
70    ) {
71        self.raw().draw_text(position, text, style, color);
72    }
73
74    pub fn image_placeholder(&mut self, rect: Rect, color: Color) {
75        self.raw().image_placeholder(rect, color);
76    }
77
78    pub fn clip_rect(&mut self, rect: Rect) {
79        self.raw().clip_rect(rect);
80    }
81
82    pub fn save(&mut self) {
83        self.raw().save();
84    }
85
86    pub fn restore(&mut self) {
87        self.raw().restore();
88    }
89
90    pub fn translate(&mut self, dx: f32, dy: f32) {
91        self.raw().translate(dx, dy);
92    }
93
94    /// Runs an experimental low-level command sink against this canvas.
95    ///
96    /// `RawCanvas` and the command types behind it are available from
97    /// `widgetkit_render::unstable`. They are intentionally not re-exported by the top-level
98    /// `widgetkit` facade and may change in any `0.x` release.
99    pub fn experimental_raw(&mut self, f: impl FnOnce(&mut RawCanvas<'_>)) {
100        let mut raw = self.raw();
101        f(&mut raw);
102    }
103
104    fn raw(&mut self) -> RawCanvas<'_> {
105        RawCanvas {
106            commands: &mut self.commands,
107        }
108    }
109
110    #[doc(hidden)]
111    pub fn into_frame(self) -> RenderFrame {
112        RenderFrame::from_list(self.size, self.commands)
113    }
114}
115
116impl RawCanvas<'_> {
117    pub fn push(&mut self, command: RenderCommand) {
118        self.commands.push(command);
119    }
120
121    pub fn clear(&mut self, color: Color) {
122        self.push(RenderCommand::Clear(ClearCommand {
123            paint: Paint::solid(color),
124        }));
125    }
126
127    pub fn fill_rect(&mut self, rect: Rect, color: Color) {
128        self.push(RenderCommand::Fill(FillCommand {
129            shape: FillShape::Rect(rect),
130            fill: Fill::solid(color),
131        }));
132    }
133
134    pub fn fill_round_rect(&mut self, rect: Rect, radius: f32, color: Color) {
135        self.push(RenderCommand::Fill(FillCommand {
136            shape: FillShape::RoundRect {
137                rect,
138                radius: radius.max(0.0),
139            },
140            fill: Fill::solid(color),
141        }));
142    }
143
144    pub fn fill_circle(&mut self, center: Point, radius: f32, color: Color) {
145        self.push(RenderCommand::Fill(FillCommand {
146            shape: FillShape::Circle {
147                center,
148                radius: radius.max(0.0),
149            },
150            fill: Fill::solid(color),
151        }));
152    }
153
154    pub fn fill_ellipse(&mut self, center: Point, radius_x: f32, radius_y: f32, color: Color) {
155        self.push(RenderCommand::Fill(FillCommand {
156            shape: FillShape::Ellipse {
157                center,
158                radius_x: radius_x.max(0.0),
159                radius_y: radius_y.max(0.0),
160            },
161            fill: Fill::solid(color),
162        }));
163    }
164
165    pub fn stroke_line(&mut self, start: Point, end: Point, stroke: Stroke, color: Color) {
166        self.push(RenderCommand::Stroke(StrokeCommand {
167            shape: StrokeShape::Line { start, end },
168            stroke,
169            paint: Paint::solid(color),
170        }));
171    }
172
173    pub fn draw_text(
174        &mut self,
175        position: Point,
176        text: impl Into<String>,
177        style: TextStyle,
178        color: Color,
179    ) {
180        self.push(RenderCommand::Text(TextCommand {
181            position,
182            text: text.into(),
183            style,
184            paint: Paint::solid(color),
185        }));
186    }
187
188    pub fn image_placeholder(&mut self, rect: Rect, color: Color) {
189        self.push(RenderCommand::Image(ImageCommand {
190            rect,
191            source: ImageSource::Placeholder,
192            paint: Paint::solid(color),
193        }));
194    }
195
196    pub fn clip_rect(&mut self, rect: Rect) {
197        self.push(RenderCommand::Clip(ClipCommand {
198            primitive: ClipPrimitive::Rect(rect),
199        }));
200    }
201
202    pub fn save(&mut self) {
203        self.push(RenderCommand::State(StateCommand::Save));
204    }
205
206    pub fn restore(&mut self) {
207        self.push(RenderCommand::State(StateCommand::Restore));
208    }
209
210    pub fn translate(&mut self, dx: f32, dy: f32) {
211        self.push(RenderCommand::Transform(TransformCommand {
212            transform: Transform::translation(dx, dy),
213        }));
214    }
215}
216
217// TODO(v0.3): image draw API stabilization
218// TODO(v0.3): transform stack stabilization
219// TODO(v0.3): richer clipping model
220// TODO(v0.5): map declarative nodes to Canvas without API gaps