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