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