svg.rs

  1use super::constrain_size_preserving_aspect_ratio;
  2use crate::json::ToJson;
  3use crate::{
  4    color::Color,
  5    geometry::{
  6        rect::RectF,
  7        vector::{vec2f, Vector2F},
  8    },
  9    scene, Element, LayoutContext, SceneBuilder, SizeConstraint, View, ViewContext,
 10};
 11use serde_derive::Deserialize;
 12use serde_json::json;
 13use std::{borrow::Cow, ops::Range};
 14
 15pub struct Svg {
 16    path: Cow<'static, str>,
 17    color: Color,
 18}
 19
 20impl Svg {
 21    pub fn new(path: impl Into<Cow<'static, str>>) -> Self {
 22        Self {
 23            path: path.into(),
 24            color: Color::black(),
 25        }
 26    }
 27
 28    pub fn for_style<V: View>(style: SvgStyle) -> impl Element<V> {
 29        Self::new(style.asset)
 30            .with_color(style.color)
 31            .constrained()
 32            .with_width(style.dimensions.width)
 33            .with_height(style.dimensions.height)
 34    }
 35
 36    pub fn with_color(mut self, color: Color) -> Self {
 37        self.color = color;
 38        self
 39    }
 40}
 41
 42impl<V: View> Element<V> for Svg {
 43    type LayoutState = Option<usvg::Tree>;
 44    type PaintState = ();
 45
 46    fn layout(
 47        &mut self,
 48        constraint: SizeConstraint,
 49        _: &mut V,
 50        cx: &mut LayoutContext<V>,
 51    ) -> (Vector2F, Self::LayoutState) {
 52        match cx.asset_cache.svg(&self.path) {
 53            Ok(tree) => {
 54                let size = constrain_size_preserving_aspect_ratio(
 55                    constraint.max,
 56                    from_usvg_rect(tree.svg_node().view_box.rect).size(),
 57                );
 58                (size, Some(tree))
 59            }
 60            Err(_error) => {
 61                #[cfg(not(any(test, feature = "test-support")))]
 62                log::error!("{}", _error);
 63                (constraint.min, None)
 64            }
 65        }
 66    }
 67
 68    fn paint(
 69        &mut self,
 70        scene: &mut SceneBuilder,
 71        bounds: RectF,
 72        _visible_bounds: RectF,
 73        svg: &mut Self::LayoutState,
 74        _: &mut V,
 75        _: &mut ViewContext<V>,
 76    ) {
 77        if let Some(svg) = svg.clone() {
 78            scene.push_icon(scene::Icon {
 79                bounds,
 80                svg,
 81                path: self.path.clone(),
 82                color: self.color,
 83            });
 84        }
 85    }
 86
 87    fn rect_for_text_range(
 88        &self,
 89        _: Range<usize>,
 90        _: RectF,
 91        _: RectF,
 92        _: &Self::LayoutState,
 93        _: &Self::PaintState,
 94        _: &V,
 95        _: &ViewContext<V>,
 96    ) -> Option<RectF> {
 97        None
 98    }
 99
100    fn debug(
101        &self,
102        bounds: RectF,
103        _: &Self::LayoutState,
104        _: &Self::PaintState,
105        _: &V,
106        _: &ViewContext<V>,
107    ) -> serde_json::Value {
108        json!({
109            "type": "Svg",
110            "bounds": bounds.to_json(),
111            "path": self.path,
112            "color": self.color.to_json(),
113        })
114    }
115}
116
117#[derive(Clone, Deserialize, Default)]
118pub struct SvgStyle {
119    pub color: Color,
120    pub asset: String,
121    pub dimensions: Dimensions,
122}
123
124#[derive(Clone, Deserialize, Default)]
125pub struct Dimensions {
126    pub width: f32,
127    pub height: f32,
128}
129
130impl Dimensions {
131    pub fn to_vec(&self) -> Vector2F {
132        vec2f(self.width, self.height)
133    }
134}
135
136fn from_usvg_rect(rect: usvg::Rect) -> RectF {
137    RectF::new(
138        vec2f(rect.x() as f32, rect.y() as f32),
139        vec2f(rect.width() as f32, rect.height() as f32),
140    )
141}