mirror of
https://github.com/emilk/egui.git
synced 2026-08-31 13:50:04 -04:00
<!-- Please read the "Making a PR" section of [`CONTRIBUTING.md`](https://github.com/emilk/egui/blob/master/CONTRIBUTING.md) before opening a Pull Request! * Keep your PR:s small and focused. * The PR title is what ends up in the changelog, so make it descriptive! * If applicable, add a screenshot or gif. * If it is a non-trivial addition, consider adding a demo for it to `egui_demo_lib`, or a new example. * Do NOT open PR:s from your `master` branch, as that makes it hard for maintainers to add commits to your PR. * Remember to run `cargo fmt` and `cargo clippy`. * Open the PR as a draft until you have self-reviewed it and run `./scripts/check.sh`. * When you have addressed a PR comment, mark it as resolved. Please be patient! I will review your PR, but my time is limited! --> Related to #3482 Not sure what the "best practice" is, to me it seems like one should import from "the original location" if possible, but now it should at least be possible to not re-export ahash without any breakage in the egui code base (but possibly in projects using egui, so one should probably deprecate it if one would like to go that path). It also seems like epaint re-exports ahash.
103 lines
2.9 KiB
Rust
103 lines
2.9 KiB
Rust
use std::{mem::size_of, path::Path, sync::Arc};
|
|
|
|
use ahash::HashMap;
|
|
|
|
use egui::{
|
|
load::{BytesPoll, ImageLoadResult, ImageLoader, ImagePoll, LoadError, SizeHint},
|
|
mutex::Mutex,
|
|
ColorImage,
|
|
};
|
|
|
|
type Entry = Result<Arc<ColorImage>, String>;
|
|
|
|
#[derive(Default)]
|
|
pub struct SvgLoader {
|
|
cache: Mutex<HashMap<(String, SizeHint), Entry>>,
|
|
}
|
|
|
|
impl SvgLoader {
|
|
pub const ID: &'static str = egui::generate_loader_id!(SvgLoader);
|
|
}
|
|
|
|
fn is_supported(uri: &str) -> bool {
|
|
let Some(ext) = Path::new(uri).extension().and_then(|ext| ext.to_str()) else {
|
|
return false;
|
|
};
|
|
|
|
ext == "svg"
|
|
}
|
|
|
|
impl ImageLoader for SvgLoader {
|
|
fn id(&self) -> &str {
|
|
Self::ID
|
|
}
|
|
|
|
fn load(&self, ctx: &egui::Context, uri: &str, size_hint: SizeHint) -> ImageLoadResult {
|
|
if !is_supported(uri) {
|
|
return Err(LoadError::NotSupported);
|
|
}
|
|
|
|
let uri = uri.to_owned();
|
|
|
|
let mut cache = self.cache.lock();
|
|
// We can't avoid the `uri` clone here without unsafe code.
|
|
if let Some(entry) = cache.get(&(uri.clone(), size_hint)).cloned() {
|
|
match entry {
|
|
Ok(image) => Ok(ImagePoll::Ready { image }),
|
|
Err(err) => Err(LoadError::Loading(err)),
|
|
}
|
|
} else {
|
|
match ctx.try_load_bytes(&uri) {
|
|
Ok(BytesPoll::Ready { bytes, .. }) => {
|
|
log::trace!("started loading {uri:?}");
|
|
let result = crate::image::load_svg_bytes_with_size(&bytes, Some(size_hint))
|
|
.map(Arc::new);
|
|
log::trace!("finished loading {uri:?}");
|
|
cache.insert((uri, size_hint), result.clone());
|
|
match result {
|
|
Ok(image) => Ok(ImagePoll::Ready { image }),
|
|
Err(err) => Err(LoadError::Loading(err)),
|
|
}
|
|
}
|
|
Ok(BytesPoll::Pending { size }) => Ok(ImagePoll::Pending { size }),
|
|
Err(err) => Err(err),
|
|
}
|
|
}
|
|
}
|
|
|
|
fn forget(&self, uri: &str) {
|
|
self.cache.lock().retain(|(u, _), _| u != uri);
|
|
}
|
|
|
|
fn forget_all(&self) {
|
|
self.cache.lock().clear();
|
|
}
|
|
|
|
fn byte_size(&self) -> usize {
|
|
self.cache
|
|
.lock()
|
|
.values()
|
|
.map(|result| match result {
|
|
Ok(image) => image.pixels.len() * size_of::<egui::Color32>(),
|
|
Err(err) => err.len(),
|
|
})
|
|
.sum()
|
|
}
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use super::*;
|
|
|
|
#[test]
|
|
fn check_support() {
|
|
// inverse of same test in `image_loader.rs`
|
|
assert!(!is_supported("https://test.png"));
|
|
assert!(!is_supported("test.jpeg"));
|
|
assert!(!is_supported("http://test.gif"));
|
|
assert!(!is_supported("test.webp"));
|
|
assert!(!is_supported("file://test"));
|
|
assert!(is_supported("test.svg"));
|
|
}
|
|
}
|