refactor(widget): improvements to button and icon widgets

This commit is contained in:
Michael Aaron Murphy 2023-09-13 15:47:32 +02:00 committed by Michael Murphy
parent 7f0943924a
commit 9dbc1be269
20 changed files with 399 additions and 558 deletions

View file

@ -1,7 +1,7 @@
// Copyright 2023 System76 <info@system76.com>
// SPDX-License-Identifier: MPL-2.0
use super::{Builder, Icon};
use super::{Icon, Named};
use crate::widget::{image, svg};
use std::borrow::Cow;
use std::ffi::OsStr;
@ -13,7 +13,7 @@ use std::path::PathBuf;
pub struct Handle {
pub symbolic: bool,
#[setters(skip)]
pub variant: Variant,
pub data: Data,
}
impl Handle {
@ -24,16 +24,12 @@ impl Handle {
#[must_use]
#[derive(Clone, Debug, Hash)]
pub enum Variant {
pub enum Data {
Name(Named),
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 {
@ -41,10 +37,10 @@ pub fn from_path(path: PathBuf) -> Handle {
.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))
data: if path.extension().is_some_and(|ext| ext == OsStr::new("svg")) {
Data::Svg(svg::Handle::from_path(path))
} else {
Variant::Image(image::Handle::from_path(path))
Data::Image(image::Handle::from_path(path))
},
}
}
@ -59,7 +55,7 @@ pub fn from_raster_bytes(
) -> Handle {
Handle {
symbolic: false,
variant: Variant::Image(image::Handle::from_memory(bytes)),
data: Data::Image(image::Handle::from_memory(bytes)),
}
}
@ -75,7 +71,7 @@ pub fn from_raster_pixels(
) -> Handle {
Handle {
symbolic: false,
variant: Variant::Image(image::Handle::from_pixels(width, height, pixels)),
data: Data::Image(image::Handle::from_pixels(width, height, pixels)),
}
}
@ -83,6 +79,6 @@ pub fn from_raster_pixels(
pub fn from_svg_bytes(bytes: impl Into<Cow<'static, [u8]>>) -> Handle {
Handle {
symbolic: false,
variant: Variant::Svg(svg::Handle::from_memory(bytes)),
data: Data::Svg(svg::Handle::from_memory(bytes)),
}
}

View file

@ -3,18 +3,19 @@
//! Lazily-generated SVG icon widget for Iced.
mod builder;
pub use builder::Builder;
mod named;
use std::ffi::OsStr;
use std::sync::Arc;
pub mod handle;
pub use handle::Handle;
pub use named::Named;
mod handle;
pub use handle::{from_path, from_raster_bytes, from_raster_pixels, from_svg_bytes, Data, 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 {
@ -28,38 +29,9 @@ pub fn icon(handle: Handle) -> Icon {
}
}
/// 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))
/// Create an icon handle from its XDG icon name.
pub fn from_name(name: impl Into<Arc<str>>) -> Named {
Named::new(name)
}
/// An image which may be an SVG or PNG.
@ -80,19 +52,40 @@ pub struct Icon {
impl Icon {
#[must_use]
fn into_element<Message: 'static>(self) -> Element<'static, Message> {
match self.handle.variant {
handle::Variant::Image(handle) => Image::new(handle)
let from_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)
.into()
};
let from_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(),
.into()
};
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),
}
}
}

View file

@ -1,33 +1,38 @@
// Copyright 2023 System76 <info@system76.com>
// SPDX-License-Identifier: MPL-2.0
use super::{handle, Handle, Icon};
use std::path::PathBuf;
use super::{Handle, Icon};
use std::{path::PathBuf, sync::Arc};
#[must_use]
#[derive(derive_setters::Setters)]
pub struct Builder<'a> {
#[derive(derive_setters::Setters, Clone, Debug, Hash)]
pub struct Named {
/// Name of icon to locate in an XDG icon path.
name: &'a str,
pub(super) name: Arc<str>,
/// Checks for a fallback if the icon was not found.
fallback: bool,
pub fallback: bool,
/// Restrict the lookup to a given scale.
#[setters(strip_option)]
scale: Option<u16>,
pub scale: Option<u16>,
/// Restrict the lookup to a given size.
#[setters(strip_option)]
size: Option<u16>,
pub size: Option<u16>,
/// Whether the icon is symbolic or not.
pub symbolic: bool,
/// Prioritizes SVG over PNG
prefer_svg: bool,
pub prefer_svg: bool,
}
impl<'a> Builder<'a> {
pub const fn new(name: &'a str) -> Self {
impl Named {
pub fn new(name: impl Into<Arc<str>>) -> Self {
let name = name.into();
Self {
symbolic: name.ends_with("-symbolic"),
name,
fallback: true,
size: None,
@ -37,12 +42,13 @@ impl<'a> Builder<'a> {
}
#[must_use]
pub fn path(mut self) -> Option<PathBuf> {
pub fn path(self) -> Option<PathBuf> {
let mut name = &*self.name;
crate::icon_theme::DEFAULT.with(|theme| {
let theme = theme.borrow();
let locate = || {
let mut lookup = freedesktop_icons::lookup(self.name)
let mut lookup = freedesktop_icons::lookup(name)
.with_theme(theme.as_ref())
.with_cache();
@ -65,10 +71,8 @@ impl<'a> Builder<'a> {
// 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;
for new_name in name.rmatch_indices('-').map(|(pos, _)| &name[..pos]) {
name = new_name;
result = locate();
if result.is_some() {
break;
@ -81,11 +85,9 @@ impl<'a> Builder<'a> {
}
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)
Handle {
symbolic: self.symbolic,
data: super::Data::Name(self),
}
}
@ -101,14 +103,20 @@ impl<'a> Builder<'a> {
}
}
impl<'a> From<Builder<'a>> for Handle {
fn from(builder: Builder<'a>) -> Self {
impl From<Named> for Handle {
fn from(builder: Named) -> Self {
builder.handle()
}
}
impl<'a> From<Builder<'a>> for Icon {
fn from(builder: Builder<'a>) -> Self {
impl From<Named> for Icon {
fn from(builder: Named) -> Self {
builder.icon()
}
}
impl<'a, Message: 'static> From<Named> for crate::Element<'a, Message> {
fn from(builder: Named) -> Self {
builder.icon().into()
}
}