Skip to main content

gpui/elements/
image_cache.rs

1use crate::{
2    AnyElement, AnyEntity, App, AppContext, Asset, AssetLogger, Bounds, Element, ElementId, Entity,
3    GlobalElementId, ImageAssetLoader, ImageCacheError, InspectorElementId, IntoElement, LayoutId,
4    ParentElement, 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        _inspector_id: Option<&InspectorElementId>,
113        window: &mut Window,
114        cx: &mut App,
115    ) -> (LayoutId, Self::RequestLayoutState) {
116        let image_cache = self.image_cache_provider.provide(window, cx);
117        window.with_image_cache(Some(image_cache), |window| {
118            let child_layout_ids = self
119                .children
120                .iter_mut()
121                .map(|child| child.request_layout(window, cx))
122                .collect::<SmallVec<_>>();
123            let mut style = Style::default();
124            style.refine(&self.style);
125            let layout_id = window.request_layout(style, child_layout_ids.iter().copied(), cx);
126            (layout_id, child_layout_ids)
127        })
128    }
129
130    fn prepaint(
131        &mut self,
132        _id: Option<&GlobalElementId>,
133        _inspector_id: Option<&InspectorElementId>,
134        _bounds: Bounds<Pixels>,
135        _request_layout: &mut Self::RequestLayoutState,
136        window: &mut Window,
137        cx: &mut App,
138    ) -> Self::PrepaintState {
139        for child in &mut self.children {
140            child.prepaint(window, cx);
141        }
142    }
143
144    fn paint(
145        &mut self,
146        _id: Option<&GlobalElementId>,
147        _inspector_id: Option<&InspectorElementId>,
148        _bounds: Bounds<Pixels>,
149        _request_layout: &mut Self::RequestLayoutState,
150        _prepaint: &mut Self::PrepaintState,
151        window: &mut Window,
152        cx: &mut App,
153    ) {
154        let image_cache = self.image_cache_provider.provide(window, cx);
155        window.with_image_cache(Some(image_cache), |window| {
156            for child in &mut self.children {
157                child.paint(window, cx);
158            }
159        })
160    }
161}
162
163/// Owns an image load and its cached result.
164///
165/// Dropping the entry cancels a pending load. Image caches must remove loaded
166/// images from windows before discarding the entry.
167pub struct ImageCacheItem(CachedLoad<Result<Arc<RenderImage>, ImageCacheError>>);
168
169impl std::fmt::Debug for ImageCacheItem {
170    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
171        f.debug_struct("ImageCacheItem")
172            .field("result", &self.get())
173            .finish()
174    }
175}
176
177impl ImageCacheItem {
178    /// Starts loading an image into a new cache entry.
179    pub fn new(source: &Resource, cx: &mut App) -> Self {
180        let future = AssetLogger::<ImageAssetLoader>::load(source.clone(), cx);
181        Self(CachedLoad::new(future, cx))
182    }
183
184    /// Returns the cached result without subscribing to completion notifications.
185    pub fn get(&self) -> Option<Result<Arc<RenderImage>, ImageCacheError>> {
186        self.0.get()
187    }
188
189    /// Returns the cached result or subscribes the current view to completion.
190    pub fn use_image(&self, window: &Window) -> Option<Result<Arc<RenderImage>, ImageCacheError>> {
191        self.0.use_by(window.current_view())
192    }
193}
194
195/// An object that can handle the caching and unloading of images.
196/// Implementations of this trait should ensure that images are removed from all windows when they are no longer needed.
197pub trait ImageCache: 'static {
198    /// Load an image given a resource
199    /// returns the result of loading the image if it has finished loading, or None if it is still loading
200    fn load(
201        &mut self,
202        resource: &Resource,
203        window: &mut Window,
204        cx: &mut App,
205    ) -> Option<Result<Arc<RenderImage>, ImageCacheError>>;
206}
207
208/// An object that can create an ImageCache during the render phase.
209/// See the ImageCache trait for more information.
210pub trait ImageCacheProvider: 'static {
211    /// Called during the request_layout phase to create an ImageCache.
212    fn provide(&mut self, _window: &mut Window, _cx: &mut App) -> AnyImageCache;
213}
214
215impl<T: ImageCache> ImageCacheProvider for Entity<T> {
216    fn provide(&mut self, _window: &mut Window, _cx: &mut App) -> AnyImageCache {
217        self.clone().into()
218    }
219}
220
221/// An implementation of ImageCache, that uses an LRU caching strategy to unload images when the cache is full
222pub struct RetainAllImageCache(HashMap<u64, ImageCacheItem>);
223
224impl fmt::Debug for RetainAllImageCache {
225    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
226        f.debug_struct("HashMapImageCache")
227            .field("num_images", &self.0.len())
228            .finish()
229    }
230}
231
232impl RetainAllImageCache {
233    /// Create a new image cache.
234    #[inline]
235    pub fn new(cx: &mut App) -> Entity<Self> {
236        let e = cx.new(|_cx| RetainAllImageCache(HashMap::new()));
237        cx.observe_release(&e, |image_cache, cx| {
238            for (_, item) in std::mem::replace(&mut image_cache.0, HashMap::new()) {
239                if let Some(Ok(image)) = item.get() {
240                    cx.drop_image(image, None);
241                }
242            }
243        })
244        .detach();
245        e
246    }
247
248    /// Load an image from the given source.
249    ///
250    /// Returns `None` if the image is loading.
251    pub fn load(
252        &mut self,
253        source: &Resource,
254        window: &mut Window,
255        cx: &mut App,
256    ) -> Option<Result<Arc<RenderImage>, ImageCacheError>> {
257        let hash = hash(source);
258
259        self.0
260            .entry(hash)
261            .or_insert_with(|| ImageCacheItem::new(source, cx))
262            .use_image(window)
263    }
264
265    /// Clear the image cache.
266    pub fn clear(&mut self, window: &mut Window, cx: &mut App) {
267        for (_, item) in std::mem::replace(&mut self.0, HashMap::new()) {
268            if let Some(Ok(image)) = item.get() {
269                cx.drop_image(image, Some(window));
270            }
271        }
272    }
273
274    /// Remove the image from the cache by the given source.
275    pub fn remove(&mut self, source: &Resource, window: &mut Window, cx: &mut App) {
276        let hash = hash(source);
277        if let Some(item) = self.0.remove(&hash)
278            && let Some(Ok(image)) = item.get()
279        {
280            cx.drop_image(image, Some(window));
281        }
282    }
283
284    /// Returns the number of images in the cache.
285    pub fn len(&self) -> usize {
286        self.0.len()
287    }
288
289    /// Returns true if the cache is empty.
290    pub fn is_empty(&self) -> bool {
291        self.0.is_empty()
292    }
293}
294
295impl ImageCache for RetainAllImageCache {
296    fn load(
297        &mut self,
298        resource: &Resource,
299        window: &mut Window,
300        cx: &mut App,
301    ) -> Option<Result<Arc<RenderImage>, ImageCacheError>> {
302        RetainAllImageCache::load(self, resource, window, cx)
303    }
304}
305
306/// Constructs a retain-all image cache that uses the element state associated with the given ID.
307pub fn retain_all(id: impl Into<ElementId>) -> RetainAllImageCacheProvider {
308    RetainAllImageCacheProvider { id: id.into() }
309}
310
311/// A provider struct for creating a retain-all image cache inline
312pub struct RetainAllImageCacheProvider {
313    id: ElementId,
314}
315
316impl ImageCacheProvider for RetainAllImageCacheProvider {
317    fn provide(&mut self, window: &mut Window, cx: &mut App) -> AnyImageCache {
318        window
319            .with_global_id(self.id.clone(), |global_id, window| {
320                window.with_element_state::<Entity<RetainAllImageCache>, _>(
321                    global_id,
322                    |cache, _window| {
323                        let mut cache = cache.unwrap_or_else(|| RetainAllImageCache::new(cx));
324                        (cache.clone(), cache)
325                    },
326                )
327            })
328            .into()
329    }
330}