mirror of
https://github.com/emilk/egui.git
synced 2026-08-30 13:20:05 -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.
129 lines
3.7 KiB
Rust
129 lines
3.7 KiB
Rust
use ahash::HashMap;
|
|
use egui::{
|
|
load::{Bytes, BytesLoadResult, BytesLoader, BytesPoll, LoadError},
|
|
mutex::Mutex,
|
|
};
|
|
use std::{sync::Arc, task::Poll};
|
|
|
|
#[derive(Clone)]
|
|
struct File {
|
|
bytes: Arc<[u8]>,
|
|
mime: Option<String>,
|
|
}
|
|
|
|
impl File {
|
|
fn from_response(uri: &str, response: ehttp::Response) -> Result<Self, String> {
|
|
if !response.ok {
|
|
match response.text() {
|
|
Some(response_text) => {
|
|
return Err(format!(
|
|
"failed to load {uri:?}: {} {} {response_text}",
|
|
response.status, response.status_text
|
|
))
|
|
}
|
|
None => {
|
|
return Err(format!(
|
|
"failed to load {uri:?}: {} {}",
|
|
response.status, response.status_text
|
|
))
|
|
}
|
|
}
|
|
}
|
|
|
|
let mime = response.content_type().map(|v| v.to_owned());
|
|
let bytes = response.bytes.into();
|
|
|
|
Ok(Self { bytes, mime })
|
|
}
|
|
}
|
|
|
|
type Entry = Poll<Result<File, String>>;
|
|
|
|
#[derive(Default)]
|
|
pub struct EhttpLoader {
|
|
cache: Arc<Mutex<HashMap<String, Entry>>>,
|
|
}
|
|
|
|
impl EhttpLoader {
|
|
pub const ID: &'static str = egui::generate_loader_id!(EhttpLoader);
|
|
}
|
|
|
|
const PROTOCOLS: &[&str] = &["http://", "https://"];
|
|
|
|
fn starts_with_one_of(s: &str, prefixes: &[&str]) -> bool {
|
|
prefixes.iter().any(|prefix| s.starts_with(prefix))
|
|
}
|
|
|
|
impl BytesLoader for EhttpLoader {
|
|
fn id(&self) -> &str {
|
|
Self::ID
|
|
}
|
|
|
|
fn load(&self, ctx: &egui::Context, uri: &str) -> BytesLoadResult {
|
|
if !starts_with_one_of(uri, PROTOCOLS) {
|
|
return Err(LoadError::NotSupported);
|
|
}
|
|
|
|
let mut cache = self.cache.lock();
|
|
if let Some(entry) = cache.get(uri).cloned() {
|
|
match entry {
|
|
Poll::Ready(Ok(file)) => Ok(BytesPoll::Ready {
|
|
size: None,
|
|
bytes: Bytes::Shared(file.bytes),
|
|
mime: file.mime,
|
|
}),
|
|
Poll::Ready(Err(err)) => Err(LoadError::Loading(err)),
|
|
Poll::Pending => Ok(BytesPoll::Pending { size: None }),
|
|
}
|
|
} else {
|
|
log::trace!("started loading {uri:?}");
|
|
|
|
let uri = uri.to_owned();
|
|
cache.insert(uri.clone(), Poll::Pending);
|
|
drop(cache);
|
|
|
|
ehttp::fetch(ehttp::Request::get(uri.clone()), {
|
|
let ctx = ctx.clone();
|
|
let cache = self.cache.clone();
|
|
move |response| {
|
|
let result = match response {
|
|
Ok(response) => File::from_response(&uri, response),
|
|
Err(err) => {
|
|
// Log details; return summary
|
|
log::error!("Failed to load {uri:?}: {err}");
|
|
Err(format!("Failed to load {uri:?}"))
|
|
}
|
|
};
|
|
log::trace!("finished loading {uri:?}");
|
|
cache.lock().insert(uri, Poll::Ready(result));
|
|
ctx.request_repaint();
|
|
}
|
|
});
|
|
|
|
Ok(BytesPoll::Pending { size: None })
|
|
}
|
|
}
|
|
|
|
fn forget(&self, uri: &str) {
|
|
let _ = self.cache.lock().remove(uri);
|
|
}
|
|
|
|
fn forget_all(&self) {
|
|
self.cache.lock().clear();
|
|
}
|
|
|
|
fn byte_size(&self) -> usize {
|
|
self.cache
|
|
.lock()
|
|
.values()
|
|
.map(|entry| match entry {
|
|
Poll::Ready(Ok(file)) => {
|
|
file.bytes.len() + file.mime.as_ref().map_or(0, |m| m.len())
|
|
}
|
|
Poll::Ready(Err(err)) => err.len(),
|
|
_ => 0,
|
|
})
|
|
.sum()
|
|
}
|
|
}
|