mirror of
https://github.com/emilk/egui.git
synced 2026-06-27 07:03:14 -04:00
[example_web] show loading of an image
Required some redesign of `TextureAllocator` as well as some improvements to the fetch API.
This commit is contained in:
@@ -1,18 +1,50 @@
|
||||
use egui_web::fetch::Response;
|
||||
use std::sync::mpsc::Receiver;
|
||||
|
||||
struct Image {
|
||||
size: (usize, usize),
|
||||
pixels: Vec<egui::Srgba>,
|
||||
}
|
||||
|
||||
impl Image {
|
||||
fn decode(bytes: &[u8]) -> Option<Image> {
|
||||
use image::GenericImageView;
|
||||
let image = image::load_from_memory(&bytes).ok()?;
|
||||
let image_buffer = image.to_rgba();
|
||||
let size = (image.width() as usize, image.height() as usize);
|
||||
let pixels = image_buffer.into_vec();
|
||||
assert_eq!(size.0 * size.1 * 4, pixels.len());
|
||||
let pixels = pixels
|
||||
.chunks(4)
|
||||
.map(|p| egui::Srgba::from_rgba_unmultiplied(p[0], p[1], p[2], p[3]))
|
||||
.collect();
|
||||
|
||||
Some(Image { size, pixels })
|
||||
}
|
||||
}
|
||||
|
||||
struct Resource {
|
||||
/// HTTP response
|
||||
response: Response,
|
||||
|
||||
/// If set, the response was an image.
|
||||
image: Option<Image>,
|
||||
}
|
||||
|
||||
pub struct ExampleApp {
|
||||
url: String,
|
||||
receivers: Vec<Receiver<Result<Response, String>>>,
|
||||
fetch_result: Option<Result<Response, String>>,
|
||||
in_progress: Option<Receiver<Result<Response, String>>>,
|
||||
result: Option<Result<Resource, String>>,
|
||||
texture_id: Option<egui::TextureId>,
|
||||
}
|
||||
|
||||
impl Default for ExampleApp {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
url: "https://raw.githubusercontent.com/emilk/egui/master/README.md".to_owned(),
|
||||
receivers: Default::default(),
|
||||
fetch_result: Default::default(),
|
||||
in_progress: Default::default(),
|
||||
result: Default::default(),
|
||||
texture_id: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -23,7 +55,7 @@ impl egui::app::App for ExampleApp {
|
||||
fn ui(
|
||||
&mut self,
|
||||
ctx: &std::sync::Arc<egui::Context>,
|
||||
_integration_context: &mut egui::app::IntegrationContext,
|
||||
integration_context: &mut egui::app::IntegrationContext,
|
||||
) {
|
||||
egui::CentralPanel::default().show(ctx, |ui| {
|
||||
ui.heading("HTTP Get inside of Egui");
|
||||
@@ -32,49 +64,24 @@ impl egui::app::App for ExampleApp {
|
||||
"(source code)"
|
||||
));
|
||||
|
||||
{
|
||||
let mut trigger_fetch = false;
|
||||
|
||||
ui.horizontal(|ui| {
|
||||
ui.label("URL:");
|
||||
trigger_fetch |= ui.text_edit_singleline(&mut self.url).lost_kb_focus;
|
||||
|
||||
if ui.button("Egui README.md").clicked {
|
||||
self.url = "https://raw.githubusercontent.com/emilk/egui/master/README.md"
|
||||
.to_owned();
|
||||
trigger_fetch = true;
|
||||
}
|
||||
if ui.button("Source code for this file").clicked {
|
||||
self.url = format!(
|
||||
"https://raw.githubusercontent.com/emilk/egui/master/{}",
|
||||
file!()
|
||||
);
|
||||
trigger_fetch = true;
|
||||
}
|
||||
if ui_url(ui, &mut self.url) {
|
||||
let (sender, receiver) = std::sync::mpsc::channel();
|
||||
self.in_progress = Some(receiver);
|
||||
let url = self.url.clone();
|
||||
egui_web::spawn_future(async move {
|
||||
sender.send(egui_web::fetch::get(&url).await).ok();
|
||||
// TODO: trigger egui repaint somehow
|
||||
});
|
||||
trigger_fetch |= ui.button("GET").clicked;
|
||||
|
||||
if trigger_fetch {
|
||||
let (sender, receiver) = std::sync::mpsc::channel();
|
||||
self.receivers.push(receiver);
|
||||
let url = self.url.clone();
|
||||
|
||||
let future = async move {
|
||||
let result = egui_web::fetch::get_text(&url).await;
|
||||
sender.send(result).ok();
|
||||
// TODO: trigger egui repaint somehow
|
||||
};
|
||||
|
||||
egui_web::spawn_future(future);
|
||||
}
|
||||
}
|
||||
|
||||
// Show finished download (if any):
|
||||
if let Some(result) = &self.fetch_result {
|
||||
ui.separator();
|
||||
ui.separator();
|
||||
|
||||
if self.in_progress.is_some() {
|
||||
ui.label("Please wait...");
|
||||
} else if let Some(result) = &self.result {
|
||||
match result {
|
||||
Ok(response) => {
|
||||
ui_response(ui, response);
|
||||
Ok(resource) => {
|
||||
ui_resouce(ui, self.texture_id, resource);
|
||||
}
|
||||
Err(error) => {
|
||||
// This should only happen if the fetch API isn't available or something similar.
|
||||
@@ -84,25 +91,100 @@ impl egui::app::App for ExampleApp {
|
||||
}
|
||||
});
|
||||
|
||||
for i in (0..self.receivers.len()).rev() {
|
||||
if let Ok(result) = self.receivers[i].try_recv() {
|
||||
self.fetch_result = Some(result);
|
||||
let _ = self.receivers.swap_remove(i);
|
||||
self.poll_receiver(integration_context);
|
||||
}
|
||||
}
|
||||
|
||||
impl ExampleApp {
|
||||
fn load_image(
|
||||
&mut self,
|
||||
integration_context: &mut egui::app::IntegrationContext,
|
||||
response: &Response,
|
||||
) -> Option<Image> {
|
||||
let tex_allocator = integration_context.tex_allocator.as_mut()?;
|
||||
|
||||
if matches!(
|
||||
response.header_content_type.as_str(),
|
||||
"image/jpeg" | "image/png"
|
||||
) {
|
||||
let image = Image::decode(&response.bytes)?;
|
||||
let texture_id = self.texture_id.unwrap_or_else(|| tex_allocator.alloc());
|
||||
self.texture_id = Some(texture_id);
|
||||
tex_allocator.set_srgba_premultiplied(texture_id, image.size, &image.pixels);
|
||||
return Some(image);
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
fn poll_receiver(&mut self, integration_context: &mut egui::app::IntegrationContext) {
|
||||
if let Some(receiver) = &mut self.in_progress {
|
||||
// Are we there yet?
|
||||
if let Ok(result) = receiver.try_recv() {
|
||||
self.in_progress = None;
|
||||
self.result = Some(result.map(|response| Resource {
|
||||
image: self.load_image(integration_context, &response),
|
||||
response,
|
||||
}));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn ui_response(ui: &mut egui::Ui, response: &Response) {
|
||||
ui.monospace(format!("url: {}", response.url));
|
||||
fn ui_url(ui: &mut egui::Ui, url: &mut String) -> bool {
|
||||
let mut trigger_fetch = false;
|
||||
|
||||
ui.horizontal(|ui| {
|
||||
ui.label("URL:");
|
||||
trigger_fetch |= ui.text_edit_singleline(url).lost_kb_focus;
|
||||
|
||||
if ui.button("Source code for this example").clicked {
|
||||
*url = format!(
|
||||
"https://raw.githubusercontent.com/emilk/egui/master/{}",
|
||||
file!()
|
||||
);
|
||||
trigger_fetch = true;
|
||||
}
|
||||
if ui.button("Random image").clicked {
|
||||
let seed = ui.input().time;
|
||||
let width = 640;
|
||||
let height = 480;
|
||||
*url = format!("https://picsum.photos/seed/{}/{}/{}", seed, width, height);
|
||||
trigger_fetch = true;
|
||||
}
|
||||
});
|
||||
trigger_fetch |= ui.button("GET").clicked;
|
||||
|
||||
trigger_fetch
|
||||
}
|
||||
|
||||
fn ui_resouce(ui: &mut egui::Ui, texture_id: Option<egui::TextureId>, resource: &Resource) {
|
||||
let Resource { response, image } = resource;
|
||||
|
||||
ui.monospace(format!("url: {}", response.url));
|
||||
ui.monospace(format!(
|
||||
"status: {} ({})",
|
||||
"status: {} ({})",
|
||||
response.status, response.status_text
|
||||
));
|
||||
ui.monospace(format!("Content-Type: {}", response.header_content_type));
|
||||
ui.monospace(format!(
|
||||
"Size: {:.1} kB",
|
||||
response.bytes.len() as f32 / 1000.0
|
||||
));
|
||||
|
||||
ui.monospace("Body:");
|
||||
ui.separator();
|
||||
egui::ScrollArea::auto_sized().show(ui, |ui| {
|
||||
ui.monospace(&response.body);
|
||||
});
|
||||
if let Some(image) = image {
|
||||
if let Some(texture_id) = texture_id {
|
||||
ui.image(
|
||||
texture_id,
|
||||
egui::Vec2::new(image.size.0 as f32, image.size.1 as f32),
|
||||
);
|
||||
}
|
||||
} else if let Some(text) = &response.text {
|
||||
ui.monospace("Body:");
|
||||
ui.separator();
|
||||
egui::ScrollArea::auto_sized().show(ui, |ui| {
|
||||
ui.monospace(text);
|
||||
});
|
||||
} else {
|
||||
ui.monospace("[binary]");
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user