1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
//! From [github-gist](https://gist.github.com/rhmoller/a054523c02cf3c4732fec6cdd26aab61)

use futures::task::{Context, Poll};
use std::cell::RefCell;
use std::future::Future;
use std::pin::Pin;
use std::rc::Rc;
use wasm_bindgen::prelude::*;
use wasm_bindgen::JsCast;
use web_sys::HtmlImageElement;

/// A future for loading a [HtmlImageElement](https://docs.rs/web-sys/0.3.39/web_sys/struct.HtmlImageElement.html)
/// that will resolve when the image has fully loaded.
///
/// Example:
/// ```ignored
/// let image = ImageFuture::new("assets/sprite_sheet.png").await;
/// ```
///
/// It more or less replicates the promise in these lines of JS
/// ```javascript
/// const loadImage = src => new Promise((resolve, reject) => {
///  const img = new Image();
///  img.onload = resolve;
///  img.onerror = reject;
///  img.src = src;
/// })
/// ```
pub struct ImageFuture {
    image: Option<HtmlImageElement>,
    load_failed: Rc<RefCell<bool>>,
}

impl ImageFuture {
    /// # Panics
    /// Got panic if failed to create new image element.
    #[must_use]
    pub fn new(src: &str, srcset: Option<&str>) -> Self {
        let image = HtmlImageElement::new().unwrap();
        image.set_src(src);
        if let Some(srcset) = srcset {
            image.set_srcset(srcset);
        }
        Self {
            image: Some(image),
            load_failed: Rc::new(RefCell::new(false)),
        }
    }
}

impl Future for ImageFuture {
    type Output = Result<HtmlImageElement, ()>;

    fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
        match &self.image {
            Some(image) => {
                return if image.complete() {
                    let image = self.image.take().unwrap();
                    let failed = *self.load_failed.borrow();

                    if failed {
                        Poll::Ready(Err(()))
                    } else {
                        Poll::Ready(Ok(image))
                    }
                } else {
                    let waker = cx.waker().clone();
                    let on_load_closure = Closure::wrap(Box::new(move || {
                        waker.wake_by_ref();
                    }) as Box<dyn FnMut()>);
                    image.set_onload(Some(on_load_closure.as_ref().unchecked_ref()));
                    on_load_closure.forget();

                    let waker = cx.waker().clone();
                    let failed_flag = self.load_failed.clone();
                    let on_error_closure = Closure::wrap(Box::new(move || {
                        *failed_flag.borrow_mut() = true;
                        waker.wake_by_ref();
                    })
                        as Box<dyn FnMut()>);
                    image.set_onerror(Some(on_error_closure.as_ref().unchecked_ref()));
                    on_error_closure.forget();

                    Poll::Pending
                };
            }
            _ => Poll::Ready(Err(())),
        }
    }
}