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