cosmic/widget/icon/
mod.rs
1mod named;
7use std::ffi::OsStr;
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, Rectangle};
19use iced_core::Rotation;
20
21pub 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
34pub fn from_name(name: impl Into<Arc<str>>) -> Named {
36 Named::new(name)
37}
38
39#[must_use]
41#[derive(Clone, Setters)]
42pub struct Icon {
43 #[setters(skip)]
44 handle: Handle,
45 class: crate::theme::Svg,
46 pub(super) size: u16,
47 content_fit: ContentFit,
48 #[setters(strip_option)]
49 width: Option<Length>,
50 #[setters(strip_option)]
51 height: Option<Length>,
52 #[setters(strip_option)]
53 rotation: Option<Rotation>,
54}
55
56impl Icon {
57 #[must_use]
58 pub fn into_svg_handle(self) -> Option<crate::widget::svg::Handle> {
59 match self.handle.data {
60 Data::Name(named) => {
61 if let Some(path) = named.path() {
62 if path.extension().is_some_and(|ext| ext == OsStr::new("svg")) {
63 return Some(iced_core::svg::Handle::from_path(path));
64 }
65 }
66 }
67
68 Data::Image(_) => (),
69 Data::Svg(handle) => return Some(handle),
70 }
71
72 None
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 .rotation(self.rotation.unwrap_or_default())
88 .content_fit(self.content_fit)
89 .into()
90 };
91
92 let from_svg = |handle| {
93 Svg::<crate::Theme>::new(handle)
94 .class(self.class.clone())
95 .width(
96 self.width
97 .unwrap_or_else(|| Length::Fixed(f32::from(self.size))),
98 )
99 .height(
100 self.height
101 .unwrap_or_else(|| Length::Fixed(f32::from(self.size))),
102 )
103 .rotation(self.rotation.unwrap_or_default())
104 .content_fit(self.content_fit)
105 .symbolic(self.handle.symbolic)
106 .into()
107 };
108
109 match self.handle.data {
110 Data::Name(named) => {
111 if let Some(path) = named.path() {
112 if path.extension().is_some_and(|ext| ext == OsStr::new("svg")) {
113 from_svg(iced_core::svg::Handle::from_path(path))
114 } else {
115 from_image(iced_core::image::Handle::from_path(path))
116 }
117 } else {
118 let bytes: &'static [u8] = &[];
119 from_svg(iced_core::svg::Handle::from_memory(bytes))
120 }
121 }
122
123 Data::Image(handle) => from_image(handle),
124 Data::Svg(handle) => from_svg(handle),
125 }
126 }
127}
128
129impl<'a, Message: 'a> From<Icon> for Element<'a, Message> {
130 fn from(icon: Icon) -> Self {
131 icon.view::<Message>()
132 }
133}
134
135pub fn draw(renderer: &mut crate::Renderer, handle: &Handle, icon_bounds: Rectangle) {
137 enum IcedHandle {
138 Svg(iced_core::svg::Handle),
139 Image(iced_core::image::Handle),
140 }
141
142 let iced_handle = match handle.clone().data {
143 Data::Name(named) => named.path().map(|path| {
144 if path.extension().is_some_and(|ext| ext == OsStr::new("svg")) {
145 IcedHandle::Svg(iced_core::svg::Handle::from_path(path))
146 } else {
147 IcedHandle::Image(iced_core::image::Handle::from_path(path))
148 }
149 }),
150
151 Data::Image(handle) => Some(IcedHandle::Image(handle)),
152 Data::Svg(handle) => Some(IcedHandle::Svg(handle)),
153 };
154
155 match iced_handle {
156 Some(IcedHandle::Svg(handle)) => iced_core::svg::Renderer::draw_svg(
157 renderer,
158 iced_core::svg::Svg::new(handle),
159 icon_bounds,
160 ),
161
162 Some(IcedHandle::Image(handle)) => {
163 iced_core::image::Renderer::draw_image(
164 renderer,
165 handle,
166 iced_core::image::FilterMethod::Linear,
167 icon_bounds,
168 iced_core::Radians::from(0),
169 1.0,
170 [0.0; 4],
171 );
172 }
173
174 None => {}
175 }
176}