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/// Scopes an image cache to the [`img`](crate::img) elements below it.
13///
14/// Each image in the children uses the cache that `image_cache_provider` returns, instead of the
15/// app-wide cache. Pass an `Entity` of an [`ImageCache`] to share one cache, or [`retain_all`] to get
16/// a cache that lives with this element's state. The provider is called on every layout and paint.
17///
18/// Only [`crate::ImageSource::Resource`] sources use the cache. See [`crate::Img::image_cache`].
19///
20/// # Examples
21///
22/// ```
23/// # use gpui::*;
24/// let gallery = image_cache(retain_all("gallery")).children((0..3).map(|ix| {
25///     img(format!("https://example.com/photos/{ix}.jpg"))
26///         .size(px(120.))
27///         .into_any_element()
28/// }));
29/// ```
30pub fn image_cache(image_cache_provider: impl ImageCacheProvider) -> ImageCacheElement {
31    ImageCacheElement {
32        image_cache_provider: Box::new(image_cache_provider),
33        style: StyleRefinement::default(),
34        children: SmallVec::default(),
35    }
36}
37
38/// A type-erased [`ImageCache`], so one [`img`](crate::img) can hold any cache type.
39///
40/// Built from an `Entity` of an [`ImageCache`] with `From`.
41#[derive(Clone)]
42pub struct AnyImageCache {
43    image_cache: AnyEntity,
44    load_fn: fn(
45        image_cache: &AnyEntity,
46        resource: &Resource,
47        window: &mut Window,
48        cx: &mut App,
49    ) -> Option<Result<Arc<RenderImage>, ImageCacheError>>,
50}
51
52impl<I: ImageCache> From<Entity<I>> for AnyImageCache {
53    fn from(image_cache: Entity<I>) -> Self {
54        Self {
55            image_cache: image_cache.into_any(),
56            load_fn: any_image_cache::load::<I>,
57        }
58    }
59}
60
61impl AnyImageCache {
62    /// Asks the cache for `resource`. Returns `None` while the image loads, and the result once it is
63    /// ready. See [`ImageCache::load`].
64    pub fn load(
65        &self,
66        resource: &Resource,
67        window: &mut Window,
68        cx: &mut App,
69    ) -> Option<Result<Arc<RenderImage>, ImageCacheError>> {
70        (self.load_fn)(&self.image_cache, resource, window, cx)
71    }
72}
73
74mod any_image_cache {
75    use super::*;
76
77    pub(crate) fn load<I: 'static + ImageCache>(
78        image_cache: &AnyEntity,
79        resource: &Resource,
80        window: &mut Window,
81        cx: &mut App,
82    ) -> Option<Result<Arc<RenderImage>, ImageCacheError>> {
83        let image_cache = image_cache.clone().downcast::<I>().unwrap();
84        image_cache.update(cx, |image_cache, cx| image_cache.load(resource, window, cx))
85    }
86}
87
88/// The element that [`image_cache`] returns. It installs the cache for its children during layout
89/// and paint, and lays out like a `div`.
90pub struct ImageCacheElement {
91    image_cache_provider: Box<dyn ImageCacheProvider>,
92    style: StyleRefinement,
93    children: SmallVec<[AnyElement; 2]>,
94}
95
96impl ParentElement for ImageCacheElement {
97    fn extend(&mut self, elements: impl IntoIterator<Item = AnyElement>) {
98        self.children.extend(elements)
99    }
100}
101
102impl Styled for ImageCacheElement {
103    fn style(&mut self) -> &mut StyleRefinement {
104        &mut self.style
105    }
106}
107
108impl IntoElement for ImageCacheElement {
109    type Element = Self;
110
111    fn into_element(self) -> Self::Element {
112        self
113    }
114}
115
116impl Element for ImageCacheElement {
117    type RequestLayoutState = SmallVec<[LayoutId; 4]>;
118    type PrepaintState = ();
119
120    fn id(&self) -> Option<ElementId> {
121        None
122    }
123
124    fn source_location(&self) -> Option<&'static core::panic::Location<'static>> {
125        None
126    }
127
128    fn request_layout(
129        &mut self,
130        _id: Option<&GlobalElementId>,
131        _inspector_id: Option<&InspectorElementId>,
132        window: &mut Window,
133        cx: &mut App,
134    ) -> (LayoutId, Self::RequestLayoutState) {
135        let image_cache = self.image_cache_provider.provide(window, cx);
136        window.with_image_cache(Some(image_cache), |window| {
137            let child_layout_ids = self
138                .children
139                .iter_mut()
140                .map(|child| child.request_layout(window, cx))
141                .collect::<SmallVec<_>>();
142            let mut style = Style::default();
143            style.refine(&self.style);
144            let layout_id = window.request_layout(style, child_layout_ids.iter().copied(), cx);
145            (layout_id, child_layout_ids)
146        })
147    }
148
149    fn prepaint(
150        &mut self,
151        _id: Option<&GlobalElementId>,
152        _inspector_id: Option<&InspectorElementId>,
153        _bounds: Bounds<Pixels>,
154        _request_layout: &mut Self::RequestLayoutState,
155        window: &mut Window,
156        cx: &mut App,
157    ) -> Self::PrepaintState {
158        for child in &mut self.children {
159            child.prepaint(window, cx);
160        }
161    }
162
163    fn paint(
164        &mut self,
165        _id: Option<&GlobalElementId>,
166        _inspector_id: Option<&InspectorElementId>,
167        _bounds: Bounds<Pixels>,
168        _request_layout: &mut Self::RequestLayoutState,
169        _prepaint: &mut Self::PrepaintState,
170        window: &mut Window,
171        cx: &mut App,
172    ) {
173        let image_cache = self.image_cache_provider.provide(window, cx);
174        window.with_image_cache(Some(image_cache), |window| {
175            for child in &mut self.children {
176                child.paint(window, cx);
177            }
178        })
179    }
180}
181
182/// Owns an image load and its cached result.
183///
184/// Dropping the entry cancels a pending load. Image caches must remove loaded
185/// images from windows before discarding the entry.
186pub struct ImageCacheItem(CachedLoad<Result<Arc<RenderImage>, ImageCacheError>>);
187
188impl std::fmt::Debug for ImageCacheItem {
189    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
190        f.debug_struct("ImageCacheItem")
191            .field("result", &self.get())
192            .finish()
193    }
194}
195
196impl ImageCacheItem {
197    /// Starts loading an image into a new cache entry.
198    pub fn new(source: &Resource, cx: &mut App) -> Self {
199        let future = AssetLogger::<ImageAssetLoader>::load(source.clone(), cx);
200        Self(CachedLoad::new(future, cx))
201    }
202
203    /// Returns the cached result without subscribing to completion notifications.
204    pub fn get(&self) -> Option<Result<Arc<RenderImage>, ImageCacheError>> {
205        self.0.get()
206    }
207
208    /// Returns the cached result or subscribes the current view to completion.
209    pub fn use_image(&self, window: &Window) -> Option<Result<Arc<RenderImage>, ImageCacheError>> {
210        self.0.use_by(window.current_view())
211    }
212}
213
214/// A store of decoded images that [`img`](crate::img) elements read from.
215///
216/// Implement it to control how images load and when they are released. [`RetainAllImageCache`]
217/// keeps every image until you clear it. Release images you no longer need, so they are removed
218/// from all windows.
219pub trait ImageCache: 'static {
220    /// Returns the image for `resource` if it is ready, or `None` while it loads.
221    ///
222    /// This runs on every layout and paint of each image that uses the cache, so it must return
223    /// quickly. Start the load in the background, return `None`, and notify the current view when
224    /// the load finishes, as [`RetainAllImageCache`] does. The element is then drawn again.
225    fn load(
226        &mut self,
227        resource: &Resource,
228        window: &mut Window,
229        cx: &mut App,
230    ) -> Option<Result<Arc<RenderImage>, ImageCacheError>>;
231}
232
233/// Creates the [`AnyImageCache`] for an [`image_cache`] element.
234///
235/// An `Entity` of an [`ImageCache`] implements it, and so does the provider from [`retain_all`].
236pub trait ImageCacheProvider: 'static {
237    /// Returns the cache for the children of an [`image_cache`] element.
238    ///
239    /// Called in both the layout and the paint phase.
240    fn provide(&mut self, _window: &mut Window, _cx: &mut App) -> AnyImageCache;
241}
242
243impl<T: ImageCache> ImageCacheProvider for Entity<T> {
244    fn provide(&mut self, _window: &mut Window, _cx: &mut App) -> AnyImageCache {
245        self.clone().into()
246    }
247}
248
249/// An [`ImageCache`] that keeps every image it loads until you clear it, remove a source, or drop
250/// the cache.
251///
252/// Create one with [`RetainAllImageCache::new`], or let [`retain_all`] create one for an element.
253/// Loads run on the background executor, and the view that asked is notified when each finishes.
254/// Dropping the cache drops its decoded images from the windows.
255pub struct RetainAllImageCache(HashMap<u64, ImageCacheItem>);
256
257impl fmt::Debug for RetainAllImageCache {
258    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
259        f.debug_struct("HashMapImageCache")
260            .field("num_images", &self.0.len())
261            .finish()
262    }
263}
264
265impl RetainAllImageCache {
266    /// Creates a cache as an app entity. Its images are dropped from the windows when the entity is
267    /// released.
268    ///
269    /// Keep the returned `Entity` alive for as long as its images are needed.
270    #[inline]
271    pub fn new(cx: &mut App) -> Entity<Self> {
272        let e = cx.new(|_cx| RetainAllImageCache(HashMap::new()));
273        cx.observe_release(&e, |image_cache, cx| {
274            for (_, item) in std::mem::replace(&mut image_cache.0, HashMap::new()) {
275                if let Some(Ok(image)) = item.get() {
276                    cx.drop_image(image, None);
277                }
278            }
279        })
280        .detach();
281        e
282    }
283
284    /// Returns the image for `source`, starting its load on the first request.
285    ///
286    /// Returns `None` while the image loads. After that, it returns the same result on every call,
287    /// including an error. Requests for one source share a single load. See [`ImageCache::load`].
288    pub fn load(
289        &mut self,
290        source: &Resource,
291        window: &mut Window,
292        cx: &mut App,
293    ) -> Option<Result<Arc<RenderImage>, ImageCacheError>> {
294        let hash = hash(source);
295
296        self.0
297            .entry(hash)
298            .or_insert_with(|| ImageCacheItem::new(source, cx))
299            .use_image(window)
300    }
301
302    /// Removes every entry and drops the decoded images from the windows.
303    ///
304    /// A load still in progress is forgotten, so the next request for its source starts again.
305    pub fn clear(&mut self, window: &mut Window, cx: &mut App) {
306        for (_, item) in std::mem::replace(&mut self.0, HashMap::new()) {
307            if let Some(Ok(image)) = item.get() {
308                cx.drop_image(image, Some(window));
309            }
310        }
311    }
312
313    /// Removes one source and drops its decoded image from the windows. The next request for it
314    /// loads it again.
315    pub fn remove(&mut self, source: &Resource, window: &mut Window, cx: &mut App) {
316        let hash = hash(source);
317        if let Some(item) = self.0.remove(&hash)
318            && let Some(Ok(image)) = item.get()
319        {
320            cx.drop_image(image, Some(window));
321        }
322    }
323
324    /// Returns the number of sources in the cache, including ones still loading.
325    pub fn len(&self) -> usize {
326        self.0.len()
327    }
328
329    /// Returns `true` if the cache holds no sources, including ones still loading.
330    pub fn is_empty(&self) -> bool {
331        self.0.is_empty()
332    }
333}
334
335impl ImageCache for RetainAllImageCache {
336    fn load(
337        &mut self,
338        resource: &Resource,
339        window: &mut Window,
340        cx: &mut App,
341    ) -> Option<Result<Arc<RenderImage>, ImageCacheError>> {
342        RetainAllImageCache::load(self, resource, window, cx)
343    }
344}
345
346/// Returns a provider for an [`image_cache`] element that keeps one [`RetainAllImageCache`] in
347/// the element state under `id`.
348///
349/// The cache lasts as long as the element state, which is dropped when the element is no longer
350/// rendered. Give each gallery its own `id`, so they don't share a cache. For the bigger picture,
351/// see the [images guide](crate::_documentation::guides::images_and_assets).
352///
353/// # Examples
354///
355/// ```
356/// # use gpui::*;
357/// let thumbnails = image_cache(retain_all("thumbnails")).child(
358///     img("https://example.com/thumb.png").size(px(64.)),
359/// );
360/// ```
361pub fn retain_all(id: impl Into<ElementId>) -> RetainAllImageCacheProvider {
362    RetainAllImageCacheProvider { id: id.into() }
363}
364
365/// The [`ImageCacheProvider`] that [`retain_all`] returns.
366pub struct RetainAllImageCacheProvider {
367    id: ElementId,
368}
369
370impl ImageCacheProvider for RetainAllImageCacheProvider {
371    fn provide(&mut self, window: &mut Window, cx: &mut App) -> AnyImageCache {
372        window
373            .with_global_id(self.id.clone(), |global_id, window| {
374                window.with_element_state::<Entity<RetainAllImageCache>, _>(
375                    global_id,
376                    |cache, _window| {
377                        let mut cache = cache.unwrap_or_else(|| RetainAllImageCache::new(cx));
378                        (cache.clone(), cache)
379                    },
380                )
381            })
382            .into()
383    }
384}