Code cleanup
This commit is contained in:
parent
ebf204a9ae
commit
99a2a52510
4 changed files with 167 additions and 137 deletions
|
@ -6,9 +6,21 @@ pub struct Texture {
|
||||||
pub version: u64,
|
pub version: u64,
|
||||||
pub width: usize,
|
pub width: usize,
|
||||||
pub height: usize,
|
pub height: usize,
|
||||||
|
/// luminance and alpha, linear space 0-255
|
||||||
pub pixels: Vec<u8>,
|
pub pixels: Vec<u8>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Texture {
|
||||||
|
/// Returns the textures as `sRGBA` premultiplied pixels, row by row, top to bottom.
|
||||||
|
pub fn srgba_pixels<'slf>(&'slf self) -> impl Iterator<Item = super::Srgba> + 'slf {
|
||||||
|
use super::Srgba;
|
||||||
|
let srgba_from_luminance_lut: Vec<Srgba> = (0..=255).map(Srgba::white_alpha).collect();
|
||||||
|
self.pixels
|
||||||
|
.iter()
|
||||||
|
.map(move |&l| srgba_from_luminance_lut[l as usize])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl std::ops::Index<(usize, usize)> for Texture {
|
impl std::ops::Index<(usize, usize)> for Texture {
|
||||||
type Output = u8;
|
type Output = u8;
|
||||||
|
|
||||||
|
|
|
@ -95,64 +95,6 @@ impl Painter {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn alloc_user_texture(&mut self) -> egui::TextureId {
|
|
||||||
for (i, tex) in self.user_textures.iter_mut().enumerate() {
|
|
||||||
if tex.is_none() {
|
|
||||||
*tex = Some(Default::default());
|
|
||||||
return egui::TextureId::User(i as u64);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
let id = egui::TextureId::User(self.user_textures.len() as u64);
|
|
||||||
self.user_textures.push(Some(Default::default()));
|
|
||||||
id
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn set_user_texture(
|
|
||||||
&mut self,
|
|
||||||
id: egui::TextureId,
|
|
||||||
size: (usize, usize),
|
|
||||||
pixels: &[Srgba],
|
|
||||||
) {
|
|
||||||
assert_eq!(size.0 * size.1, pixels.len());
|
|
||||||
|
|
||||||
if let egui::TextureId::User(id) = id {
|
|
||||||
if let Some(user_texture) = self.user_textures.get_mut(id as usize) {
|
|
||||||
if let Some(user_texture) = user_texture {
|
|
||||||
let pixels: Vec<Vec<(u8, u8, u8, u8)>> = pixels
|
|
||||||
.chunks(size.0 as usize)
|
|
||||||
.map(|row| row.iter().map(|srgba| srgba.to_tuple()).collect())
|
|
||||||
.collect();
|
|
||||||
|
|
||||||
*user_texture = UserTexture {
|
|
||||||
pixels,
|
|
||||||
gl_texture: None,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn free_user_texture(&mut self, id: egui::TextureId) {
|
|
||||||
if let egui::TextureId::User(id) = id {
|
|
||||||
let index = id as usize;
|
|
||||||
if index < self.user_textures.len() {
|
|
||||||
self.user_textures[index] = None;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn get_texture(&self, texture_id: egui::TextureId) -> Option<&SrgbTexture2d> {
|
|
||||||
match texture_id {
|
|
||||||
egui::TextureId::Egui => self.egui_texture.as_ref(),
|
|
||||||
egui::TextureId::User(id) => self
|
|
||||||
.user_textures
|
|
||||||
.get(id as usize)?
|
|
||||||
.as_ref()?
|
|
||||||
.gl_texture
|
|
||||||
.as_ref(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn upload_egui_texture(
|
fn upload_egui_texture(
|
||||||
&mut self,
|
&mut self,
|
||||||
facade: &dyn glium::backend::Facade,
|
facade: &dyn glium::backend::Facade,
|
||||||
|
@ -179,20 +121,6 @@ impl Painter {
|
||||||
self.egui_texture_version = Some(texture.version);
|
self.egui_texture_version = Some(texture.version);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn upload_pending_user_textures(&mut self, facade: &dyn glium::backend::Facade) {
|
|
||||||
for user_texture in &mut self.user_textures {
|
|
||||||
if let Some(user_texture) = user_texture {
|
|
||||||
if user_texture.gl_texture.is_none() {
|
|
||||||
let pixels = std::mem::take(&mut user_texture.pixels);
|
|
||||||
let format = texture::SrgbFormat::U8U8U8U8;
|
|
||||||
let mipmaps = texture::MipmapsOption::NoMipmap;
|
|
||||||
user_texture.gl_texture =
|
|
||||||
Some(SrgbTexture2d::with_format(facade, pixels, format, mipmaps).unwrap());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Main entry-point for painting a frame
|
/// Main entry-point for painting a frame
|
||||||
pub fn paint_jobs(
|
pub fn paint_jobs(
|
||||||
&mut self,
|
&mut self,
|
||||||
|
@ -326,4 +254,80 @@ impl Painter {
|
||||||
.unwrap();
|
.unwrap();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
// user textures: this is an experimental feature.
|
||||||
|
// No need to implement this in your Egui integration!
|
||||||
|
|
||||||
|
pub fn alloc_user_texture(&mut self) -> egui::TextureId {
|
||||||
|
for (i, tex) in self.user_textures.iter_mut().enumerate() {
|
||||||
|
if tex.is_none() {
|
||||||
|
*tex = Some(Default::default());
|
||||||
|
return egui::TextureId::User(i as u64);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let id = egui::TextureId::User(self.user_textures.len() as u64);
|
||||||
|
self.user_textures.push(Some(Default::default()));
|
||||||
|
id
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn set_user_texture(
|
||||||
|
&mut self,
|
||||||
|
id: egui::TextureId,
|
||||||
|
size: (usize, usize),
|
||||||
|
pixels: &[Srgba],
|
||||||
|
) {
|
||||||
|
assert_eq!(size.0 * size.1, pixels.len());
|
||||||
|
|
||||||
|
if let egui::TextureId::User(id) = id {
|
||||||
|
if let Some(user_texture) = self.user_textures.get_mut(id as usize) {
|
||||||
|
if let Some(user_texture) = user_texture {
|
||||||
|
let pixels: Vec<Vec<(u8, u8, u8, u8)>> = pixels
|
||||||
|
.chunks(size.0 as usize)
|
||||||
|
.map(|row| row.iter().map(|srgba| srgba.to_tuple()).collect())
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
*user_texture = UserTexture {
|
||||||
|
pixels,
|
||||||
|
gl_texture: None,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn free_user_texture(&mut self, id: egui::TextureId) {
|
||||||
|
if let egui::TextureId::User(id) = id {
|
||||||
|
let index = id as usize;
|
||||||
|
if index < self.user_textures.len() {
|
||||||
|
self.user_textures[index] = None;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_texture(&self, texture_id: egui::TextureId) -> Option<&SrgbTexture2d> {
|
||||||
|
match texture_id {
|
||||||
|
egui::TextureId::Egui => self.egui_texture.as_ref(),
|
||||||
|
egui::TextureId::User(id) => self
|
||||||
|
.user_textures
|
||||||
|
.get(id as usize)?
|
||||||
|
.as_ref()?
|
||||||
|
.gl_texture
|
||||||
|
.as_ref(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn upload_pending_user_textures(&mut self, facade: &dyn glium::backend::Facade) {
|
||||||
|
for user_texture in &mut self.user_textures {
|
||||||
|
if let Some(user_texture) = user_texture {
|
||||||
|
if user_texture.gl_texture.is_none() {
|
||||||
|
let pixels = std::mem::take(&mut user_texture.pixels);
|
||||||
|
let format = texture::SrgbFormat::U8U8U8U8;
|
||||||
|
let mipmaps = texture::MipmapsOption::NoMipmap;
|
||||||
|
user_texture.gl_texture =
|
||||||
|
Some(SrgbTexture2d::with_format(facade, pixels, format, mipmaps).unwrap());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -234,8 +234,7 @@ impl Painter {
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut pixels: Vec<u8> = Vec::with_capacity(texture.pixels.len() * 4);
|
let mut pixels: Vec<u8> = Vec::with_capacity(texture.pixels.len() * 4);
|
||||||
for &alpha in &texture.pixels {
|
for srgba in texture.srgba_pixels() {
|
||||||
let srgba = Srgba::white_alpha(alpha);
|
|
||||||
pixels.push(srgba.r());
|
pixels.push(srgba.r());
|
||||||
pixels.push(srgba.g());
|
pixels.push(srgba.g());
|
||||||
pixels.push(srgba.b());
|
pixels.push(srgba.b());
|
||||||
|
|
|
@ -1,28 +1,6 @@
|
||||||
use egui_web::fetch::Response;
|
use egui_web::fetch::Response;
|
||||||
use std::sync::mpsc::Receiver;
|
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 {
|
struct Resource {
|
||||||
/// HTTP response
|
/// HTTP response
|
||||||
response: Response,
|
response: Response,
|
||||||
|
@ -31,11 +9,22 @@ struct Resource {
|
||||||
image: Option<Image>,
|
image: Option<Image>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Resource {
|
||||||
|
fn from_response(response: Response) -> Self {
|
||||||
|
let image = if response.header_content_type.starts_with("image/") {
|
||||||
|
Image::decode(&response.bytes)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
};
|
||||||
|
Self { response, image }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub struct ExampleApp {
|
pub struct ExampleApp {
|
||||||
url: String,
|
url: String,
|
||||||
in_progress: Option<Receiver<Result<Response, String>>>,
|
in_progress: Option<Receiver<Result<Response, String>>>,
|
||||||
result: Option<Result<Resource, String>>,
|
result: Option<Result<Resource, String>>,
|
||||||
texture_id: Option<egui::TextureId>,
|
tex_mngr: TexMngr,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for ExampleApp {
|
impl Default for ExampleApp {
|
||||||
|
@ -44,7 +33,7 @@ impl Default for ExampleApp {
|
||||||
url: "https://raw.githubusercontent.com/emilk/egui/master/README.md".to_owned(),
|
url: "https://raw.githubusercontent.com/emilk/egui/master/README.md".to_owned(),
|
||||||
in_progress: Default::default(),
|
in_progress: Default::default(),
|
||||||
result: Default::default(),
|
result: Default::default(),
|
||||||
texture_id: None,
|
tex_mngr: Default::default(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -58,7 +47,7 @@ impl egui::app::App for ExampleApp {
|
||||||
integration_context: &mut egui::app::IntegrationContext,
|
integration_context: &mut egui::app::IntegrationContext,
|
||||||
) {
|
) {
|
||||||
egui::CentralPanel::default().show(ctx, |ui| {
|
egui::CentralPanel::default().show(ctx, |ui| {
|
||||||
ui.heading("HTTP Get inside of Egui");
|
ui.heading("Egui Example App");
|
||||||
ui.add(egui::github_link_file!(
|
ui.add(egui::github_link_file!(
|
||||||
"https://github.com/emilk/egui/blob/master/",
|
"https://github.com/emilk/egui/blob/master/",
|
||||||
"(source code)"
|
"(source code)"
|
||||||
|
@ -81,7 +70,7 @@ impl egui::app::App for ExampleApp {
|
||||||
} else if let Some(result) = &self.result {
|
} else if let Some(result) = &self.result {
|
||||||
match result {
|
match result {
|
||||||
Ok(resource) => {
|
Ok(resource) => {
|
||||||
ui_resouce(ui, self.texture_id, resource);
|
ui_resouce(ui, integration_context, &mut self.tex_mngr, resource);
|
||||||
}
|
}
|
||||||
Err(error) => {
|
Err(error) => {
|
||||||
// This should only happen if the fetch API isn't available or something similar.
|
// This should only happen if the fetch API isn't available or something similar.
|
||||||
|
@ -91,40 +80,11 @@ impl egui::app::App for ExampleApp {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
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 {
|
if let Some(receiver) = &mut self.in_progress {
|
||||||
// Are we there yet?
|
// Are we there yet?
|
||||||
if let Ok(result) = receiver.try_recv() {
|
if let Ok(result) = receiver.try_recv() {
|
||||||
self.in_progress = None;
|
self.in_progress = None;
|
||||||
self.result = Some(result.map(|response| Resource {
|
self.result = Some(result.map(Resource::from_response));
|
||||||
image: self.load_image(integration_context, &response),
|
|
||||||
response,
|
|
||||||
}));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -136,7 +96,10 @@ fn ui_url(ui: &mut egui::Ui, url: &mut String) -> bool {
|
||||||
ui.horizontal(|ui| {
|
ui.horizontal(|ui| {
|
||||||
ui.label("URL:");
|
ui.label("URL:");
|
||||||
trigger_fetch |= ui.text_edit_singleline(url).lost_kb_focus;
|
trigger_fetch |= ui.text_edit_singleline(url).lost_kb_focus;
|
||||||
|
trigger_fetch |= ui.button("GET").clicked;
|
||||||
|
});
|
||||||
|
|
||||||
|
ui.horizontal(|ui| {
|
||||||
if ui.button("Source code for this example").clicked {
|
if ui.button("Source code for this example").clicked {
|
||||||
*url = format!(
|
*url = format!(
|
||||||
"https://raw.githubusercontent.com/emilk/egui/master/{}",
|
"https://raw.githubusercontent.com/emilk/egui/master/{}",
|
||||||
|
@ -152,12 +115,16 @@ fn ui_url(ui: &mut egui::Ui, url: &mut String) -> bool {
|
||||||
trigger_fetch = true;
|
trigger_fetch = true;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
trigger_fetch |= ui.button("GET").clicked;
|
|
||||||
|
|
||||||
trigger_fetch
|
trigger_fetch
|
||||||
}
|
}
|
||||||
|
|
||||||
fn ui_resouce(ui: &mut egui::Ui, texture_id: Option<egui::TextureId>, resource: &Resource) {
|
fn ui_resouce(
|
||||||
|
ui: &mut egui::Ui,
|
||||||
|
integration_context: &mut egui::app::IntegrationContext,
|
||||||
|
tex_mngr: &mut TexMngr,
|
||||||
|
resource: &Resource,
|
||||||
|
) {
|
||||||
let Resource { response, image } = resource;
|
let Resource { response, image } = resource;
|
||||||
|
|
||||||
ui.monospace(format!("url: {}", response.url));
|
ui.monospace(format!("url: {}", response.url));
|
||||||
|
@ -172,11 +139,9 @@ fn ui_resouce(ui: &mut egui::Ui, texture_id: Option<egui::TextureId>, resource:
|
||||||
));
|
));
|
||||||
|
|
||||||
if let Some(image) = image {
|
if let Some(image) = image {
|
||||||
if let Some(texture_id) = texture_id {
|
if let Some(texture_id) = tex_mngr.texture(integration_context, &response.url, &image) {
|
||||||
ui.image(
|
let size = egui::Vec2::new(image.size.0 as f32, image.size.1 as f32);
|
||||||
texture_id,
|
ui.image(texture_id, size);
|
||||||
egui::Vec2::new(image.size.0 as f32, image.size.1 as f32),
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
} else if let Some(text) = &response.text {
|
} else if let Some(text) = &response.text {
|
||||||
ui.monospace("Body:");
|
ui.monospace("Body:");
|
||||||
|
@ -188,3 +153,53 @@ fn ui_resouce(ui: &mut egui::Ui, texture_id: Option<egui::TextureId>, resource:
|
||||||
ui.monospace("[binary]");
|
ui.monospace("[binary]");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ----------------------------------------------------------------------------
|
||||||
|
// Texture/image handling is very manual at the moment.
|
||||||
|
|
||||||
|
/// Immediate mode texture managers that supports at most one texture at the time :)
|
||||||
|
#[derive(Default)]
|
||||||
|
struct TexMngr {
|
||||||
|
loaded_url: String,
|
||||||
|
texture_id: Option<egui::TextureId>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TexMngr {
|
||||||
|
fn texture(
|
||||||
|
&mut self,
|
||||||
|
integration_context: &mut egui::app::IntegrationContext,
|
||||||
|
url: &str,
|
||||||
|
image: &Image,
|
||||||
|
) -> Option<egui::TextureId> {
|
||||||
|
let tex_allocator = integration_context.tex_allocator.as_mut()?;
|
||||||
|
let texture_id = self.texture_id.unwrap_or_else(|| tex_allocator.alloc());
|
||||||
|
self.texture_id = Some(texture_id);
|
||||||
|
if self.loaded_url != url {
|
||||||
|
self.loaded_url = url.to_owned();
|
||||||
|
tex_allocator.set_srgba_premultiplied(texture_id, image.size, &image.pixels);
|
||||||
|
}
|
||||||
|
Some(texture_id)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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 })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in a new issue