freya_core/elements/
rect.rs

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