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    /// Checks if the cursor point is inside the rounded rectangle of this element,
458    /// using local coordinates relative to the element's visible area for improved precision with large absolute coordinates.
459    fn is_point_inside(&self, context: EventMeasurementContext) -> bool {
460        let area = context.layout_node.visible_area();
461        let cursor = context.cursor.to_f32();
462        let local_area = Area::new((0., 0.).into(), area.size);
463        let rounded_rect = self.render_rect(&local_area, context.scale_factor as f32);
464        let local_x = cursor.x - area.min_x();
465        let local_y = cursor.y - area.min_y();
466        rounded_rect.contains(SkRect::new(
467            local_x,
468            local_y,
469            local_x.next_up(),
470            local_y.next_up(),
471        ))
472    }
473
474    fn clip(&self, context: ClipContext) {
475        let area = context.visible_area;
476
477        let rounded_rect = self.render_rect(area, context.scale_factor as f32);
478
479        context
480            .canvas
481            .clip_rrect(rounded_rect, ClipOp::Intersect, true);
482    }
483
484    fn render(&self, context: RenderContext) {
485        let style = self.style();
486
487        let area = context.layout_node.visible_area();
488        let corner_radius = style.corner_radius.with_scale(context.scale_factor as f32);
489
490        let mut path = PathBuilder::new();
491        let mut paint = Paint::default();
492        paint.set_anti_alias(true);
493        paint.set_style(PaintStyle::Fill);
494        style.background.apply_to_paint(&mut paint, area);
495
496        // Container
497        let rounded_rect = self.render_rect(&area, context.scale_factor as f32);
498        if corner_radius.smoothing > 0.0 {
499            path.add_path(&corner_radius.smoothed_path(rounded_rect));
500        } else {
501            path.add_rrect(rounded_rect, None, None);
502        }
503
504        let mut path = path.detach();
505        context.canvas.draw_path(&path, &paint);
506
507        // Shadows
508        for shadow in style.shadows.iter() {
509            if shadow.color != Color::TRANSPARENT {
510                let shadow = shadow.with_scale(context.scale_factor as f32);
511
512                Self::render_shadow(
513                    context.canvas,
514                    &mut path,
515                    rounded_rect,
516                    area,
517                    &shadow,
518                    &corner_radius,
519                );
520            }
521        }
522
523        // Borders
524        for border in style.borders.iter() {
525            if border.is_visible() {
526                let border = border.with_scale(context.scale_factor as f32);
527                let rect = *rounded_rect.rect();
528                Self::render_border(context.canvas, rect, &border, &corner_radius);
529            }
530        }
531    }
532}
533
534pub struct Rect {
535    element: RectElement,
536    elements: Vec<Element>,
537    key: DiffKey,
538}
539
540impl ChildrenExt for Rect {
541    fn get_children(&mut self) -> &mut Vec<Element> {
542        &mut self.elements
543    }
544}
545
546impl KeyExt for Rect {
547    fn write_key(&mut self) -> &mut DiffKey {
548        &mut self.key
549    }
550}
551
552impl EventHandlersExt for Rect {
553    fn get_event_handlers(&mut self) -> &mut FxHashMap<EventName, EventHandlerType> {
554        &mut self.element.event_handlers
555    }
556}
557
558impl AccessibilityExt for Rect {
559    fn get_accessibility_data(&mut self) -> &mut AccessibilityData {
560        &mut self.element.accessibility
561    }
562}
563
564impl TextStyleExt for Rect {
565    fn get_text_style_data(&mut self) -> &mut TextStyleData {
566        &mut self.element.text_style_data
567    }
568}
569
570impl StyleExt for Rect {
571    fn get_style(&mut self) -> &mut StyleState {
572        &mut self.element.style
573    }
574}
575
576impl MaybeExt for Rect {}
577
578impl LayerExt for Rect {
579    fn get_layer(&mut self) -> &mut Layer {
580        &mut self.element.relative_layer
581    }
582}
583
584impl LayoutExt for Rect {
585    fn get_layout(&mut self) -> &mut LayoutData {
586        &mut self.element.layout
587    }
588}
589
590impl ContainerExt for Rect {}
591
592impl ContainerWithContentExt for Rect {}
593
594impl ScrollableExt for Rect {
595    fn get_effect(&mut self) -> &mut EffectData {
596        if self.element.effect.is_none() {
597            self.element.effect = Some(EffectData::default())
598        }
599
600        self.element.effect.as_mut().unwrap()
601    }
602}
603
604impl InteractiveExt for Rect {
605    fn get_effect(&mut self) -> &mut EffectData {
606        if self.element.effect.is_none() {
607            self.element.effect = Some(EffectData::default())
608        }
609
610        self.element.effect.as_mut().unwrap()
611    }
612}
613
614impl EffectExt for Rect {
615    fn get_effect(&mut self) -> &mut EffectData {
616        if self.element.effect.is_none() {
617            self.element.effect = Some(EffectData::default())
618        }
619
620        self.element.effect.as_mut().unwrap()
621    }
622}
623
624impl From<Rect> for Element {
625    fn from(value: Rect) -> Self {
626        Element::Element {
627            key: value.key,
628            element: Rc::new(value.element),
629            elements: value.elements,
630        }
631    }
632}
633
634impl Rect {
635    pub fn empty() -> Self {
636        Self {
637            element: RectElement::default(),
638            elements: Vec::default(),
639            key: DiffKey::None,
640        }
641    }
642
643    pub fn try_downcast(element: &dyn ElementExt) -> Option<RectElement> {
644        (element as &dyn Any).downcast_ref::<RectElement>().cloned()
645    }
646
647    pub fn color(mut self, color: impl Into<Color>) -> Self {
648        self.element.text_style_data.color = Some(color.into());
649        self
650    }
651
652    pub fn font_size(mut self, font_size: impl Into<FontSize>) -> Self {
653        self.element.text_style_data.font_size = Some(font_size.into());
654        self
655    }
656
657    pub fn overflow<S: Into<Overflow>>(mut self, overflow: S) -> Self {
658        self.element
659            .effect
660            .get_or_insert_with(Default::default)
661            .overflow = overflow.into();
662        self
663    }
664
665    pub fn rotate<R: Into<Option<f32>>>(mut self, rotation: R) -> Self {
666        self.element
667            .effect
668            .get_or_insert_with(Default::default)
669            .rotation = rotation.into();
670        self
671    }
672
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    pub fn opacity(mut self, opacity: impl Into<f32>) -> Self {
682        self.element
683            .effect
684            .get_or_insert_with(Default::default)
685            .opacity = Some(opacity.into());
686        self
687    }
688
689    pub fn blur(mut self, blur: impl Into<f32>) -> Self {
690        self.element
691            .effect
692            .get_or_insert_with(Default::default)
693            .blur = Some(blur.into());
694        self
695    }
696}