Skip to main content

freya_core/elements/
rect.rs

1//! [rect()] acts as a generic container to contain other elements inside, like a box.
2
3use std::{
4    any::Any,
5    borrow::Cow,
6    rc::Rc,
7};
8
9use freya_engine::prelude::{
10    Canvas,
11    ClipOp,
12    Paint,
13    PaintStyle,
14    PathBuilder,
15    SkBlurStyle,
16    SkMaskFilter,
17    SkPath,
18    SkPathFillType,
19    SkPoint,
20    SkRRect,
21    SkRect,
22};
23use rustc_hash::FxHashMap;
24use torin::{
25    prelude::Area,
26    scaled::Scaled,
27};
28
29use crate::{
30    diff_key::DiffKey,
31    element::{
32        ClipContext,
33        ElementExt,
34        EventHandlerType,
35        EventMeasurementContext,
36        RenderContext,
37    },
38    events::name::EventName,
39    layers::Layer,
40    prelude::*,
41    style::{
42        font_size::FontSize,
43        scale::Scale,
44        shadow::{
45            Shadow,
46            ShadowPosition,
47        },
48        transform_origin::TransformOrigin,
49    },
50    tree::DiffModifies,
51};
52
53/// [rect()] acts as a generic container to contain other elements inside, like a box.
54///
55/// Its the equivalent of `view`/`div`/`container` in other UI models.
56///
57/// See the available methods in [Rect].
58///
59/// ```rust
60/// # use freya::prelude::*;
61/// fn app() -> impl IntoElement {
62///     rect().expanded().background((0, 255, 0))
63/// }
64/// ```
65pub fn rect() -> Rect {
66    Rect::empty()
67}
68
69#[derive(PartialEq, Clone)]
70pub struct RectElement {
71    pub style: StyleState,
72    pub layout: LayoutData,
73    pub text_style_data: TextStyleData,
74    pub relative_layer: Layer,
75    pub event_handlers: FxHashMap<EventName, EventHandlerType>,
76    pub accessibility: AccessibilityData,
77    pub effect: Option<EffectData>,
78}
79
80impl Default for RectElement {
81    fn default() -> Self {
82        let mut accessibility = AccessibilityData::default();
83        accessibility
84            .builder
85            .set_role(accesskit::Role::GenericContainer);
86        Self {
87            style: Default::default(),
88            layout: Default::default(),
89            text_style_data: Default::default(),
90            relative_layer: Default::default(),
91            event_handlers: Default::default(),
92            accessibility,
93            effect: Default::default(),
94        }
95    }
96}
97
98impl RectElement {
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 = PathBuilder::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                None,
141            );
142        } else {
143            shadow_path.add_rrect(rounded_rect.with_outset(outset), None, 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        let shadow_path = shadow_path.detach();
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            // Calculate 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            // Calculate 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 = PathBuilder::new();
316            path.set_fill_type(SkPathFillType::EvenOdd);
317
318            path.add_path(&outer_corner_radius.smoothed_path(outer_rrect), None);
319
320            path.add_path(&inner_corner_radius.smoothed_path(inner_rrect), None);
321
322            let path = path.detach();
323            BorderShape::Path(path)
324        } else {
325            BorderShape::DRRect(outer_rrect, inner_rrect)
326        }
327    }
328
329    fn outer_border_path_corner_radius(
330        alignment: BorderAlignment,
331        corner_radius: f32,
332        width_1: f32,
333        width_2: f32,
334    ) -> f32 {
335        if alignment == BorderAlignment::Inner || corner_radius == 0.0 {
336            return corner_radius;
337        }
338
339        let mut offset = if width_1 == 0.0 {
340            width_2
341        } else if width_2 == 0.0 {
342            width_1
343        } else {
344            width_1.min(width_2)
345        };
346
347        if alignment == BorderAlignment::Center {
348            offset *= 0.5;
349        }
350
351        corner_radius + offset
352    }
353
354    fn inner_border_path_corner_radius(
355        alignment: BorderAlignment,
356        corner_radius: f32,
357        width_1: f32,
358        width_2: f32,
359    ) -> f32 {
360        if alignment == BorderAlignment::Outer || corner_radius == 0.0 {
361            return corner_radius;
362        }
363
364        let mut offset = if width_1 == 0.0 {
365            width_2
366        } else if width_2 == 0.0 {
367            width_1
368        } else {
369            width_1.min(width_2)
370        };
371
372        if alignment == BorderAlignment::Center {
373            offset *= 0.5;
374        }
375
376        corner_radius - offset
377    }
378}
379
380impl ElementExt for RectElement {
381    fn changed(&self, other: &Rc<dyn ElementExt>) -> bool {
382        let Some(rect) = (other.as_ref() as &dyn Any).downcast_ref::<Self>() else {
383            return false;
384        };
385
386        self != rect
387    }
388
389    fn diff(&self, other: &Rc<dyn ElementExt>) -> DiffModifies {
390        let Some(rect) = (other.as_ref() as &dyn Any).downcast_ref::<Self>() else {
391            return DiffModifies::all();
392        };
393
394        let mut diff = DiffModifies::empty();
395
396        if self.style != rect.style {
397            diff.insert(DiffModifies::STYLE);
398        }
399
400        if self.effect != rect.effect {
401            diff.insert(DiffModifies::EFFECT);
402        }
403
404        if !self.layout.self_layout_eq(&rect.layout.layout) {
405            diff.insert(DiffModifies::STYLE);
406            diff.insert(DiffModifies::LAYOUT);
407        }
408
409        if !self.layout.inner_layout_eq(&rect.layout.layout) {
410            diff.insert(DiffModifies::STYLE);
411            diff.insert(DiffModifies::INNER_LAYOUT);
412        }
413
414        if self.accessibility != rect.accessibility {
415            diff.insert(DiffModifies::ACCESSIBILITY);
416        }
417
418        if self.relative_layer != rect.relative_layer {
419            diff.insert(DiffModifies::LAYER);
420        }
421
422        if self.event_handlers != rect.event_handlers {
423            diff.insert(DiffModifies::EVENT_HANDLERS);
424        }
425
426        if self.text_style_data != rect.text_style_data {
427            diff.insert(DiffModifies::TEXT_STYLE);
428        }
429
430        diff
431    }
432
433    fn layout(&'_ self) -> Cow<'_, LayoutData> {
434        Cow::Borrowed(&self.layout)
435    }
436
437    fn effect(&'_ self) -> Option<Cow<'_, EffectData>> {
438        self.effect.as_ref().map(Cow::Borrowed)
439    }
440
441    fn style(&'_ self) -> Cow<'_, StyleState> {
442        Cow::Borrowed(&self.style)
443    }
444
445    fn text_style(&'_ self) -> Cow<'_, TextStyleData> {
446        Cow::Borrowed(&self.text_style_data)
447    }
448
449    fn accessibility(&'_ self) -> Cow<'_, AccessibilityData> {
450        Cow::Borrowed(&self.accessibility)
451    }
452
453    fn layer(&self) -> Layer {
454        self.relative_layer
455    }
456
457    fn events_handlers(&'_ self) -> Option<Cow<'_, FxHashMap<EventName, EventHandlerType>>> {
458        Some(Cow::Borrowed(&self.event_handlers))
459    }
460
461    /// Checks if the cursor point is inside the rounded rectangle of this element,
462    /// using local coordinates relative to the element's visible area for improved precision with large absolute coordinates.
463    fn is_point_inside(&self, context: EventMeasurementContext) -> bool {
464        let area = context.layout_node.visible_area();
465        let cursor = context.cursor.to_f32();
466        let local_area = Area::new((0., 0.).into(), area.size);
467        let rounded_rect = self.render_rect(&local_area, context.scale_factor as f32);
468        let local_x = cursor.x - area.min_x();
469        let local_y = cursor.y - area.min_y();
470        rounded_rect.contains(SkRect::new(
471            local_x,
472            local_y,
473            local_x.next_up(),
474            local_y.next_up(),
475        ))
476    }
477
478    fn clip(&self, context: ClipContext) {
479        let area = context.visible_area;
480
481        let rounded_rect = self.render_rect(area, context.scale_factor as f32);
482
483        context
484            .canvas
485            .clip_rrect(rounded_rect, ClipOp::Intersect, true);
486    }
487
488    fn render(&self, context: RenderContext) {
489        let style = self.style();
490
491        let area = context.layout_node.visible_area();
492        let corner_radius = style.corner_radius.with_scale(context.scale_factor as f32);
493
494        let mut path = PathBuilder::new();
495        let mut paint = Paint::default();
496        paint.set_anti_alias(true);
497        paint.set_style(PaintStyle::Fill);
498        style.background.apply_to_paint(&mut paint, area);
499
500        // Container
501        let rounded_rect = self.render_rect(&area, context.scale_factor as f32);
502        if corner_radius.smoothing > 0.0 {
503            path.add_path(&corner_radius.smoothed_path(rounded_rect), None);
504        } else {
505            path.add_rrect(rounded_rect, None, None);
506        }
507
508        let mut path = path.detach();
509        context.canvas.draw_path(&path, &paint);
510
511        // Shadows
512        for shadow in style.shadows.iter() {
513            if shadow.color != Color::TRANSPARENT {
514                let shadow = shadow.with_scale(context.scale_factor as f32);
515
516                Self::render_shadow(
517                    context.canvas,
518                    &mut path,
519                    rounded_rect,
520                    area,
521                    &shadow,
522                    &corner_radius,
523                );
524            }
525        }
526
527        // Borders
528        for border in style.borders.iter() {
529            if border.is_visible() {
530                let border = border.with_scale(context.scale_factor as f32);
531                let rect = *rounded_rect.rect();
532                Self::render_border(context.canvas, rect, &border, &corner_radius);
533            }
534        }
535    }
536}
537
538pub struct Rect {
539    element: RectElement,
540    elements: Vec<Element>,
541    key: DiffKey,
542}
543
544impl ChildrenExt for Rect {
545    fn get_children(&mut self) -> &mut Vec<Element> {
546        &mut self.elements
547    }
548}
549
550impl KeyExt for Rect {
551    fn write_key(&mut self) -> &mut DiffKey {
552        &mut self.key
553    }
554}
555
556impl EventHandlersExt for Rect {
557    fn get_event_handlers(&mut self) -> &mut FxHashMap<EventName, EventHandlerType> {
558        &mut self.element.event_handlers
559    }
560}
561
562impl AccessibilityExt for Rect {
563    fn get_accessibility_data(&mut self) -> &mut AccessibilityData {
564        &mut self.element.accessibility
565    }
566}
567
568impl TextStyleExt for Rect {
569    fn get_text_style_data(&mut self) -> &mut TextStyleData {
570        &mut self.element.text_style_data
571    }
572}
573
574impl StyleExt for Rect {
575    fn get_style(&mut self) -> &mut StyleState {
576        &mut self.element.style
577    }
578}
579
580impl MaybeExt for Rect {}
581
582impl LayerExt for Rect {
583    fn get_layer(&mut self) -> &mut Layer {
584        &mut self.element.relative_layer
585    }
586}
587
588impl LayoutExt for Rect {
589    fn get_layout(&mut self) -> &mut LayoutData {
590        &mut self.element.layout
591    }
592}
593
594impl ContainerExt for Rect {}
595
596impl ContainerWithContentExt for Rect {}
597
598impl ScrollableExt for Rect {
599    fn get_effect(&mut self) -> &mut EffectData {
600        if self.element.effect.is_none() {
601            self.element.effect = Some(EffectData::default())
602        }
603
604        self.element.effect.as_mut().unwrap()
605    }
606}
607
608impl InteractiveExt for Rect {
609    fn get_effect(&mut self) -> &mut EffectData {
610        if self.element.effect.is_none() {
611            self.element.effect = Some(EffectData::default())
612        }
613
614        self.element.effect.as_mut().unwrap()
615    }
616}
617
618impl EffectExt for Rect {
619    fn get_effect(&mut self) -> &mut EffectData {
620        if self.element.effect.is_none() {
621            self.element.effect = Some(EffectData::default())
622        }
623
624        self.element.effect.as_mut().unwrap()
625    }
626}
627
628impl From<Rect> for Element {
629    fn from(value: Rect) -> Self {
630        Element::Element {
631            key: value.key,
632            element: Rc::new(value.element),
633            elements: value.elements,
634        }
635    }
636}
637
638impl Rect {
639    pub fn empty() -> Self {
640        Self {
641            element: RectElement::default(),
642            elements: Vec::default(),
643            key: DiffKey::None,
644        }
645    }
646
647    pub fn try_downcast(element: &dyn ElementExt) -> Option<RectElement> {
648        (element as &dyn Any).downcast_ref::<RectElement>().cloned()
649    }
650
651    pub fn color(mut self, color: impl Into<Color>) -> Self {
652        self.element.text_style_data.color = Some(Fill::Color(color.into()));
653        self
654    }
655
656    pub fn font_size(mut self, font_size: impl Into<FontSize>) -> Self {
657        self.element.text_style_data.font_size = Some(font_size.into());
658        self
659    }
660
661    pub fn overflow<S: Into<Overflow>>(mut self, overflow: S) -> Self {
662        self.element
663            .effect
664            .get_or_insert_with(Default::default)
665            .overflow = overflow.into();
666        self
667    }
668
669    pub fn rotate<R: Into<Option<f32>>>(mut self, rotation: R) -> Self {
670        self.element
671            .effect
672            .get_or_insert_with(Default::default)
673            .rotation = rotation.into();
674        self
675    }
676
677    pub fn scale(mut self, scale: impl Into<Scale>) -> Self {
678        self.element
679            .effect
680            .get_or_insert_with(Default::default)
681            .scale = Some(scale.into());
682        self
683    }
684
685    /// Set the point that the scale and rotation effects pivot around.
686    ///
687    /// Defaults to the element's center.
688    pub fn transform_origin(mut self, transform_origin: impl Into<TransformOrigin>) -> Self {
689        self.element
690            .effect
691            .get_or_insert_with(Default::default)
692            .transform_origin = transform_origin.into();
693        self
694    }
695
696    pub fn opacity(mut self, opacity: impl Into<f32>) -> Self {
697        self.element
698            .effect
699            .get_or_insert_with(Default::default)
700            .opacity = Some(opacity.into());
701        self
702    }
703
704    pub fn blur(mut self, blur: impl Into<f32>) -> Self {
705        self.element
706            .effect
707            .get_or_insert_with(Default::default)
708            .blur = Some(blur.into());
709        self
710    }
711}