1use crate::{DevicePixels, Pixels, Result, SharedString, Size, size};
2use gpui_util::ResultExt;
3use smallvec::SmallVec;
4
5use image::{Delay, EncodableLayout, Frame};
6use std::{
7 borrow::Cow,
8 collections::BTreeMap,
9 fmt,
10 hash::Hash,
11 sync::{
12 Arc,
13 atomic::{AtomicUsize, Ordering::SeqCst},
14 },
15};
16
17pub trait AssetSource: 'static + Send + Sync {
25 fn load(&self, path: &str) -> Result<Option<Cow<'static, [u8]>>>;
29
30 fn list(&self, path: &str) -> Result<Vec<SharedString>>;
34
35 fn iter_preloaded(&self) -> Box<dyn Iterator<Item = (SharedString, AssetEntry)> + '_> {
40 let paths = self.list("").unwrap_or_default();
41 let iter = paths.into_iter().filter_map(move |path| {
42 let entry = AssetEntry::from(self.load(&path).ok().flatten()?);
43 Some((path, entry))
44 });
45 Box::new(iter)
46 }
47
48 fn iter_ondemand(self: Arc<Self>) -> Box<dyn Iterator<Item = (SharedString, AssetEntry)>> {
54 use gpui_util::ResultExt;
55 let paths = self.list("").unwrap_or_default();
56 let iter = paths.into_iter().map(move |path| {
57 let entry = AssetEntry::from({
59 let source = self.clone();
60 let path = path.clone();
61 move || source.load(path.as_str()).log_err().flatten()
62 });
63 (path, entry)
64 });
65 Box::new(iter)
66 }
67}
68
69impl AssetSource for () {
70 fn load(&self, _path: &str) -> Result<Option<Cow<'static, [u8]>>> {
71 Ok(None)
72 }
73
74 fn list(&self, _path: &str) -> Result<Vec<SharedString>> {
75 Ok(vec![])
76 }
77}
78
79pub type AssetData = Cow<'static, [u8]>;
82pub type FnAssetLoader = Arc<dyn Fn() -> Option<AssetData> + 'static + Send + Sync>;
87pub enum AssetEntry {
92 PreLoaded(AssetData),
95 OnDemand(FnAssetLoader),
98}
99impl From<AssetData> for AssetEntry {
100 fn from(value: AssetData) -> Self {
101 Self::PreLoaded(value)
102 }
103}
104impl<F> From<F> for AssetEntry
105where
106 F: Fn() -> Option<AssetData> + 'static + Send + Sync,
107{
108 fn from(value: F) -> Self {
109 Self::OnDemand(Arc::new(value))
110 }
111}
112
113#[derive(thiserror::Error, Debug)]
115#[error("An asset with the pathkey {0:?} already exists")]
116pub struct DuplicateAssetPath(SharedString);
117
118#[derive(Default)]
127pub struct AssetRegistry {
128 entries: BTreeMap<SharedString, AssetEntry>,
129}
130
131impl<T: AssetSource + 'static> From<T> for AssetRegistry {
132 fn from(value: T) -> Self {
133 let shared: Arc<dyn AssetSource> = Arc::new(value);
134 Self::from(shared)
135 }
136}
137
138impl From<Arc<dyn AssetSource>> for AssetRegistry {
141 fn from(value: Arc<dyn AssetSource>) -> Self {
142 let mut assets = Self::default();
143 let _ = assets.extend(value.iter_ondemand());
144 assets
145 }
146}
147
148impl<KeyType, ValueType> FromIterator<(KeyType, ValueType)> for AssetRegistry
149where
150 KeyType: AsRef<str>,
151 ValueType: Into<AssetEntry>,
152{
153 fn from_iter<T: IntoIterator<Item = (KeyType, ValueType)>>(iter: T) -> Self {
154 iter.into_iter()
155 .fold(Self::default(), |mut assets, (key, value)| {
156 assets.insert(key.as_ref(), value).log_err();
157 assets
158 })
159 }
160}
161
162impl AssetRegistry {
163 pub fn insert(
192 &mut self,
193 path: impl Into<SharedString>,
194 asset: impl Into<AssetEntry>,
195 ) -> Result<(), DuplicateAssetPath> {
196 let path = path.into();
197 if self.entries.contains_key(&path) {
198 return Err(DuplicateAssetPath(path));
199 }
200 self.entries.insert(path, asset.into());
201 Ok(())
202 }
203
204 pub fn load<'this>(&'this self, path: impl AsRef<str>) -> Option<Cow<'this, [u8]>> {
210 match self.entries.get(path.as_ref())? {
211 AssetEntry::PreLoaded(Cow::Borrowed(data)) => Some(Cow::Borrowed(data)),
212 AssetEntry::PreLoaded(Cow::Owned(data)) => Some(Cow::Borrowed(data.as_bytes())),
213 AssetEntry::OnDemand(loader) => loader(),
214 }
215 }
216
217 pub fn extend<IterType, KeyType, EntryType>(
224 &mut self,
225 into_iter: IterType,
226 ) -> Result<(), Vec<DuplicateAssetPath>>
227 where
228 IterType: IntoIterator<Item = (KeyType, EntryType)>,
229 KeyType: AsRef<str>,
230 EntryType: Into<AssetEntry>,
231 {
232 let mut result = Ok(());
233 for (path_key, into_entry) in into_iter.into_iter() {
234 if let Err(err) = self.insert(path_key.as_ref(), into_entry.into()) {
235 gpui_util::log_err(&err);
236 result = Err({
237 let mut errs: Vec<DuplicateAssetPath> = result.err().unwrap_or_default();
238 errs.push(err);
239 errs
240 });
241 }
242 }
243 result
244 }
245
246 #[cfg(feature = "embedded-assets")]
252 pub fn iter_embed<T: rust_embed::RustEmbed>()
253 -> impl Iterator<Item = (Cow<'static, str>, AssetEntry)> + 'static {
254 T::iter().filter_map(|path| {
255 let file = T::get(path.as_ref())?;
256 Some((path, AssetEntry::PreLoaded(file.data)))
257 })
258 }
259}
260
261#[derive(Copy, Clone, Debug, Eq, PartialEq, Ord, PartialOrd, Hash)]
264pub struct ImageId(pub usize);
265
266#[derive(PartialEq, Eq, Hash, Clone)]
267#[expect(missing_docs)]
268pub struct RenderImageParams {
269 pub image_id: ImageId,
270 pub frame_index: usize,
271}
272
273pub struct RenderImage {
278 pub id: ImageId,
280 pub(crate) scale_factor: f32,
282 data: SmallVec<[Frame; 1]>,
283}
284
285impl PartialEq for RenderImage {
286 fn eq(&self, other: &Self) -> bool {
287 self.id == other.id
288 }
289}
290
291impl Eq for RenderImage {}
292
293impl RenderImage {
294 pub fn new(data: impl Into<SmallVec<[Frame; 1]>>) -> Self {
312 static NEXT_ID: AtomicUsize = AtomicUsize::new(0);
313
314 Self {
315 id: ImageId(NEXT_ID.fetch_add(1, SeqCst)),
316 scale_factor: 1.0,
317 data: data.into(),
318 }
319 }
320
321 pub fn as_bytes(&self, frame_index: usize) -> Option<&[u8]> {
326 self.data
327 .get(frame_index)
328 .map(|frame| frame.buffer().as_raw().as_slice())
329 }
330
331 pub fn size(&self, frame_index: usize) -> Size<DevicePixels> {
333 self.data
334 .get(frame_index)
335 .map(|frame| {
336 let (width, height) = frame.buffer().dimensions();
337 size(width.into(), height.into())
338 })
339 .unwrap_or_default()
340 }
341
342 pub(crate) fn render_size(&self, frame_index: usize) -> Size<Pixels> {
344 self.size(frame_index)
345 .map(|v| (v.0 as f32 / self.scale_factor).into())
346 }
347
348 pub fn delay(&self, frame_index: usize) -> Delay {
351 self.data
352 .get(frame_index)
353 .map(|frame| frame.delay())
354 .unwrap_or(Delay::from_numer_denom_ms(100, 1))
355 }
356
357 pub fn frame_count(&self) -> usize {
359 self.data.len()
360 }
361}
362
363impl fmt::Debug for RenderImage {
364 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
365 f.debug_struct("ImageData")
366 .field("id", &self.id)
367 .field("size", &self.data.first().map(|f| f.buffer().dimensions()))
368 .finish()
369 }
370}
371
372#[cfg(test)]
373mod tests {
374 use super::*;
375 use smallvec::SmallVec;
376
377 #[test]
378 fn empty_render_image_does_not_panic() {
379 let image = RenderImage::new(SmallVec::new());
380 assert_eq!(image.frame_count(), 0);
381 assert_eq!(image.size(0), Size::default());
382 assert_eq!(image.as_bytes(0), None);
383 assert_eq!(image.render_size(0), Size::default());
384 assert_eq!(image.delay(0), Delay::from_numer_denom_ms(100, 1));
385 let _ = format!("{image:?}");
386 }
387
388 fn example_assets() -> Vec<(&'static str, AssetData)> {
389 vec![
390 ("asset0", Cow::Owned(vec![0u8])),
391 ("asset1", Cow::Owned(vec![1u8])),
392 ("asset2", Cow::Owned(vec![2u8])),
393 ("asset3", Cow::Owned(vec![3u8])),
394 ]
395 }
396
397 fn test_load_from(registry: &AssetRegistry) {
398 for (key, value) in example_assets() {
399 let loaded = registry.load(key).map(|cow| cow.to_vec());
400 assert_eq!(loaded, Some(value.to_vec()));
401 }
402 }
403
404 struct StubAssetSource;
405 impl AssetSource for StubAssetSource {
406 fn load(&self, path: &str) -> Result<Option<Cow<'static, [u8]>>> {
407 for (key, data) in example_assets() {
408 if key == path {
409 return Ok(Some(data));
410 }
411 }
412 unimplemented!()
413 }
414
415 fn list(&self, _path: &str) -> Result<Vec<SharedString>> {
416 Ok(example_assets()
417 .into_iter()
418 .map(|(key, _)| key.into())
419 .collect())
420 }
421 }
422
423 #[test]
424 fn test_from_assetsource() {
425 let assets = AssetRegistry::from(StubAssetSource);
426 test_load_from(&assets);
427 }
428
429 #[test]
430 fn test_from_arc_assetsource() {
431 let arc: Arc<dyn AssetSource> = Arc::new(StubAssetSource);
432 let assets = AssetRegistry::from(arc);
433 test_load_from(&assets);
434 }
435
436 #[test]
437 fn test_fromiter_assetdata() {
438 let assets = AssetRegistry::from_iter(example_assets());
439 test_load_from(&assets);
440 }
441
442 #[test]
443 fn test_fromiter_assetentry() {
444 let assets = AssetRegistry::from_iter(
445 example_assets()
446 .into_iter()
447 .map(|(key, value)| (key, AssetEntry::from(value)))
448 .chain(std::iter::once((
449 "asset6",
450 AssetEntry::OnDemand(Arc::new(|| Some(AssetData::Owned(vec![6u8])))),
451 ))),
452 );
453 test_load_from(&assets);
454 assert_eq!(
455 assets.load("asset6").map(|cow| cow.to_vec()),
456 Some(vec![6u8])
457 );
458 }
459
460 #[test]
461 fn test_insert() {
462 let mut assets = AssetRegistry::default();
463 for (key, value) in example_assets() {
464 let result = assets.insert(key, value);
465 assert!(result.is_ok());
466 }
467 test_load_from(&assets);
468 }
469
470 #[test]
471 fn test_extend() {
472 let mut assets = AssetRegistry::default();
473 let result = assets.extend(
474 example_assets()
475 .into_iter()
476 .map(|(key, value)| (key, AssetEntry::PreLoaded(value))),
477 );
478 assert!(result.is_ok());
479 test_load_from(&assets);
480 }
481
482 #[test]
483 fn test_extend_assetsource_preloaded() {
484 let mut assets = AssetRegistry::default();
485 let result = assets.extend(StubAssetSource.iter_preloaded());
486 assert!(result.is_ok());
487 test_load_from(&assets);
488 }
489
490 #[test]
491 fn test_extend_assetsource_ondemand() {
492 let mut assets = AssetRegistry::default();
493 let result = assets.extend(Arc::new(StubAssetSource).iter_ondemand());
494 assert!(result.is_ok());
495 test_load_from(&assets);
496 }
497}