Skip to main content

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