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.
13pub fn image_cache<I: ImageCache>(image_cache: &Entity<I>) -> ImageCacheElement {
14 ImageCacheElement {
15 image_cache: image_cache.clone().into(),
16 style: StyleRefinement::default(),
17 children: SmallVec::default(),
18 }
19}
20
21/// A dynamically typed image cache, which can be used to store any image cache
22#[derive(Clone)]
23pub struct AnyImageCache {
24 image_cache: AnyEntity,
25 load_fn: fn(
26 image_cache: &AnyEntity,
27 resource: &Resource,
28 window: &mut Window,
29 cx: &mut App,
30 ) -> Option<Result<Arc<RenderImage>, ImageCacheError>>,
31}
32
33impl<I: ImageCache> From<Entity<I>> for AnyImageCache {
34 fn from(image_cache: Entity<I>) -> Self {
35 Self {
36 image_cache: image_cache.into_any(),
37 load_fn: any_image_cache::load::<I>,
38 }
39 }
40}
41
42impl AnyImageCache {
43 /// Load an image given a resource
44 /// returns the result of loading the image if it has finished loading, or None if it is still loading
45 pub fn load(
46 &self,
47 resource: &Resource,
48 window: &mut Window,
49 cx: &mut App,
50 ) -> Option<Result<Arc<RenderImage>, ImageCacheError>> {
51 (self.load_fn)(&self.image_cache, resource, window, cx)
52 }
53}
54
55mod any_image_cache {
56 use super::*;
57
58 pub(crate) fn load<I: 'static + ImageCache>(
59 image_cache: &AnyEntity,
60 resource: &Resource,
61 window: &mut Window,
62 cx: &mut App,
63 ) -> Option<Result<Arc<RenderImage>, ImageCacheError>> {
64 let image_cache = image_cache.clone().downcast::<I>().unwrap();
65 return image_cache.update(cx, |image_cache, cx| image_cache.load(resource, window, cx));
66 }
67}
68
69/// An image cache element.
70pub struct ImageCacheElement {
71 image_cache: AnyImageCache,
72 style: StyleRefinement,
73 children: SmallVec<[AnyElement; 2]>,
74}
75
76impl ParentElement for ImageCacheElement {
77 fn extend(&mut self, elements: impl IntoIterator<Item = AnyElement>) {
78 self.children.extend(elements)
79 }
80}
81
82impl Styled for ImageCacheElement {
83 fn style(&mut self) -> &mut StyleRefinement {
84 &mut self.style
85 }
86}
87
88impl IntoElement for ImageCacheElement {
89 type Element = Self;
90
91 fn into_element(self) -> Self::Element {
92 self
93 }
94}
95
96impl Element for ImageCacheElement {
97 type RequestLayoutState = SmallVec<[LayoutId; 4]>;
98 type PrepaintState = ();
99
100 fn id(&self) -> Option<ElementId> {
101 None
102 }
103
104 fn request_layout(
105 &mut self,
106 _id: Option<&GlobalElementId>,
107 window: &mut Window,
108 cx: &mut App,
109 ) -> (LayoutId, Self::RequestLayoutState) {
110 window.with_image_cache(self.image_cache.clone(), |window| {
111 let child_layout_ids = self
112 .children
113 .iter_mut()
114 .map(|child| child.request_layout(window, cx))
115 .collect::<SmallVec<_>>();
116 let mut style = Style::default();
117 style.refine(&self.style);
118 let layout_id = window.request_layout(style, child_layout_ids.iter().copied(), cx);
119 (layout_id, child_layout_ids)
120 })
121 }
122
123 fn prepaint(
124 &mut self,
125 _id: Option<&GlobalElementId>,
126 _bounds: Bounds<Pixels>,
127 _request_layout: &mut Self::RequestLayoutState,
128 window: &mut Window,
129 cx: &mut App,
130 ) -> Self::PrepaintState {
131 for child in &mut self.children {
132 child.prepaint(window, cx);
133 }
134 }
135
136 fn paint(
137 &mut self,
138 _id: Option<&GlobalElementId>,
139 _bounds: Bounds<Pixels>,
140 _request_layout: &mut Self::RequestLayoutState,
141 _prepaint: &mut Self::PrepaintState,
142 window: &mut Window,
143 cx: &mut App,
144 ) {
145 window.with_image_cache(self.image_cache.clone(), |window| {
146 for child in &mut self.children {
147 child.paint(window, cx);
148 }
149 })
150 }
151}
152
153type ImageLoadingTask = Shared<Task<Result<Arc<RenderImage>, ImageCacheError>>>;
154
155enum CacheItem {
156 Loading(ImageLoadingTask),
157 Loaded(Result<Arc<RenderImage>, ImageCacheError>),
158}
159
160impl CacheItem {
161 fn get(&mut self) -> Option<Result<Arc<RenderImage>, ImageCacheError>> {
162 match self {
163 CacheItem::Loading(task) => {
164 let res = task.now_or_never()?;
165 *self = CacheItem::Loaded(res.clone());
166 Some(res)
167 }
168 CacheItem::Loaded(res) => Some(res.clone()),
169 }
170 }
171}
172
173/// An object that can handle the caching and unloading of images.
174/// Implementations of this trait should ensure that images are removed from all windows when they are no longer needed.
175pub trait ImageCache: 'static {
176 /// Load an image given a resource
177 /// returns the result of loading the image if it has finished loading, or None if it is still loading
178 fn load(
179 &mut self,
180 resource: &Resource,
181 window: &mut Window,
182 cx: &mut App,
183 ) -> Option<Result<Arc<RenderImage>, ImageCacheError>>;
184}
185
186/// An implementation of ImageCache, that uses an LRU caching strategy to unload images when the cache is full
187pub struct HashMapImageCache(HashMap<u64, CacheItem>);
188
189impl fmt::Debug for HashMapImageCache {
190 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
191 f.debug_struct("HashMapImageCache")
192 .field("num_images", &self.0.len())
193 .finish()
194 }
195}
196
197impl HashMapImageCache {
198 /// Create a new image cache.
199 #[inline]
200 pub fn new(cx: &mut App) -> Entity<Self> {
201 let e = cx.new(|_cx| HashMapImageCache(HashMap::new()));
202 cx.observe_release(&e, |image_cache, cx| {
203 for (_, mut item) in std::mem::replace(&mut image_cache.0, HashMap::new()) {
204 if let Some(Ok(image)) = item.get() {
205 cx.drop_image(image, None);
206 }
207 }
208 })
209 .detach();
210 e
211 }
212
213 /// Load an image from the given source.
214 ///
215 /// Returns `None` if the image is loading.
216 pub fn load(
217 &mut self,
218 source: &Resource,
219 window: &mut Window,
220 cx: &mut App,
221 ) -> Option<Result<Arc<RenderImage>, ImageCacheError>> {
222 let hash = hash(source);
223
224 if let Some(item) = self.0.get_mut(&hash) {
225 return item.get();
226 }
227
228 let fut = AssetLogger::<ImageAssetLoader>::load(source.clone(), cx);
229 let task = cx.background_executor().spawn(fut).shared();
230 self.0.insert(hash, CacheItem::Loading(task.clone()));
231
232 let entity = window.current_view();
233 window
234 .spawn(cx, {
235 async move |cx| {
236 _ = task.await;
237 cx.on_next_frame(move |_, cx| {
238 cx.notify(entity);
239 });
240 }
241 })
242 .detach();
243
244 None
245 }
246
247 /// Clear the image cache.
248 pub fn clear(&mut self, window: &mut Window, cx: &mut App) {
249 for (_, mut item) in std::mem::replace(&mut self.0, HashMap::new()) {
250 if let Some(Ok(image)) = item.get() {
251 cx.drop_image(image, Some(window));
252 }
253 }
254 }
255
256 /// Remove the image from the cache by the given source.
257 pub fn remove(&mut self, source: &Resource, window: &mut Window, cx: &mut App) {
258 let hash = hash(source);
259 if let Some(mut item) = self.0.remove(&hash) {
260 if let Some(Ok(image)) = item.get() {
261 cx.drop_image(image, Some(window));
262 }
263 }
264 }
265
266 /// Returns the number of images in the cache.
267 pub fn len(&self) -> usize {
268 self.0.len()
269 }
270
271 /// Returns true if the cache is empty.
272 pub fn is_empty(&self) -> bool {
273 self.0.is_empty()
274 }
275}
276
277impl ImageCache for HashMapImageCache {
278 fn load(
279 &mut self,
280 resource: &Resource,
281 window: &mut Window,
282 cx: &mut App,
283 ) -> Option<Result<Arc<RenderImage>, ImageCacheError>> {
284 HashMapImageCache::load(self, resource, window, cx)
285 }
286}