use crate::{ App, Bounds, Element, GlobalElementId, Hitbox, InteractiveElement, Interactivity, IntoElement, LayoutId, Pixels, Point, Radians, SharedString, Size, StyleRefinement, Styled, TransformationMatrix, Window, geometry::Negate as _, point, px, radians, size, }; use util::ResultExt; /// An SVG element. pub struct Svg { interactivity: Interactivity, transformation: Option, path: Option, } /// Create a new SVG element. pub fn svg() -> Svg { Svg { interactivity: Interactivity::default(), transformation: None, path: None, } } impl Svg { /// Set the path to the SVG file for this element. pub fn path(mut self, path: impl Into) -> Self { self.path = Some(path.into()); self } /// Transform the SVG element with the given transformation. /// Note that this won't effect the hitbox or layout of the element, only the rendering. pub fn with_transformation(mut self, transformation: Transformation) -> Self { self.transformation = Some(transformation); self } } impl Element for Svg { type RequestLayoutState = (); type PrepaintState = Option; fn id(&self) -> Option { self.interactivity.element_id.clone() } fn request_layout( &mut self, global_id: Option<&GlobalElementId>, window: &mut Window, cx: &mut App, ) -> (LayoutId, Self::RequestLayoutState) { let layout_id = self.interactivity .request_layout(global_id, window, cx, |style, window, cx| { window.request_layout(style, None, cx) }); (layout_id, ()) } fn prepaint( &mut self, global_id: Option<&GlobalElementId>, bounds: Bounds, _request_layout: &mut Self::RequestLayoutState, window: &mut Window, cx: &mut App, ) -> Option { self.interactivity.prepaint( global_id, bounds, bounds.size, window, cx, |_, _, hitbox, _, _| hitbox, ) } fn paint( &mut self, global_id: Option<&GlobalElementId>, bounds: Bounds, _request_layout: &mut Self::RequestLayoutState, hitbox: &mut Option, window: &mut Window, cx: &mut App, ) where Self: Sized, { self.interactivity.paint( global_id, bounds, hitbox.as_ref(), window, cx, |style, window, cx| { if let Some((path, color)) = self.path.as_ref().zip(style.text.color) { let transformation = self .transformation .as_ref() .map(|transformation| { transformation.into_matrix(bounds.center(), window.scale_factor()) }) .unwrap_or_default(); window .paint_svg(bounds, path.clone(), transformation, color, cx) .log_err(); } }, ) } } impl IntoElement for Svg { type Element = Self; fn into_element(self) -> Self::Element { self } } impl Styled for Svg { fn style(&mut self) -> &mut StyleRefinement { &mut self.interactivity.base_style } } impl InteractiveElement for Svg { fn interactivity(&mut self) -> &mut Interactivity { &mut self.interactivity } } /// A transformation to apply to an SVG element. #[derive(Clone, Copy, Debug, PartialEq)] pub struct Transformation { scale: Size, translate: Point, rotate: Radians, } impl Default for Transformation { fn default() -> Self { Self { scale: size(1.0, 1.0), translate: point(px(0.0), px(0.0)), rotate: radians(0.0), } } } impl Transformation { /// Create a new Transformation with the specified scale along each axis. pub fn scale(scale: Size) -> Self { Self { scale, translate: point(px(0.0), px(0.0)), rotate: radians(0.0), } } /// Create a new Transformation with the specified translation. pub fn translate(translate: Point) -> Self { Self { scale: size(1.0, 1.0), translate, rotate: radians(0.0), } } /// Create a new Transformation with the specified rotation in radians. pub fn rotate(rotate: impl Into) -> Self { let rotate = rotate.into(); Self { scale: size(1.0, 1.0), translate: point(px(0.0), px(0.0)), rotate, } } /// Update the scaling factor of this transformation. pub fn with_scaling(mut self, scale: Size) -> Self { self.scale = scale; self } /// Update the translation value of this transformation. pub fn with_translation(mut self, translate: Point) -> Self { self.translate = translate; self } /// Update the rotation angle of this transformation. pub fn with_rotation(mut self, rotate: impl Into) -> Self { self.rotate = rotate.into(); self } fn into_matrix(self, center: Point, scale_factor: f32) -> TransformationMatrix { //Note: if you read this as a sequence of matrix multiplications, start from the bottom TransformationMatrix::unit() .translate(center.scale(scale_factor) + self.translate.scale(scale_factor)) .rotate(self.rotate) .scale(self.scale) .translate(center.scale(scale_factor).negate()) } }