Skip to main content

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        opacity: 1.0,
29        class: crate::theme::Svg::default(),
30        rotation: None,
31        width: None,
32    }
33}
34
35/// Create an icon handle from its XDG icon name.
36pub fn from_name(name: impl Into<Arc<str>>) -> Named {
37    Named::new(name)
38}
39
40/// An image which may be an SVG or PNG.
41#[must_use]
42#[derive(Clone, Setters)]
43pub struct Icon {
44    #[setters(skip)]
45    handle: Handle,
46    class: crate::theme::Svg,
47    #[setters(skip)]
48    pub(super) size: u16,
49    opacity: f32,
50    content_fit: ContentFit,
51    #[setters(strip_option)]
52    width: Option<Length>,
53    #[setters(strip_option)]
54    height: Option<Length>,
55    #[setters(strip_option)]
56    rotation: Option<Rotation>,
57}
58
59impl Icon {
60    #[must_use]
61    pub fn into_svg_handle(self) -> Option<crate::widget::svg::Handle> {
62        match self.handle.data {
63            Data::Image(_) => (),
64            Data::Svg(handle) => return Some(handle),
65        }
66
67        None
68    }
69
70    pub fn size(mut self, size: u16) -> Self {
71        self.size = size;
72        self
73    }
74
75    #[must_use]
76    fn view<'a, Message: 'a>(self) -> Element<'a, Message> {
77        let from_image = |handle| {
78            Image::new(handle)
79                .width(
80                    self.width
81                        .unwrap_or_else(|| Length::Fixed(f32::from(self.size))),
82                )
83                .height(
84                    self.height
85                        .unwrap_or_else(|| Length::Fixed(f32::from(self.size))),
86                )
87                .opacity(self.opacity)
88                .rotation(self.rotation.unwrap_or_default())
89                .content_fit(self.content_fit)
90                .into()
91        };
92
93        let from_svg = |handle| {
94            Svg::<crate::Theme>::new(handle)
95                .class(self.class.clone())
96                .width(
97                    self.width
98                        .unwrap_or_else(|| Length::Fixed(f32::from(self.size))),
99                )
100                .height(
101                    self.height
102                        .unwrap_or_else(|| Length::Fixed(f32::from(self.size))),
103                )
104                .opacity(self.opacity)
105                .rotation(self.rotation.unwrap_or_default())
106                .content_fit(self.content_fit)
107                .symbolic(self.handle.symbolic)
108                .into()
109        };
110
111        match self.handle.data {
112            Data::Image(handle) => from_image(handle),
113            Data::Svg(handle) => from_svg(handle),
114        }
115    }
116}
117
118impl<'a, Message: 'a> From<Icon> for Element<'a, Message> {
119    fn from(icon: Icon) -> Self {
120        icon.view::<Message>()
121    }
122}
123
124/// Draw an icon in the given bounds via the runtime's renderer.
125pub fn draw(renderer: &mut crate::Renderer, handle: &Handle, icon_bounds: Rectangle) {
126    match handle.clone().data {
127        Data::Svg(handle) => iced_core::svg::Renderer::draw_svg(
128            renderer,
129            iced_core::svg::Svg::new(handle),
130            icon_bounds,
131            icon_bounds,
132        ),
133
134        Data::Image(handle) => {
135            iced_core::image::Renderer::draw_image(
136                renderer,
137                iced_core::Image {
138                    handle,
139                    filter_method: iced_core::image::FilterMethod::Linear,
140                    rotation: Radians(0.),
141                    border_radius: [0.0; 4].into(),
142                    opacity: 1.0,
143                    snap: true,
144                },
145                icon_bounds,
146                icon_bounds,
147            );
148        }
149    }
150}