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