freya_core/elements/
rect.rs

1//! [rect()] acts as a generic container to contain other elements inside, like a box.
2
3use std::{
4    any::Any,
5    borrow::Cow,
6    rc::Rc,
7};
8
9use freya_engine::prelude::{
10    Canvas,
11    ClipOp,
12    Paint,
13    PaintStyle,
14    PathBuilder,
15    SkBlurStyle,
16    SkMaskFilter,
17    SkPath,
18    SkPathFillType,
19    SkPoint,
20    SkRRect,
21    SkRect,
22};
23use rustc_hash::FxHashMap;
24use torin::{
25    prelude::Area,
26    scaled::Scaled,
27};
28
29use crate::{
30    diff_key::DiffKey,
31    element::{
32        ClipContext,
33        ElementExt,
34        EventHandlerType,
35        EventMeasurementContext,
36        RenderContext,
37    },
38    events::name::EventName,
39    layers::Layer,
40    prelude::*,
41    style::{
42        font_size::FontSize,
43        scale::Scale,
44        shadow::{
45            Shadow,
46            ShadowPosition,
47        },
48    },
49    tree::DiffModifies,
50};
51
52/// [rect()] acts as a generic container to contain other elements inside, like a box.
53///
54/// Its the equivalent of `view`/`div`/`container` in other UI models.
55///
56/// See the available methods in [Rect].
57///
58/// ```rust
59/// # use freya::prelude::*;
60/// fn app() -> impl IntoElement {
61///     rect().expanded().background((0, 255, 0))
62/// }
63/// ```
64pub fn rect() -> Rect {
65    Rect::empty()
66}
67
68#[derive(PartialEq, Clone)]
69pub struct RectElement {
70    pub style: StyleState,
71    pub layout: LayoutData,
72    pub text_style_data: TextStyleData,
73    pub relative_layer: Layer,
74    pub event_handlers: FxHashMap<EventName, EventHandlerType>,
75    pub accessibility: AccessibilityData,
76    pub effect: Option<EffectData>,
77}
78
79impl Default for RectElement {
80    fn default() -> Self {
81        let mut accessibility = AccessibilityData::default();
82        accessibility
83            .builder
84            .set_role(accesskit::Role::GenericContainer);
85        Self {
86            style: Default::default(),
87            layout: Default::default(),
88            text_style_data: Default::default(),
89            relative_layer: Default::default(),
90            event_handlers: Default::default(),
91            accessibility,
92            effect: Default::default(),
93        }
94    }
95}
96
97impl RectElement {
98    pub fn render_shadow(
99        canvas: &Canvas,
100        path: &mut SkPath,
101        rounded_rect: SkRRect,
102        _area: Area,
103        shadow: &Shadow,
104        corner_radius: &CornerRadius,
105    ) {
106        let mut shadow_path = PathBuilder::new();
107        let mut shadow_paint = Paint::default();
108        shadow_paint.set_anti_alias(true);
109        shadow_paint.set_color(shadow.color);
110
111        // Shadows can be either outset or inset
112        // If they are outset, we fill a copy of the path outset by spread_radius, and blur it.
113        // Otherwise, we draw a stroke with the inner portion being spread_radius width, and the outer portion being blur_radius width.
114        let outset: SkPoint = match shadow.position {
115            ShadowPosition::Normal => {
116                shadow_paint.set_style(PaintStyle::Fill);
117                (shadow.spread, shadow.spread).into()
118            }
119            ShadowPosition::Inset => {
120                shadow_paint.set_style(PaintStyle::Stroke);
121                shadow_paint.set_stroke_width(shadow.blur / 2.0 + shadow.spread);
122                (-shadow.spread / 2.0, -shadow.spread / 2.0).into()
123            }
124        };
125
126        // Apply gassuan blur to the copied path.
127        if shadow.blur > 0.0 {
128            shadow_paint.set_mask_filter(SkMaskFilter::blur(
129                SkBlurStyle::Normal,
130                shadow.blur / 2.0,
131                false,
132            ));
133        }
134
135        // Add either the RRect or smoothed path based on whether smoothing is used.
136        if corner_radius.smoothing > 0.0 {
137            shadow_path.add_path(&corner_radius.smoothed_path(rounded_rect.with_outset(outset)));
138        } else {
139            shadow_path.add_rrect(rounded_rect.with_outset(outset), None, None);
140        }
141
142        // Offset our path by the shadow's x and y coordinates.
143        shadow_path.offset((shadow.x, shadow.y));
144
145        // Exclude the original path bounds from the shadow using a clip, then draw the shadow.
146        canvas.save();
147        canvas.clip_path(
148            path,
149            match shadow.position {
150                ShadowPosition::Normal => ClipOp::Difference,
151                ShadowPosition::Inset => ClipOp::Intersect,
152            },
153            true,
154        );
155        let shadow_path = shadow_path.detach();
156        canvas.draw_path(&shadow_path, &shadow_paint);
157        canvas.restore();
158    }
159
160    pub fn render_border(
161        canvas: &Canvas,
162        rect: SkRect,
163        border: &Border,
164        corner_radius: &CornerRadius,
165    ) {
166        let mut border_paint = Paint::default();
167        border_paint.set_style(PaintStyle::Fill);
168        border_paint.set_anti_alias(true);
169        border_paint.set_color(border.fill);
170
171        match Self::border_shape(rect, corner_radius, border) {
172            BorderShape::DRRect(outer, inner) => {
173                canvas.draw_drrect(outer, inner, &border_paint);
174            }
175            BorderShape::Path(path) => {
176                canvas.draw_path(&path, &border_paint);
177            }
178        }
179    }
180
181    /// Returns a `Path` that will draw a [`Border`] around a base rectangle.
182    ///
183    /// We don't use Skia's stroking API here, since we might need different widths for each side.
184    pub fn border_shape(
185        base_rect: SkRect,
186        base_corner_radius: &CornerRadius,
187        border: &Border,
188    ) -> BorderShape {
189        let border_alignment = border.alignment;
190        let border_width = border.width;
191
192        // First we create a path that is outset from the rect by a certain amount on each side.
193        //
194        // Let's call this the outer border path.
195        let (outer_rrect, outer_corner_radius) = {
196            // Calculate the outer corner radius for the border.
197            let corner_radius = CornerRadius {
198                top_left: Self::outer_border_path_corner_radius(
199                    border_alignment,
200                    base_corner_radius.top_left,
201                    border_width.top,
202                    border_width.left,
203                ),
204                top_right: Self::outer_border_path_corner_radius(
205                    border_alignment,
206                    base_corner_radius.top_right,
207                    border_width.top,
208                    border_width.right,
209                ),
210                bottom_left: Self::outer_border_path_corner_radius(
211                    border_alignment,
212                    base_corner_radius.bottom_left,
213                    border_width.bottom,
214                    border_width.left,
215                ),
216                bottom_right: Self::outer_border_path_corner_radius(
217                    border_alignment,
218                    base_corner_radius.bottom_right,
219                    border_width.bottom,
220                    border_width.right,
221                ),
222                smoothing: base_corner_radius.smoothing,
223            };
224
225            let rrect = SkRRect::new_rect_radii(
226                {
227                    let mut rect = base_rect;
228                    let alignment_scale = match border_alignment {
229                        BorderAlignment::Outer => 1.0,
230                        BorderAlignment::Center => 0.5,
231                        BorderAlignment::Inner => 0.0,
232                    };
233
234                    rect.left -= border_width.left * alignment_scale;
235                    rect.top -= border_width.top * alignment_scale;
236                    rect.right += border_width.right * alignment_scale;
237                    rect.bottom += border_width.bottom * alignment_scale;
238
239                    rect
240                },
241                &[
242                    (corner_radius.top_left, corner_radius.top_left).into(),
243                    (corner_radius.top_right, corner_radius.top_right).into(),
244                    (corner_radius.bottom_right, corner_radius.bottom_right).into(),
245                    (corner_radius.bottom_left, corner_radius.bottom_left).into(),
246                ],
247            );
248
249            (rrect, corner_radius)
250        };
251
252        // After the outer path, we will then move to the inner bounds of the border.
253        let (inner_rrect, inner_corner_radius) = {
254            // Calculate the inner corner radius for the border.
255            let corner_radius = CornerRadius {
256                top_left: Self::inner_border_path_corner_radius(
257                    border_alignment,
258                    base_corner_radius.top_left,
259                    border_width.top,
260                    border_width.left,
261                ),
262                top_right: Self::inner_border_path_corner_radius(
263                    border_alignment,
264                    base_corner_radius.top_right,
265                    border_width.top,
266                    border_width.right,
267                ),
268                bottom_left: Self::inner_border_path_corner_radius(
269                    border_alignment,
270                    base_corner_radius.bottom_left,
271                    border_width.bottom,
272                    border_width.left,
273                ),
274                bottom_right: Self::inner_border_path_corner_radius(
275                    border_alignment,
276                    base_corner_radius.bottom_right,
277                    border_width.bottom,
278                    border_width.right,
279                ),
280                smoothing: base_corner_radius.smoothing,
281            };
282
283            let rrect = SkRRect::new_rect_radii(
284                {
285                    let mut rect = base_rect;
286                    let alignment_scale = match border_alignment {
287                        BorderAlignment::Outer => 0.0,
288                        BorderAlignment::Center => 0.5,
289                        BorderAlignment::Inner => 1.0,
290                    };
291
292                    rect.left += border_width.left * alignment_scale;
293                    rect.top += border_width.top * alignment_scale;
294                    rect.right -= border_width.right * alignment_scale;
295                    rect.bottom -= border_width.bottom * alignment_scale;
296
297                    rect
298                },
299                &[
300                    (corner_radius.top_left, corner_radius.top_left).into(),
301                    (corner_radius.top_right, corner_radius.top_right).into(),
302                    (corner_radius.bottom_right, corner_radius.bottom_right).into(),
303                    (corner_radius.bottom_left, corner_radius.bottom_left).into(),
304                ],
305            );
306
307            (rrect, corner_radius)
308        };
309
310        if base_corner_radius.smoothing > 0.0 {
311            let mut path = PathBuilder::new();
312            path.set_fill_type(SkPathFillType::EvenOdd);
313
314            path.add_path(&outer_corner_radius.smoothed_path(outer_rrect));
315
316            path.add_path(&inner_corner_radius.smoothed_path(inner_rrect));
317
318            let path = path.detach();
319            BorderShape::Path(path)
320        } else {
321            BorderShape::DRRect(outer_rrect, inner_rrect)
322        }
323    }
324
325    fn outer_border_path_corner_radius(
326        alignment: BorderAlignment,
327        corner_radius: f32,
328        width_1: f32,
329        width_2: f32,
330    ) -> f32 {
331        if alignment == BorderAlignment::Inner || corner_radius == 0.0 {
332            return corner_radius;
333        }
334
335        let mut offset = if width_1 == 0.0 {
336            width_2
337        } else if width_2 == 0.0 {
338            width_1
339        } else {
340            width_1.min(width_2)
341        };
342
343        if alignment == BorderAlignment::Center {
344            offset *= 0.5;
345        }
346
347        corner_radius + offset
348    }
349
350    fn inner_border_path_corner_radius(
351        alignment: BorderAlignment,
352        corner_radius: f32,
353        width_1: f32,
354        width_2: f32,
355    ) -> f32 {
356        if alignment == BorderAlignment::Outer || corner_radius == 0.0 {
357            return corner_radius;
358        }
359
360        let mut offset = if width_1 == 0.0 {
361            width_2
362        } else if width_2 == 0.0 {
363            width_1
364        } else {
365            width_1.min(width_2)
366        };
367
368        if alignment == BorderAlignment::Center {
369            offset *= 0.5;
370        }
371
372        corner_radius - offset
373    }
374}
375
376impl ElementExt for RectElement {
377    fn changed(&self, other: &Rc<dyn ElementExt>) -> bool {
378        let Some(rect) = (other.as_ref() as &dyn Any).downcast_ref::<Self>() else {
379            return false;
380        };
381
382        self != rect
383    }
384
385    fn diff(&self, other: &Rc<dyn ElementExt>) -> DiffModifies {
386        let Some(rect) = (other.as_ref() as &dyn Any).downcast_ref::<Self>() else {
387            return DiffModifies::all();
388        };
389
390        let mut diff = DiffModifies::empty();
391
392        if self.style != rect.style {
393            diff.insert(DiffModifies::STYLE);
394        }
395
396        if self.effect != rect.effect {
397            diff.insert(DiffModifies::EFFECT);
398        }
399
400        if !self.layout.self_layout_eq(&rect.layout.layout) {
401            diff.insert(DiffModifies::STYLE);
402            diff.insert(DiffModifies::LAYOUT);
403        }
404
405        if !self.layout.inner_layout_eq(&rect.layout.layout) {
406            diff.insert(DiffModifies::STYLE);
407            diff.insert(DiffModifies::INNER_LAYOUT);
408        }
409
410        if self.accessibility != rect.accessibility {
411            diff.insert(DiffModifies::ACCESSIBILITY);
412        }
413
414        if self.relative_layer != rect.relative_layer {
415            diff.insert(DiffModifies::LAYER);
416        }
417
418        if self.event_handlers != rect.event_handlers {
419            diff.insert(DiffModifies::EVENT_HANDLERS);
420        }
421
422        if self.text_style_data != rect.text_style_data {
423            diff.insert(DiffModifies::TEXT_STYLE);
424        }
425
426        diff
427    }
428
429    fn layout(&'_ self) -> Cow<'_, LayoutData> {
430        Cow::Borrowed(&self.layout)
431    }
432
433    fn effect(&'_ self) -> Option<Cow<'_, EffectData>> {
434        self.effect.as_ref().map(Cow::Borrowed)
435    }
436
437    fn style(&'_ self) -> Cow<'_, StyleState> {
438        Cow::Borrowed(&self.style)
439    }
440
441    fn text_style(&'_ self) -> Cow<'_, TextStyleData> {
442        Cow::Borrowed(&self.text_style_data)
443    }
444
445    fn accessibility(&'_ self) -> Cow<'_, AccessibilityData> {
446        Cow::Borrowed(&self.accessibility)
447    }
448
449    fn layer(&self) -> Layer {
450        self.relative_layer
451    }
452
453    fn events_handlers(&'_ self) -> Option<Cow<'_, FxHashMap<EventName, EventHandlerType>>> {
454        Some(Cow::Borrowed(&self.event_handlers))
455    }
456
457    fn is_point_inside(&self, context: EventMeasurementContext) -> bool {
458        let area = context.layout_node.visible_area();
459        let cursor = context.cursor.to_f32();
460        let rounded_rect = self.render_rect(&area, context.scale_factor as f32);
461        rounded_rect.contains(SkRect::new(
462            cursor.x,
463            cursor.y,
464            cursor.x + 0.0001,
465            cursor.y + 0.0001,
466        ))
467    }
468
469    fn clip(&self, context: ClipContext) {
470        let area = context.visible_area;
471
472        let rounded_rect = self.render_rect(area, context.scale_factor as f32);
473
474        context
475            .canvas
476            .clip_rrect(rounded_rect, ClipOp::Intersect, true);
477    }
478
479    fn render(&self, context: RenderContext) {
480        let style = self.style();
481
482        let area = context.layout_node.visible_area();
483        let corner_radius = style.corner_radius.with_scale(context.scale_factor as f32);
484
485        let mut path = PathBuilder::new();
486        let mut paint = Paint::default();
487        paint.set_anti_alias(true);
488        paint.set_style(PaintStyle::Fill);
489        style.background.apply_to_paint(&mut paint, area);
490
491        // Container
492        let rounded_rect = self.render_rect(&area, context.scale_factor as f32);
493        if corner_radius.smoothing > 0.0 {
494            path.add_path(&corner_radius.smoothed_path(rounded_rect));
495        } else {
496            path.add_rrect(rounded_rect, None, None);
497        }
498
499        let mut path = path.detach();
500        context.canvas.draw_path(&path, &paint);
501
502        // Shadows
503        for shadow in style.shadows.iter() {
504            if shadow.color != Color::TRANSPARENT {
505                let shadow = shadow.with_scale(context.scale_factor as f32);
506
507                Self::render_shadow(
508                    context.canvas,
509                    &mut path,
510                    rounded_rect,
511                    area,
512                    &shadow,
513                    &corner_radius,
514                );
515            }
516        }
517
518        // Borders
519        for border in style.borders.iter() {
520            if border.is_visible() {
521                let border = border.with_scale(context.scale_factor as f32);
522                let rect = *rounded_rect.rect();
523                Self::render_border(context.canvas, rect, &border, &corner_radius);
524            }
525        }
526    }
527}
528
529pub struct Rect {
530    element: RectElement,
531    elements: Vec<Element>,
532    key: DiffKey,
533}
534
535impl ChildrenExt for Rect {
536    fn get_children(&mut self) -> &mut Vec<Element> {
537        &mut self.elements
538    }
539}
540
541impl KeyExt for Rect {
542    fn write_key(&mut self) -> &mut DiffKey {
543        &mut self.key
544    }
545}
546
547impl EventHandlersExt for Rect {
548    fn get_event_handlers(&mut self) -> &mut FxHashMap<EventName, EventHandlerType> {
549        &mut self.element.event_handlers
550    }
551}
552
553impl AccessibilityExt for Rect {
554    fn get_accessibility_data(&mut self) -> &mut AccessibilityData {
555        &mut self.element.accessibility
556    }
557}
558
559impl TextStyleExt for Rect {
560    fn get_text_style_data(&mut self) -> &mut TextStyleData {
561        &mut self.element.text_style_data
562    }
563}
564
565impl StyleExt for Rect {
566    fn get_style(&mut self) -> &mut StyleState {
567        &mut self.element.style
568    }
569}
570
571impl MaybeExt for Rect {}
572
573impl LayerExt for Rect {
574    fn get_layer(&mut self) -> &mut Layer {
575        &mut self.element.relative_layer
576    }
577}
578
579impl LayoutExt for Rect {
580    fn get_layout(&mut self) -> &mut LayoutData {
581        &mut self.element.layout
582    }
583}
584
585impl ContainerExt for Rect {}
586
587impl ContainerWithContentExt for Rect {}
588
589impl ScrollableExt for Rect {
590    fn get_effect(&mut self) -> &mut EffectData {
591        if self.element.effect.is_none() {
592            self.element.effect = Some(EffectData::default())
593        }
594
595        self.element.effect.as_mut().unwrap()
596    }
597}
598
599impl InteractiveExt for Rect {
600    fn get_effect(&mut self) -> &mut EffectData {
601        if self.element.effect.is_none() {
602            self.element.effect = Some(EffectData::default())
603        }
604
605        self.element.effect.as_mut().unwrap()
606    }
607}
608
609impl From<Rect> for Element {
610    fn from(value: Rect) -> Self {
611        Element::Element {
612            key: value.key,
613            element: Rc::new(value.element),
614            elements: value.elements,
615        }
616    }
617}
618
619impl Rect {
620    pub fn empty() -> Self {
621        Self {
622            element: RectElement::default(),
623            elements: Vec::default(),
624            key: DiffKey::None,
625        }
626    }
627
628    pub fn try_downcast(element: &dyn ElementExt) -> Option<RectElement> {
629        (element as &dyn Any).downcast_ref::<RectElement>().cloned()
630    }
631
632    pub fn color(mut self, color: impl Into<Color>) -> Self {
633        self.element.text_style_data.color = Some(color.into());
634        self
635    }
636
637    pub fn font_size(mut self, font_size: impl Into<FontSize>) -> Self {
638        self.element.text_style_data.font_size = Some(font_size.into());
639        self
640    }
641
642    pub fn overflow<S: Into<Overflow>>(mut self, overflow: S) -> Self {
643        self.element
644            .effect
645            .get_or_insert_with(Default::default)
646            .overflow = overflow.into();
647        self
648    }
649
650    pub fn rotate<R: Into<Option<f32>>>(mut self, rotation: R) -> Self {
651        self.element
652            .effect
653            .get_or_insert_with(Default::default)
654            .rotation = rotation.into();
655        self
656    }
657
658    pub fn scale(mut self, scale: impl Into<Scale>) -> Self {
659        self.element
660            .effect
661            .get_or_insert_with(Default::default)
662            .scale = Some(scale.into());
663        self
664    }
665
666    pub fn opacity(mut self, opacity: impl Into<f32>) -> Self {
667        self.element
668            .effect
669            .get_or_insert_with(Default::default)
670            .opacity = Some(opacity.into());
671        self
672    }
673
674    pub fn blur(mut self, blur: impl Into<f32>) -> Self {
675        self.element
676            .effect
677            .get_or_insert_with(Default::default)
678            .blur = Some(blur.into());
679        self
680    }
681}