@@ -3,7 +3,10 @@ use floem_renderer::{
3
3
usvg:: { self , Tree } ,
4
4
Renderer ,
5
5
} ;
6
- use peniko:: { kurbo:: Size , Brush } ;
6
+ use peniko:: {
7
+ kurbo:: { Point , Size } ,
8
+ Brush , GradientKind ,
9
+ } ;
7
10
use sha2:: { Digest , Sha256 } ;
8
11
9
12
use crate :: { id:: ViewId , prop, prop_extractor, style:: TextColor , style_class, view:: View } ;
@@ -24,6 +27,9 @@ pub struct Svg {
24
27
svg_tree : Option < Tree > ,
25
28
svg_hash : Option < Vec < u8 > > ,
26
29
svg_style : SvgStyle ,
30
+ svg_string : String ,
31
+ svg_css : Option < String > ,
32
+ css_prop : Option < Box < dyn SvgCssPropExtractor > > ,
27
33
}
28
34
29
35
style_class ! ( pub SvgClass ) ;
@@ -61,29 +67,48 @@ impl From<&str> for SvgStrFn {
61
67
}
62
68
}
63
69
70
+ pub trait SvgCssPropExtractor {
71
+ fn read_custom ( & mut self , cx : & mut crate :: context:: StyleCx ) -> bool ;
72
+ fn css_string ( & self ) -> String ;
73
+ }
74
+
75
+ #[ derive( Debug , Clone ) ]
76
+ pub enum SvgOrStyle {
77
+ Svg ( String ) ,
78
+ Style ( String ) ,
79
+ }
80
+
64
81
impl Svg {
65
82
pub fn update_value < S : Into < String > > ( self , svg_str : impl Fn ( ) -> S + ' static ) -> Self {
66
83
let id = self . id ;
67
84
create_effect ( move |_| {
68
85
let new_svg_str = svg_str ( ) ;
69
- id. update_state ( new_svg_str. into ( ) ) ;
86
+ id. update_state ( SvgOrStyle :: Svg ( new_svg_str. into ( ) ) ) ;
70
87
} ) ;
71
88
self
72
89
}
90
+
91
+ pub fn set_css_extractor ( mut self , css : impl SvgCssPropExtractor + ' static ) -> Self {
92
+ self . css_prop = Some ( Box :: new ( css) ) ;
93
+ self
94
+ }
73
95
}
74
96
75
97
pub fn svg ( svg_str_fn : impl Into < SvgStrFn > + ' static ) -> Svg {
76
98
let id = ViewId :: new ( ) ;
77
99
let svg_str_fn: SvgStrFn = svg_str_fn. into ( ) ;
78
100
create_effect ( move |_| {
79
101
let new_svg_str = ( svg_str_fn. str_fn ) ( ) ;
80
- id. update_state ( new_svg_str) ;
102
+ id. update_state ( SvgOrStyle :: Svg ( new_svg_str) ) ;
81
103
} ) ;
82
104
Svg {
83
105
id,
84
106
svg_tree : None ,
85
107
svg_hash : None ,
86
108
svg_style : Default :: default ( ) ,
109
+ svg_string : Default :: default ( ) ,
110
+ css_prop : None ,
111
+ svg_css : None ,
87
112
}
88
113
. class ( SvgClass )
89
114
}
@@ -95,12 +120,35 @@ impl View for Svg {
95
120
96
121
fn style_pass ( & mut self , cx : & mut crate :: context:: StyleCx < ' _ > ) {
97
122
self . svg_style . read ( cx) ;
123
+ if let Some ( prop_reader) = & mut self . css_prop {
124
+ if prop_reader. read_custom ( cx) {
125
+ self . id
126
+ . update_state ( SvgOrStyle :: Style ( prop_reader. css_string ( ) ) ) ;
127
+ }
128
+ }
98
129
}
99
130
100
131
fn update ( & mut self , _cx : & mut crate :: context:: UpdateCx , state : Box < dyn std:: any:: Any > ) {
101
- if let Ok ( state) = state. downcast :: < String > ( ) {
102
- let text = & * state;
103
- self . svg_tree = Tree :: from_str ( text, & usvg:: Options :: default ( ) ) . ok ( ) ;
132
+ if let Ok ( state) = state. downcast :: < SvgOrStyle > ( ) {
133
+ let ( text, style) = match * state {
134
+ SvgOrStyle :: Svg ( text) => {
135
+ self . svg_string = text;
136
+ ( & self . svg_string , self . svg_css . clone ( ) )
137
+ }
138
+ SvgOrStyle :: Style ( css) => {
139
+ self . svg_css = Some ( css) ;
140
+ ( & self . svg_string , self . svg_css . clone ( ) )
141
+ }
142
+ } ;
143
+
144
+ self . svg_tree = Tree :: from_str (
145
+ text,
146
+ & usvg:: Options {
147
+ style_sheet : style,
148
+ ..Default :: default ( )
149
+ } ,
150
+ )
151
+ . ok ( ) ;
104
152
105
153
let mut hasher = Sha256 :: new ( ) ;
106
154
hasher. update ( text) ;
@@ -121,7 +169,73 @@ impl View for Svg {
121
169
} else {
122
170
self . svg_style . text_color ( ) . map ( Brush :: Solid )
123
171
} ;
124
- cx. draw_svg ( floem_renderer:: Svg { tree, hash } , rect, color. as_ref ( ) ) ;
172
+ cx. draw_svg ( crate :: RendererSvg { tree, hash } , rect, color. as_ref ( ) ) ;
173
+ }
174
+ }
175
+ }
176
+
177
+ pub fn brush_to_css_string ( brush : & Brush ) -> String {
178
+ match brush {
179
+ Brush :: Solid ( color) => {
180
+ let r = ( color. components [ 0 ] * 255.0 ) . round ( ) as u8 ;
181
+ let g = ( color. components [ 1 ] * 255.0 ) . round ( ) as u8 ;
182
+ let b = ( color. components [ 2 ] * 255.0 ) . round ( ) as u8 ;
183
+ let a = color. components [ 3 ] ;
184
+
185
+ if a < 1.0 {
186
+ format ! ( "rgba({}, {}, {}, {})" , r, g, b, a)
187
+ } else {
188
+ format ! ( "#{:02x}{:02x}{:02x}" , r, g, b)
189
+ }
190
+ }
191
+ Brush :: Gradient ( gradient) => {
192
+ match & gradient. kind {
193
+ GradientKind :: Linear { start, end } => {
194
+ let angle_degrees = calculate_angle ( start, end) ;
195
+
196
+ let mut css = format ! ( "linear-gradient({}deg, " , angle_degrees) ;
197
+
198
+ for ( i, stop) in gradient. stops . iter ( ) . enumerate ( ) {
199
+ let color = & stop. color ;
200
+ let r = ( color. components [ 0 ] * 255.0 ) . round ( ) as u8 ;
201
+ let g = ( color. components [ 1 ] * 255.0 ) . round ( ) as u8 ;
202
+ let b = ( color. components [ 2 ] * 255.0 ) . round ( ) as u8 ;
203
+ let a = color. components [ 3 ] ;
204
+
205
+ let color_str = if a < 1.0 {
206
+ format ! ( "rgba({}, {}, {}, {})" , r, g, b, a)
207
+ } else {
208
+ format ! ( "#{:02x}{:02x}{:02x}" , r, g, b)
209
+ } ;
210
+
211
+ css. push_str ( & format ! ( "{} {}%" , color_str, ( stop. offset * 100.0 ) . round( ) ) ) ;
212
+
213
+ if i < gradient. stops . len ( ) - 1 {
214
+ css. push_str ( ", " ) ;
215
+ }
216
+ }
217
+
218
+ css. push ( ')' ) ;
219
+ css
220
+ }
221
+
222
+ _ => "currentColor" . to_string ( ) , // Fallback for unsupported gradient types
223
+ }
125
224
}
225
+ Brush :: Image ( _) => "currentColor" . to_string ( ) ,
126
226
}
127
227
}
228
+
229
+ fn calculate_angle ( start : & Point , end : & Point ) -> f64 {
230
+ let angle_rad = ( end. y - start. y ) . atan2 ( end. x - start. x ) ;
231
+
232
+ // CSS angles are measured clockwise from the positive y-axis
233
+ let mut angle_deg = 90.0 - angle_rad. to_degrees ( ) ;
234
+
235
+ // Normalize to 0-360 range
236
+ if angle_deg < 0.0 {
237
+ angle_deg += 360.0 ;
238
+ }
239
+
240
+ angle_deg
241
+ }
0 commit comments