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