From 598bfaa6111c38633cbcd9d875ef8384108fb40d Mon Sep 17 00:00:00 2001 From: Ashley Wulber Date: Fri, 7 Jul 2023 16:39:22 -0400 Subject: [PATCH] feat: icon default fallbacks --- examples/cosmic/src/window/demo.rs | 20 +++++--- src/widget/icon.rs | 73 +++++++++++++++++++-------- src/widget/segmented_button/widget.rs | 4 +- 3 files changed, 67 insertions(+), 30 deletions(-) diff --git a/examples/cosmic/src/window/demo.rs b/examples/cosmic/src/window/demo.rs index 243ba07..095f267 100644 --- a/examples/cosmic/src/window/demo.rs +++ b/examples/cosmic/src/window/demo.rs @@ -3,7 +3,7 @@ use cosmic::{ cosmic_theme, iced::widget::{checkbox, column, pick_list, progress_bar, radio, slider, text, text_input}, iced::{id, Alignment, Length}, - theme::{self, Button as ButtonTheme, Theme, ThemeType}, + theme::{self, Button as ButtonTheme, ThemeType}, widget::{ button, container, icon, segmented_button, segmented_selection, settings, spin_button, toggler, view_switcher, @@ -417,11 +417,19 @@ impl State { .padding(8) .width(Length::Fill) .into(), - container(text("Primary container with some text").size(24)) - .layer(cosmic_theme::Layer::Primary) - .padding(8) - .width(Length::Fill) - .into(), + container(column![ + text( + "Primary container with some text and a couple icons testing default fallbacks" + ) + .size(24), + icon("microphone-sensitivity-high-symbolic-test", 24) + .style(cosmic::theme::Svg::SymbolicActive), + icon("microphone-sensitivity-high-symbolic-test", 16).default_fallbacks(false) + ]) + .layer(cosmic_theme::Layer::Primary) + .padding(8) + .width(Length::Fill) + .into(), container(text("Secondary container with some text").size(24)) .layer(cosmic_theme::Layer::Secondary) .padding(8) diff --git a/src/widget/icon.rs b/src/widget/icon.rs index 7691f73..9e31eea 100644 --- a/src/widget/icon.rs +++ b/src/widget/icon.rs @@ -13,6 +13,7 @@ use std::{ borrow::Cow, collections::hash_map::DefaultHasher, ffi::OsStr, hash::Hash, hash::Hasher, path::Path, path::PathBuf, }; +use tracing::error; #[derive(Clone, Debug, Hash)] pub enum Handle { @@ -30,30 +31,30 @@ pub enum IconSource<'a> { impl<'a> IconSource<'a> { /// Loads the icon as either an image or svg [`Handle`]. #[must_use] - pub fn load(&self, size: u16, theme: Option<&str>, svg: bool) -> Handle { - let name_path_buffer: Option; + pub fn load( + &self, + size: u16, + theme: Option<&str>, + svg: bool, + default_fallbacks: bool, + ) -> Handle { + let mut name_path_buffer: Option; let icon: Option<&Path> = match self { IconSource::Handle(handle) => return handle.clone(), IconSource::Path(ref path) => Some(path), #[cfg(unix)] IconSource::Name(ref name) => { - let icon = crate::settings::DEFAULT_ICON_THEME.with(|default_theme| { - let default_theme: &str = &default_theme.borrow(); - freedesktop_icons::lookup(name) - .with_size(size) - .with_theme(theme.unwrap_or(default_theme)) - .with_cache() - .find() - }); - - name_path_buffer = if icon.is_none() { - freedesktop_icons::lookup(name) - .with_size(size) - .with_cache() - .find() - } else { - icon - }; + name_path_buffer = None; + if let Some(path) = load_icon(name, size, theme) { + name_path_buffer = Some(path); + } else if default_fallbacks { + for name in name.rmatch_indices('-').map(|(pos, _)| &name[..pos]) { + if let Some(path) = load_icon(name, size, theme) { + name_path_buffer = Some(path); + break; + } + } + } name_path_buffer.as_deref() } @@ -71,7 +72,7 @@ impl<'a> IconSource<'a> { let handle = if let Some(path) = icon { svg::Handle::from_path(path) } else { - eprintln!("svg icon '{self:?}' size {size} not found"); + error!("svg icon '{self:?}' size {size} not found"); svg::Handle::from_memory(Vec::new()) }; @@ -79,7 +80,7 @@ impl<'a> IconSource<'a> { } else if let Some(icon) = icon { Handle::Image(icon.into()) } else { - eprintln!("icon '{self:?}' size {size} not found"); + error!("icon '{self:?}' size {size} not found"); Handle::Image(image::Handle::from_memory(Vec::new())) } } @@ -189,6 +190,7 @@ pub struct Icon<'a> { #[setters(strip_option)] height: Option, force_svg: bool, + default_fallbacks: bool, } // XXX Hopefully this will be enough precision @@ -230,6 +232,7 @@ pub fn icon<'a>(source: impl Into>, size: u16) -> Icon<'a> { theme: None, width: None, force_svg: false, + default_fallbacks: true, } } @@ -266,7 +269,12 @@ impl<'a> Icon<'a> { std::mem::swap(&mut source, &mut self.source); iced::widget::lazy(hash, move |_| -> Element { - match source.load(self.size, self.theme.as_deref(), self.force_svg) { + match source.load( + self.size, + self.theme.as_deref(), + self.force_svg, + self.default_fallbacks, + ) { Handle::Svg(handle) => self.svg_element(handle), Handle::Image(handle) => self.raster_element(handle), } @@ -280,3 +288,24 @@ impl<'a, Message: 'static> From> for Element<'a, Message> { icon.into_element::() } } + +#[must_use] +pub fn load_icon(name: &str, size: u16, theme: Option<&str>) -> Option { + let icon = crate::settings::DEFAULT_ICON_THEME.with(|default_theme| { + let default_theme = default_theme.borrow(); + freedesktop_icons::lookup(name) + .with_size(size) + .with_theme(theme.unwrap_or(&default_theme)) + .with_cache() + .find() + }); + + if icon.is_none() { + freedesktop_icons::lookup(name) + .with_size(size) + .with_cache() + .find() + } else { + icon + } +} diff --git a/src/widget/segmented_button/widget.rs b/src/widget/segmented_button/widget.rs index cee18b3..d7114fa 100644 --- a/src/widget/segmented_button/widget.rs +++ b/src/widget/segmented_button/widget.rs @@ -542,7 +542,7 @@ where bounds.x += offset; bounds.width -= offset; - match icon.load(self.icon_size, None, false) { + match icon.load(self.icon_size, None, false, true) { icon::Handle::Image(_handle) => { unimplemented!() } @@ -583,7 +583,7 @@ where let width = f32::from(self.icon_size); let icon_bounds = close_bounds(original_bounds, width, self.button_padding); - match self.close_icon.load(self.icon_size, None, false) { + match self.close_icon.load(self.icon_size, None, false, true) { icon::Handle::Image(_handle) => { unimplemented!() }