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