Skip to main content

gpui_core/elements/
svg.rs

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