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
15#[derive(derive_setters::Setters)]
23#[setters(generate = false)]
24pub struct Svg {
25 interactivity: Interactivity,
26 #[setters(generate, rename = "with_transformation", strip_option)]
31 transformation: Option<Transformation>,
32 #[setters(generate, strip_option, into)]
40 path: Option<SharedString>,
41 #[setters(generate, strip_option, into)]
49 external_path: Option<SharedString>,
50 data: Option<Arc<[u8]>>,
51 data_path: Option<SharedString>,
52}
53
54#[track_caller]
68pub fn svg() -> Svg {
69 Svg {
70 interactivity: Interactivity::new(),
71 transformation: None,
72 path: None,
73 external_path: None,
74 data: None,
75 data_path: None,
76 }
77}
78
79impl Svg {
80 pub fn data(mut self, data: &[u8]) -> Self {
86 let mut hasher = std::collections::hash_map::DefaultHasher::new();
88 data.hash(&mut hasher);
89 let hash = hasher.finish();
90 let path = SharedString::from(format!("__binary_svg__{}", hash));
91 self.data = Some(Arc::from(data));
92 self.data_path = Some(path);
93 self
94 }
95}
96
97impl Element for Svg {
98 type RequestLayoutState = ();
99 type PrepaintState = Option<Hitbox>;
100
101 fn id(&self) -> Option<crate::ElementId> {
102 self.interactivity.element_id.clone()
103 }
104
105 fn source_location(&self) -> Option<&'static std::panic::Location<'static>> {
106 self.interactivity.source_location()
107 }
108
109 fn request_layout(
110 &mut self,
111 global_id: Option<&GlobalElementId>,
112 inspector_id: Option<&InspectorElementId>,
113 window: &mut Window,
114 cx: &mut App,
115 ) -> (LayoutId, Self::RequestLayoutState) {
116 let layout_id = self.interactivity.request_layout(
117 global_id,
118 inspector_id,
119 window,
120 cx,
121 |style, window, cx| window.request_layout(style, None, cx),
122 );
123 (layout_id, ())
124 }
125
126 fn prepaint(
127 &mut self,
128 global_id: Option<&GlobalElementId>,
129 inspector_id: Option<&InspectorElementId>,
130 bounds: Bounds<Pixels>,
131 _request_layout: &mut Self::RequestLayoutState,
132 window: &mut Window,
133 cx: &mut App,
134 ) -> Option<Hitbox> {
135 self.interactivity.prepaint(
136 global_id,
137 inspector_id,
138 bounds,
139 bounds.size,
140 window,
141 cx,
142 |_, _, hitbox, _, _| hitbox,
143 )
144 }
145
146 fn paint(
147 &mut self,
148 global_id: Option<&GlobalElementId>,
149 inspector_id: Option<&InspectorElementId>,
150 bounds: Bounds<Pixels>,
151 _request_layout: &mut Self::RequestLayoutState,
152 hitbox: &mut Option<Hitbox>,
153 window: &mut Window,
154 cx: &mut App,
155 ) where
156 Self: Sized,
157 {
158 self.interactivity.paint(
159 global_id,
160 inspector_id,
161 bounds,
162 hitbox.as_ref(),
163 window,
164 cx,
165 |style, window, cx| {
166 let transformation = self
167 .transformation
168 .as_ref()
169 .map(|transformation| {
170 transformation.into_matrix(bounds.center(), window.scale_factor())
171 })
172 .unwrap_or_default();
173
174 if let Some((data, path)) = self.data.as_ref().zip(self.data_path.as_ref()) {
175 if let Some(color) = style.text.color {
176 window
177 .paint_svg(
178 bounds,
179 path.clone(),
180 Some(&**data),
181 transformation,
182 color,
183 cx,
184 )
185 .log_err();
186 }
187 } else if let Some((path, color)) =
188 self.external_path.as_ref().zip(style.text.color)
189 {
190 let Some(bytes) = window
191 .use_asset::<SvgAsset>(path, cx)
192 .and_then(|asset| asset.log_err())
193 else {
194 return;
195 };
196
197 window
198 .paint_svg(
199 bounds,
200 path.clone(),
201 Some(&bytes),
202 transformation,
203 color,
204 cx,
205 )
206 .log_err();
207 } else if let Some((path, color)) = self.path.as_ref().zip(style.text.color) {
208 window
209 .paint_svg(bounds, path.clone(), None, transformation, color, cx)
210 .log_err();
211 }
212 },
213 )
214 }
215}
216
217impl IntoElement for Svg {
218 type Element = Self;
219
220 fn into_element(self) -> Self::Element {
221 self
222 }
223}
224
225impl Styled for Svg {
226 fn style(&mut self) -> &mut StyleRefinement {
227 &mut self.interactivity.base_style
228 }
229}
230
231impl InteractiveElement for Svg {
232 fn interactivity(&mut self) -> &mut Interactivity {
233 &mut self.interactivity
234 }
235}
236
237#[derive(Clone, Copy, Debug, PartialEq, derive_setters::Setters)]
258#[setters(generate = false)]
259pub struct Transformation {
260 #[setters(generate, rename = "with_scaling")]
262 scale: Size<f32>,
263 #[setters(generate, rename = "with_translation")]
265 translate: Point<Pixels>,
266 #[setters(generate, rename = "with_rotation", into)]
268 rotate: Radians,
269}
270
271impl Default for Transformation {
272 fn default() -> Self {
273 Self {
274 scale: size(1.0, 1.0),
275 translate: point(px(0.0), px(0.0)),
276 rotate: radians(0.0),
277 }
278 }
279}
280
281impl Transformation {
282 pub fn scale(scale: Size<f32>) -> Self {
284 Self {
285 scale,
286 translate: point(px(0.0), px(0.0)),
287 rotate: radians(0.0),
288 }
289 }
290
291 pub fn translate(translate: Point<Pixels>) -> Self {
293 Self {
294 scale: size(1.0, 1.0),
295 translate,
296 rotate: radians(0.0),
297 }
298 }
299
300 pub fn rotate(rotate: impl Into<Radians>) -> Self {
302 let rotate = rotate.into();
303 Self {
304 scale: size(1.0, 1.0),
305 translate: point(px(0.0), px(0.0)),
306 rotate,
307 }
308 }
309
310 fn into_matrix(self, center: Point<Pixels>, scale_factor: f32) -> TransformationMatrix {
311 TransformationMatrix::unit()
313 .translate(center.scale(scale_factor) + self.translate.scale(scale_factor))
314 .rotate(self.rotate)
315 .scale(self.scale)
316 .translate(center.scale(-scale_factor))
317 }
318}
319
320enum SvgAsset {}
321
322impl Asset for SvgAsset {
323 type Source = SharedString;
324 type Output = Result<Arc<[u8]>, Arc<std::io::Error>>;
325
326 fn load(
327 source: Self::Source,
328 _cx: &mut App,
329 ) -> impl Future<Output = Self::Output> + Send + 'static {
330 async move {
331 let bytes = fs::read(Path::new(source.as_ref())).map_err(|e| Arc::new(e))?;
332 let bytes = Arc::from(bytes);
333 Ok(bytes)
334 }
335 }
336}