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