freya_core/render/utils/
borders.rs

1use freya_engine::prelude::*;
2use torin::prelude::Area;
3
4use crate::values::{
5    Border,
6    BorderAlignment,
7    CornerRadius,
8};
9
10pub enum BorderShape {
11    DRRect(RRect, RRect),
12    Path(Path),
13}
14
15pub fn render_border(
16    canvas: &Canvas,
17    rect: Rect,
18    area: Area,
19    border: &Border,
20    corner_radius: &CornerRadius,
21) {
22    // Create a new paint
23    let mut border_paint = Paint::default();
24    border_paint.set_style(PaintStyle::Fill);
25    border_paint.set_anti_alias(true);
26
27    border.fill.apply_to_paint(&mut border_paint, area);
28
29    match border_shape(rect, corner_radius, border) {
30        BorderShape::DRRect(outer, inner) => {
31            canvas.draw_drrect(outer, inner, &border_paint);
32        }
33        BorderShape::Path(path) => {
34            canvas.draw_path(&path, &border_paint);
35        }
36    }
37}
38
39/// Returns a `Path` that will draw a [`Border`] around a base rectangle.
40///
41/// We don't use Skia's stroking API here, since we might need different widths for each side.
42pub fn border_shape(
43    base_rect: Rect,
44    base_corner_radius: &CornerRadius,
45    border: &Border,
46) -> BorderShape {
47    let border_alignment = border.alignment;
48    let border_width = border.width;
49
50    // First we create a path that is outset from the rect by a certain amount on each side.
51    //
52    // Let's call this the outer border path.
53    let (outer_rrect, outer_corner_radius) = {
54        // Calculuate the outer corner radius for the border.
55        let corner_radius = CornerRadius {
56            top_left: outer_border_path_corner_radius(
57                border_alignment,
58                base_corner_radius.top_left,
59                border_width.top,
60                border_width.left,
61            ),
62            top_right: outer_border_path_corner_radius(
63                border_alignment,
64                base_corner_radius.top_right,
65                border_width.top,
66                border_width.right,
67            ),
68            bottom_left: outer_border_path_corner_radius(
69                border_alignment,
70                base_corner_radius.bottom_left,
71                border_width.bottom,
72                border_width.left,
73            ),
74            bottom_right: outer_border_path_corner_radius(
75                border_alignment,
76                base_corner_radius.bottom_right,
77                border_width.bottom,
78                border_width.right,
79            ),
80            smoothing: base_corner_radius.smoothing,
81        };
82
83        let rrect = RRect::new_rect_radii(
84            {
85                let mut rect = base_rect;
86                let alignment_scale = match border_alignment {
87                    BorderAlignment::Outer => 1.0,
88                    BorderAlignment::Center => 0.5,
89                    BorderAlignment::Inner => 0.0,
90                };
91
92                rect.left -= border_width.left * alignment_scale;
93                rect.top -= border_width.top * alignment_scale;
94                rect.right += border_width.right * alignment_scale;
95                rect.bottom += border_width.bottom * alignment_scale;
96
97                rect
98            },
99            &[
100                (corner_radius.top_left, corner_radius.top_left).into(),
101                (corner_radius.top_right, corner_radius.top_right).into(),
102                (corner_radius.bottom_right, corner_radius.bottom_right).into(),
103                (corner_radius.bottom_left, corner_radius.bottom_left).into(),
104            ],
105        );
106
107        (rrect, corner_radius)
108    };
109
110    // After the outer path, we will then move to the inner bounds of the border.
111    let (inner_rrect, inner_corner_radius) = {
112        // Calculuate the inner corner radius for the border.
113        let corner_radius = CornerRadius {
114            top_left: inner_border_path_corner_radius(
115                border_alignment,
116                base_corner_radius.top_left,
117                border_width.top,
118                border_width.left,
119            ),
120            top_right: inner_border_path_corner_radius(
121                border_alignment,
122                base_corner_radius.top_right,
123                border_width.top,
124                border_width.right,
125            ),
126            bottom_left: inner_border_path_corner_radius(
127                border_alignment,
128                base_corner_radius.bottom_left,
129                border_width.bottom,
130                border_width.left,
131            ),
132            bottom_right: inner_border_path_corner_radius(
133                border_alignment,
134                base_corner_radius.bottom_right,
135                border_width.bottom,
136                border_width.right,
137            ),
138            smoothing: base_corner_radius.smoothing,
139        };
140
141        let rrect = RRect::new_rect_radii(
142            {
143                let mut rect = base_rect;
144                let alignment_scale = match border_alignment {
145                    BorderAlignment::Outer => 0.0,
146                    BorderAlignment::Center => 0.5,
147                    BorderAlignment::Inner => 1.0,
148                };
149
150                rect.left += border_width.left * alignment_scale;
151                rect.top += border_width.top * alignment_scale;
152                rect.right -= border_width.right * alignment_scale;
153                rect.bottom -= border_width.bottom * alignment_scale;
154
155                rect
156            },
157            &[
158                (corner_radius.top_left, corner_radius.top_left).into(),
159                (corner_radius.top_right, corner_radius.top_right).into(),
160                (corner_radius.bottom_right, corner_radius.bottom_right).into(),
161                (corner_radius.bottom_left, corner_radius.bottom_left).into(),
162            ],
163        );
164
165        (rrect, corner_radius)
166    };
167
168    if base_corner_radius.smoothing > 0.0 {
169        let mut path = Path::new();
170        path.set_fill_type(PathFillType::EvenOdd);
171
172        path.add_path(
173            &outer_corner_radius.smoothed_path(outer_rrect),
174            Point::new(outer_rrect.rect().x(), outer_rrect.rect().y()),
175            None,
176        );
177
178        path.add_path(
179            &inner_corner_radius.smoothed_path(inner_rrect),
180            Point::new(inner_rrect.rect().x(), inner_rrect.rect().y()),
181            None,
182        );
183
184        BorderShape::Path(path)
185    } else {
186        BorderShape::DRRect(outer_rrect, inner_rrect)
187    }
188}
189
190fn outer_border_path_corner_radius(
191    alignment: BorderAlignment,
192    corner_radius: f32,
193    width_1: f32,
194    width_2: f32,
195) -> f32 {
196    if alignment == BorderAlignment::Inner || corner_radius == 0.0 {
197        return corner_radius;
198    }
199
200    let mut offset = if width_1 == 0.0 {
201        width_2
202    } else if width_2 == 0.0 {
203        width_1
204    } else {
205        width_1.min(width_2)
206    };
207
208    if alignment == BorderAlignment::Center {
209        offset *= 0.5;
210    }
211
212    corner_radius + offset
213}
214
215fn inner_border_path_corner_radius(
216    alignment: BorderAlignment,
217    corner_radius: f32,
218    width_1: f32,
219    width_2: f32,
220) -> f32 {
221    if alignment == BorderAlignment::Outer || corner_radius == 0.0 {
222        return corner_radius;
223    }
224
225    let mut offset = if width_1 == 0.0 {
226        width_2
227    } else if width_2 == 0.0 {
228        width_1
229    } else {
230        width_1.min(width_2)
231    };
232
233    if alignment == BorderAlignment::Center {
234        offset *= 0.5;
235    }
236
237    corner_radius - offset
238}