//! Svg widgets display vector graphics in your application. //! //! # Example //! ```no_run //! # mod iced { pub mod widget { pub use iced_widget::*; } } //! # pub type State = (); //! # pub type Element<'a, Message> = iced_widget::core::Element<'a, Message, iced_widget::Theme, iced_widget::Renderer>; //! use iced::widget::svg; //! //! enum Message { //! // ... //! } //! //! fn view(state: &State) -> Element<'_, Message> { //! svg("tiger.svg").into() //! } //! ``` use crate::core::layout; use crate::core::mouse; use crate::core::renderer; use crate::core::svg; use crate::core::widget::Tree; use crate::core::{ Color, ContentFit, Element, Layout, Length, Point, Rectangle, Rotation, Size, Theme, Vector, Widget, }; use std::path::PathBuf; pub use crate::core::svg::Handle; /// A vector graphics image. /// /// An [`Svg`] image resizes smoothly without losing any quality. /// /// [`Svg`] images can have a considerable rendering cost when resized, /// specially when they are complex. /// /// # Example /// ```no_run /// # mod iced { pub mod widget { pub use iced_widget::*; } } /// # pub type State = (); /// # pub type Element<'a, Message> = iced_widget::core::Element<'a, Message, iced_widget::Theme, iced_widget::Renderer>; /// use iced::widget::svg; /// /// enum Message { /// // ... /// } /// /// fn view(state: &State) -> Element<'_, Message> { /// svg("tiger.svg").into() /// } /// ``` #[allow(missing_debug_implementations)] pub struct Svg<'a, Theme = crate::Theme> where Theme: Catalog, { handle: Handle, width: Length, height: Length, content_fit: ContentFit, class: Theme::Class<'a>, rotation: Rotation, opacity: f32, } impl<'a, Theme> Svg<'a, Theme> where Theme: Catalog, { /// Creates a new [`Svg`] from the given [`Handle`]. pub fn new(handle: impl Into) -> Self { Svg { handle: handle.into(), width: Length::Fill, height: Length::Shrink, content_fit: ContentFit::Contain, class: Theme::default(), rotation: Rotation::default(), opacity: 1.0, } } /// Creates a new [`Svg`] that will display the contents of the file at the /// provided path. #[must_use] pub fn from_path(path: impl Into) -> Self { Self::new(Handle::from_path(path)) } /// Sets the width of the [`Svg`]. #[must_use] pub fn width(mut self, width: impl Into) -> Self { self.width = width.into(); self } /// Sets the height of the [`Svg`]. #[must_use] pub fn height(mut self, height: impl Into) -> Self { self.height = height.into(); self } /// Sets the [`ContentFit`] of the [`Svg`]. /// /// Defaults to [`ContentFit::Contain`] #[must_use] pub fn content_fit(self, content_fit: ContentFit) -> Self { Self { content_fit, ..self } } /// Sets the style of the [`Svg`]. #[must_use] pub fn style(mut self, style: impl Fn(&Theme, Status) -> Style + 'a) -> Self where Theme::Class<'a>: From>, { self.class = (Box::new(style) as StyleFn<'a, Theme>).into(); self } /// Sets the style class of the [`Svg`]. #[cfg(feature = "advanced")] #[must_use] pub fn class(mut self, class: impl Into>) -> Self { self.class = class.into(); self } /// Applies the given [`Rotation`] to the [`Svg`]. pub fn rotation(mut self, rotation: impl Into) -> Self { self.rotation = rotation.into(); self } /// Sets the opacity of the [`Svg`]. /// /// It should be in the [0.0, 1.0] range—`0.0` meaning completely transparent, /// and `1.0` meaning completely opaque. pub fn opacity(mut self, opacity: impl Into) -> Self { self.opacity = opacity.into(); self } } impl Widget for Svg<'_, Theme> where Renderer: svg::Renderer, Theme: Catalog, { fn size(&self) -> Size { Size { width: self.width, height: self.height, } } fn layout( &self, _tree: &mut Tree, renderer: &Renderer, limits: &layout::Limits, ) -> layout::Node { // The raw w/h of the underlying image let Size { width, height } = renderer.measure_svg(&self.handle); let image_size = Size::new(width as f32, height as f32); // The rotated size of the svg let rotated_size = self.rotation.apply(image_size); // The size to be available to the widget prior to `Shrink`ing let raw_size = limits.resolve(self.width, self.height, rotated_size); // The uncropped size of the image when fit to the bounds above let full_size = self.content_fit.fit(rotated_size, raw_size); // Shrink the widget to fit the resized image, if requested let final_size = Size { width: match self.width { Length::Shrink => f32::min(raw_size.width, full_size.width), _ => raw_size.width, }, height: match self.height { Length::Shrink => f32::min(raw_size.height, full_size.height), _ => raw_size.height, }, }; layout::Node::new(final_size) } fn draw( &self, _state: &Tree, renderer: &mut Renderer, theme: &Theme, _style: &renderer::Style, layout: Layout<'_>, cursor: mouse::Cursor, _viewport: &Rectangle, ) { let Size { width, height } = renderer.measure_svg(&self.handle); let image_size = Size::new(width as f32, height as f32); let rotated_size = self.rotation.apply(image_size); let bounds = layout.bounds(); let adjusted_fit = self.content_fit.fit(rotated_size, bounds.size()); let scale = Vector::new( adjusted_fit.width / rotated_size.width, adjusted_fit.height / rotated_size.height, ); let final_size = image_size * scale; let position = match self.content_fit { ContentFit::None => Point::new( bounds.x + (rotated_size.width - adjusted_fit.width) / 2.0, bounds.y + (rotated_size.height - adjusted_fit.height) / 2.0, ), _ => Point::new( bounds.center_x() - final_size.width / 2.0, bounds.center_y() - final_size.height / 2.0, ), }; let drawing_bounds = Rectangle::new(position, final_size); let is_mouse_over = cursor.is_over(bounds); let status = if is_mouse_over { Status::Hovered } else { Status::Idle }; let style = theme.style(&self.class, status); let render = |renderer: &mut Renderer| { renderer.draw_svg( svg::Svg { handle: self.handle.clone(), color: style.color, rotation: self.rotation.radians(), opacity: self.opacity, }, drawing_bounds, ); }; if adjusted_fit.width > bounds.width || adjusted_fit.height > bounds.height { renderer.with_layer(bounds, render); } else { render(renderer); } } } impl<'a, Message, Theme, Renderer> From> for Element<'a, Message, Theme, Renderer> where Theme: Catalog + 'a, Renderer: svg::Renderer + 'a, { fn from(icon: Svg<'a, Theme>) -> Element<'a, Message, Theme, Renderer> { Element::new(icon) } } /// The possible status of an [`Svg`]. #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum Status { /// The [`Svg`] is idle. Idle, /// The [`Svg`] is being hovered. Hovered, } /// The appearance of an [`Svg`]. #[derive(Debug, Clone, Copy, PartialEq, Default)] pub struct Style { /// The [`Color`] filter of an [`Svg`]. /// /// Useful for coloring a symbolic icon. /// /// `None` keeps the original color. pub color: Option, } /// The theme catalog of an [`Svg`]. pub trait Catalog { /// The item class of the [`Catalog`]. type Class<'a>; /// The default class produced by the [`Catalog`]. fn default<'a>() -> Self::Class<'a>; /// The [`Style`] of a class with the given status. fn style(&self, class: &Self::Class<'_>, status: Status) -> Style; } impl Catalog for Theme { type Class<'a> = StyleFn<'a, Self>; fn default<'a>() -> Self::Class<'a> { Box::new(|_theme, _status| Style::default()) } fn style(&self, class: &Self::Class<'_>, status: Status) -> Style { class(self, status) } } /// A styling function for an [`Svg`]. /// /// This is just a boxed closure: `Fn(&Theme, Status) -> Style`. pub type StyleFn<'a, Theme> = Box Style + 'a>; impl From