feat(icon): optimize & bundle icons with crabtime for non-unix platforms
This commit is contained in:
parent
ce0868582b
commit
639326fcc3
27 changed files with 128 additions and 189 deletions
|
|
@ -132,10 +132,6 @@ impl<'a, Message: Clone + 'static> From<Button<'a, Message>> for Element<'a, Mes
|
|||
fn from(mut builder: Button<'a, Message>) -> Element<'a, Message> {
|
||||
let mut content = Vec::with_capacity(2);
|
||||
|
||||
if let icon::Data::Name(ref mut named) = builder.variant.handle.data {
|
||||
named.size = Some(builder.icon_size);
|
||||
}
|
||||
|
||||
content.push(
|
||||
crate::widget::icon(builder.variant.handle.clone())
|
||||
.size(builder.icon_size)
|
||||
|
|
|
|||
|
|
@ -91,21 +91,15 @@ impl<Message> Button<'_, Message> {
|
|||
|
||||
impl<'a, Message: Clone + 'static> From<Button<'a, Message>> for Element<'a, Message> {
|
||||
fn from(mut builder: Button<'a, Message>) -> Element<'a, Message> {
|
||||
let trailing_icon = builder.variant.trailing_icon.map(|mut i| {
|
||||
if let icon::Data::Name(ref mut named) = i.data {
|
||||
named.size = Some(builder.icon_size);
|
||||
}
|
||||
let trailing_icon = builder
|
||||
.variant
|
||||
.trailing_icon
|
||||
.map(crate::widget::icon::Handle::icon);
|
||||
|
||||
i.icon()
|
||||
});
|
||||
|
||||
let leading_icon = builder.variant.leading_icon.map(|mut i| {
|
||||
if let icon::Data::Name(ref mut named) = i.data {
|
||||
named.size = Some(builder.icon_size);
|
||||
}
|
||||
|
||||
i.icon()
|
||||
});
|
||||
let leading_icon = builder
|
||||
.variant
|
||||
.leading_icon
|
||||
.map(crate::widget::icon::Handle::icon);
|
||||
|
||||
let label: Option<Element<'_, _>> = (!builder.label.is_empty()).then(|| {
|
||||
let font = crate::font::Font {
|
||||
|
|
|
|||
|
|
@ -230,10 +230,6 @@ impl<Item: Clone + PartialEq + 'static> State<Item> {
|
|||
pub fn new() -> Self {
|
||||
Self {
|
||||
icon: match icon::from_name("pan-down-symbolic").size(16).handle().data {
|
||||
icon::Data::Name(named) => named
|
||||
.path()
|
||||
.filter(|path| path.extension().is_some_and(|ext| ext == OsStr::new("svg")))
|
||||
.map(iced_core::svg::Handle::from_path),
|
||||
icon::Data::Svg(handle) => Some(handle),
|
||||
icon::Data::Image(_) => None,
|
||||
},
|
||||
|
|
|
|||
|
|
@ -407,10 +407,6 @@ impl State {
|
|||
pub fn new() -> Self {
|
||||
Self {
|
||||
icon: match icon::from_name("pan-down-symbolic").size(16).handle().data {
|
||||
icon::Data::Name(named) => named
|
||||
.path()
|
||||
.filter(|path| path.extension().is_some_and(|ext| ext == OsStr::new("svg")))
|
||||
.map(iced_core::svg::Handle::from_path),
|
||||
icon::Data::Svg(handle) => Some(handle),
|
||||
icon::Data::Image(_) => None,
|
||||
},
|
||||
|
|
|
|||
|
|
@ -449,25 +449,12 @@ impl<'a, Message: Clone + 'static> HeaderBar<'a, Message> {
|
|||
fn window_controls(&mut self) -> Element<'a, Message> {
|
||||
macro_rules! icon {
|
||||
($name:expr, $size:expr, $on_press:expr) => {{
|
||||
#[cfg(target_os = "linux")]
|
||||
let icon = {
|
||||
widget::icon::from_name($name)
|
||||
.apply(widget::button::icon)
|
||||
.padding(8)
|
||||
};
|
||||
|
||||
#[cfg(not(target_os = "linux"))]
|
||||
let icon = {
|
||||
widget::icon::from_svg_bytes(include_bytes!(concat!(
|
||||
"../../res/icons/",
|
||||
$name,
|
||||
".svg"
|
||||
)))
|
||||
.symbolic(true)
|
||||
.apply(widget::button::icon)
|
||||
.padding(8)
|
||||
};
|
||||
|
||||
icon.class(crate::theme::Button::HeaderBar)
|
||||
.selected(self.focused)
|
||||
.icon_size($size)
|
||||
|
|
|
|||
62
src/widget/icon/bundle.rs
Normal file
62
src/widget/icon/bundle.rs
Normal 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! {}
|
||||
};
|
||||
|
|
@ -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)),
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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 => {}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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))
|
||||
})
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -28,18 +28,12 @@ pub const fn nav_bar_toggle<Message>() -> NavBarToggle<Message> {
|
|||
impl<Message: 'static + Clone> From<NavBarToggle<Message>> for Element<'_, Message> {
|
||||
fn from(nav_bar_toggle: NavBarToggle<Message>) -> Self {
|
||||
let icon = if nav_bar_toggle.active {
|
||||
widget::icon::from_svg_bytes(
|
||||
&include_bytes!("../../res/icons/navbar-open-symbolic.svg")[..],
|
||||
)
|
||||
.symbolic(true)
|
||||
"navbar-open-symbolic"
|
||||
} else {
|
||||
widget::icon::from_svg_bytes(
|
||||
&include_bytes!("../../res/icons/navbar-closed-symbolic.svg")[..],
|
||||
)
|
||||
.symbolic(true)
|
||||
"navbar-closed-symbolic"
|
||||
};
|
||||
|
||||
widget::button::icon(icon)
|
||||
widget::button::icon(widget::icon::from_name(icon))
|
||||
.padding([8, 16])
|
||||
.on_press_maybe(nav_bar_toggle.on_toggle)
|
||||
.selected(nav_bar_toggle.selected)
|
||||
|
|
|
|||
|
|
@ -115,7 +115,7 @@ where
|
|||
}
|
||||
}
|
||||
|
||||
fn increment<T>(value: T, step: T, min: T, max: T) -> T
|
||||
fn increment<T>(value: T, step: T, _min: T, max: T) -> T
|
||||
where
|
||||
T: Copy + Sub<Output = T> + Add<Output = T> + PartialOrd,
|
||||
{
|
||||
|
|
@ -126,7 +126,7 @@ where
|
|||
}
|
||||
}
|
||||
|
||||
fn decrement<T>(value: T, step: T, min: T, max: T) -> T
|
||||
fn decrement<T>(value: T, step: T, min: T, _max: T) -> T
|
||||
where
|
||||
T: Copy + Sub<Output = T> + Add<Output = T> + PartialOrd,
|
||||
{
|
||||
|
|
@ -149,25 +149,25 @@ where
|
|||
}
|
||||
}
|
||||
}
|
||||
macro_rules! make_button {
|
||||
($spin_button:expr, $icon:expr, $operation:expr) => {{
|
||||
#[cfg(target_os = "linux")]
|
||||
let button = icon::from_name($icon);
|
||||
|
||||
#[cfg(not(target_os = "linux"))]
|
||||
let button =
|
||||
icon::from_svg_bytes(include_bytes!(concat!["../../res/icons/", $icon, ".svg"]))
|
||||
.symbolic(true);
|
||||
|
||||
button
|
||||
.apply(button::icon)
|
||||
.on_press(($spin_button.on_press)($operation(
|
||||
$spin_button.value,
|
||||
$spin_button.step,
|
||||
$spin_button.min,
|
||||
$spin_button.max,
|
||||
)))
|
||||
}};
|
||||
fn make_button<'a, T, Message>(
|
||||
spin_button: &SpinButton<'a, T, Message>,
|
||||
icon: &'static str,
|
||||
operation: fn(T, T, T, T) -> T,
|
||||
) -> Element<'a, Message>
|
||||
where
|
||||
Message: Clone + 'static,
|
||||
T: Copy + Sub<Output = T> + Add<Output = T> + PartialOrd,
|
||||
{
|
||||
icon::from_name(icon)
|
||||
.apply(button::icon)
|
||||
.on_press((spin_button.on_press)(operation(
|
||||
spin_button.value,
|
||||
spin_button.step,
|
||||
spin_button.min,
|
||||
spin_button.max,
|
||||
)))
|
||||
.into()
|
||||
}
|
||||
|
||||
fn horizontal_variant<T, Message>(spin_button: SpinButton<'_, T, Message>) -> Element<'_, Message>
|
||||
|
|
@ -175,8 +175,8 @@ where
|
|||
Message: Clone + 'static,
|
||||
T: Copy + Sub<Output = T> + Add<Output = T> + PartialOrd,
|
||||
{
|
||||
let decrement_button = make_button!(spin_button, "list-remove-symbolic", decrement);
|
||||
let increment_button = make_button!(spin_button, "list-add-symbolic", increment);
|
||||
let decrement_button = make_button(&spin_button, "list-remove-symbolic", decrement);
|
||||
let increment_button = make_button(&spin_button, "list-add-symbolic", increment);
|
||||
|
||||
let label = text::body(spin_button.label)
|
||||
.apply(container)
|
||||
|
|
@ -198,8 +198,8 @@ where
|
|||
Message: Clone + 'static,
|
||||
T: Copy + Sub<Output = T> + Add<Output = T> + PartialOrd,
|
||||
{
|
||||
let decrement_button = make_button!(spin_button, "list-remove-symbolic", decrement);
|
||||
let increment_button = make_button!(spin_button, "list-add-symbolic", increment);
|
||||
let decrement_button = make_button(&spin_button, "list-remove-symbolic", decrement);
|
||||
let increment_button = make_button(&spin_button, "list-add-symbolic", increment);
|
||||
|
||||
let label = text::body(spin_button.label)
|
||||
.apply(container)
|
||||
|
|
|
|||
|
|
@ -33,20 +33,11 @@ impl<'a, Message: 'static + Clone> Warning<'a, Message> {
|
|||
pub fn into_widget(self) -> widget::Container<'a, Message, crate::Theme, Renderer> {
|
||||
let label = widget::container(crate::widget::text(self.message)).width(Length::Fill);
|
||||
|
||||
#[cfg(target_os = "linux")]
|
||||
let close_button = icon::from_name("window-close-symbolic")
|
||||
.size(16)
|
||||
.apply(widget::button::icon)
|
||||
.on_press_maybe(self.on_close);
|
||||
|
||||
#[cfg(not(target_os = "linux"))]
|
||||
let close_button =
|
||||
icon::from_svg_bytes(include_bytes!("../../res/icons/window-close-symbolic.svg"))
|
||||
.symbolic(true)
|
||||
.apply(widget::button::icon)
|
||||
.icon_size(16)
|
||||
.on_press_maybe(self.on_close);
|
||||
|
||||
widget::row::with_capacity(2)
|
||||
.push(label)
|
||||
.push(close_button)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue