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