feat(icon): optimize & bundle icons with crabtime for non-unix platforms

This commit is contained in:
Michael Aaron Murphy 2025-11-11 23:02:57 +01:00 committed by Michael Murphy
parent ce0868582b
commit 639326fcc3
27 changed files with 128 additions and 189 deletions

62
src/widget/icon/bundle.rs Normal file
View file

@ -0,0 +1,62 @@
// Copyright 2025 System76 <info@system76.com>
// SPDX-License-Identifier: MPL-2.0
//! Embedded icons for platforms which do not support icon themes yet.
/// Icon bundling is not enabled on unix platforms.
pub fn get(icon_name: &str) -> Option<super::Data> {
None
}
#[cfg(not(unix))]
/// Get a bundled icon on non-unix platforms.
pub fn get(icon_name: &str) -> Option<super::Data> {
ICONS
.get(icon_name)
.map(|bytes| super::Data::Svg(crate::iced::widget::svg::Handle::from_memory(*bytes)))
}
#[cfg(not(unix))]
#[crabtime::expression]
fn comptime_icon_bundler() -> String {
let manifest_dir = std::path::Path::new(crabtime::WORKSPACE_PATH);
let icon_paths = [
"cosmic-icons/freedesktop/scalable",
"cosmic-icons/extra/scalable",
];
let key_value_assignments = icon_paths
.into_iter()
.map(|path| manifest_dir.join(path))
.inspect(|icon_path| assert!(icon_path.exists(), "path = {icon_path:?}"))
.map(|icon_path| std::fs::read_dir(icon_path).unwrap())
.flat_map(|dir| {
dir.flat_map(|entry| entry.unwrap().path().read_dir().unwrap())
.map(|entry| {
let entry = entry.unwrap();
let path = entry.path().canonicalize().unwrap();
let file_name = path.file_stem().unwrap().to_str().unwrap().to_owned();
let path = path.into_os_string().into_string().unwrap();
(file_name, path)
})
})
.fold(
std::collections::BTreeMap::new(),
|mut set, (name, path)| {
set.insert(name, path);
set
},
)
.into_iter()
.fold(String::new(), |mut output, (name, path)| {
output.push_str(&format!(" \"{name}\" => include_bytes!(\"{path}\"),\n"));
output
});
["phf::phf_map!(\n", &key_value_assignments, ")"].concat()
}
#[cfg(not(unix))]
static ICONS: phf::Map<&'static str, &'static [u8]> = {
comptime_icon_bundler! {}
};

View file

@ -1,7 +1,7 @@
// Copyright 2023 System76 <info@system76.com>
// SPDX-License-Identifier: MPL-2.0
use super::{Icon, Named};
use super::Icon;
use crate::widget::{image, svg};
use std::borrow::Cow;
use std::ffi::OsStr;
@ -26,7 +26,7 @@ impl Handle {
#[must_use]
#[derive(Clone, Debug, Hash)]
pub enum Data {
Name(Named),
// Name(Named),
Image(image::Handle),
Svg(svg::Handle),
}
@ -94,7 +94,7 @@ pub fn from_raster_pixels(
/// Create a SVG handle from memory.
pub fn from_svg_bytes(bytes: impl Into<Cow<'static, [u8]>>) -> Handle {
Handle {
symbolic: false,
symbolic: true,
data: Data::Svg(svg::Handle::from_memory(bytes)),
}
}

View file

@ -3,8 +3,8 @@
//! Lazily-generated SVG icon widget for Iced.
mod bundle;
mod named;
use std::ffi::OsStr;
use std::sync::Arc;
pub use named::{IconFallback, Named};
@ -58,14 +58,6 @@ impl Icon {
#[must_use]
pub fn into_svg_handle(self) -> Option<crate::widget::svg::Handle> {
match self.handle.data {
Data::Name(named) => {
if let Some(path) = named.path() {
if path.extension().is_some_and(|ext| ext == OsStr::new("svg")) {
return Some(iced_core::svg::Handle::from_path(path));
}
}
}
Data::Image(_) => (),
Data::Svg(handle) => return Some(handle),
}
@ -76,12 +68,6 @@ impl Icon {
#[must_use]
pub fn size(mut self, size: u16) -> Self {
self.size = size;
// ensures correct icon size variant selection
if let Data::Name(named) = &self.handle.data {
let mut new_named = named.clone();
new_named.size = Some(size);
self.handle = new_named.handle();
}
self
}
@ -120,19 +106,6 @@ impl Icon {
};
match self.handle.data {
Data::Name(named) => {
if let Some(path) = named.path() {
if path.extension().is_some_and(|ext| ext == OsStr::new("svg")) {
from_svg(iced_core::svg::Handle::from_path(path))
} else {
from_image(iced_core::image::Handle::from_path(path))
}
} else {
let bytes: &'static [u8] = &[];
from_svg(iced_core::svg::Handle::from_memory(bytes))
}
}
Data::Image(handle) => from_image(handle),
Data::Svg(handle) => from_svg(handle),
}
@ -147,32 +120,14 @@ impl<'a, Message: 'a> From<Icon> for Element<'a, Message> {
/// Draw an icon in the given bounds via the runtime's renderer.
pub fn draw(renderer: &mut crate::Renderer, handle: &Handle, icon_bounds: Rectangle) {
enum IcedHandle {
Svg(iced_core::svg::Handle),
Image(iced_core::image::Handle),
}
let iced_handle = match handle.clone().data {
Data::Name(named) => named.path().map(|path| {
if path.extension().is_some_and(|ext| ext == OsStr::new("svg")) {
IcedHandle::Svg(iced_core::svg::Handle::from_path(path))
} else {
IcedHandle::Image(iced_core::image::Handle::from_path(path))
}
}),
Data::Image(handle) => Some(IcedHandle::Image(handle)),
Data::Svg(handle) => Some(IcedHandle::Svg(handle)),
};
match iced_handle {
Some(IcedHandle::Svg(handle)) => iced_core::svg::Renderer::draw_svg(
match handle.clone().data {
Data::Svg(handle) => iced_core::svg::Renderer::draw_svg(
renderer,
iced_core::svg::Svg::new(handle),
icon_bounds,
),
Some(IcedHandle::Image(handle)) => {
Data::Image(handle) => {
iced_core::image::Renderer::draw_image(
renderer,
handle,
@ -183,7 +138,5 @@ pub fn draw(renderer: &mut crate::Renderer, handle: &Handle, icon_bounds: Rectan
[0.0; 4],
);
}
None => {}
}
}

View file

@ -2,7 +2,7 @@
// SPDX-License-Identifier: MPL-2.0
use super::{Handle, Icon};
use std::{borrow::Cow, path::PathBuf, sync::Arc};
use std::{borrow::Cow, ffi::OsStr, path::PathBuf, sync::Arc};
#[derive(Debug, Clone, Default, Hash)]
/// Fallback icon to use if the icon was not found.
@ -116,9 +116,21 @@ impl Named {
#[inline]
pub fn handle(self) -> Handle {
let name = self.name.clone();
Handle {
symbolic: self.symbolic,
data: super::Data::Name(self),
data: if let Some(path) = self.path() {
if path.extension().is_some_and(|ext| ext == OsStr::new("svg")) {
super::Data::Svg(iced_core::svg::Handle::from_path(path))
} else {
super::Data::Image(iced_core::image::Handle::from_path(path))
}
} else {
super::bundle::get(&name).unwrap_or_else(|| {
let bytes: &'static [u8] = &[];
super::Data::Svg(iced_core::svg::Handle::from_memory(bytes))
})
},
}
}