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