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 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
39pub 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 let (outer_rrect, outer_corner_radius) = {
54 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 let (inner_rrect, inner_corner_radius) = {
112 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}