use super::Sprite;
use image::{ImageError, ImageReader, RgbaImage};
use kas::layout::LogicalSize;
use kas::prelude::*;
use kas::theme::MarginStyle;
use kas::util::warn_about_error;
use std::path::{Path, PathBuf};
#[autoimpl(Debug ignore self.1)]
struct SetImage(PathBuf, Option<RgbaImage>);
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
enum State {
#[default]
Empty,
Loading,
Loaded,
}
#[impl_self]
mod Image {
#[derive(Debug, Default)]
#[widget]
#[layout(self.raw)]
pub struct Image {
core: widget_core!(),
#[widget]
raw: Sprite,
path: PathBuf,
state: State,
}
impl Self {
#[inline]
pub fn new(path: impl Into<PathBuf>) -> Self {
Image {
core: Default::default(),
raw: Sprite::default(),
path: path.into(),
state: State::Empty,
}
}
pub fn clear(&mut self, cx: &mut ConfigCx) {
self.path.clear();
self.state = State::Loading;
cx.send(self.id(), SetImage(PathBuf::new(), None));
}
pub fn set(&mut self, cx: &mut ConfigCx, path: &Path) {
if self.path == path {
return;
}
self.path = path.to_path_buf();
self.state = State::Empty;
self.configure(cx);
}
pub fn set_logical_size(&mut self, size: impl Into<LogicalSize>) {
self.raw.set_logical_size(size);
}
#[must_use]
pub fn with_logical_size(mut self, size: impl Into<LogicalSize>) -> Self {
self.raw.set_logical_size(size);
self
}
#[must_use]
#[inline]
pub fn with_margin_style(mut self, style: MarginStyle) -> Self {
self.raw = self.raw.with_margin_style(style);
self
}
#[must_use]
#[inline]
pub fn with_fixed_aspect_ratio(mut self, fixed: bool) -> Self {
self.raw = self.raw.with_fixed_aspect_ratio(fixed);
self
}
#[must_use]
#[inline]
pub fn with_stretch(mut self, stretch: Stretch) -> Self {
self.raw = self.raw.with_stretch(stretch);
self
}
}
impl Tile for Self {
fn role(&self, _: &mut dyn RoleCx) -> Role<'_> {
Role::Image
}
}
impl Events for Self {
type Data = ();
fn configure(&mut self, cx: &mut ConfigCx) {
if self.state == State::Empty && !self.path.as_os_str().is_empty() {
self.state = State::Loading;
let path = self.path.clone();
cx.send_spawn(self.id(), async {
let result = ImageReader::open(&path)
.and_then(|reader| reader.with_guessed_format())
.map_err(ImageError::IoError)
.and_then(|reader| reader.decode())
.map(|image| image.into_rgba8())
.inspect_err(|err| warn_about_error("Failed to read image", err))
.ok();
SetImage(path, result)
});
}
}
fn handle_messages(&mut self, cx: &mut EventCx, _: &Self::Data) {
if let Some(SetImage(path, result)) = cx.try_pop() {
if path != self.path {
return;
}
self.state = State::Loaded;
if let Some(image) = result {
let size = image.dimensions().cast();
let draw = cx.draw_shared();
let handle = match draw.image_alloc(kas::draw::ImageFormat::Rgba8, size) {
Ok(handle) => handle,
Err(err) => {
log::warn!("Image: allocate failed: {err}");
return;
}
};
match draw.image_upload(&handle, &image) {
Ok(_) => cx.redraw(),
Err(err) => log::warn!("Image: image upload failed: {err}"),
};
self.raw.set(cx, handle);
} else {
self.raw.clear(cx);
}
}
}
}
}