@@ -2,6 +2,353 @@ use crate::math::{BoundingBox, Dimensions};
22use crate :: render_commands:: { Custom , RenderCommand , RenderCommandConfig } ;
33use crate :: text:: TextConfig ;
44use 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+
5352use skia_safe:: {
6353 Canvas , ClipOp , Color , Font , Image , Paint , Point , RRect , Rect , SamplingOptions , Typeface ,
7354} ;
0 commit comments