353 lines · 11.0 KB · Rust
RawDownload
use crate::{    AnyElement, AnyEntity, App, AppContext, Asset, AssetLogger, Bounds, Element, ElementId, Entity,    GlobalElementId, ImageAssetLoader, ImageCacheError, InspectorElementId, IntoElement, LayoutId,    ParentElement, Pixels, RenderImage, Resource, Style, StyleRefinement, Styled, Task, Window,    hash,}; use futures::{FutureExt, future::Shared};use refineable::Refineable;use smallvec::SmallVec;use std::{collections::HashMap, fmt, sync::Arc}; /// An image cache element, all its child img elements will use the cache specified by this element./// Note that this could as simple as passing an `Entity<T: ImageCache>`pub fn image_cache(image_cache_provider: impl ImageCacheProvider) -> ImageCacheElement {    ImageCacheElement {        image_cache_provider: Box::new(image_cache_provider),        style: StyleRefinement::default(),        children: SmallVec::default(),    }} /// A dynamically typed image cache, which can be used to store any image cache#[derive(Clone)]pub struct AnyImageCache {    image_cache: AnyEntity,    load_fn: fn(        image_cache: &AnyEntity,        resource: &Resource,        window: &mut Window,        cx: &mut App,    ) -> Option<Result<Arc<RenderImage>, ImageCacheError>>,} impl<I: ImageCache> From<Entity<I>> for AnyImageCache {    fn from(image_cache: Entity<I>) -> Self {        Self {            image_cache: image_cache.into_any(),            load_fn: any_image_cache::load::<I>,        }    }} impl AnyImageCache {    /// Load an image given a resource    /// returns the result of loading the image if it has finished loading, or None if it is still loading    pub fn load(        &self,        resource: &Resource,        window: &mut Window,        cx: &mut App,    ) -> Option<Result<Arc<RenderImage>, ImageCacheError>> {        (self.load_fn)(&self.image_cache, resource, window, cx)    }} mod any_image_cache {    use super::*;     pub(crate) fn load<I: 'static + ImageCache>(        image_cache: &AnyEntity,        resource: &Resource,        window: &mut Window,        cx: &mut App,    ) -> Option<Result<Arc<RenderImage>, ImageCacheError>> {        let image_cache = image_cache.clone().downcast::<I>().unwrap();        image_cache.update(cx, |image_cache, cx| image_cache.load(resource, window, cx))    }} /// An image cache element.pub struct ImageCacheElement {    image_cache_provider: Box<dyn ImageCacheProvider>,    style: StyleRefinement,    children: SmallVec<[AnyElement; 2]>,} impl ParentElement for ImageCacheElement {    fn extend(&mut self, elements: impl IntoIterator<Item = AnyElement>) {        self.children.extend(elements)    }} impl Styled for ImageCacheElement {    fn style(&mut self) -> &mut StyleRefinement {        &mut self.style    }} impl IntoElement for ImageCacheElement {    type Element = Self;     fn into_element(self) -> Self::Element {        self    }} impl Element for ImageCacheElement {    type RequestLayoutState = SmallVec<[LayoutId; 4]>;    type PrepaintState = ();     fn id(&self) -> Option<ElementId> {        None    }     fn source_location(&self) -> Option<&'static core::panic::Location<'static>> {        None    }     fn request_layout(        &mut self,        _id: Option<&GlobalElementId>,        _inspector_id: Option<&InspectorElementId>,        window: &mut Window,        cx: &mut App,    ) -> (LayoutId, Self::RequestLayoutState) {        let image_cache = self.image_cache_provider.provide(window, cx);        window.with_image_cache(Some(image_cache), |window| {            let child_layout_ids = self                .children                .iter_mut()                .map(|child| child.request_layout(window, cx))                .collect::<SmallVec<_>>();            let mut style = Style::default();            style.refine(&self.style);            let layout_id = window.request_layout(style, child_layout_ids.iter().copied(), cx);            (layout_id, child_layout_ids)        })    }     fn prepaint(        &mut self,        _id: Option<&GlobalElementId>,        _inspector_id: Option<&InspectorElementId>,        _bounds: Bounds<Pixels>,        _request_layout: &mut Self::RequestLayoutState,        window: &mut Window,        cx: &mut App,    ) -> Self::PrepaintState {        for child in &mut self.children {            child.prepaint(window, cx);        }    }     fn paint(        &mut self,        _id: Option<&GlobalElementId>,        _inspector_id: Option<&InspectorElementId>,        _bounds: Bounds<Pixels>,        _request_layout: &mut Self::RequestLayoutState,        _prepaint: &mut Self::PrepaintState,        window: &mut Window,        cx: &mut App,    ) {        let image_cache = self.image_cache_provider.provide(window, cx);        window.with_image_cache(Some(image_cache), |window| {            for child in &mut self.children {                child.paint(window, cx);            }        })    }} /// An image loading task associated with an image cache.pub type ImageLoadingTask = Shared<Task<Result<Arc<RenderImage>, ImageCacheError>>>; /// An image cache itempub enum ImageCacheItem {    /// The associated image is currently loading    Loading(ImageLoadingTask),    /// This item has loaded an image.    Loaded(Result<Arc<RenderImage>, ImageCacheError>),} impl std::fmt::Debug for ImageCacheItem {    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {        let status = match self {            ImageCacheItem::Loading(_) => &"Loading...".to_string(),            ImageCacheItem::Loaded(render_image) => &format!("{:?}", render_image),        };        f.debug_struct("ImageCacheItem")            .field("status", status)            .finish()    }} impl ImageCacheItem {    /// Attempt to get the image from the cache item.    pub fn get(&mut self) -> Option<Result<Arc<RenderImage>, ImageCacheError>> {        match self {            ImageCacheItem::Loading(task) => {                let res = task.now_or_never()?;                *self = ImageCacheItem::Loaded(res.clone());                Some(res)            }            ImageCacheItem::Loaded(res) => Some(res.clone()),        }    }} /// An object that can handle the caching and unloading of images./// Implementations of this trait should ensure that images are removed from all windows when they are no longer needed.pub trait ImageCache: 'static {    /// Load an image given a resource    /// returns the result of loading the image if it has finished loading, or None if it is still loading    fn load(        &mut self,        resource: &Resource,        window: &mut Window,        cx: &mut App,    ) -> Option<Result<Arc<RenderImage>, ImageCacheError>>;} /// An object that can create an ImageCache during the render phase./// See the ImageCache trait for more information.pub trait ImageCacheProvider: 'static {    /// Called during the request_layout phase to create an ImageCache.    fn provide(&mut self, _window: &mut Window, _cx: &mut App) -> AnyImageCache;} impl<T: ImageCache> ImageCacheProvider for Entity<T> {    fn provide(&mut self, _window: &mut Window, _cx: &mut App) -> AnyImageCache {        self.clone().into()    }} /// An implementation of ImageCache, that uses an LRU caching strategy to unload images when the cache is fullpub struct RetainAllImageCache(HashMap<u64, ImageCacheItem>); impl fmt::Debug for RetainAllImageCache {    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {        f.debug_struct("HashMapImageCache")            .field("num_images", &self.0.len())            .finish()    }} impl RetainAllImageCache {    /// Create a new image cache.    #[inline]    pub fn new(cx: &mut App) -> Entity<Self> {        let e = cx.new(|_cx| RetainAllImageCache(HashMap::new()));        cx.observe_release(&e, |image_cache, cx| {            for (_, mut item) in std::mem::replace(&mut image_cache.0, HashMap::new()) {                if let Some(Ok(image)) = item.get() {                    cx.drop_image(image, None);                }            }        })        .detach();        e    }     /// Load an image from the given source.    ///    /// Returns `None` if the image is loading.    pub fn load(        &mut self,        source: &Resource,        window: &mut Window,        cx: &mut App,    ) -> Option<Result<Arc<RenderImage>, ImageCacheError>> {        let hash = hash(source);         if let Some(item) = self.0.get_mut(&hash) {            return item.get();        }         let fut = AssetLogger::<ImageAssetLoader>::load(source.clone(), cx);        let task = cx.background_executor().spawn(fut).shared();        self.0.insert(hash, ImageCacheItem::Loading(task.clone()));         let entity = window.current_view();        window            .spawn(cx, {                async move |cx| {                    _ = task.await;                    cx.on_next_frame(move |_, cx| {                        cx.notify(entity);                    });                }            })            .detach();         None    }     /// Clear the image cache.    pub fn clear(&mut self, window: &mut Window, cx: &mut App) {        for (_, mut item) in std::mem::replace(&mut self.0, HashMap::new()) {            if let Some(Ok(image)) = item.get() {                cx.drop_image(image, Some(window));            }        }    }     /// Remove the image from the cache by the given source.    pub fn remove(&mut self, source: &Resource, window: &mut Window, cx: &mut App) {        let hash = hash(source);        if let Some(mut item) = self.0.remove(&hash)            && let Some(Ok(image)) = item.get()        {            cx.drop_image(image, Some(window));        }    }     /// Returns the number of images in the cache.    pub fn len(&self) -> usize {        self.0.len()    }     /// Returns true if the cache is empty.    pub fn is_empty(&self) -> bool {        self.0.is_empty()    }} impl ImageCache for RetainAllImageCache {    fn load(        &mut self,        resource: &Resource,        window: &mut Window,        cx: &mut App,    ) -> Option<Result<Arc<RenderImage>, ImageCacheError>> {        RetainAllImageCache::load(self, resource, window, cx)    }} /// Constructs a retain-all image cache that uses the element state associated with the given ID.pub fn retain_all(id: impl Into<ElementId>) -> RetainAllImageCacheProvider {    RetainAllImageCacheProvider { id: id.into() }} /// A provider struct for creating a retain-all image cache inlinepub struct RetainAllImageCacheProvider {    id: ElementId,} impl ImageCacheProvider for RetainAllImageCacheProvider {    fn provide(&mut self, window: &mut Window, cx: &mut App) -> AnyImageCache {        window            .with_global_id(self.id.clone(), |global_id, window| {                window.with_element_state::<Entity<RetainAllImageCache>, _>(                    global_id,                    |cache, _window| {                        let mut cache = cache.unwrap_or_else(|| RetainAllImageCache::new(cx));                        (cache.clone(), cache)                    },                )            })            .into()    }}