svg.rs

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