cosmic/widget/icon/
mod.rs

1// Copyright 2022 System76 <info@system76.com>
2// SPDX-License-Identifier: MPL-2.0
3
4//! Lazily-generated SVG icon widget for Iced.
5
6mod bundle;
7mod named;
8use std::sync::Arc;
9
10pub use named::{IconFallback, Named};
11
12mod handle;
13pub use handle::{Data, Handle, from_path, from_raster_bytes, from_raster_pixels, from_svg_bytes};
14
15use crate::Element;
16use derive_setters::Setters;
17use iced::widget::{Image, Svg};
18use iced::{ContentFit, Length, Radians, Rectangle};
19use iced_core::Rotation;
20
21/// Create an [`Icon`] from a pre-existing [`Handle`]
22pub fn icon(handle: Handle) -> Icon {
23    Icon {
24        content_fit: ContentFit::Fill,
25        handle,
26        height: None,
27        size: 16,
28        class: crate::theme::Svg::default(),
29        rotation: None,
30        width: None,
31    }
32}
33
34/// Create an icon handle from its XDG icon name.
35pub fn from_name(name: impl Into<Arc<str>>) -> Named {
36    Named::new(name)
37}
38
39/// An image which may be an SVG or PNG.
40#[must_use]
41#[derive(Clone, Setters)]
42pub struct Icon {
43    #[setters(skip)]
44    handle: Handle,
45    class: crate::theme::Svg,
46    #[setters(skip)]
47    pub(super) size: u16,
48    content_fit: ContentFit,
49    #[setters(strip_option)]
50    width: Option<Length>,
51    #[setters(strip_option)]
52    height: Option<Length>,
53    #[setters(strip_option)]
54    rotation: Option<Rotation>,
55}
56
57impl Icon {
58    #[must_use]
59    pub fn into_svg_handle(self) -> Option<crate::widget::svg::Handle> {
60        match self.handle.data {
61            Data::Image(_) => (),
62            Data::Svg(handle) => return Some(handle),
63        }
64
65        None
66    }
67
68    #[must_use]
69    pub fn size(mut self, size: u16) -> Self {
70        self.size = size;
71        self
72    }
73
74    #[must_use]
75    fn view<'a, Message: 'a>(self) -> Element<'a, Message> {
76        let from_image = |handle| {
77            Image::new(handle)
78                .width(
79                    self.width
80                        .unwrap_or_else(|| Length::Fixed(f32::from(self.size))),
81                )
82                .height(
83                    self.height
84                        .unwrap_or_else(|| Length::Fixed(f32::from(self.size))),
85                )
86                .rotation(self.rotation.unwrap_or_default())
87                .content_fit(self.content_fit)
88                .into()
89        };
90
91        let from_svg = |handle| {
92            Svg::<crate::Theme>::new(handle)
93                .class(self.class.clone())
94                .width(
95                    self.width
96                        .unwrap_or_else(|| Length::Fixed(f32::from(self.size))),
97                )
98                .height(
99                    self.height
100                        .unwrap_or_else(|| Length::Fixed(f32::from(self.size))),
101                )
102                .rotation(self.rotation.unwrap_or_default())
103                .content_fit(self.content_fit)
104                .symbolic(self.handle.symbolic)
105                .into()
106        };
107
108        match self.handle.data {
109            Data::Image(handle) => from_image(handle),
110            Data::Svg(handle) => from_svg(handle),
111        }
112    }
113}
114
115impl<'a, Message: 'a> From<Icon> for Element<'a, Message> {
116    fn from(icon: Icon) -> Self {
117        icon.view::<Message>()
118    }
119}
120
121/// Draw an icon in the given bounds via the runtime's renderer.
122pub fn draw(renderer: &mut crate::Renderer, handle: &Handle, icon_bounds: Rectangle) {
123    match handle.clone().data {
124        Data::Svg(handle) => iced_core::svg::Renderer::draw_svg(
125            renderer,
126            iced_core::svg::Svg::new(handle),
127            icon_bounds,
128            icon_bounds,
129        ),
130
131        Data::Image(handle) => {
132            iced_core::image::Renderer::draw_image(
133                renderer,
134                iced_core::Image {
135                    handle,
136                    filter_method: iced_core::image::FilterMethod::Linear,
137                    rotation: Radians(0.),
138                    border_radius: [0.0; 4].into(),
139                    opacity: 1.0,
140                    snap: true,
141                },
142                icon_bounds,
143                icon_bounds,
144            );
145        }
146    }
147}