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 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: View>(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: View> 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 LayoutContext<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 scene: &mut SceneBuilder,
72 bounds: RectF,
73 _visible_bounds: RectF,
74 svg: &mut Self::LayoutState,
75 _: &mut V,
76 _: &mut ViewContext<V>,
77 ) {
78 if let Some(svg) = svg.clone() {
79 scene.push_icon(scene::Icon {
80 bounds,
81 svg,
82 path: self.path.clone(),
83 color: self.color,
84 });
85 }
86 }
87
88 fn rect_for_text_range(
89 &self,
90 _: Range<usize>,
91 _: RectF,
92 _: RectF,
93 _: &Self::LayoutState,
94 _: &Self::PaintState,
95 _: &V,
96 _: &ViewContext<V>,
97 ) -> Option<RectF> {
98 None
99 }
100
101 fn debug(
102 &self,
103 bounds: RectF,
104 _: &Self::LayoutState,
105 _: &Self::PaintState,
106 _: &V,
107 _: &ViewContext<V>,
108 ) -> serde_json::Value {
109 json!({
110 "type": "Svg",
111 "bounds": bounds.to_json(),
112 "path": self.path,
113 "color": self.color.to_json(),
114 })
115 }
116}
117
118#[derive(Clone, Deserialize, Default, JsonSchema)]
119pub struct SvgStyle {
120 pub color: Color,
121 pub asset: String,
122 pub dimensions: Dimensions,
123}
124
125#[derive(Clone, Deserialize, Default, JsonSchema)]
126pub struct Dimensions {
127 pub width: f32,
128 pub height: f32,
129}
130
131impl Dimensions {
132 pub fn to_vec(&self) -> Vector2F {
133 vec2f(self.width, self.height)
134 }
135}
136
137fn from_usvg_rect(rect: usvg::Rect) -> RectF {
138 RectF::new(
139 vec2f(rect.x() as f32, rect.y() as f32),
140 vec2f(rect.width() as f32, rect.height() as f32),
141 )
142}