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