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, InspectorElementId, InteractiveElement,
10 Interactivity, IntoElement, LayoutId, Pixels, Point, Radians, SharedString, Size,
11 StyleRefinement, Styled, 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 inspector_id: Option<&InspectorElementId>,
88 window: &mut Window,
89 cx: &mut App,
90 ) -> (LayoutId, Self::RequestLayoutState) {
91 let layout_id = self.interactivity.request_layout(
92 global_id,
93 inspector_id,
94 window,
95 cx,
96 |style, window, cx| window.request_layout(style, None, cx),
97 );
98 (layout_id, ())
99 }
100
101 fn prepaint(
102 &mut self,
103 global_id: Option<&GlobalElementId>,
104 inspector_id: Option<&InspectorElementId>,
105 bounds: Bounds<Pixels>,
106 _request_layout: &mut Self::RequestLayoutState,
107 window: &mut Window,
108 cx: &mut App,
109 ) -> Option<Hitbox> {
110 self.interactivity.prepaint(
111 global_id,
112 inspector_id,
113 bounds,
114 bounds.size,
115 window,
116 cx,
117 |_, _, hitbox, _, _| hitbox,
118 )
119 }
120
121 fn paint(
122 &mut self,
123 global_id: Option<&GlobalElementId>,
124 inspector_id: Option<&InspectorElementId>,
125 bounds: Bounds<Pixels>,
126 _request_layout: &mut Self::RequestLayoutState,
127 hitbox: &mut Option<Hitbox>,
128 window: &mut Window,
129 cx: &mut App,
130 ) where
131 Self: Sized,
132 {
133 self.interactivity.paint(
134 global_id,
135 inspector_id,
136 bounds,
137 hitbox.as_ref(),
138 window,
139 cx,
140 |style, window, cx| {
141 let transformation = self
142 .transformation
143 .as_ref()
144 .map(|transformation| {
145 transformation.into_matrix(bounds.center(), window.scale_factor())
146 })
147 .unwrap_or_default();
148
149 if let Some((data, path)) = self.data.as_ref().zip(self.data_path.as_ref()) {
150 if let Some(color) = style.text.color {
151 window
152 .paint_svg(
153 bounds,
154 path.clone(),
155 Some(&**data),
156 transformation,
157 color,
158 cx,
159 )
160 .log_err();
161 }
162 } else if let Some((path, color)) =
163 self.external_path.as_ref().zip(style.text.color)
164 {
165 let Some(bytes) = window
166 .use_asset::<SvgAsset>(path, cx)
167 .and_then(|asset| asset.log_err())
168 else {
169 return;
170 };
171
172 window
173 .paint_svg(
174 bounds,
175 path.clone(),
176 Some(&bytes),
177 transformation,
178 color,
179 cx,
180 )
181 .log_err();
182 } else if let Some((path, color)) = self.path.as_ref().zip(style.text.color) {
183 window
184 .paint_svg(bounds, path.clone(), None, transformation, color, cx)
185 .log_err();
186 }
187 },
188 )
189 }
190}
191
192impl IntoElement for Svg {
193 type Element = Self;
194
195 fn into_element(self) -> Self::Element {
196 self
197 }
198}
199
200impl Styled for Svg {
201 fn style(&mut self) -> &mut StyleRefinement {
202 &mut self.interactivity.base_style
203 }
204}
205
206impl InteractiveElement for Svg {
207 fn interactivity(&mut self) -> &mut Interactivity {
208 &mut self.interactivity
209 }
210}
211
212#[derive(Clone, Copy, Debug, PartialEq)]
214pub struct Transformation {
215 scale: Size<f32>,
216 translate: Point<Pixels>,
217 rotate: Radians,
218}
219
220impl Default for Transformation {
221 fn default() -> Self {
222 Self {
223 scale: size(1.0, 1.0),
224 translate: point(px(0.0), px(0.0)),
225 rotate: radians(0.0),
226 }
227 }
228}
229
230impl Transformation {
231 pub fn scale(scale: Size<f32>) -> Self {
233 Self {
234 scale,
235 translate: point(px(0.0), px(0.0)),
236 rotate: radians(0.0),
237 }
238 }
239
240 pub fn translate(translate: Point<Pixels>) -> Self {
242 Self {
243 scale: size(1.0, 1.0),
244 translate,
245 rotate: radians(0.0),
246 }
247 }
248
249 pub fn rotate(rotate: impl Into<Radians>) -> Self {
251 let rotate = rotate.into();
252 Self {
253 scale: size(1.0, 1.0),
254 translate: point(px(0.0), px(0.0)),
255 rotate,
256 }
257 }
258
259 pub fn with_scaling(mut self, scale: Size<f32>) -> Self {
261 self.scale = scale;
262 self
263 }
264
265 pub fn with_translation(mut self, translate: Point<Pixels>) -> Self {
267 self.translate = translate;
268 self
269 }
270
271 pub fn with_rotation(mut self, rotate: impl Into<Radians>) -> Self {
273 self.rotate = rotate.into();
274 self
275 }
276
277 fn into_matrix(self, center: Point<Pixels>, scale_factor: f32) -> TransformationMatrix {
278 TransformationMatrix::unit()
280 .translate(center.scale(scale_factor) + self.translate.scale(scale_factor))
281 .rotate(self.rotate)
282 .scale(self.scale)
283 .translate(center.scale(-scale_factor))
284 }
285}
286
287enum SvgAsset {}
288
289impl Asset for SvgAsset {
290 type Source = SharedString;
291 type Output = Result<Arc<[u8]>, Arc<std::io::Error>>;
292
293 fn load(
294 source: Self::Source,
295 _cx: &mut App,
296 ) -> impl Future<Output = Self::Output> + Send + 'static {
297 async move {
298 let bytes = fs::read(Path::new(source.as_ref())).map_err(|e| Arc::new(e))?;
299 let bytes = Arc::from(bytes);
300 Ok(bytes)
301 }
302 }
303}