Skip to main content

gpui_authoring/elements/
image_cache.rs

1use crate::{
2    AnyElement, AnyEntity, App, AppContext, Asset, AssetLogger, Bounds, Element, ElementId, Entity,
3    GlobalElementId, ImageAssetLoader, ImageCacheError, IntoElement, LayoutId, ParentElement,
4    Pixels, RenderImage, Resource, Style, StyleRefinement, Styled, Window, hash,
5};
6
7use crate::asset_cache::CachedLoad;
8use refineable::Refineable;
9use smallvec::SmallVec;
10use std::{collections::HashMap, fmt, sync::Arc};
11
12/// An image cache element, all its child img elements will use the cache specified by this element.
13/// Note that this could as simple as passing an `Entity<T: ImageCache>`
14pub fn image_cache(image_cache_provider: impl ImageCacheProvider) -> ImageCacheElement {
15    ImageCacheElement {
16        image_cache_provider: Box::new(image_cache_provider),
17        style: StyleRefinement::default(),
18        children: SmallVec::default(),
19    }
20}
21
22/// A dynamically typed image cache, which can be used to store any image cache
23#[derive(Clone)]
24pub struct AnyImageCache {
25    image_cache: AnyEntity,
26    load_fn: fn(
27        image_cache: &AnyEntity,
28        resource: &Resource,
29        window: &mut Window,
30        cx: &mut App,
31    ) -> Option<Result<Arc<RenderImage>, ImageCacheError>>,
32}
33
34impl<I: ImageCache> From<Entity<I>> for AnyImageCache {
35    fn from(image_cache: Entity<I>) -> Self {
36        Self {
37            image_cache: image_cache.into_any(),
38            load_fn: any_image_cache::load::<I>,
39        }
40    }
41}
42
43impl AnyImageCache {
44    /// Load an image given a resource
45    /// returns the result of loading the image if it has finished loading, or None if it is still loading
46    pub fn load(
47        &self,
48        resource: &Resource,
49        window: &mut Window,
50        cx: &mut App,
51    ) -> Option<Result<Arc<RenderImage>, ImageCacheError>> {
52        (self.load_fn)(&self.image_cache, resource, window, cx)
53    }
54}
55
56mod any_image_cache {
57    use super::*;
58
59    pub(crate) fn load<I: 'static + ImageCache>(
60        image_cache: &AnyEntity,
61        resource: &Resource,
62        window: &mut Window,
63        cx: &mut App,
64    ) -> Option<Result<Arc<RenderImage>, ImageCacheError>> {
65        let image_cache = image_cache.clone().downcast::<I>().unwrap();
66        image_cache.update(cx, |image_cache, cx| image_cache.load(resource, window, cx))
67    }
68}
69
70/// An image cache element.
71pub struct ImageCacheElement {
72    image_cache_provider: Box<dyn ImageCacheProvider>,
73    style: StyleRefinement,
74    children: SmallVec<[AnyElement; 2]>,
75}
76
77impl ParentElement for ImageCacheElement {
78    fn extend(&mut self, elements: impl IntoIterator<Item = AnyElement>) {
79        self.children.extend(elements)
80    }
81}
82
83impl Styled for ImageCacheElement {
84    fn style(&mut self) -> &mut StyleRefinement {
85        &mut self.style
86    }
87}
88
89impl IntoElement for ImageCacheElement {
90    type Element = Self;
91
92    fn into_element(self) -> Self::Element {
93        self
94    }
95}
96
97impl Element for ImageCacheElement {
98    type RequestLayoutState = SmallVec<[LayoutId; 4]>;
99    type PrepaintState = ();
100
101    fn id(&self) -> Option<ElementId> {
102        None
103    }
104
105    fn source_location(&self) -> Option<&'static core::panic::Location<'static>> {
106        None
107    }
108
109    fn request_layout(
110        &mut self,
111        _id: Option<&GlobalElementId>,
112        window: &mut Window,
113        cx: &mut App,
114    ) -> (LayoutId, Self::RequestLayoutState) {
115        let image_cache = self.image_cache_provider.provide(window, cx);
116        window.with_image_cache(Some(image_cache), |window| {
117            let child_layout_ids = self
118                .children
119                .iter_mut()
120                .map(|child| child.request_layout(window, cx))
121                .collect::<SmallVec<_>>();
122            let mut style = Style::default();
123            style.refine(&self.style);
124            let layout_id = window.request_layout(style, child_layout_ids.iter().copied(), cx);
125            (layout_id, child_layout_ids)
126        })
127    }
128
129    fn prepaint(
130        &mut self,
131        _id: Option<&GlobalElementId>,
132        _bounds: Bounds<Pixels>,
133        _request_layout: &mut Self::RequestLayoutState,
134        window: &mut Window,
135        cx: &mut App,
136    ) -> Self::PrepaintState {
137        for child in &mut self.children {
138            child.prepaint(window, cx);
139        }
140    }
141
142    fn paint(
143        &mut self,
144        _id: Option<&GlobalElementId>,
145        _bounds: Bounds<Pixels>,
146        _request_layout: &mut Self::RequestLayoutState,
147        _prepaint: &mut Self::PrepaintState,
148        window: &mut Window,
149        cx: &mut App,
150    ) {
151        let image_cache = self.image_cache_provider.provide(window, cx);
152        window.with_image_cache(Some(image_cache), |window| {
153            for child in &mut self.children {
154                child.paint(window, cx);
155            }
156        })
157    }
158}
159
160/// Owns an image load and its cached result.
161///
162/// Dropping the entry cancels a pending load. Image caches must remove loaded
163/// images from windows before discarding the entry.
164pub struct ImageCacheItem(CachedLoad<Result<Arc<RenderImage>, ImageCacheError>>);
165
166impl std::fmt::Debug for ImageCacheItem {
167    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
168        f.debug_struct("ImageCacheItem")
169            .field("result", &self.get())
170            .finish()
171    }
172}
173
174impl ImageCacheItem {
175    /// Starts loading an image into a new cache entry.
176    pub fn new(source: &Resource, cx: &mut App) -> Self {
177        let future = AssetLogger::<ImageAssetLoader>::load(source.clone(), cx);
178        Self(CachedLoad::new(future, cx))
179    }
180
181    /// Returns the cached result without subscribing to completion notifications.
182    pub fn get(&self) -> Option<Result<Arc<RenderImage>, ImageCacheError>> {
183        self.0.get()
184    }
185
186    /// Returns the cached result or subscribes the current view to completion.
187    pub fn use_image(&self, window: &Window) -> Option<Result<Arc<RenderImage>, ImageCacheError>> {
188        self.0.use_by(window.current_view())
189    }
190}
191
192/// An object that can handle the caching and unloading of images.
193/// Implementations of this trait should ensure that images are removed from all windows when they are no longer needed.
194pub trait ImageCache: 'static {
195    /// Load an image given a resource
196    /// returns the result of loading the image if it has finished loading, or None if it is still loading
197    fn load(
198        &mut self,
199        resource: &Resource,
200        window: &mut Window,
201        cx: &mut App,
202    ) -> Option<Result<Arc<RenderImage>, ImageCacheError>>;
203}
204
205/// An object that can create an ImageCache during the render phase.
206/// See the ImageCache trait for more information.
207pub trait ImageCacheProvider: 'static {
208    /// Called during the request_layout phase to create an ImageCache.
209    fn provide(&mut self, _window: &mut Window, _cx: &mut App) -> AnyImageCache;
210}
211
212impl<T: ImageCache> ImageCacheProvider for Entity<T> {
213    fn provide(&mut self, _window: &mut Window, _cx: &mut App) -> AnyImageCache {
214        self.clone().into()
215    }
216}
217
218/// An implementation of ImageCache, that uses an LRU caching strategy to unload images when the cache is full
219pub struct RetainAllImageCache(HashMap<u64, ImageCacheItem>);
220
221impl fmt::Debug for RetainAllImageCache {
222    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
223        f.debug_struct("HashMapImageCache")
224            .field("num_images", &self.0.len())
225            .finish()
226    }
227}
228
229impl RetainAllImageCache {
230    /// Create a new image cache.
231    #[inline]
232    pub fn new(cx: &mut App) -> Entity<Self> {
233        let e = cx.new(|_cx| RetainAllImageCache(HashMap::new()));
234        cx.observe_release(&e, |image_cache, cx| {
235            for (_, item) in std::mem::replace(&mut image_cache.0, HashMap::new()) {
236                if let Some(Ok(image)) = item.get() {
237                    cx.drop_image(image, None);
238                }
239            }
240        })
241        .detach();
242        e
243    }
244
245    /// Load an image from the given source.
246    ///
247    /// Returns `None` if the image is loading.
248    pub fn load(
249        &mut self,
250        source: &Resource,
251        window: &mut Window,
252        cx: &mut App,
253    ) -> Option<Result<Arc<RenderImage>, ImageCacheError>> {
254        let hash = hash(source);
255
256        self.0
257            .entry(hash)
258            .or_insert_with(|| ImageCacheItem::new(source, cx))
259            .use_image(window)
260    }
261
262    /// Clear the image cache.
263    pub fn clear(&mut self, window: &mut Window, cx: &mut App) {
264        for (_, item) in std::mem::replace(&mut self.0, HashMap::new()) {
265            if let Some(Ok(image)) = item.get() {
266                cx.drop_image(image, Some(window));
267            }
268        }
269    }
270
271    /// Remove the image from the cache by the given source.
272    pub fn remove(&mut self, source: &Resource, window: &mut Window, cx: &mut App) {
273        let hash = hash(source);
274        if let Some(item) = self.0.remove(&hash)
275            && let Some(Ok(image)) = item.get()
276        {
277            cx.drop_image(image, Some(window));
278        }
279    }
280
281    /// Returns the number of images in the cache.
282    pub fn len(&self) -> usize {
283        self.0.len()
284    }
285
286    /// Returns true if the cache is empty.
287    pub fn is_empty(&self) -> bool {
288        self.0.is_empty()
289    }
290}
291
292impl ImageCache for RetainAllImageCache {
293    fn load(
294        &mut self,
295        resource: &Resource,
296        window: &mut Window,
297        cx: &mut App,
298    ) -> Option<Result<Arc<RenderImage>, ImageCacheError>> {
299        RetainAllImageCache::load(self, resource, window, cx)
300    }
301}
302
303/// Constructs a retain-all image cache that uses the element state associated with the given ID.
304pub fn retain_all(id: impl Into<ElementId>) -> RetainAllImageCacheProvider {
305    RetainAllImageCacheProvider { id: id.into() }
306}
307
308/// A provider struct for creating a retain-all image cache inline
309pub struct RetainAllImageCacheProvider {
310    id: ElementId,
311}
312
313impl ImageCacheProvider for RetainAllImageCacheProvider {
314    fn provide(&mut self, window: &mut Window, cx: &mut App) -> AnyImageCache {
315        window
316            .with_global_id(self.id.clone(), |global_id, window| {
317                window.with_element_state::<Entity<RetainAllImageCache>, _>(
318                    global_id,
319                    |cache, _window| {
320                        let mut cache = cache.unwrap_or_else(|| RetainAllImageCache::new(cx));
321                        (cache.clone(), cache)
322                    },
323                )
324            })
325            .into()
326    }
327}