Skip to main content

freya_components/
canvas.rs

1use std::{
2    any::Any,
3    borrow::Cow,
4    cell::RefCell,
5    collections::HashMap,
6    rc::Rc,
7};
8
9use freya_core::{
10    integration::*,
11    prelude::*,
12};
13use freya_engine::prelude::{
14    Canvas as SkiaCanvas,
15    ClipOp,
16    FontCollection,
17    Paint,
18    PaintStyle,
19    SkRect,
20};
21use torin::prelude::Size2D;
22
23/// Drawing context passed to a [`Canvas`]'s render callback.
24pub struct CanvasContext<'a> {
25    /// Skia canvas to draw onto.
26    pub canvas: &'a SkiaCanvas,
27    /// Font collection used for text rendering.
28    pub font_collection: &'a mut FontCollection,
29    /// Drawing area size in logical coordinates.
30    pub size: Size2D,
31    /// Resolved text styling inherited by the canvas, for drawing themed text.
32    pub text_style_state: &'a TextStyleState,
33}
34
35type Callback = Rc<RefCell<dyn FnMut(&mut CanvasContext)>>;
36
37pub struct RenderCallback(Callback);
38
39impl RenderCallback {
40    pub fn new(callback: impl FnMut(&mut CanvasContext) + 'static) -> Self {
41        Self(Rc::new(RefCell::new(callback)))
42    }
43
44    pub fn call(&self, data: &mut CanvasContext) {
45        (self.0.borrow_mut())(data)
46    }
47}
48
49impl Clone for RenderCallback {
50    fn clone(&self) -> Self {
51        Self(self.0.clone())
52    }
53}
54
55impl PartialEq for RenderCallback {
56    fn eq(&self, _other: &Self) -> bool {
57        true
58    }
59}
60
61impl<H: FnMut(&mut CanvasContext) + 'static> From<H> for RenderCallback {
62    fn from(value: H) -> Self {
63        RenderCallback::new(value)
64    }
65}
66
67#[derive(PartialEq, Clone)]
68pub struct CanvasElement {
69    pub layout: LayoutData,
70    pub event_handlers: FxHashMap<EventName, EventHandlerType>,
71    pub effect: Option<EffectData>,
72    pub on_render: RenderCallback,
73}
74
75impl CanvasElement {}
76
77impl ElementExt for CanvasElement {
78    fn changed(&self, other: &Rc<dyn ElementExt>) -> bool {
79        let Some(rect) = (other.as_ref() as &dyn Any).downcast_ref::<Self>() else {
80            return false;
81        };
82
83        self != rect
84    }
85
86    fn diff(&self, other: &Rc<dyn ElementExt>) -> DiffModifies {
87        let Some(rect) = (other.as_ref() as &dyn Any).downcast_ref::<Self>() else {
88            return DiffModifies::all();
89        };
90
91        let mut diff = DiffModifies::empty();
92
93        if self.effect != rect.effect {
94            diff.insert(DiffModifies::EFFECT);
95        }
96
97        if !self.layout.self_layout_eq(&rect.layout.layout) {
98            diff.insert(DiffModifies::STYLE);
99            diff.insert(DiffModifies::LAYOUT);
100        }
101
102        if !self.layout.inner_layout_eq(&rect.layout.layout) {
103            diff.insert(DiffModifies::STYLE);
104            diff.insert(DiffModifies::INNER_LAYOUT);
105        }
106
107        if self.event_handlers != rect.event_handlers {
108            diff.insert(DiffModifies::EVENT_HANDLERS);
109        }
110
111        if self.on_render != rect.on_render {
112            diff.insert(DiffModifies::STYLE);
113        }
114
115        diff
116    }
117
118    fn layout(&'_ self) -> Cow<'_, LayoutData> {
119        Cow::Borrowed(&self.layout)
120    }
121
122    fn effect(&'_ self) -> Option<Cow<'_, EffectData>> {
123        self.effect.as_ref().map(Cow::Borrowed)
124    }
125
126    fn style(&'_ self) -> Cow<'_, StyleState> {
127        Cow::Owned(StyleState::default())
128    }
129
130    fn text_style(&'_ self) -> Cow<'_, TextStyleData> {
131        Cow::Owned(TextStyleData::default())
132    }
133
134    fn accessibility(&'_ self) -> Cow<'_, AccessibilityData> {
135        Cow::Owned(AccessibilityData::default())
136    }
137
138    fn events_handlers(&'_ self) -> Option<Cow<'_, FxHashMap<EventName, EventHandlerType>>> {
139        Some(Cow::Borrowed(&self.event_handlers))
140    }
141
142    fn clip(&self, context: ClipContext) {
143        let area = context.visible_area;
144
145        context.canvas.clip_rect(
146            SkRect::new(area.min_x(), area.min_y(), area.max_x(), area.max_y()),
147            ClipOp::Intersect,
148            true,
149        );
150    }
151
152    fn render(&self, context: RenderContext) {
153        let style = self.style();
154        let area = context.layout_node.visible_area();
155
156        let mut paint = Paint::default();
157        paint.set_anti_alias(true);
158        paint.set_style(PaintStyle::Fill);
159        style.background.apply_to_paint(&mut paint, area);
160
161        context.canvas.draw_rect(
162            SkRect::new(area.min_x(), area.min_y(), area.max_x(), area.max_y()),
163            &paint,
164        );
165
166        context.canvas.translate((area.min_x(), area.min_y()));
167        context
168            .canvas
169            .scale((context.scale_factor as f32, context.scale_factor as f32));
170
171        let mut canvas_context = CanvasContext {
172            canvas: context.canvas,
173            font_collection: context.font_collection,
174            size: area.size / context.scale_factor as f32,
175            text_style_state: context.text_style_state,
176        };
177        self.on_render.call(&mut canvas_context);
178        context.canvas.restore();
179    }
180}
181
182pub struct Canvas {
183    element: CanvasElement,
184    elements: Vec<Element>,
185    key: DiffKey,
186}
187
188impl ChildrenExt for Canvas {
189    fn get_children(&mut self) -> &mut Vec<Element> {
190        &mut self.elements
191    }
192}
193
194impl KeyExt for Canvas {
195    fn write_key(&mut self) -> &mut DiffKey {
196        &mut self.key
197    }
198}
199
200impl EventHandlersExt for Canvas {
201    fn get_event_handlers(&mut self) -> &mut FxHashMap<EventName, EventHandlerType> {
202        &mut self.element.event_handlers
203    }
204}
205
206impl MaybeExt for Canvas {}
207
208impl From<Canvas> for Element {
209    fn from(value: Canvas) -> Self {
210        Element::Element {
211            key: value.key,
212            element: Rc::new(value.element),
213            elements: value.elements,
214        }
215    }
216}
217
218/// Create a new `Canvas` element.
219///
220/// See the available methods in [Canvas].
221pub fn canvas(on_render: RenderCallback) -> Canvas {
222    Canvas::new(on_render)
223}
224
225impl Canvas {
226    pub fn new(on_render: RenderCallback) -> Self {
227        Self {
228            element: CanvasElement {
229                on_render,
230                layout: LayoutData::default(),
231                event_handlers: HashMap::default(),
232                effect: None,
233            },
234            elements: Vec::default(),
235            key: DiffKey::None,
236        }
237    }
238
239    pub fn try_downcast(element: &dyn ElementExt) -> Option<CanvasElement> {
240        (element as &dyn Any)
241            .downcast_ref::<CanvasElement>()
242            .cloned()
243    }
244}
245
246impl LayoutExt for Canvas {
247    fn get_layout(&mut self) -> &mut LayoutData {
248        &mut self.element.layout
249    }
250}
251
252impl ContainerExt for Canvas {}
253
254impl ContainerWithContentExt for Canvas {}