Skip to main content

gpui_phosphor/
assets.rs

1use crate::{IconError, IconName, Weight, generated};
2use gpui::{AssetSource, Result, SharedString};
3use std::borrow::Cow;
4
5/// An [`AssetSource`] containing the enabled Phosphor SVG weights.
6///
7/// Paths are namespaced under `phosphor`, for example
8/// `phosphor/regular/heart.svg`. The source returns an error for a requested
9/// icon weight that is known but disabled, which makes missing feature flags
10/// actionable instead of silently producing a blank SVG.
11#[derive(Clone, Copy, Debug, Default)]
12pub struct PhosphorAssets;
13
14impl PhosphorAssets {
15    /// Build a source that falls back to an application’s existing assets.
16    ///
17    /// Phosphor paths take precedence. Other paths are delegated to
18    /// `fallback`, so this can be passed directly to
19    /// [`gpui::Application::with_assets`].
20    pub fn with_fallback<S>(fallback: S) -> PhosphorAssetSource<S>
21    where
22        S: AssetSource,
23    {
24        PhosphorAssetSource { fallback }
25    }
26
27    fn load_known(path: &str) -> Result<Option<Cow<'static, [u8]>>> {
28        if let Some(bytes) = generated::asset_bytes(path) {
29            return Ok(Some(Cow::Borrowed(bytes)));
30        }
31
32        if let Some((weight, name)) = requested_icon(path)
33            && IconName::from_name(name).is_some()
34            && !weight.is_enabled()
35        {
36            return Err(anyhow::anyhow!(IconError::WeightNotEnabled(weight)));
37        }
38
39        Ok(None)
40    }
41
42    fn listed_paths(path: &str) -> Vec<SharedString> {
43        Weight::ALL
44            .into_iter()
45            .filter(|weight| weight.is_enabled())
46            .flat_map(|weight| {
47                IconName::ALL
48                    .iter()
49                    .copied()
50                    .filter_map(move |name| name.path(weight).ok())
51            })
52            .filter(|asset| path.is_empty() || asset.starts_with(path))
53            .collect()
54    }
55}
56
57impl AssetSource for PhosphorAssets {
58    fn load(&self, path: &str) -> Result<Option<Cow<'static, [u8]>>> {
59        Self::load_known(path)?
60            .map(Some)
61            .ok_or_else(|| anyhow::anyhow!("could not find Phosphor asset at path `{path}`"))
62    }
63
64    fn list(&self, path: &str) -> Result<Vec<SharedString>> {
65        Ok(Self::listed_paths(path))
66    }
67}
68
69/// A composable source that serves Phosphor first and delegates every other
70/// asset request to an application-provided source.
71///
72/// Create one with [`PhosphorAssets::with_fallback`]. This type is generic so
73/// it works with file-backed sources, embedded asset bundles, and other
74/// custom [`AssetSource`] implementations without imposing a dependency on
75/// any particular application framework.
76pub struct PhosphorAssetSource<S> {
77    fallback: S,
78}
79
80impl<S> PhosphorAssetSource<S> {
81    /// Return a reference to the delegated application asset source.
82    #[must_use]
83    pub const fn fallback(&self) -> &S {
84        &self.fallback
85    }
86}
87
88impl<S> AssetSource for PhosphorAssetSource<S>
89where
90    S: AssetSource,
91{
92    fn load(&self, path: &str) -> Result<Option<Cow<'static, [u8]>>> {
93        match PhosphorAssets::load_known(path)? {
94            Some(asset) => Ok(Some(asset)),
95            None => self.fallback.load(path),
96        }
97    }
98
99    fn list(&self, path: &str) -> Result<Vec<SharedString>> {
100        let mut assets = self.fallback.list(path)?;
101        for asset in PhosphorAssets::listed_paths(path) {
102            if !assets.contains(&asset) {
103                assets.push(asset);
104            }
105        }
106        Ok(assets)
107    }
108}
109
110fn requested_icon(path: &str) -> Option<(Weight, &str)> {
111    let path = path.strip_prefix("phosphor/")?;
112    let (weight, name) = path.split_once('/')?;
113    let name = name.strip_suffix(".svg")?;
114    Some((Weight::from_name(weight)?, name))
115}