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