feat!(widget): rewrite button & icon widget APIs

This commit is contained in:
Michael Aaron Murphy 2023-09-01 07:29:19 +02:00 committed by Michael Murphy
parent 18debe546d
commit 4e4eeaac12
60 changed files with 2191 additions and 1113 deletions

114
src/widget/icon/builder.rs Normal file
View file

@ -0,0 +1,114 @@
// Copyright 2023 System76 <info@system76.com>
// SPDX-License-Identifier: MPL-2.0
use super::{handle, Handle, Icon};
use std::path::PathBuf;
#[must_use]
#[derive(derive_setters::Setters)]
pub struct Builder<'a> {
/// Name of icon to locate in an XDG icon path.
name: &'a str,
/// Checks for a fallback if the icon was not found.
fallback: bool,
/// Restrict the lookup to a given scale.
#[setters(strip_option)]
scale: Option<u16>,
/// Restrict the lookup to a given size.
#[setters(strip_option)]
size: Option<u16>,
/// Prioritizes SVG over PNG
prefer_svg: bool,
}
impl<'a> Builder<'a> {
pub const fn new(name: &'a str) -> Self {
Self {
name,
fallback: true,
size: None,
scale: None,
prefer_svg: false,
}
}
#[must_use]
pub fn path(mut self) -> Option<PathBuf> {
crate::icon_theme::DEFAULT.with(|theme| {
let theme = theme.borrow();
let locate = || {
let mut lookup = freedesktop_icons::lookup(self.name)
.with_theme(theme.as_ref())
.with_cache();
if let Some(scale) = self.scale {
lookup = lookup.with_scale(scale);
}
if let Some(size) = self.size {
lookup = lookup.with_size(size);
}
if self.prefer_svg {
lookup = lookup.force_svg();
}
lookup.find()
};
let mut result = locate();
// On failure, attempt to locate fallback icon.
if result.is_none() && self.fallback {
let name = std::mem::take(&mut self.name);
for name in name.rmatch_indices('-').map(|(pos, _)| &name[..pos]) {
self.name = name;
result = locate();
if result.is_some() {
break;
}
}
}
result
})
}
pub fn handle(self) -> Handle {
if let Some(path) = self.path() {
handle::from_path(path)
} else {
let bytes: &'static [u8] = &[];
handle::from_svg_bytes(bytes)
}
}
pub fn icon(self) -> Icon {
let size = self.size;
let icon = super::icon(self.handle());
match size {
Some(size) => icon.size(size),
None => icon,
}
}
}
impl<'a> From<Builder<'a>> for Handle {
fn from(builder: Builder<'a>) -> Self {
builder.handle()
}
}
impl<'a> From<Builder<'a>> for Icon {
fn from(builder: Builder<'a>) -> Self {
builder.icon()
}
}

88
src/widget/icon/handle.rs Normal file
View file

@ -0,0 +1,88 @@
// Copyright 2023 System76 <info@system76.com>
// SPDX-License-Identifier: MPL-2.0
use super::{Builder, Icon};
use crate::widget::{image, svg};
use std::borrow::Cow;
use std::ffi::OsStr;
use std::hash::Hash;
use std::path::PathBuf;
#[must_use]
#[derive(Clone, Debug, Hash, derive_setters::Setters)]
pub struct Handle {
pub symbolic: bool,
#[setters(skip)]
pub variant: Variant,
}
impl Handle {
pub fn icon(self) -> Icon {
super::icon(self)
}
}
#[must_use]
#[derive(Clone, Debug, Hash)]
pub enum Variant {
Image(image::Handle),
Svg(svg::Handle),
}
/// Create an icon handle from its XDG icon name.
pub fn from_name(name: &str) -> Builder {
Builder::new(name)
}
/// Create an icon handle from its path.
pub fn from_path(path: PathBuf) -> Handle {
Handle {
symbolic: path
.file_stem()
.and_then(OsStr::to_str)
.is_some_and(|name| name.ends_with("-symbolic")),
variant: if path.extension().is_some_and(|ext| ext == OsStr::new("svg")) {
Variant::Svg(svg::Handle::from_path(path))
} else {
Variant::Image(image::Handle::from_path(path))
},
}
}
/// Create an image handle from memory.
pub fn from_raster_bytes(
bytes: impl Into<Cow<'static, [u8]>>
+ std::convert::AsRef<[u8]>
+ std::marker::Send
+ std::marker::Sync
+ 'static,
) -> Handle {
Handle {
symbolic: false,
variant: Variant::Image(image::Handle::from_memory(bytes)),
}
}
/// Create an image handle from RGBA data, where you must define the width and height.
pub fn from_raster_pixels(
width: u32,
height: u32,
pixels: impl Into<Cow<'static, [u8]>>
+ std::convert::AsRef<[u8]>
+ std::marker::Send
+ std::marker::Sync
+ 'static,
) -> Handle {
Handle {
symbolic: false,
variant: Variant::Image(image::Handle::from_pixels(width, height, pixels)),
}
}
/// Create a SVG handle from memory.
pub fn from_svg_bytes(bytes: impl Into<Cow<'static, [u8]>>) -> Handle {
Handle {
symbolic: false,
variant: Variant::Svg(svg::Handle::from_memory(bytes)),
}
}

104
src/widget/icon/mod.rs Normal file
View file

@ -0,0 +1,104 @@
// Copyright 2022 System76 <info@system76.com>
// SPDX-License-Identifier: MPL-2.0
//! Lazily-generated SVG icon widget for Iced.
mod builder;
pub use builder::Builder;
pub mod handle;
pub use handle::Handle;
use crate::{Element, Renderer};
use derive_setters::Setters;
use iced::widget::{Image, Svg};
use iced::{ContentFit, Length};
use std::borrow::Cow;
use std::path::PathBuf;
/// Create an [`Icon`] from a pre-existing [`Handle`]
pub fn icon(handle: Handle) -> Icon {
Icon {
content_fit: ContentFit::Fill,
handle,
height: None,
size: 16,
style: crate::theme::Svg::default(),
width: None,
}
}
/// Create an [`Icon`] from its path.
pub fn from_path(path: PathBuf) -> Icon {
icon(handle::from_path(path))
}
/// Create an image [`Icon`] from memory.
pub fn from_raster_bytes(
bytes: impl Into<Cow<'static, [u8]>>
+ std::convert::AsRef<[u8]>
+ std::marker::Send
+ std::marker::Sync
+ 'static,
) -> Icon {
icon(handle::from_raster_bytes(bytes))
}
/// Create an image [`Icon`] from RGBA data, where you must define the width and height.
pub fn from_raster_pixels(
width: u32,
height: u32,
pixels: impl Into<Cow<'static, [u8]>>
+ std::convert::AsRef<[u8]>
+ std::marker::Send
+ std::marker::Sync
+ 'static,
) -> Icon {
icon(handle::from_raster_pixels(width, height, pixels))
}
/// Create a SVG [`Icon`] from memory.
pub fn from_svg_bytes(bytes: impl Into<Cow<'static, [u8]>>) -> Icon {
icon(handle::from_svg_bytes(bytes))
}
/// An image which may be an SVG or PNG.
#[must_use]
#[derive(Clone, Setters)]
pub struct Icon {
#[setters(skip)]
handle: Handle,
style: crate::theme::Svg,
pub(super) size: u16,
content_fit: ContentFit,
#[setters(strip_option)]
width: Option<Length>,
#[setters(strip_option)]
height: Option<Length>,
}
impl Icon {
#[must_use]
fn into_element<Message: 'static>(self) -> Element<'static, Message> {
match self.handle.variant {
handle::Variant::Image(handle) => Image::new(handle)
.width(self.width.unwrap_or(Length::Fixed(f32::from(self.size))))
.height(self.height.unwrap_or(Length::Fixed(f32::from(self.size))))
.content_fit(self.content_fit)
.into(),
handle::Variant::Svg(handle) => Svg::<Renderer>::new(handle)
.style(self.style.clone())
.width(self.width.unwrap_or(Length::Fixed(f32::from(self.size))))
.height(self.height.unwrap_or(Length::Fixed(f32::from(self.size))))
.content_fit(self.content_fit)
.symbolic(self.handle.symbolic)
.into(),
}
}
}
impl<Message: 'static> From<Icon> for Element<'static, Message> {
fn from(icon: Icon) -> Self {
icon.into_element::<Message>()
}
}