zu_util/
image_future.rs

1// Copyright (c) 2024 Xu Shaohua <shaohua@biofan.org>. All rights reserved.
2// Use of this source is governed by Lesser General Public License
3// that can be found in the LICENSE file.
4
5//! From [github-gist](https://gist.github.com/rhmoller/a054523c02cf3c4732fec6cdd26aab61)
6
7use futures::task::{Context, Poll};
8use std::cell::RefCell;
9use std::future::Future;
10use std::pin::Pin;
11use std::rc::Rc;
12use wasm_bindgen::prelude::*;
13use wasm_bindgen::JsCast;
14use web_sys::HtmlImageElement;
15
16/// A future for loading a [HtmlImageElement](https://docs.rs/web-sys/0.3.39/web_sys/struct.HtmlImageElement.html)
17/// that will resolve when the image has fully loaded.
18///
19/// Example:
20/// ```ignored
21/// let image = ImageFuture::new("assets/sprite_sheet.png").await;
22/// ```
23///
24/// It more or less replicates the promise in these lines of JS
25/// ```javascript
26/// const loadImage = src => new Promise((resolve, reject) => {
27///  const img = new Image();
28///  img.onload = resolve;
29///  img.onerror = reject;
30///  img.src = src;
31/// })
32/// ```
33pub struct ImageFuture {
34    image: Option<HtmlImageElement>,
35    load_failed: Rc<RefCell<bool>>,
36}
37
38impl ImageFuture {
39    /// # Panics
40    /// Got panic if failed to create new image element.
41    #[must_use]
42    pub fn new(src: &str, srcset: Option<&str>) -> Self {
43        let image = HtmlImageElement::new().unwrap();
44        image.set_src(src);
45        if let Some(srcset) = srcset {
46            image.set_srcset(srcset);
47        }
48        Self {
49            image: Some(image),
50            load_failed: Rc::new(RefCell::new(false)),
51        }
52    }
53}
54
55impl Future for ImageFuture {
56    type Output = Result<HtmlImageElement, ()>;
57
58    fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
59        match &self.image {
60            Some(image) => {
61                return if image.complete() {
62                    let image = self.image.take().unwrap();
63                    let failed = *self.load_failed.borrow();
64
65                    if failed {
66                        Poll::Ready(Err(()))
67                    } else {
68                        Poll::Ready(Ok(image))
69                    }
70                } else {
71                    let waker = cx.waker().clone();
72                    let on_load_closure = Closure::wrap(Box::new(move || {
73                        waker.wake_by_ref();
74                    }) as Box<dyn FnMut()>);
75                    image.set_onload(Some(on_load_closure.as_ref().unchecked_ref()));
76                    on_load_closure.forget();
77
78                    let waker = cx.waker().clone();
79                    let failed_flag = self.load_failed.clone();
80                    let on_error_closure = Closure::wrap(Box::new(move || {
81                        *failed_flag.borrow_mut() = true;
82                        waker.wake_by_ref();
83                    })
84                        as Box<dyn FnMut()>);
85                    image.set_onerror(Some(on_error_closure.as_ref().unchecked_ref()));
86                    on_error_closure.forget();
87
88                    Poll::Pending
89                };
90            }
91            _ => Poll::Ready(Err(())),
92        }
93    }
94}