1use crate::{IconError, IconName, Weight, generated};
2use gpui::{AssetSource, Result, SharedString};
3use std::borrow::Cow;
4
5#[derive(Clone, Copy, Debug, Default)]
12pub struct PhosphorAssets;
13
14impl PhosphorAssets {
15 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
69pub struct PhosphorAssetSource<S> {
77 fallback: S,
78}
79
80impl<S> PhosphorAssetSource<S> {
81 #[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}