// Copyright 2023 System76 // SPDX-License-Identifier: MPL-2.0 use super::{Handle, Icon}; use std::{borrow::Cow, path::PathBuf, sync::Arc}; #[derive(Debug, Clone, Default, Hash)] /// Fallback icon to use if the icon was not found. pub enum IconFallback { #[default] /// Default fallback using the icon name. Default, /// Fallback to specific icon names. Names(Vec>), } #[must_use] #[derive(derive_setters::Setters, Clone, Debug, Hash)] pub struct Named { /// Name of icon to locate in an XDG icon path. pub(super) name: Arc, /// Checks for a fallback if the icon was not found. pub fallback: Option, /// Restrict the lookup to a given scale. #[setters(strip_option)] pub scale: Option, /// Restrict the lookup to a given size. #[setters(strip_option)] pub size: Option, /// Whether the icon is symbolic or not. pub symbolic: bool, /// Prioritizes SVG over PNG pub prefer_svg: bool, } impl Named { pub fn new(name: impl Into>) -> Self { let name = name.into(); Self { symbolic: name.ends_with("-symbolic"), name, fallback: Some(IconFallback::Default), size: None, scale: None, prefer_svg: false, } } #[cfg(not(windows))] #[must_use] pub fn path(self) -> Option { let name = &*self.name; let fallback = &self.fallback; let locate = |theme: &str, name| { let mut lookup = freedesktop_icons::lookup(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 theme = crate::icon_theme::DEFAULT.lock().unwrap(); let themes = if theme.as_ref() == crate::icon_theme::COSMIC { vec![theme.as_ref()] } else { vec![theme.as_ref(), crate::icon_theme::COSMIC] }; let mut result = themes.iter().find_map(|t| locate(t, name)); // On failure, attempt to locate fallback icon. if result.is_none() { if matches!(fallback, Some(IconFallback::Default)) { for new_name in name.rmatch_indices('-').map(|(pos, _)| &name[..pos]) { result = themes.iter().find_map(|t| locate(t, new_name)); if result.is_some() { break; } } } else if let Some(IconFallback::Names(fallbacks)) = fallback { for fallback in fallbacks { result = themes.iter().find_map(|t| locate(t, fallback)); if result.is_some() { break; } } } } result } #[cfg(windows)] #[must_use] pub fn path(self) -> Option { //TODO: implement icon lookup for Windows None } pub fn handle(self) -> Handle { Handle { symbolic: self.symbolic, data: super::Data::Name(self), } } 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 From for Handle { fn from(builder: Named) -> Self { builder.handle() } } impl From for Icon { fn from(builder: Named) -> Self { builder.icon() } } impl From for crate::Element<'_, Message> { fn from(builder: Named) -> Self { builder.icon().into() } }