use crate::graphics::Texture2D;
use crate::graphics::TextureConfig;
use crate::Vector;
use std::{cell::Cell, rc::Rc};
use web_sys::{HtmlImageElement, WebGlRenderingContext};
use crate::{Domain, ErrorMessage, JsError, NutsCheck, PaddleResult, Rectangle};
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct Image {
pub(crate) texture: Texture2D,
pub(crate) region: Rectangle,
}
struct BindTextureMessage {
payload: Rc<Cell<BindTexturePayload>>,
}
enum BindTexturePayload {
Request(HtmlImageElement),
Response(Texture2D),
Done,
}
pub struct ImageLoader {
gl: WebGlRenderingContext,
texture_config: TextureConfig,
}
impl Image {
pub async fn load(src: &str) -> PaddleResult<Self> {
let el = HtmlImageElement::new().map_err(JsError::from_js_value)?;
el.set_src(src);
let promise = el.decode();
wasm_bindgen_futures::JsFuture::from(promise)
.await
.map_err(JsError::from_js_value)?;
let cell = Rc::new(Cell::new(BindTexturePayload::Request(el)));
let msg = BindTextureMessage {
payload: cell.clone(),
};
nuts::publish_awaiting_response(msg).await;
let texture = match cell.take() {
BindTexturePayload::Response(data) => data,
_ => return Err(ErrorMessage::technical("Texture loading failed".to_owned())),
};
let region = Rectangle::new_sized((1.0, 1.0));
Ok(Image { texture, region })
}
pub fn natural_width(&self) -> f32 {
(self.texture.texel_width * self.region.width()).abs()
}
pub fn natural_height(&self) -> f32 {
(self.texture.texel_height * self.region.height()).abs()
}
pub fn natural_size(&self) -> Vector {
(self.natural_width(), self.natural_height()).into()
}
pub fn subimage_texels(&self, rect: Rectangle) -> Image {
let img = Image {
texture: self.texture.clone(),
region: Rectangle::new(
(
self.region.pos.x + rect.pos.x / self.natural_width(),
self.region.pos.y + rect.pos.y / self.natural_height(),
),
(rect.width(), rect.height()),
),
};
debug_assert!(img.region.x() <= 1.0);
debug_assert!(img.region.y() <= 1.0);
debug_assert!(img.region.x() >= 0.0);
debug_assert!(img.region.y() >= 0.0);
debug_assert!(img.region.width() <= 1.0);
debug_assert!(img.region.height() <= 1.0);
debug_assert!(img.region.width() >= 0.0);
debug_assert!(img.region.height() >= 0.0);
img
}
pub fn subimage(&self, rect: Rectangle) -> Image {
let img = Image {
texture: self.texture.clone(),
region: Rectangle::new(
(
self.region.pos.x + rect.pos.x,
self.region.pos.y + rect.pos.y,
),
(rect.width(), rect.height()),
),
};
debug_assert!(img.region.x() <= 1.0);
debug_assert!(img.region.y() <= 1.0);
debug_assert!(img.region.x() >= 0.0);
debug_assert!(img.region.y() >= 0.0);
debug_assert!(img.region.width() <= 1.0);
debug_assert!(img.region.height() <= 1.0);
debug_assert!(img.region.width() >= 0.0);
debug_assert!(img.region.height() >= 0.0);
img
}
}
impl ImageLoader {
pub fn register(gl: WebGlRenderingContext, texture_config: TextureConfig) {
let activity = nuts::new_domained_activity(Self { gl, texture_config }, &Domain::Frame);
activity.subscribe(move |a, msg: &BindTextureMessage| {
if let BindTexturePayload::Request(el) = msg.payload.take() {
if let Some(data) = Texture2D::new(&a.gl, &el, &a.texture_config).nuts_check() {
msg.payload.replace(BindTexturePayload::Response(data));
}
}
})
}
}
impl Default for BindTexturePayload {
fn default() -> Self {
Self::Done
}
}