use std::collections::HashMap;
use std::sync::Mutex;
use yog_gfx::draw2d::{self, Draw2D};
use yog_gfx::image::{self as img, ImageSource, Stretch, fit};
use yog_gfx::{GfxContext, ImageSizeFn, Rect, Vec2};
use crate::widget::Image;
struct Host {
size: Option<ImageSizeFn>,
cache: Option<HashMap<String, Vec2>>,
}
static HOST: Mutex<Host> = Mutex::new(Host {
size: None,
cache: None,
});
pub fn use_host_images(ctx: &GfxContext) {
if let Ok(mut h) = HOST.lock() {
h.size = Some(ctx.image_size_fn());
}
}
const FALLBACK_SIZE: Vec2 = Vec2::new(16.0, 16.0);
const SIZE_CACHE_CAP: usize = 4096;
fn offline_size(source: &str) -> Vec2 {
match ImageSource::parse(source) {
Some(ImageSource::Sprite(name)) => {
img::sprite(&name).map_or(FALLBACK_SIZE, |s| s.size(true))
}
_ => FALLBACK_SIZE,
}
}
pub fn source_size(source: &str) -> Vec2 {
let Ok(mut h) = HOST.lock() else {
return offline_size(source);
};
let Some(size_fn) = h.size else {
return offline_size(source);
};
let cache = h.cache.get_or_insert_with(HashMap::new);
if let Some(size) = cache.get(source) {
return *size;
}
match draw2d::image_size_with(size_fn, source) {
Some(size) => {
if cache.len() > SIZE_CACHE_CAP {
cache.clear();
}
cache.insert(source.to_owned(), size);
size
}
None => offline_size(source),
}
}
pub fn natural_size(image: &Image) -> Vec2 {
match image.region {
Some(r) if r.w > 0.0 && r.h > 0.0 => Vec2::new(r.w, r.h),
_ => source_size(&image.source),
}
}
fn whole_only(source: &str) -> bool {
matches!(
ImageSource::parse(source),
Some(ImageSource::Item(_) | ImageSource::Block(_) | ImageSource::Sprite(_))
)
}
pub(crate) fn render(d2d: &Draw2D, image: &Image, bx: Rect, clip: Option<Rect>) {
let natural = natural_size(image);
let whole = whole_only(&image.source);
let stretch = match image.stretch {
Stretch::UniformToFill if whole => Stretch::Uniform,
s => s,
};
let (dest, shown) = fit(stretch, natural, bx);
if dest.w <= 0.0 || dest.h <= 0.0 {
return;
}
if whole {
let inside = clip.is_none_or(|c| {
let c = c.outset(crate::render::CLIP_SLACK);
dest.x >= c.x && dest.y >= c.y && dest.right() <= c.right() && dest.bottom() <= c.bottom()
});
if inside {
d2d.image_region(&image.source, dest, None, image.tint);
}
return;
}
let region = match image.region {
Some(r) if r.w > 0.0 && r.h > 0.0 => r,
_ => Rect::new(0.0, 0.0, natural.x, natural.y),
};
let mut src = Rect::new(region.x + shown.x * region.w, region.y + shown.y * region.h, shown.w * region.w, shown.h * region.h);
let mut dest = dest;
if let Some(c) = clip {
let Some(cut) = dest.intersect(&c) else {
return;
};
let (sx, sy) = (src.w / dest.w, src.h / dest.h);
src = Rect::new(src.x + (cut.x - dest.x) * sx, src.y + (cut.y - dest.y) * sy, cut.w * sx, cut.h * sy);
dest = cut;
}
d2d.image_region(&image.source, dest, Some(src), image.tint);
}