use crate::{LibraryModel, MainWindow, PreviewThumbnails};
use indicatrix_vault::db::sqlite::Database;
use slint::{ComponentHandle, Rgba8Pixel, SharedPixelBuffer};
use std::{
collections::HashMap,
sync::{Arc, Mutex, PoisonError},
thread,
};
enum CacheState {
Loading,
Ready {
front: Option<SharedPixelBuffer<Rgba8Pixel>>,
top: Option<SharedPixelBuffer<Rgba8Pixel>>,
},
}
type Cache = Arc<Mutex<HashMap<i64, CacheState>>>;
pub fn setup_preview_thumbnail_callback(ui: &MainWindow, db: &Arc<Mutex<Database>>) {
let cache: Cache = Arc::new(Mutex::new(HashMap::new()));
let db = Arc::clone(db);
let ui_weak = ui.as_weak();
ui.global::<LibraryModel>()
.on_get_preview_thumbnails(move |id: i32, _cache_version: i32| {
let entry_id = i64::from(id);
{
let guard = cache.lock().unwrap_or_else(PoisonError::into_inner);
match guard.get(&entry_id) {
Some(CacheState::Ready { front, top }) => {
return to_slint_thumbnails(front.as_ref(), top.as_ref());
}
Some(CacheState::Loading) => return PreviewThumbnails::default(),
None => {}
}
}
cache
.lock()
.unwrap_or_else(PoisonError::into_inner)
.insert(entry_id, CacheState::Loading);
let db = Arc::clone(&db);
let cache = Arc::clone(&cache);
let ui_weak = ui_weak.clone();
thread::spawn(move || {
let images = {
let guard = db.lock().unwrap_or_else(PoisonError::into_inner);
guard.get_preview_images(entry_id).unwrap_or_default()
};
let front = images.front.as_deref().and_then(decode_to_pixel_buffer);
let top = images.top.as_deref().and_then(decode_to_pixel_buffer);
cache
.lock()
.unwrap_or_else(PoisonError::into_inner)
.insert(entry_id, CacheState::Ready { front, top });
let _ = ui_weak.upgrade_in_event_loop(|ui| {
ui.global::<LibraryModel>().set_preview_cache_version(
ui.global::<LibraryModel>().get_preview_cache_version() + 1,
);
});
});
PreviewThumbnails::default()
});
}
fn decode_to_pixel_buffer(png: &[u8]) -> Option<SharedPixelBuffer<Rgba8Pixel>> {
let decoded = image::load_from_memory(png).ok()?.to_rgba8();
let (width, height) = decoded.dimensions();
let mut buffer = SharedPixelBuffer::<Rgba8Pixel>::new(width, height);
let dst: &mut [Rgba8Pixel] = buffer.make_mut_slice();
let src: &[Rgba8Pixel] = bytemuck::cast_slice(decoded.as_raw());
dst.copy_from_slice(src);
Some(buffer)
}
fn to_slint_thumbnails(
front: Option<&SharedPixelBuffer<Rgba8Pixel>>,
top: Option<&SharedPixelBuffer<Rgba8Pixel>>,
) -> PreviewThumbnails {
PreviewThumbnails {
front: front.map_or_else(slint::Image::default, |buf| {
slint::Image::from_rgba8(buf.clone())
}),
top: top.map_or_else(slint::Image::default, |buf| {
slint::Image::from_rgba8(buf.clone())
}),
has_front: front.is_some(),
has_top: top.is_some(),
}
}