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    },
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 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 = PathBuilder::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                None,
140            );
141        } else {
142            shadow_path.add_rrect(rounded_rect.with_outset(outset), None, None);
143        }
144
145        // Offset our path by the shadow's x and y coordinates.
146        shadow_path.offset((shadow.x, shadow.y));
147
148        // Exclude the original path bounds from the shadow using a clip, then draw the shadow.
149        canvas.save();
150        canvas.clip_path(
151            path,
152            match shadow.position {
153                ShadowPosition::Normal => ClipOp::Difference,
154                ShadowPosition::Inset => ClipOp::Intersect,
155            },
156            true,
157        );
158        let shadow_path = shadow_path.detach();
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            // Calculate 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            // Calculate 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 = PathBuilder::new();
315            path.set_fill_type(SkPathFillType::EvenOdd);
316
317            path.add_path(&outer_corner_radius.smoothed_path(outer_rrect), None);
318
319            path.add_path(&inner_corner_radius.smoothed_path(inner_rrect), None);
320
321            let path = path.detach();
322            BorderShape::Path(path)
323        } else {
324            BorderShape::DRRect(outer_rrect, inner_rrect)
325        }
326    }
327
328    fn outer_border_path_corner_radius(
329        alignment: BorderAlignment,
330        corner_radius: f32,
331        width_1: f32,
332        width_2: f32,
333    ) -> f32 {
334        if alignment == BorderAlignment::Inner || corner_radius == 0.0 {
335            return corner_radius;
336        }
337
338        let mut offset = if width_1 == 0.0 {
339            width_2
340        } else if width_2 == 0.0 {
341            width_1
342        } else {
343            width_1.min(width_2)
344        };
345
346        if alignment == BorderAlignment::Center {
347            offset *= 0.5;
348        }
349
350        corner_radius + offset
351    }
352
353    fn inner_border_path_corner_radius(
354        alignment: BorderAlignment,
355        corner_radius: f32,
356        width_1: f32,
357        width_2: f32,
358    ) -> f32 {
359        if alignment == BorderAlignment::Outer || corner_radius == 0.0 {
360            return corner_radius;
361        }
362
363        let mut offset = if width_1 == 0.0 {
364            width_2
365        } else if width_2 == 0.0 {
366            width_1
367        } else {
368            width_1.min(width_2)
369        };
370
371        if alignment == BorderAlignment::Center {
372            offset *= 0.5;
373        }
374
375        corner_radius - offset
376    }
377}
378
379impl ElementExt for RectElement {
380    fn changed(&self, other: &Rc<dyn ElementExt>) -> bool {
381        let Some(rect) = (other.as_ref() as &dyn Any).downcast_ref::<Self>() else {
382            return false;
383        };
384
385        self != rect
386    }
387
388    fn diff(&self, other: &Rc<dyn ElementExt>) -> DiffModifies {
389        let Some(rect) = (other.as_ref() as &dyn Any).downcast_ref::<Self>() else {
390            return DiffModifies::all();
391        };
392
393        let mut diff = DiffModifies::empty();
394
395        if self.style != rect.style {
396            diff.insert(DiffModifies::STYLE);
397        }
398
399        if self.effect != rect.effect {
400            diff.insert(DiffModifies::EFFECT);
401        }
402
403        if !self.layout.self_layout_eq(&rect.layout.layout) {
404            diff.insert(DiffModifies::STYLE);
405            diff.insert(DiffModifies::LAYOUT);
406        }
407
408        if !self.layout.inner_layout_eq(&rect.layout.layout) {
409            diff.insert(DiffModifies::STYLE);
410            diff.insert(DiffModifies::INNER_LAYOUT);
411        }
412
413        if self.accessibility != rect.accessibility {
414            diff.insert(DiffModifies::ACCESSIBILITY);
415        }
416
417        if self.relative_layer != rect.relative_layer {
418            diff.insert(DiffModifies::LAYER);
419        }
420
421        if self.event_handlers != rect.event_handlers {
422            diff.insert(DiffModifies::EVENT_HANDLERS);
423        }
424
425        if self.text_style_data != rect.text_style_data {
426            diff.insert(DiffModifies::TEXT_STYLE);
427        }
428
429        diff
430    }
431
432    fn layout(&'_ self) -> Cow<'_, LayoutData> {
433        Cow::Borrowed(&self.layout)
434    }
435
436    fn effect(&'_ self) -> Option<Cow<'_, EffectData>> {
437        self.effect.as_ref().map(Cow::Borrowed)
438    }
439
440    fn style(&'_ self) -> Cow<'_, StyleState> {
441        Cow::Borrowed(&self.style)
442    }
443
444    fn text_style(&'_ self) -> Cow<'_, TextStyleData> {
445        Cow::Borrowed(&self.text_style_data)
446    }
447
448    fn accessibility(&'_ self) -> Cow<'_, AccessibilityData> {
449        Cow::Borrowed(&self.accessibility)
450    }
451
452    fn layer(&self) -> Layer {
453        self.relative_layer
454    }
455
456    fn events_handlers(&'_ self) -> Option<Cow<'_, FxHashMap<EventName, EventHandlerType>>> {
457        Some(Cow::Borrowed(&self.event_handlers))
458    }
459
460    /// Checks if the cursor point is inside the rounded rectangle of this element,
461    /// using local coordinates relative to the element's visible area for improved precision with large absolute coordinates.
462    fn is_point_inside(&self, context: EventMeasurementContext) -> bool {
463        let area = context.layout_node.visible_area();
464        let cursor = context.cursor.to_f32();
465        let local_area = Area::new((0., 0.).into(), area.size);
466        let rounded_rect = self.render_rect(&local_area, context.scale_factor as f32);
467        let local_x = cursor.x - area.min_x();
468        let local_y = cursor.y - area.min_y();
469        rounded_rect.contains(SkRect::new(
470            local_x,
471            local_y,
472            local_x.next_up(),
473            local_y.next_up(),
474        ))
475    }
476
477    fn clip(&self, context: ClipContext) {
478        let area = context.visible_area;
479
480        let rounded_rect = self.render_rect(area, context.scale_factor as f32);
481
482        context
483            .canvas
484            .clip_rrect(rounded_rect, ClipOp::Intersect, true);
485    }
486
487    fn render(&self, context: RenderContext) {
488        let style = self.style();
489
490        let area = context.layout_node.visible_area();
491        let corner_radius = style.corner_radius.with_scale(context.scale_factor as f32);
492
493        let mut path = PathBuilder::new();
494        let mut paint = Paint::default();
495        paint.set_anti_alias(true);
496        paint.set_style(PaintStyle::Fill);
497        style.background.apply_to_paint(&mut paint, area);
498
499        // Container
500        let rounded_rect = self.render_rect(&area, context.scale_factor as f32);
501        if corner_radius.smoothing > 0.0 {
502            path.add_path(&corner_radius.smoothed_path(rounded_rect), None);
503        } else {
504            path.add_rrect(rounded_rect, None, None);
505        }
506
507        let mut path = path.detach();
508        context.canvas.draw_path(&path, &paint);
509
510        // Shadows
511        for shadow in style.shadows.iter() {
512            if shadow.color != Color::TRANSPARENT {
513                let shadow = shadow.with_scale(context.scale_factor as f32);
514
515                Self::render_shadow(
516                    context.canvas,
517                    &mut path,
518                    rounded_rect,
519                    area,
520                    &shadow,
521                    &corner_radius,
522                );
523            }
524        }
525
526        // Borders
527        for border in style.borders.iter() {
528            if border.is_visible() {
529                let border = border.with_scale(context.scale_factor as f32);
530                let rect = *rounded_rect.rect();
531                Self::render_border(context.canvas, rect, &border, &corner_radius);
532            }
533        }
534    }
535}
536
537pub struct Rect {
538    element: RectElement,
539    elements: Vec<Element>,
540    key: DiffKey,
541}
542
543impl ChildrenExt for Rect {
544    fn get_children(&mut self) -> &mut Vec<Element> {
545        &mut self.elements
546    }
547}
548
549impl KeyExt for Rect {
550    fn write_key(&mut self) -> &mut DiffKey {
551        &mut self.key
552    }
553}
554
555impl EventHandlersExt for Rect {
556    fn get_event_handlers(&mut self) -> &mut FxHashMap<EventName, EventHandlerType> {
557        &mut self.element.event_handlers
558    }
559}
560
561impl AccessibilityExt for Rect {
562    fn get_accessibility_data(&mut self) -> &mut AccessibilityData {
563        &mut self.element.accessibility
564    }
565}
566
567impl TextStyleExt for Rect {
568    fn get_text_style_data(&mut self) -> &mut TextStyleData {
569        &mut self.element.text_style_data
570    }
571}
572
573impl StyleExt for Rect {
574    fn get_style(&mut self) -> &mut StyleState {
575        &mut self.element.style
576    }
577}
578
579impl MaybeExt for Rect {}
580
581impl LayerExt for Rect {
582    fn get_layer(&mut self) -> &mut Layer {
583        &mut self.element.relative_layer
584    }
585}
586
587impl LayoutExt for Rect {
588    fn get_layout(&mut self) -> &mut LayoutData {
589        &mut self.element.layout
590    }
591}
592
593impl ContainerExt for Rect {}
594
595impl ContainerWithContentExt for Rect {}
596
597impl ScrollableExt for Rect {
598    fn get_effect(&mut self) -> &mut EffectData {
599        if self.element.effect.is_none() {
600            self.element.effect = Some(EffectData::default())
601        }
602
603        self.element.effect.as_mut().unwrap()
604    }
605}
606
607impl InteractiveExt for Rect {
608    fn get_effect(&mut self) -> &mut EffectData {
609        if self.element.effect.is_none() {
610            self.element.effect = Some(EffectData::default())
611        }
612
613        self.element.effect.as_mut().unwrap()
614    }
615}
616
617impl EffectExt for Rect {
618    fn get_effect(&mut self) -> &mut EffectData {
619        if self.element.effect.is_none() {
620            self.element.effect = Some(EffectData::default())
621        }
622
623        self.element.effect.as_mut().unwrap()
624    }
625}
626
627impl From<Rect> for Element {
628    fn from(value: Rect) -> Self {
629        Element::Element {
630            key: value.key,
631            element: Rc::new(value.element),
632            elements: value.elements,
633        }
634    }
635}
636
637impl Rect {
638    pub fn empty() -> Self {
639        Self {
640            element: RectElement::default(),
641            elements: Vec::default(),
642            key: DiffKey::None,
643        }
644    }
645
646    pub fn try_downcast(element: &dyn ElementExt) -> Option<RectElement> {
647        (element as &dyn Any).downcast_ref::<RectElement>().cloned()
648    }
649
650    pub fn color(mut self, color: impl Into<Color>) -> Self {
651        self.element.text_style_data.color = Some(Fill::Color(color.into()));
652        self
653    }
654
655    pub fn font_size(mut self, font_size: impl Into<FontSize>) -> Self {
656        self.element.text_style_data.font_size = Some(font_size.into());
657        self
658    }
659
660    pub fn overflow<S: Into<Overflow>>(mut self, overflow: S) -> Self {
661        self.element
662            .effect
663            .get_or_insert_with(Default::default)
664            .overflow = overflow.into();
665        self
666    }
667
668    pub fn rotate<R: Into<Option<f32>>>(mut self, rotation: R) -> Self {
669        self.element
670            .effect
671            .get_or_insert_with(Default::default)
672            .rotation = rotation.into();
673        self
674    }
675
676    pub fn scale(mut self, scale: impl Into<Scale>) -> Self {
677        self.element
678            .effect
679            .get_or_insert_with(Default::default)
680            .scale = Some(scale.into());
681        self
682    }
683
684    pub fn opacity(mut self, opacity: impl Into<f32>) -> Self {
685        self.element
686            .effect
687            .get_or_insert_with(Default::default)
688            .opacity = Some(opacity.into());
689        self
690    }
691
692    pub fn blur(mut self, blur: impl Into<f32>) -> Self {
693        self.element
694            .effect
695            .get_or_insert_with(Default::default)
696            .blur = Some(blur.into());
697        self
698    }
699}