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}