1use crate::{
2 App, Bounds, Element, GlobalElementId, Hitbox, InteractiveElement, Interactivity, IntoElement,
3 LayoutId, Pixels, Point, Radians, SharedString, Size, StyleRefinement, Styled,
4 TransformationMatrix, Window, geometry::Negate as _, point, px, radians, size,
5};
6use util::ResultExt;
7
8/// An SVG element.
9pub struct Svg {
10 interactivity: Interactivity,
11 transformation: Option<Transformation>,
12 path: Option<SharedString>,
13}
14
15/// Create a new SVG element.
16pub fn svg() -> Svg {
17 Svg {
18 interactivity: Interactivity::default(),
19 transformation: None,
20 path: None,
21 }
22}
23
24impl Svg {
25 /// Set the path to the SVG file for this element.
26 pub fn path(mut self, path: impl Into<SharedString>) -> Self {
27 self.path = Some(path.into());
28 self
29 }
30
31 /// Transform the SVG element with the given transformation.
32 /// Note that this won't effect the hitbox or layout of the element, only the rendering.
33 pub fn with_transformation(mut self, transformation: Transformation) -> Self {
34 self.transformation = Some(transformation);
35 self
36 }
37}
38
39impl Element for Svg {
40 type RequestLayoutState = ();
41 type PrepaintState = Option<Hitbox>;
42
43 fn id(&self) -> Option<crate::ElementId> {
44 self.interactivity.element_id.clone()
45 }
46
47 fn request_layout(
48 &mut self,
49 global_id: Option<&GlobalElementId>,
50 window: &mut Window,
51 cx: &mut App,
52 ) -> (LayoutId, Self::RequestLayoutState) {
53 let layout_id =
54 self.interactivity
55 .request_layout(global_id, window, cx, |style, window, cx| {
56 window.request_layout(style, None, cx)
57 });
58 (layout_id, ())
59 }
60
61 fn prepaint(
62 &mut self,
63 global_id: Option<&GlobalElementId>,
64 bounds: Bounds<Pixels>,
65 _request_layout: &mut Self::RequestLayoutState,
66 window: &mut Window,
67 cx: &mut App,
68 ) -> Option<Hitbox> {
69 self.interactivity.prepaint(
70 global_id,
71 bounds,
72 bounds.size,
73 window,
74 cx,
75 |_, _, hitbox, _, _| hitbox,
76 )
77 }
78
79 fn paint(
80 &mut self,
81 global_id: Option<&GlobalElementId>,
82 bounds: Bounds<Pixels>,
83 _request_layout: &mut Self::RequestLayoutState,
84 hitbox: &mut Option<Hitbox>,
85 window: &mut Window,
86 cx: &mut App,
87 ) where
88 Self: Sized,
89 {
90 self.interactivity.paint(
91 global_id,
92 bounds,
93 hitbox.as_ref(),
94 window,
95 cx,
96 |style, window, cx| {
97 if let Some((path, color)) = self.path.as_ref().zip(style.text.color) {
98 let transformation = self
99 .transformation
100 .as_ref()
101 .map(|transformation| {
102 transformation.into_matrix(bounds.center(), window.scale_factor())
103 })
104 .unwrap_or_default();
105
106 window
107 .paint_svg(bounds, path.clone(), transformation, color, cx)
108 .log_err();
109 }
110 },
111 )
112 }
113}
114
115impl IntoElement for Svg {
116 type Element = Self;
117
118 fn into_element(self) -> Self::Element {
119 self
120 }
121}
122
123impl Styled for Svg {
124 fn style(&mut self) -> &mut StyleRefinement {
125 &mut self.interactivity.base_style
126 }
127}
128
129impl InteractiveElement for Svg {
130 fn interactivity(&mut self) -> &mut Interactivity {
131 &mut self.interactivity
132 }
133}
134
135/// A transformation to apply to an SVG element.
136#[derive(Clone, Copy, Debug, PartialEq)]
137pub struct Transformation {
138 scale: Size<f32>,
139 translate: Point<Pixels>,
140 rotate: Radians,
141}
142
143impl Default for Transformation {
144 fn default() -> Self {
145 Self {
146 scale: size(1.0, 1.0),
147 translate: point(px(0.0), px(0.0)),
148 rotate: radians(0.0),
149 }
150 }
151}
152
153impl Transformation {
154 /// Create a new Transformation with the specified scale along each axis.
155 pub fn scale(scale: Size<f32>) -> Self {
156 Self {
157 scale,
158 translate: point(px(0.0), px(0.0)),
159 rotate: radians(0.0),
160 }
161 }
162
163 /// Create a new Transformation with the specified translation.
164 pub fn translate(translate: Point<Pixels>) -> Self {
165 Self {
166 scale: size(1.0, 1.0),
167 translate,
168 rotate: radians(0.0),
169 }
170 }
171
172 /// Create a new Transformation with the specified rotation in radians.
173 pub fn rotate(rotate: impl Into<Radians>) -> Self {
174 let rotate = rotate.into();
175 Self {
176 scale: size(1.0, 1.0),
177 translate: point(px(0.0), px(0.0)),
178 rotate,
179 }
180 }
181
182 /// Update the scaling factor of this transformation.
183 pub fn with_scaling(mut self, scale: Size<f32>) -> Self {
184 self.scale = scale;
185 self
186 }
187
188 /// Update the translation value of this transformation.
189 pub fn with_translation(mut self, translate: Point<Pixels>) -> Self {
190 self.translate = translate;
191 self
192 }
193
194 /// Update the rotation angle of this transformation.
195 pub fn with_rotation(mut self, rotate: impl Into<Radians>) -> Self {
196 self.rotate = rotate.into();
197 self
198 }
199
200 fn into_matrix(self, center: Point<Pixels>, scale_factor: f32) -> TransformationMatrix {
201 //Note: if you read this as a sequence of matrix multiplications, start from the bottom
202 TransformationMatrix::unit()
203 .translate(center.scale(scale_factor) + self.translate.scale(scale_factor))
204 .rotate(self.rotate)
205 .scale(self.scale)
206 .translate(center.scale(scale_factor).negate())
207 }
208}