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 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 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 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 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 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, };
229
230 if let Some(outset) = outset {
231 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 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}