use std::collections::{HashMap, HashSet, VecDeque};
use iced::Theme;
use iced_native::image::Handle;
use crate::cache::ImageHint;
use crate::model::{ImageHash, ImageV2};
static MISSING_POSTER_DARK: &[u8] = include_bytes!("../assets/missing_poster_dark.png");
static MISSING_POSTER_LIGHT: &[u8] = include_bytes!("../assets/missing_poster_light.png");
static MISSING_BANNER: &[u8] = include_bytes!("../assets/missing_banner.png");
static MISSING_SCRENCAP: &[u8] = include_bytes!("../assets/missing_screencap.png");
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub(crate) struct ImageKey {
pub(crate) id: ImageHash,
pub(crate) hint: Option<ImageHint>,
}
pub(crate) struct Assets {
missing_poster_dark: Handle,
missing_poster_light: Handle,
missing_banner: Handle,
missing_screencap: Handle,
clear: bool,
image_queue: VecDeque<(ImageKey, ImageV2)>,
marked: Vec<(ImageKey, ImageV2)>,
images: HashMap<ImageKey, Handle>,
to_remove: HashSet<ImageKey>,
}
impl Assets {
pub(crate) fn new() -> Self {
let missing_poster_dark = Handle::from_memory(MISSING_POSTER_DARK);
let missing_poster_light = Handle::from_memory(MISSING_POSTER_LIGHT);
let missing_banner = Handle::from_memory(MISSING_BANNER);
let missing_screencap = Handle::from_memory(MISSING_SCRENCAP);
Self {
missing_poster_dark,
missing_poster_light,
missing_banner,
missing_screencap,
clear: false,
image_queue: VecDeque::new(),
marked: Vec::new(),
images: HashMap::new(),
to_remove: HashSet::new(),
}
}
#[inline]
pub(crate) fn clear(&mut self) {
self.clear = true;
}
#[inline]
pub(crate) fn is_cleared(&self) -> bool {
self.clear
}
#[allow(unused)]
pub(crate) fn mark<'a, I>(&mut self, ids: I)
where
I: IntoIterator<Item = &'a ImageV2>,
{
for id in ids {
tracing::trace!("mark: {id:?}");
let key = ImageKey {
id: id.hash(),
hint: None,
};
self.marked.push((key, id.clone()));
}
}
pub(crate) fn mark_with_hint<'a, I>(&mut self, ids: I, hint: ImageHint)
where
I: IntoIterator<Item = &'a ImageV2>,
{
for id in ids {
tracing::trace!("mark: {id:?} {hint:?}");
let key = ImageKey {
id: id.hash(),
hint: Some(hint),
};
self.marked.push((key, id.clone()));
}
}
pub(crate) fn commit(&mut self) {
if self.clear {
self.to_remove
.extend(self.images.keys().copied().collect::<HashSet<_>>());
for (key, _) in &self.marked {
self.to_remove.remove(key);
}
for image in &self.to_remove {
tracing::trace!("unloading: {image:?}");
let _ = self.images.remove(image);
}
self.to_remove.clear();
self.image_queue.clear();
self.clear = false;
}
for (key, image) in self.marked.drain(..) {
if !self.images.contains_key(&key) {
self.image_queue.push_back((key, image));
}
}
self.marked.clear();
}
pub(crate) fn insert_images(&mut self, loaded: Vec<(ImageKey, Handle)>) {
for (id, handle) in loaded {
self.images.insert(id, handle);
}
}
pub(crate) fn missing_poster(&self, theme: &Theme) -> Handle {
match theme {
Theme::Dark => self.missing_poster_dark.clone(),
_ => self.missing_poster_light.clone(),
}
}
#[allow(unused)]
pub(crate) fn image(&self, id: &ImageV2) -> Option<Handle> {
let key = ImageKey {
id: id.hash(),
hint: None,
};
self.images.get(&key).cloned()
}
pub(crate) fn image_with_hint(&self, id: &ImageV2, hint: ImageHint) -> Option<Handle> {
let key = ImageKey {
id: id.hash(),
hint: Some(hint),
};
self.images.get(&key).cloned()
}
pub(crate) fn missing_banner(&self) -> Handle {
self.missing_banner.clone()
}
pub(crate) fn missing_screencap(&self) -> Handle {
self.missing_screencap.clone()
}
pub(crate) fn next_image(&mut self) -> Option<(ImageKey, ImageV2)> {
loop {
let (key, image) = self.image_queue.pop_front()?;
if self.images.contains_key(&key) {
continue;
}
return Some((key, image));
}
}
}