image_loading.rs

  1use std::{path::Path, sync::Arc, time::Duration};
  2
  3use anyhow::anyhow;
  4use gpui::{
  5    Animation, AnimationExt, App, Application, Asset, AssetLogger, AssetSource, Bounds, Context,
  6    Hsla, ImageAssetLoader, ImageCacheError, ImgResourceLoader, LOADING_DELAY, Length, Pixels,
  7    RenderImage, Resource, SharedString, Window, WindowBounds, WindowOptions, black, div, img,
  8    prelude::*, pulsating_between, px, red, size,
  9};
 10
 11struct Assets {}
 12
 13impl AssetSource for Assets {
 14    fn load(&self, path: &str) -> anyhow::Result<Option<std::borrow::Cow<'static, [u8]>>> {
 15        std::fs::read(path)
 16            .map(Into::into)
 17            .map_err(Into::into)
 18            .map(Some)
 19    }
 20
 21    fn list(&self, path: &str) -> anyhow::Result<Vec<SharedString>> {
 22        Ok(std::fs::read_dir(path)?
 23            .filter_map(|entry| {
 24                Some(SharedString::from(
 25                    entry.ok()?.path().to_string_lossy().to_string(),
 26                ))
 27            })
 28            .collect::<Vec<_>>())
 29    }
 30}
 31
 32const IMAGE: &str = concat!(env!("CARGO_MANIFEST_DIR"), "/examples/image/app-icon.png");
 33
 34#[derive(Copy, Clone, Hash)]
 35struct LoadImageParameters {
 36    timeout: Duration,
 37    fail: bool,
 38}
 39
 40struct LoadImageWithParameters {}
 41
 42impl Asset for LoadImageWithParameters {
 43    type Source = LoadImageParameters;
 44
 45    type Output = Result<Arc<RenderImage>, ImageCacheError>;
 46
 47    fn load(
 48        parameters: Self::Source,
 49        cx: &mut App,
 50    ) -> impl std::future::Future<Output = Self::Output> + Send + 'static {
 51        let timer = cx.background_executor().timer(parameters.timeout);
 52        let data = AssetLogger::<ImageAssetLoader>::load(
 53            Resource::Path(Path::new(IMAGE).to_path_buf().into()),
 54            cx,
 55        );
 56        async move {
 57            timer.await;
 58            if parameters.fail {
 59                log::error!("Intentionally failed to load image");
 60                Err(anyhow!("Failed to load image").into())
 61            } else {
 62                data.await
 63            }
 64        }
 65    }
 66}
 67
 68struct ImageLoadingExample {}
 69
 70impl ImageLoadingExample {
 71    fn loading_element() -> impl IntoElement {
 72        div().size_full().flex_none().p_0p5().rounded_xs().child(
 73            div().size_full().with_animation(
 74                "loading-bg",
 75                Animation::new(Duration::from_secs(3))
 76                    .repeat()
 77                    .with_easing(pulsating_between(0.04, 0.24)),
 78                move |this, delta| this.bg(black().opacity(delta)),
 79            ),
 80        )
 81    }
 82
 83    fn fallback_element() -> impl IntoElement {
 84        let fallback_color: Hsla = black().opacity(0.5);
 85
 86        div().size_full().flex_none().p_0p5().child(
 87            div()
 88                .size_full()
 89                .flex()
 90                .items_center()
 91                .justify_center()
 92                .rounded_xs()
 93                .text_sm()
 94                .text_color(fallback_color)
 95                .border_1()
 96                .border_color(fallback_color)
 97                .child("?"),
 98        )
 99    }
100}
101
102impl Render for ImageLoadingExample {
103    fn render(&mut self, _window: &mut Window, _cx: &mut Context<Self>) -> impl IntoElement {
104        div().flex().flex_col().size_full().justify_around().child(
105            div().flex().flex_row().w_full().justify_around().child(
106                div()
107                    .flex()
108                    .bg(gpui::white())
109                    .size(Length::Definite(Pixels(300.0).into()))
110                    .justify_center()
111                    .items_center()
112                    .child({
113                        let image_source = LoadImageParameters {
114                            timeout: LOADING_DELAY.saturating_sub(Duration::from_millis(25)),
115                            fail: false,
116                        };
117
118                        // Load within the 'loading delay', should not show loading fallback
119                        img(move |window: &mut Window, cx: &mut App| {
120                            window.use_asset::<LoadImageWithParameters>(&image_source, cx)
121                        })
122                        .id("image-1")
123                        .border_1()
124                        .size_12()
125                        .with_fallback(|| Self::fallback_element().into_any_element())
126                        .border_color(red())
127                        .with_loading(|| Self::loading_element().into_any_element())
128                        .on_click(move |_, _, cx| {
129                            cx.remove_asset::<LoadImageWithParameters>(&image_source);
130                        })
131                    })
132                    .child({
133                        // Load after a long delay
134                        let image_source = LoadImageParameters {
135                            timeout: Duration::from_secs(5),
136                            fail: false,
137                        };
138
139                        img(move |window: &mut Window, cx: &mut App| {
140                            window.use_asset::<LoadImageWithParameters>(&image_source, cx)
141                        })
142                        .id("image-2")
143                        .with_fallback(|| Self::fallback_element().into_any_element())
144                        .with_loading(|| Self::loading_element().into_any_element())
145                        .size_12()
146                        .border_1()
147                        .border_color(red())
148                        .on_click(move |_, _, cx| {
149                            cx.remove_asset::<LoadImageWithParameters>(&image_source);
150                        })
151                    })
152                    .child({
153                        // Fail to load image after a long delay
154                        let image_source = LoadImageParameters {
155                            timeout: Duration::from_secs(5),
156                            fail: true,
157                        };
158
159                        // Fail to load after a long delay
160                        img(move |window: &mut Window, cx: &mut App| {
161                            window.use_asset::<LoadImageWithParameters>(&image_source, cx)
162                        })
163                        .id("image-3")
164                        .with_fallback(|| Self::fallback_element().into_any_element())
165                        .with_loading(|| Self::loading_element().into_any_element())
166                        .size_12()
167                        .border_1()
168                        .border_color(red())
169                        .on_click(move |_, _, cx| {
170                            cx.remove_asset::<LoadImageWithParameters>(&image_source);
171                        })
172                    })
173                    .child({
174                        // Ensure that the normal image loader doesn't spam logs
175                        let image_source = Path::new(
176                            "this/file/really/shouldn't/exist/or/won't/be/an/image/I/hope",
177                        )
178                        .to_path_buf();
179                        img(image_source.clone())
180                            .id("image-1")
181                            .border_1()
182                            .size_12()
183                            .with_fallback(|| Self::fallback_element().into_any_element())
184                            .border_color(red())
185                            .with_loading(|| Self::loading_element().into_any_element())
186                            .on_click(move |_, _, cx| {
187                                cx.remove_asset::<ImgResourceLoader>(&image_source.clone().into());
188                            })
189                    }),
190            ),
191        )
192    }
193}
194
195fn main() {
196    env_logger::init();
197    Application::new()
198        .with_assets(Assets {})
199        .run(|cx: &mut App| {
200            let options = WindowOptions {
201                window_bounds: Some(WindowBounds::Windowed(Bounds::centered(
202                    None,
203                    size(px(300.), Pixels(300.)),
204                    cx,
205                ))),
206                ..Default::default()
207            };
208            cx.open_window(options, |_, cx| {
209                cx.activate(false);
210                cx.new(|_| ImageLoadingExample {})
211            })
212            .unwrap();
213        });
214}