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