Skip to content

Commit acfed0f

Browse files
fix(skia renderer): add right border widths to rounded corners
1 parent 6db8ab1 commit acfed0f

File tree

1 file changed

+347
-0
lines changed

1 file changed

+347
-0
lines changed

src/renderers/skia.rs

Lines changed: 347 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,353 @@ use crate::math::{BoundingBox, Dimensions};
22
use crate::render_commands::{Custom, RenderCommand, RenderCommandConfig};
33
use crate::text::TextConfig;
44
use crate::{ClayLayoutScope, Color as ClayColor};
5+
use skia_safe::{
6+
Canvas, ClipOp, Color, Font, Image, Paint, PaintCap, Point, RRect, Rect, SamplingOptions, Typeface
7+
};
8+
9+
pub fn clay_to_skia_color(color: ClayColor) -> Color {
10+
Color::from_argb(
11+
(color.a).round() as u8,
12+
(color.r).round() as u8,
13+
(color.g).round() as u8,
14+
(color.b).round() as u8,
15+
)
16+
}
17+
18+
fn clay_to_skia_rect(rect: BoundingBox) -> Rect {
19+
Rect::from_xywh(rect.x, rect.y, rect.width, rect.height)
20+
}
21+
/// This is a direct* port of Clay's raylib renderer using skia_safe as the drawing API.
22+
pub fn clay_skia_render<'a, CustomElementData: 'a>(
23+
canvas: &Canvas,
24+
render_commands: impl Iterator<Item = RenderCommand<'a, Image, CustomElementData>>,
25+
mut render_custom_element: impl FnMut(
26+
&RenderCommand<'a, Image, CustomElementData>,
27+
&Custom<'a, CustomElementData>,
28+
&Canvas,
29+
),
30+
fonts: &[&Typeface],
31+
) {
32+
for command in render_commands {
33+
match command.config {
34+
RenderCommandConfig::Text(text) => {
35+
let text_data = text.text;
36+
let mut paint = Paint::default();
37+
paint.set_color(clay_to_skia_color(text.color));
38+
let font = Font::new(fonts[text.font_id as usize].clone(), text.font_size as f32);
39+
let pos = Point::new(
40+
command.bounding_box.x,
41+
command.bounding_box.y + text.font_size as f32,
42+
);
43+
canvas.draw_str(&text_data, pos, &font, &paint);
44+
}
45+
46+
RenderCommandConfig::Image(image) => {
47+
let skia_image = image.data;
48+
let mut paint = Paint::default();
49+
paint.set_color(Color::WHITE);
50+
paint.set_anti_alias(true);
51+
52+
let bounds = clay_to_skia_rect(command.bounding_box);
53+
let has_border_radius = image.corner_radii.top_left > 0.
54+
|| image.corner_radii.top_right > 0.
55+
|| image.corner_radii.bottom_left > 0.
56+
|| image.corner_radii.bottom_right > 0.;
57+
if has_border_radius {
58+
canvas.save();
59+
let rrect = RRect::new_rect_radii(
60+
bounds,
61+
&[
62+
Point::new(image.corner_radii.top_left, image.corner_radii.top_left),
63+
Point::new(image.corner_radii.top_right, image.corner_radii.top_right),
64+
Point::new(
65+
image.corner_radii.bottom_left,
66+
image.corner_radii.bottom_left,
67+
),
68+
Point::new(
69+
image.corner_radii.bottom_right,
70+
image.corner_radii.bottom_right,
71+
),
72+
],
73+
);
74+
canvas.clip_rrect(rrect, ClipOp::Intersect, true);
75+
}
76+
77+
canvas.draw_image_rect_with_sampling_options(
78+
skia_image,
79+
None,
80+
bounds,
81+
SamplingOptions::new(
82+
skia_safe::FilterMode::Linear,
83+
skia_safe::MipmapMode::Linear,
84+
),
85+
&paint,
86+
);
87+
88+
// Restore canvas state if we applied a clip
89+
if has_border_radius {
90+
canvas.restore();
91+
}
92+
}
93+
94+
RenderCommandConfig::ScissorStart() => {
95+
// Save the current state then clip to the bounding box.
96+
canvas.save();
97+
let clip_rect = clay_to_skia_rect(command.bounding_box);
98+
canvas.clip_rect(clip_rect, ClipOp::Intersect, false);
99+
}
100+
101+
RenderCommandConfig::ScissorEnd() => {
102+
// Restore the previous state
103+
canvas.restore();
104+
}
105+
106+
RenderCommandConfig::Rectangle(rect) => {
107+
let paint = {
108+
let mut p = Paint::default();
109+
p.set_color(clay_to_skia_color(rect.color));
110+
p.set_anti_alias(true);
111+
p.set_style(skia_safe::PaintStyle::Fill);
112+
p
113+
};
114+
let bounds = clay_to_skia_rect(command.bounding_box);
115+
if rect.corner_radii.top_left > 0.
116+
|| rect.corner_radii.top_right > 0.
117+
|| rect.corner_radii.bottom_left > 0.
118+
|| rect.corner_radii.bottom_right > 0.
119+
{
120+
let rrect = RRect::new_rect_radii(
121+
bounds,
122+
&[
123+
Point::new(rect.corner_radii.top_left, rect.corner_radii.top_left),
124+
Point::new(rect.corner_radii.top_right, rect.corner_radii.top_right),
125+
Point::new(
126+
rect.corner_radii.bottom_left,
127+
rect.corner_radii.bottom_left,
128+
),
129+
Point::new(
130+
rect.corner_radii.bottom_right,
131+
rect.corner_radii.bottom_right,
132+
),
133+
],
134+
);
135+
canvas.draw_rrect(rrect, &paint);
136+
} else {
137+
canvas.draw_rect(bounds, &paint);
138+
}
139+
}
140+
141+
RenderCommandConfig::Border(border) => {
142+
// Draw each border side using fill rectangles.
143+
let paint = {
144+
let mut p = Paint::default();
145+
p.set_color(clay_to_skia_color(border.color));
146+
p.set_anti_alias(true);
147+
p
148+
};
149+
150+
let bb = &command.bounding_box;
151+
152+
// Left border.
153+
if border.width.left > 0 {
154+
let rect = Rect::from_xywh(
155+
bb.x,
156+
bb.y + border.corner_radii.top_left,
157+
border.width.left as f32,
158+
bb.height - border.corner_radii.top_left - border.corner_radii.bottom_left,
159+
);
160+
canvas.draw_rect(rect, &paint);
161+
}
162+
163+
// Right border.
164+
if border.width.right > 0 {
165+
let rect = Rect::from_xywh(
166+
bb.x + bb.width - border.width.right as f32,
167+
bb.y + border.corner_radii.top_right,
168+
border.width.right as f32,
169+
bb.height
170+
- border.corner_radii.top_right
171+
- border.corner_radii.bottom_right,
172+
);
173+
canvas.draw_rect(rect, &paint);
174+
}
175+
176+
// Top border.
177+
if border.width.top > 0 {
178+
let rect = Rect::from_xywh(
179+
bb.x + border.corner_radii.top_left,
180+
bb.y,
181+
bb.width - border.corner_radii.top_left - border.corner_radii.top_right,
182+
border.width.top as f32,
183+
);
184+
canvas.draw_rect(rect, &paint);
185+
}
186+
187+
// Bottom border.
188+
if border.width.bottom > 0 {
189+
let rect = Rect::from_xywh(
190+
bb.x + border.corner_radii.bottom_left,
191+
bb.y + bb.height - border.width.bottom as f32,
192+
bb.width
193+
- border.corner_radii.bottom_left
194+
- border.corner_radii.bottom_right,
195+
border.width.bottom as f32,
196+
);
197+
canvas.draw_rect(rect, &paint);
198+
}
199+
200+
// For corner arcs, we draw strokes.
201+
let mut stroke = Paint::default();
202+
stroke.set_color(clay_to_skia_color(border.color));
203+
204+
stroke.set_style(skia_safe::paint::Style::Stroke);
205+
stroke.set_anti_alias(true);
206+
207+
// Helper to draw an arc.
208+
let mut draw_corner_arc = |canvas: &Canvas,
209+
center_x: f32,
210+
center_y: f32,
211+
radius: f32,
212+
start_angle: f32,
213+
sweep_angle: f32,
214+
width: u16
215+
| {
216+
let radius = radius - (width as f32 / 2.);
217+
let arc_rect = Rect::from_xywh(
218+
center_x - radius,
219+
center_y - radius,
220+
radius * 2.0,
221+
radius * 2.0,
222+
);
223+
224+
stroke.set_stroke_width(width as f32);
225+
stroke.set_stroke_cap(PaintCap::Round);
226+
canvas.draw_arc(arc_rect, start_angle, sweep_angle, false, &stroke);
227+
};
228+
229+
if border.corner_radii.top_left > 0. {
230+
// top-left: arc from 180 to 270 degrees.
231+
let center_x = bb.x + border.corner_radii.top_left;
232+
let center_y = bb.y + border.corner_radii.top_left;
233+
234+
draw_corner_arc(
235+
canvas,
236+
center_x,
237+
center_y,
238+
border.corner_radii.top_left,
239+
180.0,
240+
90.0/2.,
241+
border.width.left
242+
);
243+
244+
draw_corner_arc(
245+
canvas,
246+
center_x,
247+
center_y,
248+
border.corner_radii.top_left,
249+
180.0 + 90.0/2.,
250+
90.0/2.,border.width.top
251+
);
252+
}
253+
254+
if border.corner_radii.top_right > 0. {
255+
// top-right: arc from 270 to 360 degrees.
256+
let center_x = bb.x + bb.width - border.corner_radii.top_right;
257+
let center_y = bb.y + border.corner_radii.top_right;
258+
259+
draw_corner_arc(
260+
canvas,
261+
center_x,
262+
center_y,
263+
border.corner_radii.top_right,
264+
270.0,
265+
90.0/2.0,border.width.top
266+
);
267+
draw_corner_arc(
268+
canvas,
269+
center_x,
270+
center_y,
271+
border.corner_radii.top_right,
272+
270.0+90./2.,
273+
90.0/2.0,border.width.right
274+
);
275+
}
276+
277+
if border.corner_radii.bottom_left > 0. {
278+
// bottom-left: arc from 90 to 180 degrees.
279+
let center_x = bb.x + border.corner_radii.bottom_left;
280+
let center_y = bb.y + bb.height - border.corner_radii.bottom_left;
281+
282+
draw_corner_arc(
283+
canvas,
284+
center_x,
285+
center_y,
286+
border.corner_radii.bottom_left,
287+
90.0,
288+
90.0/2.,border.width.bottom
289+
);
290+
291+
draw_corner_arc(
292+
canvas,
293+
center_x,
294+
center_y,
295+
border.corner_radii.bottom_left,
296+
90.0 + 90./2.,
297+
90.0/2.,border.width.left
298+
);
299+
}
300+
301+
if border.corner_radii.bottom_right > 0. {
302+
// bottom-right: arc from 0 to 90 degrees.
303+
let center_x = bb.x + bb.width - border.corner_radii.bottom_right;
304+
let center_y = bb.y + bb.height - border.corner_radii.bottom_right;
305+
306+
draw_corner_arc(
307+
canvas,
308+
center_x,
309+
center_y,
310+
border.corner_radii.bottom_right,
311+
0.,
312+
90.0/2.,border.width.right
313+
);
314+
draw_corner_arc(
315+
canvas,
316+
center_x,
317+
center_y,
318+
border.corner_radii.bottom_right,
319+
90.0/2.,
320+
90.0/2.,border.width.bottom
321+
);
322+
}
323+
}
324+
RenderCommandConfig::Custom(ref custom) => {
325+
render_custom_element(&command, custom, canvas)
326+
}
327+
RenderCommandConfig::None() => {}
328+
}
329+
}
330+
}
331+
332+
pub type SkiaClayScope<'clay, 'render, CustomElements> =
333+
ClayLayoutScope<'clay, 'render, Image, CustomElements>;
334+
335+
pub fn get_source_dimensions_from_skia_image(image: &Image) -> Dimensions {
336+
(image.width() as f32, image.height() as f32).into()
337+
}
338+
339+
pub fn create_measure_text_function(
340+
fonts: &'static [&Typeface],
341+
) -> impl Fn(&str, &TextConfig) -> Dimensions + 'static {
342+
|text, text_config| {
343+
let font = Font::new(
344+
fonts[text_config.font_id as usize],
345+
text_config.font_size as f32,
346+
);
347+
let width = font.measure_str(text, None).0;
348+
(width, font.metrics().1.bottom - font.metrics().1.top).into()
349+
}
350+
}
351+
5352
use skia_safe::{
6353
Canvas, ClipOp, Color, Font, Image, Paint, Point, RRect, Rect, SamplingOptions, Typeface,
7354
};

0 commit comments

Comments
 (0)