1use crate::{
2 geometry::Negate as _, point, px, radians, size, Bounds, Element, GlobalElementId, Hitbox,
3 InteractiveElement, Interactivity, IntoElement, LayoutId, Pixels, Point, Radians, SharedString,
4 Size, StyleRefinement, Styled, TransformationMatrix, WindowContext,
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 cx: &mut WindowContext,
51 ) -> (LayoutId, Self::RequestLayoutState) {
52 let layout_id = self
53 .interactivity
54 .request_layout(global_id, cx, |style, cx| cx.request_layout(style, None));
55 (layout_id, ())
56 }
57
58 fn prepaint(
59 &mut self,
60 global_id: Option<&GlobalElementId>,
61 bounds: Bounds<Pixels>,
62 _request_layout: &mut Self::RequestLayoutState,
63 cx: &mut WindowContext,
64 ) -> Option<Hitbox> {
65 self.interactivity
66 .prepaint(global_id, bounds, bounds.size, cx, |_, _, hitbox, _| hitbox)
67 }
68
69 fn paint(
70 &mut self,
71 global_id: Option<&GlobalElementId>,
72 bounds: Bounds<Pixels>,
73 _request_layout: &mut Self::RequestLayoutState,
74 hitbox: &mut Option<Hitbox>,
75 cx: &mut WindowContext,
76 ) where
77 Self: Sized,
78 {
79 self.interactivity
80 .paint(global_id, bounds, hitbox.as_ref(), cx, |style, cx| {
81 if let Some((path, color)) = self.path.as_ref().zip(style.text.color) {
82 let transformation = self
83 .transformation
84 .as_ref()
85 .map(|transformation| {
86 transformation.into_matrix(bounds.center(), cx.scale_factor())
87 })
88 .unwrap_or_default();
89
90 cx.paint_svg(bounds, path.clone(), transformation, color)
91 .log_err();
92 }
93 })
94 }
95}
96
97impl IntoElement for Svg {
98 type Element = Self;
99
100 fn into_element(self) -> Self::Element {
101 self
102 }
103}
104
105impl Styled for Svg {
106 fn style(&mut self) -> &mut StyleRefinement {
107 &mut self.interactivity.base_style
108 }
109}
110
111impl InteractiveElement for Svg {
112 fn interactivity(&mut self) -> &mut Interactivity {
113 &mut self.interactivity
114 }
115}
116
117/// A transformation to apply to an SVG element.
118#[derive(Clone, Copy, Debug, PartialEq)]
119pub struct Transformation {
120 scale: Size<f32>,
121 translate: Point<Pixels>,
122 rotate: Radians,
123}
124
125impl Default for Transformation {
126 fn default() -> Self {
127 Self {
128 scale: size(1.0, 1.0),
129 translate: point(px(0.0), px(0.0)),
130 rotate: radians(0.0),
131 }
132 }
133}
134
135impl Transformation {
136 /// Create a new Transformation with the specified scale along each axis.
137 pub fn scale(scale: Size<f32>) -> Self {
138 Self {
139 scale,
140 translate: point(px(0.0), px(0.0)),
141 rotate: radians(0.0),
142 }
143 }
144
145 /// Create a new Transformation with the specified translation.
146 pub fn translate(translate: Point<Pixels>) -> Self {
147 Self {
148 scale: size(1.0, 1.0),
149 translate,
150 rotate: radians(0.0),
151 }
152 }
153
154 /// Create a new Transformation with the specified rotation in radians.
155 pub fn rotate(rotate: impl Into<Radians>) -> Self {
156 let rotate = rotate.into();
157 Self {
158 scale: size(1.0, 1.0),
159 translate: point(px(0.0), px(0.0)),
160 rotate,
161 }
162 }
163
164 /// Update the scaling factor of this transformation.
165 pub fn with_scaling(mut self, scale: Size<f32>) -> Self {
166 self.scale = scale;
167 self
168 }
169
170 /// Update the translation value of this transformation.
171 pub fn with_translation(mut self, translate: Point<Pixels>) -> Self {
172 self.translate = translate;
173 self
174 }
175
176 /// Update the rotation angle of this transformation.
177 pub fn with_rotation(mut self, rotate: impl Into<Radians>) -> Self {
178 self.rotate = rotate.into();
179 self
180 }
181
182 fn into_matrix(self, center: Point<Pixels>, scale_factor: f32) -> TransformationMatrix {
183 //Note: if you read this as a sequence of matrix multiplications, start from the bottom
184 TransformationMatrix::unit()
185 .translate(center.scale(scale_factor) + self.translate.scale(scale_factor))
186 .rotate(self.rotate)
187 .scale(self.scale)
188 .translate(center.scale(scale_factor).negate())
189 }
190}