use std::collections::BTreeMap; use eframe::NativeOptions; use egui_extras::RetainedImage; use uuid::Uuid; use eframe::epaint::ahash::HashSet; use cached_network_image::{ Image, ImageStore, FetchImage, FetchQueue, ImageCache, ImageKind, }; use directories::ProjectDirs; fn main() { tracing_subscriber::fmt::init(); eframe::run_native( "Demo", NativeOptions::default(), Box::new(move |cc| Box::new(App::new(cc.egui_ctx.clone()))), ); } pub struct App { image_store: ImageStore, fetch_queue: FetchQueue, images: ImageCache, requested_images: HashSet, show_image_map: bool, image_url: String, } impl App { pub fn new(context: egui::Context) -> Self { let repaint = context.clone(); let path = match ProjectDirs::from("com", "fireyy", "cached network image demo") { Some(proj_dirs) => Some(proj_dirs.config_dir().to_path_buf()), None => None }; let image_store = ImageStore::::new(path); Self { image_store: image_store.clone(), fetch_queue: FetchQueue::create(repaint, image_store), images: ImageCache::default(), requested_images: HashSet::default(), show_image_map: true, image_url: String::from("https://placehold.co/100x100@2x.png"), } } fn try_fetch_image(&mut self) { let (image, data) = match self.fetch_queue.try_next() { Some((image, data)) => (image, data), _ => return, }; let images = &mut self.images; if images.has_id(image.id) { return; } match RetainedImage::from_image_bytes(image.url(), &data) { Ok(img) => { images.add(image.id, img); let _ = self.requested_images.remove(&image.id); self.image_store.add(&image, &(), &data); } Err(err) => { tracing::error!("cannot create ({}) {} : {err}", image.id, image.url()) } } } fn get_images(&mut self) { let images = vec![ "https://placehold.co/600x400@2x.png", "https://placehold.co/800@2x.png", "https://placehold.co/600x400@3x.png", "https://placehold.co/800@3x.png" ]; for img in images { self.fetch_queue.fetch(self.gen_image(img.to_string(), ImageKind::Display)); } } fn options_window(&mut self, ctx: &egui::Context) { egui::Window::new("") .default_pos((100.0, 100.0)) .default_width(300.0) .show(ctx, |ui| { ui.horizontal(|ui| { ui.label("Link:"); ui.add( egui::TextEdit::singleline(&mut self.image_url) .hint_text(egui::RichText::new("Image link")) ); if ui.button("Get").clicked() { let image_url = self.image_url.clone(); self.fetch_queue.fetch(self.gen_image(image_url, ImageKind::Display)); } }); ui.separator(); if ui.button("Get the mock image").clicked() { self.get_images(); } }); } fn show_image_cache(&mut self, ctx: &egui::Context) { egui::Window::new("image cache") .open(&mut self.show_image_map) .show(ctx, |ui| { ui.vertical(|ui| { egui::CollapsingHeader::new("image cache") .default_open(false) .show(ui, |ui| { egui::ScrollArea::vertical().show(ui, |ui| { if !self.requested_images.is_empty() { ui.group(|ui| { ui.label("requested images"); ui.vertical(|ui| { let mut ids = self.requested_images.iter().collect::>(); ids.sort(); for id in ids { ui.monospace(id.to_string()); } }) }); } egui::Grid::new("image_map").num_columns(3).show(ui, |ui| { for (id, img) in self.images.map.iter().collect::>() { img.show_size(ui, egui::vec2(16.0, 16.0)); ui.label(img.debug_name()); ui.monospace(id.to_string()); ui.end_row() } }); }); }); egui::CollapsingHeader::new("disk cache") .default_open(false) .show(ui, |ui| { // TODO cache this egui::ScrollArea::vertical().show(ui, |ui| { for image in self.image_store.get_all_debug() { egui::Grid::new(image.image.id).num_columns(2).show(ui, |ui| { ui.monospace(image.image.id.to_string()); if let Some(img) = self.images.get_id(image.image.id) { img.show_max_size(ui, egui::vec2(32.0, 32.0)); } ui.end_row() }); } }); }); }); }); } fn gen_image(&self, url: String, kind: ImageKind) -> Image { let uuid = self.image_store.get_id(&url) // .unwrap_or_else(Uuid::new_v4); Image{ id: uuid, kind, url: url.clone(), meta: (), } } } impl eframe::App for App { fn update(&mut self, ctx: &egui::Context, _frame: &mut eframe::Frame) { self.try_fetch_image(); self.options_window(ctx); self.show_image_cache(ctx); } }