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
13pub 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#[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 pub fn path(mut self, path: impl Into<SharedString>) -> Self {
37 self.path = Some(path.into());
38 self
39 }
40
41 pub fn data(mut self, data: &[u8]) -> Self {
44 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 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#[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 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 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 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 pub fn with_scaling(mut self, scale: Size<f32>) -> Self {
233 self.scale = scale;
234 self
235 }
236
237 pub fn with_translation(mut self, translate: Point<Pixels>) -> Self {
239 self.translate = translate;
240 self
241 }
242
243 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 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}