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