1use std::{fs, path::Path, sync::Arc};
2
3use crate::{
4 App, Asset, Bounds, Element, GlobalElementId, Hitbox, InspectorElementId, InteractiveElement,
5 Interactivity, IntoElement, LayoutId, Pixels, Point, Radians, SharedString, Size,
6 StyleRefinement, Styled, TransformationMatrix, Window, geometry::Negate as _, point, px,
7 radians, size,
8};
9use util::ResultExt;
10
11/// An SVG element.
12pub struct Svg {
13 interactivity: Interactivity,
14 transformation: Option<Transformation>,
15 path: Option<SharedString>,
16 external_path: Option<SharedString>,
17}
18
19/// Create a new SVG element.
20#[track_caller]
21pub fn svg() -> Svg {
22 Svg {
23 interactivity: Interactivity::new(),
24 transformation: None,
25 path: None,
26 external_path: None,
27 }
28}
29
30impl Svg {
31 /// Set the path to the SVG file for this element.
32 pub fn path(mut self, path: impl Into<SharedString>) -> Self {
33 self.path = Some(path.into());
34 self
35 }
36
37 /// Set the path to the SVG file for this element.
38 pub fn external_path(mut self, path: impl Into<SharedString>) -> Self {
39 self.external_path = Some(path.into());
40 self
41 }
42
43 /// Transform the SVG element with the given transformation.
44 /// Note that this won't effect the hitbox or layout of the element, only the rendering.
45 pub fn with_transformation(mut self, transformation: Transformation) -> Self {
46 self.transformation = Some(transformation);
47 self
48 }
49}
50
51impl Element for Svg {
52 type RequestLayoutState = ();
53 type PrepaintState = Option<Hitbox>;
54
55 fn id(&self) -> Option<crate::ElementId> {
56 self.interactivity.element_id.clone()
57 }
58
59 fn source_location(&self) -> Option<&'static std::panic::Location<'static>> {
60 self.interactivity.source_location()
61 }
62
63 fn request_layout(
64 &mut self,
65 global_id: Option<&GlobalElementId>,
66 inspector_id: Option<&InspectorElementId>,
67 window: &mut Window,
68 cx: &mut App,
69 ) -> (LayoutId, Self::RequestLayoutState) {
70 let layout_id = self.interactivity.request_layout(
71 global_id,
72 inspector_id,
73 window,
74 cx,
75 |style, window, cx| window.request_layout(style, None, cx),
76 );
77 (layout_id, ())
78 }
79
80 fn prepaint(
81 &mut self,
82 global_id: Option<&GlobalElementId>,
83 inspector_id: Option<&InspectorElementId>,
84 bounds: Bounds<Pixels>,
85 _request_layout: &mut Self::RequestLayoutState,
86 window: &mut Window,
87 cx: &mut App,
88 ) -> Option<Hitbox> {
89 self.interactivity.prepaint(
90 global_id,
91 inspector_id,
92 bounds,
93 bounds.size,
94 window,
95 cx,
96 |_, _, hitbox, _, _| hitbox,
97 )
98 }
99
100 fn paint(
101 &mut self,
102 global_id: Option<&GlobalElementId>,
103 inspector_id: Option<&InspectorElementId>,
104 bounds: Bounds<Pixels>,
105 _request_layout: &mut Self::RequestLayoutState,
106 hitbox: &mut Option<Hitbox>,
107 window: &mut Window,
108 cx: &mut App,
109 ) where
110 Self: Sized,
111 {
112 self.interactivity.paint(
113 global_id,
114 inspector_id,
115 bounds,
116 hitbox.as_ref(),
117 window,
118 cx,
119 |style, window, cx| {
120 if let Some((path, color)) = self.path.as_ref().zip(style.text.color) {
121 let transformation = self
122 .transformation
123 .as_ref()
124 .map(|transformation| {
125 transformation.into_matrix(bounds.center(), window.scale_factor())
126 })
127 .unwrap_or_default();
128
129 window
130 .paint_svg(bounds, path.clone(), None, transformation, color, cx)
131 .log_err();
132 } else if let Some((path, color)) =
133 self.external_path.as_ref().zip(style.text.color)
134 {
135 let Some(bytes) = window
136 .use_asset::<SvgAsset>(path, cx)
137 .and_then(|asset| asset.log_err())
138 else {
139 return;
140 };
141
142 let transformation = self
143 .transformation
144 .as_ref()
145 .map(|transformation| {
146 transformation.into_matrix(bounds.center(), window.scale_factor())
147 })
148 .unwrap_or_default();
149
150 window
151 .paint_svg(
152 bounds,
153 path.clone(),
154 Some(&bytes),
155 transformation,
156 color,
157 cx,
158 )
159 .log_err();
160 }
161 },
162 )
163 }
164}
165
166impl IntoElement for Svg {
167 type Element = Self;
168
169 fn into_element(self) -> Self::Element {
170 self
171 }
172}
173
174impl Styled for Svg {
175 fn style(&mut self) -> &mut StyleRefinement {
176 &mut self.interactivity.base_style
177 }
178}
179
180impl InteractiveElement for Svg {
181 fn interactivity(&mut self) -> &mut Interactivity {
182 &mut self.interactivity
183 }
184}
185
186/// A transformation to apply to an SVG element.
187#[derive(Clone, Copy, Debug, PartialEq)]
188pub struct Transformation {
189 scale: Size<f32>,
190 translate: Point<Pixels>,
191 rotate: Radians,
192}
193
194impl Default for Transformation {
195 fn default() -> Self {
196 Self {
197 scale: size(1.0, 1.0),
198 translate: point(px(0.0), px(0.0)),
199 rotate: radians(0.0),
200 }
201 }
202}
203
204impl Transformation {
205 /// Create a new Transformation with the specified scale along each axis.
206 pub fn scale(scale: Size<f32>) -> Self {
207 Self {
208 scale,
209 translate: point(px(0.0), px(0.0)),
210 rotate: radians(0.0),
211 }
212 }
213
214 /// Create a new Transformation with the specified translation.
215 pub fn translate(translate: Point<Pixels>) -> Self {
216 Self {
217 scale: size(1.0, 1.0),
218 translate,
219 rotate: radians(0.0),
220 }
221 }
222
223 /// Create a new Transformation with the specified rotation in radians.
224 pub fn rotate(rotate: impl Into<Radians>) -> Self {
225 let rotate = rotate.into();
226 Self {
227 scale: size(1.0, 1.0),
228 translate: point(px(0.0), px(0.0)),
229 rotate,
230 }
231 }
232
233 /// Update the scaling factor of this transformation.
234 pub fn with_scaling(mut self, scale: Size<f32>) -> Self {
235 self.scale = scale;
236 self
237 }
238
239 /// Update the translation value of this transformation.
240 pub fn with_translation(mut self, translate: Point<Pixels>) -> Self {
241 self.translate = translate;
242 self
243 }
244
245 /// Update the rotation angle of this transformation.
246 pub fn with_rotation(mut self, rotate: impl Into<Radians>) -> Self {
247 self.rotate = rotate.into();
248 self
249 }
250
251 fn into_matrix(self, center: Point<Pixels>, scale_factor: f32) -> TransformationMatrix {
252 //Note: if you read this as a sequence of matrix multiplications, start from the bottom
253 TransformationMatrix::unit()
254 .translate(center.scale(scale_factor) + self.translate.scale(scale_factor))
255 .rotate(self.rotate)
256 .scale(self.scale)
257 .translate(center.scale(scale_factor).negate())
258 }
259}
260
261enum SvgAsset {}
262
263impl Asset for SvgAsset {
264 type Source = SharedString;
265 type Output = Result<Arc<[u8]>, Arc<std::io::Error>>;
266
267 fn load(
268 source: Self::Source,
269 _cx: &mut App,
270 ) -> impl Future<Output = Self::Output> + Send + 'static {
271 async move {
272 let bytes = fs::read(Path::new(source.as_ref())).map_err(|e| Arc::new(e))?;
273 let bytes = Arc::from(bytes);
274 Ok(bytes)
275 }
276 }
277}