svg.rs

  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}