iced_native/widget/
svg.rs1use crate::layout;
3use crate::renderer;
4use crate::svg;
5use crate::widget::Tree;
6use crate::{
7 ContentFit, Element, Layout, Length, Point, Rectangle, Size, Vector, Widget,
8};
9
10use std::path::PathBuf;
11
12pub use iced_style::svg::{Appearance, StyleSheet};
13pub use svg::Handle;
14
15#[allow(missing_debug_implementations)]
22pub struct Svg<Renderer>
23where
24 Renderer: svg::Renderer,
25 Renderer::Theme: StyleSheet,
26{
27 handle: Handle,
28 width: Length,
29 height: Length,
30 content_fit: ContentFit,
31 style: <Renderer::Theme as StyleSheet>::Style,
32}
33
34impl<Renderer> Svg<Renderer>
35where
36 Renderer: svg::Renderer,
37 Renderer::Theme: StyleSheet,
38{
39 pub fn new(handle: impl Into<Handle>) -> Self {
41 Svg {
42 handle: handle.into(),
43 width: Length::Fill,
44 height: Length::Shrink,
45 content_fit: ContentFit::Contain,
46 style: Default::default(),
47 }
48 }
49
50 #[must_use]
53 pub fn from_path(path: impl Into<PathBuf>) -> Self {
54 Self::new(Handle::from_path(path))
55 }
56
57 #[must_use]
59 pub fn width(mut self, width: impl Into<Length>) -> Self {
60 self.width = width.into();
61 self
62 }
63
64 #[must_use]
66 pub fn height(mut self, height: impl Into<Length>) -> Self {
67 self.height = height.into();
68 self
69 }
70
71 #[must_use]
75 pub fn content_fit(self, content_fit: ContentFit) -> Self {
76 Self {
77 content_fit,
78 ..self
79 }
80 }
81
82 #[must_use]
84 pub fn style(
85 mut self,
86 style: <Renderer::Theme as StyleSheet>::Style,
87 ) -> Self {
88 self.style = style;
89 self
90 }
91}
92
93impl<Message, Renderer> Widget<Message, Renderer> for Svg<Renderer>
94where
95 Renderer: svg::Renderer,
96 Renderer::Theme: iced_style::svg::StyleSheet,
97{
98 fn width(&self) -> Length {
99 self.width
100 }
101
102 fn height(&self) -> Length {
103 self.height
104 }
105
106 fn layout(
107 &self,
108 renderer: &Renderer,
109 limits: &layout::Limits,
110 ) -> layout::Node {
111 let Size { width, height } = renderer.dimensions(&self.handle);
113 let image_size = Size::new(width as f32, height as f32);
114
115 let raw_size = limits
117 .width(self.width)
118 .height(self.height)
119 .resolve(image_size);
120
121 let full_size = self.content_fit.fit(image_size, raw_size);
123
124 let final_size = Size {
126 width: match self.width {
127 Length::Shrink => f32::min(raw_size.width, full_size.width),
128 _ => raw_size.width,
129 },
130 height: match self.height {
131 Length::Shrink => f32::min(raw_size.height, full_size.height),
132 _ => raw_size.height,
133 },
134 };
135
136 layout::Node::new(final_size)
137 }
138
139 fn draw(
140 &self,
141 _state: &Tree,
142 renderer: &mut Renderer,
143 theme: &Renderer::Theme,
144 _style: &renderer::Style,
145 layout: Layout<'_>,
146 _cursor_position: Point,
147 _viewport: &Rectangle,
148 ) {
149 let Size { width, height } = renderer.dimensions(&self.handle);
150 let image_size = Size::new(width as f32, height as f32);
151
152 let bounds = layout.bounds();
153 let adjusted_fit = self.content_fit.fit(image_size, bounds.size());
154
155 let render = |renderer: &mut Renderer| {
156 let offset = Vector::new(
157 (bounds.width - adjusted_fit.width).max(0.0) / 2.0,
158 (bounds.height - adjusted_fit.height).max(0.0) / 2.0,
159 );
160
161 let drawing_bounds = Rectangle {
162 width: adjusted_fit.width,
163 height: adjusted_fit.height,
164 ..bounds
165 };
166
167 let appearance = theme.appearance(&self.style);
168
169 renderer.draw(
170 self.handle.clone(),
171 appearance.color,
172 drawing_bounds + offset,
173 );
174 };
175
176 if adjusted_fit.width > bounds.width
177 || adjusted_fit.height > bounds.height
178 {
179 renderer.with_layer(bounds, render);
180 } else {
181 render(renderer);
182 }
183 }
184}
185
186impl<'a, Message, Renderer> From<Svg<Renderer>>
187 for Element<'a, Message, Renderer>
188where
189 Renderer: svg::Renderer + 'a,
190 Renderer::Theme: iced_style::svg::StyleSheet,
191{
192 fn from(icon: Svg<Renderer>) -> Element<'a, Message, Renderer> {
193 Element::new(icon)
194 }
195}