1use crate::impl_leaf_widget;
2use crate::layout_leaf::LayoutLeaf;
3use geometry_core::Rect;
4use layout_core::{LayoutError, LayoutStyle};
5use platform_core::Event;
6use ui_tree::{Component, EventResult, RenderNode};
7
8pub struct Canvas {
9 leaf: LayoutLeaf,
10 draw: Box<dyn Fn(Rect) -> RenderNode>,
11}
12
13impl Canvas {
14 pub fn new(
15 layout_style: LayoutStyle,
16 draw_fn: impl Fn(Rect) -> RenderNode + 'static,
17 ) -> Result<Self, LayoutError> {
18 let leaf = LayoutLeaf::register(layout_style)?;
19 Ok(Self {
20 leaf,
21 draw: Box::new(draw_fn),
22 })
23 }
24}
25
26impl Canvas {
27 pub fn with_intrinsic_height(
28 height: f32,
29 draw_fn: impl Fn(geometry_core::Rect) -> ui_tree::RenderNode + 'static,
30 ) -> Result<Self, layout_core::LayoutError> {
31 Self::new(layout_core::LayoutStyle::new().height(height), draw_fn)
32 }
33}
34
35impl_leaf_widget!(Canvas);
36
37impl Component for Canvas {
38 fn view(&self) -> RenderNode {
39 let r = self.leaf.rect.get();
40 if r.width <= 0.0 || r.height <= 0.0 {
43 return RenderNode::Empty;
44 }
45 let local = Rect {
47 x: 0.0,
48 y: 0.0,
49 width: r.width,
50 height: r.height,
51 };
52 let inner = (self.draw)(local);
53 self.leaf.at_layout_position(inner)
54 }
55
56 fn on_event(&mut self, _event: &Event) -> EventResult {
57 EventResult::Ignored
58 }
59
60 fn debug_name(&self) -> &'static str {
61 "Canvas"
62 }
63}
64
65#[cfg(test)]
66mod tests {
67 use crate::context::reset_layout_runtime;
68 use std::cell::Cell;
69 use std::rc::Rc;
70
71 use layout_core::AvailableSpace;
72 use renderer_core::{Color, DrawCommand, Paint, RectStyle, ShapeStyle};
73
74 use super::*;
75 use crate::context::{compute_layout, new_container};
76 use crate::layout_item::LayoutItem;
77
78 #[test]
82 fn draw_closure_is_re_read_each_view_and_recolors() {
83 let color = Rc::new(Cell::new(Color::RED));
84 let color_read = color.clone();
85 reset_layout_runtime();
86 let canvas = Canvas::new(LayoutStyle::new().width(40.0).height(40.0), move |r| {
87 RenderNode::rect(r, RectStyle::default().with_fill(color_read.get()))
88 })
89 .unwrap();
90 let root = new_container(
91 LayoutStyle::new().width(40.0).height(40.0),
92 &[canvas.layout_node()],
93 )
94 .unwrap();
95 compute_layout(
96 root,
97 AvailableSpace::Definite(40.0),
98 AvailableSpace::Definite(40.0),
99 )
100 .unwrap();
101
102 assert_eq!(fill_of(&canvas.view()), Paint::Solid(Color::RED));
103 color.set(Color::BLUE);
104 assert_eq!(
105 fill_of(&canvas.view()),
106 Paint::Solid(Color::BLUE),
107 "draw closure must be re-read on the second view(), not cached from construction"
108 );
109 }
110
111 fn fill_of(view: &RenderNode) -> Paint {
112 let RenderNode::Transform { children, .. } = view else {
113 panic!("expected Transform")
114 };
115 let RenderNode::Primitive(DrawCommand::Rect { style, .. }) = &children[0] else {
116 panic!("expected a Rect primitive")
117 };
118 style.fill.expect("expected a fill")
119 }
120}