freya_core/elements/
rect.rs

1use freya_engine::prelude::*;
2use freya_native_core::real_dom::NodeImmutable;
3use torin::{
4    prelude::{
5        Area,
6        CursorPoint,
7        LayoutNode,
8        Point2D,
9        Size2D,
10    },
11    scaled::Scaled,
12};
13
14use super::utils::ElementUtils;
15use crate::{
16    custom_attributes::CanvasRunnerContext,
17    dom::{
18        DioxusNode,
19        ImagesCache,
20    },
21    render::{
22        border_shape,
23        render_border,
24        render_shadow,
25        BorderShape,
26    },
27    states::{
28        CanvasState,
29        StyleState,
30    },
31    values::{
32        Fill,
33        ShadowPosition,
34    },
35};
36
37pub struct RectElement;
38
39impl RectElement {
40    fn get_rounded_rect(
41        &self,
42        layout_node: &LayoutNode,
43        node_ref: &DioxusNode,
44        scale_factor: f32,
45    ) -> RRect {
46        let area = layout_node.visible_area().to_f32();
47        let node_style = &*node_ref.get::<StyleState>().unwrap();
48        let radius = node_style.corner_radius.with_scale(scale_factor);
49
50        RRect::new_rect_radii(
51            Rect::new(area.min_x(), area.min_y(), area.max_x(), area.max_y()),
52            &[
53                (radius.top_left, radius.top_left).into(),
54                (radius.top_right, radius.top_right).into(),
55                (radius.bottom_right, radius.bottom_right).into(),
56                (radius.bottom_left, radius.bottom_left).into(),
57            ],
58        )
59    }
60}
61
62impl ElementUtils for RectElement {
63    fn is_point_inside_area(
64        &self,
65        point: &CursorPoint,
66        node_ref: &DioxusNode,
67        layout_node: &LayoutNode,
68        scale_factor: f32,
69    ) -> bool {
70        let rounded_rect = self.get_rounded_rect(layout_node, node_ref, scale_factor);
71        let point = point.to_f32();
72        rounded_rect.contains(Rect::new(point.x, point.y, point.x + 1., point.y + 1.))
73    }
74
75    fn clip(
76        &self,
77        layout_node: &LayoutNode,
78        node_ref: &DioxusNode,
79        canvas: &Canvas,
80        scale_factor: f32,
81    ) {
82        let rounded_rect = self.get_rounded_rect(layout_node, node_ref, scale_factor);
83
84        canvas.clip_rrect(rounded_rect, ClipOp::Intersect, true);
85    }
86
87    fn render(
88        self,
89        layout_node: &LayoutNode,
90        node_ref: &DioxusNode,
91        canvas: &Canvas,
92        font_collection: &mut FontCollection,
93        _font_manager: &FontMgr,
94        _default_fonts: &[String],
95        _images_cache: &mut ImagesCache,
96        scale_factor: f32,
97    ) {
98        let node_style = &*node_ref.get::<StyleState>().unwrap();
99
100        let area = layout_node.visible_area().to_f32();
101        let mut path = Path::new();
102        let mut paint = Paint::default();
103        paint.set_anti_alias(true);
104        paint.set_style(PaintStyle::Fill);
105
106        node_style.background.apply_to_paint(&mut paint, area);
107
108        let corner_radius = node_style.corner_radius.with_scale(scale_factor);
109
110        // Container
111        let rounded_rect = RRect::new_rect_radii(
112            Rect::new(area.min_x(), area.min_y(), area.max_x(), area.max_y()),
113            &[
114                (corner_radius.top_left, corner_radius.top_left).into(),
115                (corner_radius.top_right, corner_radius.top_right).into(),
116                (corner_radius.bottom_right, corner_radius.bottom_right).into(),
117                (corner_radius.bottom_left, corner_radius.bottom_left).into(),
118            ],
119        );
120        if corner_radius.smoothing > 0.0 {
121            path.add_path(
122                &corner_radius.smoothed_path(rounded_rect),
123                (area.min_x(), area.min_y()),
124                None,
125            );
126        } else {
127            path.add_rrect(rounded_rect, None);
128        }
129        canvas.draw_path(&path, &paint);
130
131        // Shadows
132        for shadow in node_style.shadows.iter() {
133            if shadow.fill != Fill::Color(Color::TRANSPARENT) {
134                let shadow = shadow.with_scale(scale_factor);
135
136                render_shadow(
137                    canvas,
138                    node_style,
139                    &mut path,
140                    rounded_rect,
141                    area,
142                    &shadow,
143                    &corner_radius,
144                );
145            }
146        }
147
148        // Borders
149        for border in node_style.borders.iter() {
150            if border.is_visible() {
151                let border = border.with_scale(scale_factor);
152                let rect = rounded_rect.rect().round_in().into();
153                render_border(canvas, rect, area, &border, &corner_radius);
154            }
155        }
156
157        // Canvas reference
158        let references = node_ref.get::<CanvasState>().unwrap();
159        if let Some(canvas_ref) = &references.canvas_ref {
160            let mut ctx = CanvasRunnerContext {
161                canvas,
162                font_collection,
163                area,
164                scale_factor,
165            };
166            (canvas_ref.runner.lock().unwrap())(&mut ctx);
167        }
168    }
169
170    #[inline]
171    fn element_needs_cached_area(&self, _node_ref: &DioxusNode, style_state: &StyleState) -> bool {
172        !style_state.borders.is_empty() || !style_state.shadows.is_empty()
173    }
174
175    fn element_drawing_area(
176        &self,
177        layout_node: &LayoutNode,
178        _node_ref: &DioxusNode,
179        scale_factor: f32,
180        node_style: &StyleState,
181    ) -> Area {
182        let mut area = layout_node.visible_area();
183
184        if node_style.borders.is_empty() && node_style.shadows.is_empty() {
185            return area;
186        }
187
188        let mut path = Path::new();
189
190        let corner_radius = node_style.corner_radius.with_scale(scale_factor);
191
192        let rounded_rect = RRect::new_rect_radii(
193            Rect::new(area.min_x(), area.min_y(), area.max_x(), area.max_y()),
194            &[
195                (corner_radius.top_left, corner_radius.top_left).into(),
196                (corner_radius.top_right, corner_radius.top_right).into(),
197                (corner_radius.bottom_right, corner_radius.bottom_right).into(),
198                (corner_radius.bottom_left, corner_radius.bottom_left).into(),
199            ],
200        );
201
202        if corner_radius.smoothing > 0.0 {
203            path.add_path(
204                &corner_radius.smoothed_path(rounded_rect),
205                (area.min_x(), area.min_y()),
206                None,
207            );
208        } else {
209            path.add_rrect(rounded_rect, None);
210        }
211
212        // Shadows
213        for shadow in node_style.shadows.iter() {
214            if shadow.fill != Fill::Color(Color::TRANSPARENT) {
215                let shadow = shadow.with_scale(scale_factor);
216
217                let mut shadow_path = Path::new();
218
219                let outset: Option<Point> = match shadow.position {
220                    ShadowPosition::Normal => Some(
221                        (
222                            shadow.spread.max(shadow.blur),
223                            shadow.spread.max(shadow.blur),
224                        )
225                            .into(),
226                    ),
227                    ShadowPosition::Inset => None, // No need to consider inset shadows for the drawing area as they will always be smaller.
228                };
229
230                if let Some(outset) = outset {
231                    // Add either the RRect or smoothed path based on whether smoothing is used.
232                    if corner_radius.smoothing > 0.0 {
233                        shadow_path.add_path(
234                            &corner_radius.smoothed_path(rounded_rect.with_outset(outset)),
235                            Point::new(area.min_x(), area.min_y()) - outset,
236                            None,
237                        );
238                    } else {
239                        shadow_path.add_rrect(rounded_rect.with_outset(outset), None);
240                    }
241                }
242
243                shadow_path.offset((shadow.x, shadow.y));
244
245                // Why 3? Because it seems to be used by skia internally
246                let good_enough_blur = shadow.blur * 3.;
247
248                let shadow_bounds = *shadow_path.bounds();
249                let shadow_rect = shadow_bounds.with_outset((good_enough_blur, good_enough_blur));
250                let shadow_area = Area::new(
251                    Point2D::new(shadow_rect.x(), shadow_rect.y()),
252                    Size2D::new(shadow_rect.width(), shadow_rect.height()),
253                );
254
255                area = area.union(&shadow_area);
256            }
257        }
258
259        for border in node_style.borders.iter() {
260            if border.is_visible() {
261                let border = border.with_scale(scale_factor);
262
263                let border_shape = border_shape(*rounded_rect.rect(), &corner_radius, &border);
264                let border_bounds = match border_shape {
265                    BorderShape::DRRect(ref outer, _) => outer.bounds(),
266                    BorderShape::Path(ref path) => path.bounds(),
267                };
268                let border_area = Area::new(
269                    Point2D::new(border_bounds.x(), border_bounds.y()),
270                    Size2D::new(border_bounds.width(), border_bounds.height()),
271                );
272
273                area = area.union(&border_area.round_out());
274            }
275        }
276
277        area
278    }
279}