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

3
.gitmodules vendored
View file

@ -2,3 +2,6 @@
path = iced
url = https://github.com/pop-os/iced.git
branch = master
[submodule "icon-theme"]
path = cosmic-icons
url = https://github.com/pop-os/cosmic-icons

View file

@ -107,6 +107,8 @@ cctk = { git = "https://github.com/pop-os/cosmic-protocols", package = "cosmic-c
chrono = "0.4.42"
cosmic-config = { path = "cosmic-config" }
cosmic-settings-config = { git = "https://github.com/pop-os/cosmic-settings-daemon", optional = true }
# Compile-time generation of code
crabtime = "1.1.4"
# Internationalization
i18n-embed = { version = "0.16.0", features = [
"fluent-system",
@ -152,6 +154,10 @@ freedesktop-icons = { package = "cosmic-freedesktop-icons", git = "https://githu
freedesktop-desktop-entry = { version = "0.7.14", optional = true }
shlex = { version = "1.3.0", optional = true }
[target.'cfg(not(unix))'.dependencies]
# Used to embed bundled icons for non-unix platforms.
phf = { version = "0.13.1", features = ["macros"] }
[dependencies.cosmic-theme]
path = "cosmic-theme"
@ -222,4 +228,3 @@ dirs = "6.0.0"
[dev-dependencies]
tempfile = "3.13.0"

1
cosmic-icons Submodule

@ -0,0 +1 @@
Subproject commit 70b07582e24ec2114672256b9657ca80670bca8a

View file

@ -1,4 +0,0 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M10 4C10 3.446 9.554 3 9 3H4C3.446 3 3 3.446 3 4C3 4.554 3.446 5 4 5H9C9.554 5 10 4.554 10 4ZM7 8C7 7.446 6.554 7 6 7H4C3.446 7 3 7.446 3 8C3 8.554 3.446 9 4 9H6C6.554 9 7 8.554 7 8ZM10 12C10 11.446 9.554 11 9 11H4C3.446 11 3 11.446 3 12C3 12.554 3.446 13 4 13H9C9.554 13 10 12.554 10 12Z" fill="#232323"/>
<path d="M12 5L9 8L12 11" stroke="#232323" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
</svg>

Before

Width:  |  Height:  |  Size: 528 B

View file

@ -1,3 +0,0 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M11.8 8.61106C11.8 8.61106 12 8.37437 12 8.01163C12 7.64889 11.8 7.4122 11.8 7.4122L5.59947 1.21812C5.59947 1.21812 4.86007 0.63914 4.24935 1.36778C3.71912 1.96029 4.19936 2.61659 4.19936 2.61659L9.49979 8.01163L4.19936 13.4063C4.19936 13.4063 3.71913 14.0057 4.24936 14.6551C4.8405 15.3309 5.59947 14.8051 5.59947 14.8051L11.8 8.61106Z" fill="#232323"/>
</svg>

Before

Width:  |  Height:  |  Size: 467 B

View file

@ -1,3 +0,0 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M4.20002 8.61106C4.20002 8.61106 4.00002 8.37437 4 8.01163C3.99998 7.64889 4.20002 7.4122 4.20002 7.4122L10.4005 1.21812C10.4005 1.21812 11.1399 0.63914 11.7507 1.36778C12.2809 1.96029 11.8006 2.61659 11.8006 2.61659L6.50021 8.01163L11.8006 13.4063C11.8006 13.4063 12.2809 14.0057 11.7506 14.6551C11.1595 15.3309 10.4005 14.8051 10.4005 14.8051L4.20002 8.61106Z" fill="#232323"/>
</svg>

Before

Width:  |  Height:  |  Size: 492 B

View file

@ -1,3 +0,0 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M8 3C7.73478 3 7.48043 3.10536 7.29289 3.29289C7.10536 3.48043 7 3.73478 7 4V6.996L4 7C3.73478 7 3.48043 7.10536 3.29289 7.29289C3.10536 7.48043 3 7.73478 3 8C3 8.26522 3.10536 8.51957 3.29289 8.70711C3.48043 8.89464 3.73478 9 4 9L7 8.996V12C7 12.2652 7.10536 12.5196 7.29289 12.7071C7.48043 12.8946 7.73478 13 8 13C8.26522 13 8.51957 12.8946 8.70711 12.7071C8.89464 12.5196 9 12.2652 9 12V8.996L12 9C12.2652 9 12.5196 8.89464 12.7071 8.70711C12.8946 8.51957 13 8.26522 13 8C13 7.73478 12.8946 7.48043 12.7071 7.29289C12.5196 7.10536 12.2652 7 12 7L9 6.996V4C9 3.73478 8.89464 3.48043 8.70711 3.29289C8.51957 3.10536 8.26522 3 8 3Z" fill="#232323"/>
</svg>

Before

Width:  |  Height:  |  Size: 762 B

View file

@ -1,3 +0,0 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M4 7C3.73478 7 3.48043 7.10536 3.29289 7.29289C3.10536 7.48043 3 7.73478 3 8C3 8.26522 3.10536 8.51957 3.29289 8.70711C3.48043 8.89464 3.73478 9 4 9H12C12.2652 9 12.5196 8.89464 12.7071 8.70711C12.8946 8.51957 13 8.26522 13 8C13 7.73478 12.8946 7.48043 12.7071 7.29289C12.5196 7.10536 12.2652 7 12 7H4Z" fill="#232323"/>
</svg>

Before

Width:  |  Height:  |  Size: 433 B

View file

@ -1,10 +0,0 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<mask id="path-1-inside-1_3277_252" fill="white">
<rect x="1" y="2" width="14" height="12" rx="1"/>
</mask>
<rect x="1" y="2" width="14" height="12" rx="1" stroke="#232323" stroke-width="4" mask="url(#path-1-inside-1_3277_252)"/>
<rect x="6" y="2" width="2" height="12" fill="#232323"/>
<rect x="4" y="5" width="1" height="1" fill="#232323"/>
<rect x="4" y="7" width="1" height="1" fill="#232323"/>
<rect x="4" y="9" width="1" height="1" fill="#232323"/>
</svg>

Before

Width:  |  Height:  |  Size: 558 B

View file

@ -1,8 +0,0 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<mask id="path-1-inside-1_3277_253" fill="white">
<rect x="1" y="2" width="14" height="12" rx="1"/>
</mask>
<rect x="1" y="2" width="14" height="12" rx="1" stroke="#232323" stroke-width="4" mask="url(#path-1-inside-1_3277_253)"/>
<rect x="5" y="2" width="2" height="12" fill="#232323"/>
<path d="M7.99983 8.02603L10.9998 5C10.9998 5 11.5568 4.55672 12 5.00002C12.4432 5.44331 12 6.00002 12 6.00002L10 8.02603L12 10C12 10 12.5 10.5 12 11C11.5 11.5 11 11 11 11L7.99983 8.02603Z" fill="#232323"/>
</svg>

Before

Width:  |  Height:  |  Size: 597 B

View file

@ -1,3 +0,0 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M13 4C13 3.446 12.554 3 12 3H4C3.446 3 3 3.446 3 4C3 4.554 3.446 5 4 5H12C12.554 5 13 4.554 13 4ZM13 8C13 7.446 12.554 7 12 7H4C3.446 7 3 7.446 3 8C3 8.554 3.446 9 4 9H12C12.554 9 13 8.554 13 8ZM13 12C13 11.446 12.554 11 12 11H4C3.446 11 3 11.446 3 12C3 12.554 3.446 13 4 13H12C12.554 13 13 12.554 13 12Z" fill="#232323"/>
</svg>

Before

Width:  |  Height:  |  Size: 435 B

View file

@ -1,3 +0,0 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M5 4.00299C4.73478 4.00299 4.48043 4.10835 4.29289 4.29588C4.10536 4.48342 4 4.73777 4 5.00299C4.00006 5.26819 4.10545 5.5225 4.293 5.70999L6.586 8.00299L4.293 10.296C4.10545 10.4835 4.00006 10.7378 4 11.003C4 11.2682 4.10536 11.5226 4.29289 11.7101C4.48043 11.8976 4.73478 12.003 5 12.003C5.26519 12.0029 5.51951 11.8975 5.707 11.71L8 9.41699L10.283 11.7C10.3762 11.7959 10.4877 11.8721 10.6108 11.9241C10.734 11.9762 10.8663 12.003 11 12.003C11.2652 12.003 11.5196 11.8976 11.7071 11.7101C11.8946 11.5226 12 11.2682 12 11.003C11.9999 10.7378 11.8945 10.4835 11.707 10.296L9.414 8.00299L11.697 5.71999C11.7929 5.6268 11.8691 5.51534 11.9211 5.39218C11.9732 5.26903 12 5.13669 12 5.00299C12 4.73777 11.8946 4.48342 11.7071 4.29588C11.5196 4.10835 11.2652 4.00299 11 4.00299C10.7348 4.00305 10.4805 4.10844 10.293 4.29599L8 6.58899L5.717 4.30599C5.71369 4.30263 5.71036 4.2993 5.707 4.29599C5.51951 4.10844 5.26519 4.00305 5 4.00299Z" fill="#232323"/>
</svg>

Before

Width:  |  Height:  |  Size: 1 KiB

View file

@ -1,4 +0,0 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M4 12H8L6.5 10.5L7.78947 9.15789C7.78947 9.15789 8.26316 8.68421 7.78947 8.21053C7.31579 7.73684 6.8421 8.21053 6.8421 8.21053L5.5 9.5L4 8V12Z" fill="#232323"/>
<path d="M12 4H8L9.5 5.5L8.21053 6.84211C8.21053 6.84211 7.73684 7.31579 8.21053 7.78947C8.68421 8.26316 9.1579 7.78947 9.1579 7.78947L10.5 6.5L12 8V4Z" fill="#232323"/>
</svg>

Before

Width:  |  Height:  |  Size: 443 B

View file

@ -1,3 +0,0 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M4 10V12H12V10H4Z" fill="#232323"/>
</svg>

Before

Width:  |  Height:  |  Size: 188 B

View file

@ -1,4 +0,0 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M8 8H4L5.5 9.5L4.21053 10.8421C4.21053 10.8421 3.73684 11.3158 4.21053 11.7895C4.68421 12.2632 5.1579 11.7895 5.1579 11.7895L6.5 10.5L8 12V8Z" fill="#232323"/>
<path d="M8 8H12L10.5 6.5L11.7895 5.15789C11.7895 5.15789 12.2632 4.68421 11.7895 4.21053C11.3158 3.73684 10.8421 4.21053 10.8421 4.21053L9.5 5.5L8 4V8Z" fill="#232323"/>
</svg>

Before

Width:  |  Height:  |  Size: 443 B

View file

@ -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)

View file

@ -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 {

View file

@ -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,
},

View file

@ -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,
},

View file

@ -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
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))
})
},
}
}

View file

@ -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)

View file

@ -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)

View file

@ -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)