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, SizeConstraint, ViewContext,
 10};
 11use schemars::JsonSchema;
 12use serde_derive::Deserialize;
 13use serde_json::json;
 14use std::{borrow::Cow, ops::Range};
 15
 16pub struct Svg {
 17    path: Cow<'static, str>,
 18    color: Color,
 19}
 20
 21impl Svg {
 22    pub fn new(path: impl Into<Cow<'static, str>>) -> Self {
 23        Self {
 24            path: path.into(),
 25            color: Color::black(),
 26        }
 27    }
 28
 29    pub fn for_style<V: 'static>(style: SvgStyle) -> impl Element<V> {
 30        Self::new(style.asset)
 31            .with_color(style.color)
 32            .constrained()
 33            .with_width(style.dimensions.width)
 34            .with_height(style.dimensions.height)
 35    }
 36
 37    pub fn with_color(mut self, color: Color) -> Self {
 38        self.color = color;
 39        self
 40    }
 41}
 42
 43impl<V: 'static> Element<V> for Svg {
 44    type LayoutState = Option<usvg::Tree>;
 45    type PaintState = ();
 46
 47    fn layout(
 48        &mut self,
 49        constraint: SizeConstraint,
 50        _: &mut V,
 51        cx: &mut ViewContext<V>,
 52    ) -> (Vector2F, Self::LayoutState) {
 53        match cx.asset_cache.svg(&self.path) {
 54            Ok(tree) => {
 55                let size = constrain_size_preserving_aspect_ratio(
 56                    constraint.max,
 57                    from_usvg_rect(tree.svg_node().view_box.rect).size(),
 58                );
 59                (size, Some(tree))
 60            }
 61            Err(_error) => {
 62                #[cfg(not(any(test, feature = "test-support")))]
 63                log::error!("{}", _error);
 64                (constraint.min, None)
 65            }
 66        }
 67    }
 68
 69    fn paint(
 70        &mut self,
 71        bounds: RectF,
 72        _visible_bounds: RectF,
 73        svg: &mut Self::LayoutState,
 74        _: &mut V,
 75        cx: &mut ViewContext<V>,
 76    ) {
 77        if let Some(svg) = svg.clone() {
 78            cx.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, JsonSchema)]
118pub struct SvgStyle {
119    pub color: Color,
120    pub asset: String,
121    pub dimensions: Dimensions,
122}
123
124#[derive(Clone, Deserialize, Default, JsonSchema)]
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}