gpui-phosphor 0.1.0

An unofficial GPUI component and asset source for Phosphor Icons
Documentation
use crate::{IconError, IconName, Weight, generated};
use gpui::{AssetSource, Result, SharedString};
use std::borrow::Cow;

/// An [`AssetSource`] containing the enabled Phosphor SVG weights.
///
/// Paths are namespaced under `phosphor`, for example
/// `phosphor/regular/heart.svg`. The source returns an error for a requested
/// icon weight that is known but disabled, which makes missing feature flags
/// actionable instead of silently producing a blank SVG.
#[derive(Clone, Copy, Debug, Default)]
pub struct PhosphorAssets;

impl PhosphorAssets {
    /// Build a source that falls back to an application’s existing assets.
    ///
    /// Phosphor paths take precedence. Other paths are delegated to
    /// `fallback`, so this can be passed directly to
    /// [`gpui::Application::with_assets`].
    pub fn with_fallback<S>(fallback: S) -> PhosphorAssetSource<S>
    where
        S: AssetSource,
    {
        PhosphorAssetSource { fallback }
    }

    fn load_known(path: &str) -> Result<Option<Cow<'static, [u8]>>> {
        if let Some(bytes) = generated::asset_bytes(path) {
            return Ok(Some(Cow::Borrowed(bytes)));
        }

        if let Some((weight, name)) = requested_icon(path)
            && IconName::from_name(name).is_some()
            && !weight.is_enabled()
        {
            return Err(anyhow::anyhow!(IconError::WeightNotEnabled(weight)));
        }

        Ok(None)
    }

    fn listed_paths(path: &str) -> Vec<SharedString> {
        Weight::ALL
            .into_iter()
            .filter(|weight| weight.is_enabled())
            .flat_map(|weight| {
                IconName::ALL
                    .iter()
                    .copied()
                    .filter_map(move |name| name.path(weight).ok())
            })
            .filter(|asset| path.is_empty() || asset.starts_with(path))
            .collect()
    }
}

impl AssetSource for PhosphorAssets {
    fn load(&self, path: &str) -> Result<Option<Cow<'static, [u8]>>> {
        Self::load_known(path)?
            .map(Some)
            .ok_or_else(|| anyhow::anyhow!("could not find Phosphor asset at path `{path}`"))
    }

    fn list(&self, path: &str) -> Result<Vec<SharedString>> {
        Ok(Self::listed_paths(path))
    }
}

/// A composable source that serves Phosphor first and delegates every other
/// asset request to an application-provided source.
///
/// Create one with [`PhosphorAssets::with_fallback`]. This type is generic so
/// it works with file-backed sources, embedded asset bundles, and other
/// custom [`AssetSource`] implementations without imposing a dependency on
/// any particular application framework.
pub struct PhosphorAssetSource<S> {
    fallback: S,
}

impl<S> PhosphorAssetSource<S> {
    /// Return a reference to the delegated application asset source.
    #[must_use]
    pub const fn fallback(&self) -> &S {
        &self.fallback
    }
}

impl<S> AssetSource for PhosphorAssetSource<S>
where
    S: AssetSource,
{
    fn load(&self, path: &str) -> Result<Option<Cow<'static, [u8]>>> {
        match PhosphorAssets::load_known(path)? {
            Some(asset) => Ok(Some(asset)),
            None => self.fallback.load(path),
        }
    }

    fn list(&self, path: &str) -> Result<Vec<SharedString>> {
        let mut assets = self.fallback.list(path)?;
        for asset in PhosphorAssets::listed_paths(path) {
            if !assets.contains(&asset) {
                assets.push(asset);
            }
        }
        Ok(assets)
    }
}

fn requested_icon(path: &str) -> Option<(Weight, &str)> {
    let path = path.strip_prefix("phosphor/")?;
    let (weight, name) = path.split_once('/')?;
    let name = name.strip_suffix(".svg")?;
    Some((Weight::from_name(weight)?, name))
}