From e6c8e617c033e10ef2ae209a4c40ca093e9903db Mon Sep 17 00:00:00 2001 From: jprochazk <1665677+jprochazk@users.noreply.github.com> Date: Tue, 12 Sep 2023 11:21:25 +0200 Subject: [PATCH 01/32] add egui logo to widget gallery --- crates/egui_demo_lib/assets/icon.png | Bin 0 -> 17166 bytes crates/egui_demo_lib/src/demo/widget_gallery.rs | 5 ++++- 2 files changed, 4 insertions(+), 1 deletion(-) create mode 100644 crates/egui_demo_lib/assets/icon.png diff --git a/crates/egui_demo_lib/assets/icon.png b/crates/egui_demo_lib/assets/icon.png new file mode 100644 index 0000000000000000000000000000000000000000..cf1e6c3ebdb52209943a9affac3b463b2c85e717 GIT binary patch literal 17166 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 Date: Tue, 12 Sep 2023 11:29:19 +0200 Subject: [PATCH 02/32] improve "no image loaders" error message --- crates/egui/src/context.rs | 10 +++++++++- crates/egui/src/load.rs | 4 ++++ 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/crates/egui/src/context.rs b/crates/egui/src/context.rs index c3211315796..23d4ff9614e 100644 --- a/crates/egui/src/context.rs +++ b/crates/egui/src/context.rs @@ -2038,17 +2038,25 @@ impl Context { /// /// # Errors /// This may fail with: + /// - [`LoadError::NoImageLoaders`][no_image_loaders] if tbere are no registered image loaders. /// - [`LoadError::NotSupported`][not_supported] if none of the registered loaders support loading the given `uri`. /// - [`LoadError::Custom`][custom] if one of the loaders _does_ support loading the `uri`, but the loading process failed. /// /// ⚠ May deadlock if called from within an `ImageLoader`! /// + /// [no_image_loaders]: crate::load::LoadError::NoImageLoaders /// [not_supported]: crate::load::LoadError::NotSupported /// [custom]: crate::load::LoadError::Custom pub fn try_load_image(&self, uri: &str, size_hint: load::SizeHint) -> load::ImageLoadResult { crate::profile_function!(); - for loader in self.loaders().image.lock().iter() { + let loaders = self.loaders(); + let loaders = loaders.image.lock(); + if loaders.is_empty() { + return Err(load::LoadError::NoImageLoaders); + } + + for loader in loaders.iter() { match loader.load(self, uri, size_hint) { Err(load::LoadError::NotSupported) => continue, result => return result, diff --git a/crates/egui/src/load.rs b/crates/egui/src/load.rs index a325fc00b58..12207b2aafe 100644 --- a/crates/egui/src/load.rs +++ b/crates/egui/src/load.rs @@ -66,6 +66,9 @@ use std::{error::Error as StdError, fmt::Display, sync::Arc}; /// Represents a failed attempt at loading an image. #[derive(Clone, Debug)] pub enum LoadError { + /// There are no image loaders installed. + NoImageLoaders, + /// This loader does not support this protocol or image format. NotSupported, @@ -76,6 +79,7 @@ pub enum LoadError { impl Display for LoadError { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { + LoadError::NoImageLoaders => f.write_str("no image loaders are installed"), LoadError::NotSupported => f.write_str("not supported"), LoadError::Custom(message) => f.write_str(message), } From 344c172ecbe6fc8bb2d2cec5385faeb5a8f6da9b Mon Sep 17 00:00:00 2001 From: jprochazk <1665677+jprochazk@users.noreply.github.com> Date: Tue, 12 Sep 2023 11:52:41 +0200 Subject: [PATCH 03/32] rework static URIs to accept `Cow<'static>` --- crates/egui/src/context.rs | 5 +++-- crates/egui/src/lib.rs | 5 ++++- crates/egui/src/load.rs | 10 +++++++--- crates/egui/src/widgets/image.rs | 25 +++++++++++++++++++------ 4 files changed, 33 insertions(+), 12 deletions(-) diff --git a/crates/egui/src/context.rs b/crates/egui/src/context.rs index 23d4ff9614e..285a4254150 100644 --- a/crates/egui/src/context.rs +++ b/crates/egui/src/context.rs @@ -1,5 +1,6 @@ #![warn(missing_docs)] // Let's keep `Context` well-documented. +use std::borrow::Cow; use std::sync::Arc; use crate::load::Bytes; @@ -1911,8 +1912,8 @@ impl Context { /// Associate some static bytes with a `uri`. /// /// The same `uri` may be passed to [`Ui::image`] later to load the bytes as an image. - pub fn include_bytes(&self, uri: &'static str, bytes: impl Into) { - self.loaders().include.insert(uri, bytes.into()); + pub fn include_bytes(&self, uri: impl Into>, bytes: impl Into) { + self.loaders().include.insert(uri, bytes); } /// Returns `true` if the chain of bytes, image, or texture loaders diff --git a/crates/egui/src/lib.rs b/crates/egui/src/lib.rs index 65f0aab7457..5c734baaee9 100644 --- a/crates/egui/src/lib.rs +++ b/crates/egui/src/lib.rs @@ -442,7 +442,10 @@ pub fn warn_if_debug_build(ui: &mut crate::Ui) { #[macro_export] macro_rules! include_image { ($path: literal) => { - $crate::ImageSource::Bytes($path, $crate::load::Bytes::Static(include_bytes!($path))) + $crate::ImageSource::Bytes( + ::std::borrow::Cow::Borrowed($path), + $crate::load::Bytes::Static(include_bytes!($path)), + ) }; } diff --git a/crates/egui/src/load.rs b/crates/egui/src/load.rs index 12207b2aafe..1682b1dfa1b 100644 --- a/crates/egui/src/load.rs +++ b/crates/egui/src/load.rs @@ -59,6 +59,7 @@ use epaint::util::FloatOrd; use epaint::util::OrderedFloat; use epaint::TextureHandle; use epaint::{textures::TextureOptions, ColorImage, TextureId, Vec2}; +use std::borrow::Cow; use std::fmt::Debug; use std::ops::Deref; use std::{error::Error as StdError, fmt::Display, sync::Arc}; @@ -453,12 +454,15 @@ pub trait TextureLoader { #[derive(Default)] pub(crate) struct DefaultBytesLoader { - cache: Mutex>, + cache: Mutex, Bytes>>, } impl DefaultBytesLoader { - pub(crate) fn insert(&self, uri: &'static str, bytes: impl Into) { - self.cache.lock().entry(uri).or_insert_with(|| bytes.into()); + pub(crate) fn insert(&self, uri: impl Into>, bytes: impl Into) { + self.cache + .lock() + .entry(uri.into()) + .or_insert_with(|| bytes.into()); } } diff --git a/crates/egui/src/widgets/image.rs b/crates/egui/src/widgets/image.rs index cc98559d0a7..90e7b516a22 100644 --- a/crates/egui/src/widgets/image.rs +++ b/crates/egui/src/widgets/image.rs @@ -59,8 +59,8 @@ impl<'a> Image<'a> { /// Load the image from some raw bytes. /// /// See [`ImageSource::Bytes`]. - pub fn from_bytes(uri: &'static str, bytes: impl Into) -> Self { - Self::new(ImageSource::Bytes(uri, bytes.into())) + pub fn from_bytes(uri: impl Into>, bytes: impl Into) -> Self { + Self::new(ImageSource::Bytes(uri.into(), bytes.into())) } /// Texture options used when creating the texture. @@ -226,8 +226,7 @@ impl<'a> Image<'a> { /// This will return `` for [`ImageSource::Texture`]. pub fn uri(&self) -> &str { match &self.source { - ImageSource::Bytes(uri, _) => uri, - ImageSource::Uri(uri) => uri, + ImageSource::Bytes(uri, _) | ImageSource::Uri(uri) => uri, // Note: texture source is never in "loading" state ImageSource::Texture(_) => "", } @@ -247,7 +246,7 @@ impl<'a> Image<'a> { self.size.hint(ui.available_size()), ), ImageSource::Bytes(uri, bytes) => { - ui.ctx().include_bytes(uri.as_ref(), bytes); + ui.ctx().include_bytes(uri.clone(), bytes); ui.ctx().try_load_texture( uri.as_ref(), self.texture_options, @@ -467,7 +466,7 @@ pub enum ImageSource<'a> { /// See also [`include_image`] for an easy way to load and display static images. /// /// See [`crate::load`] for more information. - Bytes(&'static str, Bytes), + Bytes(Cow<'static, str>, Bytes), } impl<'a> From<&'a str> for ImageSource<'a> { @@ -507,10 +506,24 @@ impl<'a> From> for ImageSource<'a> { impl> From<(&'static str, T)> for ImageSource<'static> { #[inline] fn from((uri, bytes): (&'static str, T)) -> Self { + Self::Bytes(uri.into(), bytes.into()) + } +} + +impl> From<(Cow<'static, str>, T)> for ImageSource<'static> { + #[inline] + fn from((uri, bytes): (Cow<'static, str>, T)) -> Self { Self::Bytes(uri, bytes.into()) } } +impl> From<(String, T)> for ImageSource<'static> { + #[inline] + fn from((uri, bytes): (String, T)) -> Self { + Self::Bytes(uri.into(), bytes.into()) + } +} + impl> From for ImageSource<'static> { fn from(value: T) -> Self { Self::Texture(value.into()) From fb9f2980631d9b728c6bc40e187f645868a45ab2 Mon Sep 17 00:00:00 2001 From: jprochazk <1665677+jprochazk@users.noreply.github.com> Date: Tue, 12 Sep 2023 11:52:50 +0200 Subject: [PATCH 04/32] remove `RetainedImage` from `http_app` in `egui_demo_app` --- crates/egui_demo_app/src/apps/http_app.rs | 46 ++++++++++++----------- 1 file changed, 25 insertions(+), 21 deletions(-) diff --git a/crates/egui_demo_app/src/apps/http_app.rs b/crates/egui_demo_app/src/apps/http_app.rs index 33da4678f41..fe43c477b13 100644 --- a/crates/egui_demo_app/src/apps/http_app.rs +++ b/crates/egui_demo_app/src/apps/http_app.rs @@ -1,6 +1,4 @@ -#![allow(deprecated)] - -use egui_extras::RetainedImage; +use egui::Image; use poll_promise::Promise; struct Resource { @@ -10,7 +8,7 @@ struct Resource { text: Option, /// If set, the response was an image. - image: Option, + image: Option>, /// If set, the response was text with some supported syntax highlighting (e.g. ".rs" or ".md"). colored_text: Option, @@ -19,21 +17,27 @@ struct Resource { impl Resource { fn from_response(ctx: &egui::Context, response: ehttp::Response) -> Self { let content_type = response.content_type().unwrap_or_default(); - let image = if content_type.starts_with("image/") { - RetainedImage::from_image_bytes(&response.url, &response.bytes).ok() + if content_type.starts_with("image/") { + ctx.include_bytes(response.url.clone(), response.bytes.clone()); + let image = Image::from_uri(response.url.clone()); + + Self { + response, + text: None, + colored_text: None, + image: Some(image), + } } else { - None - }; - - let text = response.text(); - let colored_text = text.and_then(|text| syntax_highlighting(ctx, &response, text)); - let text = text.map(|text| text.to_owned()); - - Self { - response, - text, - image, - colored_text, + let text = response.text(); + let colored_text = text.and_then(|text| syntax_highlighting(ctx, &response, text)); + let text = text.map(|text| text.to_owned()); + + Self { + response, + text, + colored_text, + image: None, + } } } } @@ -65,6 +69,7 @@ impl eframe::App for HttpApp { }); egui::CentralPanel::default().show(ctx, |ui| { + let prev_url = self.url.clone(); let trigger_fetch = ui_url(ui, frame, &mut self.url); ui.horizontal_wrapped(|ui| { @@ -79,6 +84,7 @@ impl eframe::App for HttpApp { let (sender, promise) = Promise::new(); let request = ehttp::Request::get(&self.url); ehttp::fetch(request, move |response| { + ctx.forget_image(&prev_url); ctx.request_repaint(); // wake up UI thread let resource = response.map(|response| Resource::from_response(&ctx, response)); sender.send(resource); @@ -195,9 +201,7 @@ fn ui_resource(ui: &mut egui::Ui, resource: &Resource) { } if let Some(image) = image { - let mut size = image.size_vec2(); - size *= (ui.available_width() / size.x).min(1.0); - image.show_size(ui, size); + ui.add(image.clone()); } else if let Some(colored_text) = colored_text { colored_text.ui(ui); } else if let Some(text) = &text { From 6e82c733edc0f0149f88a4f9ad040ab480280373 Mon Sep 17 00:00:00 2001 From: jprochazk <1665677+jprochazk@users.noreply.github.com> Date: Tue, 12 Sep 2023 11:54:17 +0200 Subject: [PATCH 05/32] hide `RetainedImage` from docs --- crates/egui-wgpu/src/renderer.rs | 5 ++--- crates/egui_extras/src/lib.rs | 2 ++ 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/crates/egui-wgpu/src/renderer.rs b/crates/egui-wgpu/src/renderer.rs index c97fba88230..532c7c01413 100644 --- a/crates/egui-wgpu/src/renderer.rs +++ b/crates/egui-wgpu/src/renderer.rs @@ -605,9 +605,8 @@ impl Renderer { /// Get the WGPU texture and bind group associated to a texture that has been allocated by egui. /// - /// This could be used by custom paint hooks to render images that have been added through with - /// [`egui_extras::RetainedImage`](https://docs.rs/egui_extras/latest/egui_extras/image/struct.RetainedImage.html) - /// or [`epaint::Context::load_texture`](https://docs.rs/egui/latest/egui/struct.Context.html#method.load_texture). + /// This could be used by custom paint hooks to render images that have been added through + /// [`epaint::Context::load_texture`](https://docs.rs/egui/latest/egui/struct.Context.html#method.load_texture). pub fn texture( &self, id: &epaint::TextureId, diff --git a/crates/egui_extras/src/lib.rs b/crates/egui_extras/src/lib.rs index 3b020323e64..fe962b80e29 100644 --- a/crates/egui_extras/src/lib.rs +++ b/crates/egui_extras/src/lib.rs @@ -13,6 +13,7 @@ #[cfg(feature = "chrono")] mod datepicker; +#[doc(hidden)] pub mod image; mod layout; pub mod loaders; @@ -23,6 +24,7 @@ mod table; #[cfg(feature = "chrono")] pub use crate::datepicker::DatePickerButton; +#[doc(hidden)] #[allow(deprecated)] pub use crate::image::RetainedImage; pub(crate) use crate::layout::StripLayout; From cb5e3c4892712dd1455f8284197dbaff1e9fed69 Mon Sep 17 00:00:00 2001 From: jprochazk <1665677+jprochazk@users.noreply.github.com> Date: Wed, 13 Sep 2023 10:00:06 +0200 Subject: [PATCH 06/32] use `ui.image`/`Image` over `RawImage` --- crates/egui/src/context.rs | 6 +-- crates/egui/src/lib.rs | 2 +- crates/egui/src/load.rs | 8 +++- crates/egui/src/ui.rs | 40 +---------------- crates/egui/src/widgets/button.rs | 2 +- crates/egui/src/widgets/image.rs | 44 +++++++++++++++---- crates/egui_demo_lib/src/color_test.rs | 4 +- .../egui_demo_lib/src/demo/widget_gallery.rs | 2 +- crates/egui_extras/src/image.rs | 2 +- crates/egui_plot/src/items/mod.rs | 13 +++--- examples/images/src/main.rs | 2 +- examples/screenshot/src/main.rs | 2 +- 12 files changed, 61 insertions(+), 66 deletions(-) diff --git a/crates/egui/src/context.rs b/crates/egui/src/context.rs index 285a4254150..aca9d2baa48 100644 --- a/crates/egui/src/context.rs +++ b/crates/egui/src/context.rs @@ -1146,7 +1146,7 @@ impl Context { /// }); /// /// // Show the image: - /// ui.raw_image((texture.id(), texture.size_vec2())); + /// ui.image((texture.id(), texture.size_vec2())); /// } /// } /// ``` @@ -1692,14 +1692,14 @@ impl Context { let mut size = vec2(w as f32, h as f32); size *= (max_preview_size.x / size.x).min(1.0); size *= (max_preview_size.y / size.y).min(1.0); - ui.raw_image(SizedTexture::new(texture_id, size)) + ui.image(SizedTexture::new(texture_id, size)) .on_hover_ui(|ui| { // show larger on hover let max_size = 0.5 * ui.ctx().screen_rect().size(); let mut size = vec2(w as f32, h as f32); size *= max_size.x / size.x.max(max_size.x); size *= max_size.y / size.y.max(max_size.y); - ui.raw_image(SizedTexture::new(texture_id, size)); + ui.image(SizedTexture::new(texture_id, size)); }); ui.label(format!("{w} x {h}")); diff --git a/crates/egui/src/lib.rs b/crates/egui/src/lib.rs index 5c734baaee9..6baa0441098 100644 --- a/crates/egui/src/lib.rs +++ b/crates/egui/src/lib.rs @@ -84,7 +84,7 @@ //! ui.separator(); //! //! # let my_image = egui::TextureId::default(); -//! ui.raw_image((my_image, egui::Vec2::new(640.0, 480.0))); +//! ui.image((my_image, egui::Vec2::new(640.0, 480.0))); //! //! ui.collapsing("Click to see what is hidden!", |ui| { //! ui.label("Not much, as it turns out"); diff --git a/crates/egui/src/load.rs b/crates/egui/src/load.rs index 1682b1dfa1b..2e1cc1a471a 100644 --- a/crates/egui/src/load.rs +++ b/crates/egui/src/load.rs @@ -375,7 +375,13 @@ impl SizedTexture { impl From<(TextureId, Vec2)> for SizedTexture { #[inline] fn from((id, size): (TextureId, Vec2)) -> Self { - SizedTexture { id, size } + Self { id, size } + } +} + +impl<'a> From<&'a TextureHandle> for SizedTexture { + fn from(handle: &'a TextureHandle) -> Self { + Self::from_handle(handle) } } diff --git a/crates/egui/src/ui.rs b/crates/egui/src/ui.rs index 5b342b20039..362715a986c 100644 --- a/crates/egui/src/ui.rs +++ b/crates/egui/src/ui.rs @@ -5,7 +5,6 @@ use std::sync::Arc; use epaint::mutex::RwLock; -use crate::load::SizedTexture; use crate::{ containers::*, ecolor::*, epaint::text::Fonts, layout::*, menu::MenuState, placer::Placer, util::IdTypeMap, widgets::*, *, @@ -1585,44 +1584,7 @@ impl Ui { /// See also [`crate::Image`], [`crate::ImageSource`] and [`Self::raw_image`]. #[inline] pub fn image<'a>(&mut self, source: impl Into>) -> Response { - Image::new(source.into()).ui(self) - } - - /// Show an image created from a sized texture. - /// - /// You may use this method over [`Ui::image`] if you already have a [`TextureHandle`] - /// or a [`SizedTexture`]. - /// - /// ``` - /// # egui::__run_test_ui(|ui| { - /// struct MyImage { - /// texture: Option, - /// } - /// - /// impl MyImage { - /// fn ui(&mut self, ui: &mut egui::Ui) { - /// let texture = self - /// .texture - /// .get_or_insert_with(|| { - /// // Load the texture only once. - /// ui.ctx().load_texture( - /// "my-image", - /// egui::ColorImage::example(), - /// Default::default() - /// ) - /// }); - /// - /// // Show the image: - /// ui.raw_image((texture.id(), texture.size_vec2())); - /// } - /// } - /// # }); - /// ``` - /// - /// See also [`crate::RawImage`]. - #[inline] - pub fn raw_image(&mut self, texture: impl Into) -> Response { - RawImage::new(texture).ui(self) + Image::new(source).ui(self) } } diff --git a/crates/egui/src/widgets/button.rs b/crates/egui/src/widgets/button.rs index 38353b146d9..3334c9c430d 100644 --- a/crates/egui/src/widgets/button.rs +++ b/crates/egui/src/widgets/button.rs @@ -483,7 +483,7 @@ pub struct ImageButton<'a> { impl<'a> ImageButton<'a> { pub fn new(source: impl Into>) -> Self { Self { - image: Image::new(source.into()), + image: Image::new(source), sense: Sense::click(), frame: true, selected: false, diff --git a/crates/egui/src/widgets/image.rs b/crates/egui/src/widgets/image.rs index 90e7b516a22..687a1e208bb 100644 --- a/crates/egui/src/widgets/image.rs +++ b/crates/egui/src/widgets/image.rs @@ -32,14 +32,30 @@ pub struct Image<'a> { impl<'a> Image<'a> { /// Load the image from some source. - pub fn new(source: ImageSource<'a>) -> Self { - Self { - source, - texture_options: Default::default(), - image_options: Default::default(), - sense: Sense::hover(), - size: Default::default(), + pub fn new(source: impl Into>) -> Self { + fn new_mono(source: ImageSource<'_>) -> Image<'_> { + let size = if let ImageSource::Texture(tex) = &source { + // User is probably expecting their texture to have + // the exact size of the provided `SizedTexture`. + ImageSize { + maintain_aspect_ratio: false, + max_size: None, + fit: ImageFit::Exact(tex.size), + } + } else { + Default::default() + }; + + Image { + source, + texture_options: Default::default(), + image_options: Default::default(), + sense: Sense::hover(), + size, + } } + + new_mono(source.into()) } /// Load the image from a URI. @@ -52,8 +68,8 @@ impl<'a> Image<'a> { /// Load the image from an existing texture. /// /// See [`ImageSource::Texture`]. - pub fn from_texture(texture: SizedTexture) -> Self { - Self::new(ImageSource::Texture(texture)) + pub fn from_texture(texture: impl Into) -> Self { + Self::new(ImageSource::Texture(texture.into())) } /// Load the image from some raw bytes. @@ -157,18 +173,21 @@ impl<'a> Image<'a> { } /// Select UV range. Default is (0,0) in top-left, (1,1) bottom right. + #[inline] pub fn uv(mut self, uv: impl Into) -> Self { self.image_options.uv = uv.into(); self } /// A solid color to put behind the image. Useful for transparent images. + #[inline] pub fn bg_fill(mut self, bg_fill: impl Into) -> Self { self.image_options.bg_fill = bg_fill.into(); self } /// Multiply image color with this. Default is WHITE (no tint). + #[inline] pub fn tint(mut self, tint: impl Into) -> Self { self.image_options.tint = tint.into(); self @@ -183,6 +202,7 @@ impl<'a> Image<'a> { /// /// Due to limitations in the current implementation, /// this will turn off rounding of the image. + #[inline] pub fn rotate(mut self, angle: f32, origin: Vec2) -> Self { self.image_options.rotation = Some((Rot2::from_angle(angle), origin)); self.image_options.rounding = Rounding::ZERO; // incompatible with rotation @@ -195,6 +215,7 @@ impl<'a> Image<'a> { /// /// Due to limitations in the current implementation, /// this will turn off any rotation of the image. + #[inline] pub fn rounding(mut self, rounding: impl Into) -> Self { self.image_options.rounding = rounding.into(); if self.image_options.rounding != Rounding::ZERO { @@ -206,10 +227,12 @@ impl<'a> Image<'a> { impl<'a> Image<'a> { /// Returns the size the image will occupy in the final UI. + #[inline] pub fn calculate_size(&self, available_size: Vec2, image_size: Vec2) -> Vec2 { self.size.get(available_size, image_size) } + #[inline] pub fn size(&self) -> Option { match &self.source { ImageSource::Texture(texture) => Some(texture.size), @@ -217,6 +240,7 @@ impl<'a> Image<'a> { } } + #[inline] pub fn source(&self) -> &ImageSource<'a> { &self.source } @@ -224,6 +248,7 @@ impl<'a> Image<'a> { /// Get the `uri` that this image was constructed from. /// /// This will return `` for [`ImageSource::Texture`]. + #[inline] pub fn uri(&self) -> &str { match &self.source { ImageSource::Bytes(uri, _) | ImageSource::Uri(uri) => uri, @@ -256,6 +281,7 @@ impl<'a> Image<'a> { } } + #[inline] pub fn paint_at(&self, ui: &mut Ui, rect: Rect, texture: &SizedTexture) { paint_image_at(ui, rect, &self.image_options, texture); } diff --git a/crates/egui_demo_lib/src/color_test.rs b/crates/egui_demo_lib/src/color_test.rs index 4dc01cc50aa..ebbb3076ffc 100644 --- a/crates/egui_demo_lib/src/color_test.rs +++ b/crates/egui_demo_lib/src/color_test.rs @@ -88,7 +88,7 @@ impl ColorTest { let texel_offset = 0.5 / (g.0.len() as f32); let uv = Rect::from_min_max(pos2(texel_offset, 0.0), pos2(1.0 - texel_offset, 1.0)); ui.add( - RawImage::new((tex.id(), GRADIENT_SIZE)) + Image::from_texture((tex.id(), GRADIENT_SIZE)) .tint(vertex_color) .uv(uv), ) @@ -230,7 +230,7 @@ impl ColorTest { let texel_offset = 0.5 / (gradient.0.len() as f32); let uv = Rect::from_min_max(pos2(texel_offset, 0.0), pos2(1.0 - texel_offset, 1.0)); ui.add( - RawImage::new((tex.id(), GRADIENT_SIZE)) + Image::from_texture((tex.id(), GRADIENT_SIZE)) .bg_fill(bg_fill) .uv(uv), ) diff --git a/crates/egui_demo_lib/src/demo/widget_gallery.rs b/crates/egui_demo_lib/src/demo/widget_gallery.rs index 03d8fc35b92..8ea25ed8035 100644 --- a/crates/egui_demo_lib/src/demo/widget_gallery.rs +++ b/crates/egui_demo_lib/src/demo/widget_gallery.rs @@ -210,7 +210,7 @@ impl WidgetGallery { ui.add(doc_link_label("Image", "Image")); ui.allocate_ui(img_size * 2.0 + egui::Vec2::new(8.0, 0.0), |ui| { - ui.raw_image((texture.id(), img_size)); + ui.image((texture.id(), img_size)); ui.image(egui::include_image!("../../assets/icon.png")); }); ui.end_row(); diff --git a/crates/egui_extras/src/image.rs b/crates/egui_extras/src/image.rs index fcc30176de2..e1d0b330356 100644 --- a/crates/egui_extras/src/image.rs +++ b/crates/egui_extras/src/image.rs @@ -191,7 +191,7 @@ impl RetainedImage { // We need to convert the SVG to a texture to display it: // Future improvement: tell backend to do mip-mapping of the image to // make it look smoother when downsized. - ui.raw_image((self.texture_id(ui.ctx()), desired_size)) + ui.image((self.texture_id(ui.ctx()), desired_size)) } } diff --git a/crates/egui_plot/src/items/mod.rs b/crates/egui_plot/src/items/mod.rs index aefa6385e31..6d1e78795c5 100644 --- a/crates/egui_plot/src/items/mod.rs +++ b/crates/egui_plot/src/items/mod.rs @@ -1234,12 +1234,13 @@ impl PlotItem for PlotImage { Rect::from_two_pos(left_top_screen, right_bottom_screen) }; let screen_rotation = -*rotation as f32; - RawImage::new((*texture_id, image_screen_rect.size())) - .bg_fill(*bg_fill) - .tint(*tint) - .uv(*uv) - .rotate(screen_rotation, Vec2::splat(0.5)) - .paint_at(ui, image_screen_rect); + ui.add( + Image::from_texture((*texture_id, image_screen_rect.size())) + .bg_fill(*bg_fill) + .tint(*tint) + .uv(*uv) + .rotate(screen_rotation, Vec2::splat(0.5)), + ); if *highlight { let center = image_screen_rect.center(); let rotation = Rot2::from_angle(screen_rotation); diff --git a/examples/images/src/main.rs b/examples/images/src/main.rs index 193ab37ca48..656456cbb60 100644 --- a/examples/images/src/main.rs +++ b/examples/images/src/main.rs @@ -32,7 +32,7 @@ impl eframe::App for MyApp { .fit_to_fraction(vec2(1.0, 0.5)), ); ui.add( - egui::Image::new("https://picsum.photos/seed/1.759706314/1024".into()) + egui::Image::new("https://picsum.photos/seed/1.759706314/1024") .rounding(egui::Rounding::same(10.0)), ); }); diff --git a/examples/screenshot/src/main.rs b/examples/screenshot/src/main.rs index 9533a425a64..498d5013e0d 100644 --- a/examples/screenshot/src/main.rs +++ b/examples/screenshot/src/main.rs @@ -63,7 +63,7 @@ impl eframe::App for MyApp { }); if let Some(texture) = self.texture.as_ref() { - ui.raw_image((texture.id(), ui.available_size())); + ui.image((texture.id(), ui.available_size())); } else { ui.spinner(); } From 63d6d3170e02d039e7a26d2ca93fcb41db1b5d9d Mon Sep 17 00:00:00 2001 From: jprochazk <1665677+jprochazk@users.noreply.github.com> Date: Wed, 13 Sep 2023 10:31:17 +0200 Subject: [PATCH 07/32] remove last remanant of `RawImage` --- crates/egui/src/load.rs | 101 +---------------- crates/egui/src/load/bytes_loader.rs | 57 ++++++++++ crates/egui/src/load/texture_loader.rs | 60 ++++++++++ crates/egui/src/widgets/button.rs | 53 +++++---- crates/egui/src/widgets/image.rs | 111 ------------------- crates/egui/src/widgets/mod.rs | 2 +- crates/egui_demo_app/Cargo.toml | 1 + crates/egui_glium/examples/native_texture.rs | 3 +- 8 files changed, 158 insertions(+), 230 deletions(-) create mode 100644 crates/egui/src/load/bytes_loader.rs create mode 100644 crates/egui/src/load/texture_loader.rs diff --git a/crates/egui/src/load.rs b/crates/egui/src/load.rs index 2e1cc1a471a..50fde707a23 100644 --- a/crates/egui/src/load.rs +++ b/crates/egui/src/load.rs @@ -52,6 +52,11 @@ //! For example, a loader may determine that it doesn't support loading a specific URI //! if the protocol does not match what it expects. +mod bytes_loader; +mod texture_loader; + +use self::bytes_loader::DefaultBytesLoader; +use self::texture_loader::DefaultTextureLoader; use crate::Context; use ahash::HashMap; use epaint::mutex::Mutex; @@ -458,102 +463,6 @@ pub trait TextureLoader { fn byte_size(&self) -> usize; } -#[derive(Default)] -pub(crate) struct DefaultBytesLoader { - cache: Mutex, Bytes>>, -} - -impl DefaultBytesLoader { - pub(crate) fn insert(&self, uri: impl Into>, bytes: impl Into) { - self.cache - .lock() - .entry(uri.into()) - .or_insert_with(|| bytes.into()); - } -} - -impl BytesLoader for DefaultBytesLoader { - fn id(&self) -> &str { - generate_loader_id!(DefaultBytesLoader) - } - - fn load(&self, _: &Context, uri: &str) -> BytesLoadResult { - match self.cache.lock().get(uri).cloned() { - Some(bytes) => Ok(BytesPoll::Ready { - size: None, - bytes, - mime: None, - }), - None => Err(LoadError::NotSupported), - } - } - - 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(|bytes| bytes.len()).sum() - } -} - -#[derive(Default)] -struct DefaultTextureLoader { - cache: Mutex>, -} - -impl TextureLoader for DefaultTextureLoader { - fn id(&self) -> &str { - generate_loader_id!(DefaultTextureLoader) - } - - fn load( - &self, - ctx: &Context, - uri: &str, - texture_options: TextureOptions, - size_hint: SizeHint, - ) -> TextureLoadResult { - let mut cache = self.cache.lock(); - if let Some(handle) = cache.get(&(uri.into(), texture_options)) { - let texture = SizedTexture::from_handle(handle); - Ok(TexturePoll::Ready { texture }) - } else { - match ctx.try_load_image(uri, size_hint)? { - ImagePoll::Pending { size } => Ok(TexturePoll::Pending { size }), - ImagePoll::Ready { image } => { - let handle = ctx.load_texture(uri, image, texture_options); - let texture = SizedTexture::from_handle(&handle); - cache.insert((uri.into(), texture_options), handle); - Ok(TexturePoll::Ready { texture }) - } - } - } - } - - fn forget(&self, uri: &str) { - self.cache.lock().retain(|(u, _), _| u != uri); - } - - fn forget_all(&self) { - self.cache.lock().clear(); - } - - fn end_frame(&self, _: usize) {} - - fn byte_size(&self) -> usize { - self.cache - .lock() - .values() - .map(|texture| texture.byte_size()) - .sum() - } -} - type BytesLoaderImpl = Arc; type ImageLoaderImpl = Arc; type TextureLoaderImpl = Arc; diff --git a/crates/egui/src/load/bytes_loader.rs b/crates/egui/src/load/bytes_loader.rs new file mode 100644 index 00000000000..451ce150e28 --- /dev/null +++ b/crates/egui/src/load/bytes_loader.rs @@ -0,0 +1,57 @@ +use super::*; + +#[derive(Default)] +pub struct DefaultBytesLoader { + cache: Mutex, Bytes>>, +} + +impl DefaultBytesLoader { + pub fn insert(&self, uri: impl Into>, bytes: impl Into) { + self.cache + .lock() + .entry(uri.into()) + .or_insert_with_key(|uri| { + let bytes: Bytes = bytes.into(); + + #[cfg(feature = "log")] + log::trace!("loaded {} bytes for uri {uri:?}", bytes.len()); + + bytes + }); + } +} + +impl BytesLoader for DefaultBytesLoader { + fn id(&self) -> &str { + generate_loader_id!(DefaultBytesLoader) + } + + fn load(&self, _: &Context, uri: &str) -> BytesLoadResult { + match self.cache.lock().get(uri).cloned() { + Some(bytes) => Ok(BytesPoll::Ready { + size: None, + bytes, + mime: None, + }), + None => Err(LoadError::NotSupported), + } + } + + fn forget(&self, uri: &str) { + #[cfg(feature = "log")] + log::trace!("forget {uri:?}"); + + let _ = self.cache.lock().remove(uri); + } + + fn forget_all(&self) { + #[cfg(feature = "log")] + log::trace!("forget all"); + + self.cache.lock().clear(); + } + + fn byte_size(&self) -> usize { + self.cache.lock().values().map(|bytes| bytes.len()).sum() + } +} diff --git a/crates/egui/src/load/texture_loader.rs b/crates/egui/src/load/texture_loader.rs new file mode 100644 index 00000000000..89d616e4760 --- /dev/null +++ b/crates/egui/src/load/texture_loader.rs @@ -0,0 +1,60 @@ +use super::*; + +#[derive(Default)] +pub struct DefaultTextureLoader { + cache: Mutex>, +} + +impl TextureLoader for DefaultTextureLoader { + fn id(&self) -> &str { + crate::generate_loader_id!(DefaultTextureLoader) + } + + fn load( + &self, + ctx: &Context, + uri: &str, + texture_options: TextureOptions, + size_hint: SizeHint, + ) -> TextureLoadResult { + let mut cache = self.cache.lock(); + if let Some(handle) = cache.get(&(uri.into(), texture_options)) { + let texture = SizedTexture::from_handle(handle); + Ok(TexturePoll::Ready { texture }) + } else { + match ctx.try_load_image(uri, size_hint)? { + ImagePoll::Pending { size } => Ok(TexturePoll::Pending { size }), + ImagePoll::Ready { image } => { + let handle = ctx.load_texture(uri, image, texture_options); + let texture = SizedTexture::from_handle(&handle); + cache.insert((uri.into(), texture_options), handle); + Ok(TexturePoll::Ready { texture }) + } + } + } + } + + fn forget(&self, uri: &str) { + #[cfg(feature = "log")] + log::trace!("forget {uri:?}"); + + self.cache.lock().retain(|(u, _), _| u != uri); + } + + fn forget_all(&self) { + #[cfg(feature = "log")] + log::trace!("forget all"); + + self.cache.lock().clear(); + } + + fn end_frame(&self, _: usize) {} + + fn byte_size(&self) -> usize { + self.cache + .lock() + .values() + .map(|texture| texture.byte_size()) + .sum() + } +} diff --git a/crates/egui/src/widgets/button.rs b/crates/egui/src/widgets/button.rs index 3334c9c430d..9f973a556b2 100644 --- a/crates/egui/src/widgets/button.rs +++ b/crates/egui/src/widgets/button.rs @@ -21,7 +21,7 @@ use crate::*; /// # }); /// ``` #[must_use = "You should put this widget in an ui with `ui.add(widget);`"] -pub struct Button { +pub struct Button<'a> { text: WidgetText, shortcut_text: WidgetText, wrap: Option, @@ -34,10 +34,10 @@ pub struct Button { frame: Option, min_size: Vec2, rounding: Option, - image: Option, + image: Option>, } -impl Button { +impl<'a> Button<'a> { pub fn new(text: impl Into) -> Self { Self { text: text.into(), @@ -57,15 +57,11 @@ impl Button { /// Creates a button with an image to the left of the text. The size of the image as displayed is defined by the provided size. #[allow(clippy::needless_pass_by_value)] pub fn image_and_text( - texture_id: TextureId, - image_size: impl Into, + image_source: impl Into>, text: impl Into, ) -> Self { Self { - image: Some(widgets::RawImage::new(SizedTexture { - id: texture_id, - size: image_size.into(), - })), + image: Some(Image::new(image_source)), ..Self::new(text) } } @@ -142,7 +138,7 @@ impl Button { } } -impl Widget for Button { +impl Widget for Button<'_> { fn ui(self, ui: &mut Ui) -> Response { let Button { text, @@ -158,6 +154,11 @@ impl Widget for Button { image, } = self; + let image_size = image + .as_ref() + .and_then(|image| image.size()) + .unwrap_or(Vec2::splat(0.0)); + let frame = frame.unwrap_or_else(|| ui.visuals().button_frame); let mut button_padding = ui.spacing().button_padding; @@ -166,8 +167,8 @@ impl Widget for Button { } let mut text_wrap_width = ui.available_width() - 2.0 * button_padding.x; - if let Some(image) = &image { - text_wrap_width -= image.size().x + ui.spacing().icon_spacing; + if image.is_some() { + text_wrap_width -= image_size.x + ui.spacing().icon_spacing; } if !shortcut_text.is_empty() { text_wrap_width -= 60.0; // Some space for the shortcut text (which we never wrap). @@ -178,9 +179,9 @@ impl Widget for Button { .then(|| shortcut_text.into_galley(ui, Some(false), f32::INFINITY, TextStyle::Button)); let mut desired_size = text.size(); - if let Some(image) = &image { - desired_size.x += image.size().x + ui.spacing().icon_spacing; - desired_size.y = desired_size.y.max(image.size().y); + if image.is_some() { + desired_size.x += image_size.x + ui.spacing().icon_spacing; + desired_size.y = desired_size.y.max(image_size.y); } if let Some(shortcut_text) = &shortcut_text { desired_size.x += ui.spacing().item_spacing.x + shortcut_text.size().x; @@ -206,10 +207,10 @@ impl Widget for Button { .rect(rect.expand(visuals.expansion), rounding, fill, stroke); } - let text_pos = if let Some(image) = &image { + let text_pos = if image.is_some() { let icon_spacing = ui.spacing().icon_spacing; pos2( - rect.min.x + button_padding.x + image.size().x + icon_spacing, + rect.min.x + button_padding.x + image_size.x + icon_spacing, rect.center().y - 0.5 * text.size().y, ) } else { @@ -235,11 +236,23 @@ impl Widget for Button { let image_rect = Rect::from_min_size( pos2( rect.min.x + button_padding.x, - rect.center().y - 0.5 - (image.size().y / 2.0), + rect.center().y - 0.5 - (image_size.y / 2.0), ), - image.size(), + image_size, ); - image.paint_at(ui, image_rect); + match image.load(ui) { + Ok(TexturePoll::Ready { texture }) => { + image.paint_at(ui, image_rect, &texture); + } + Ok(TexturePoll::Pending { .. }) => { + ui.add(Spinner::new().size(image_rect.size().min_elem())) + .on_hover_text(format!("Loading {:?}…", image.uri())); + } + Err(err) => { + ui.colored_label(ui.visuals().error_fg_color, "⚠") + .on_hover_text(err.to_string()); + } + }; } } diff --git a/crates/egui/src/widgets/image.rs b/crates/egui/src/widgets/image.rs index 687a1e208bb..4b516e3c0bb 100644 --- a/crates/egui/src/widgets/image.rs +++ b/crates/egui/src/widgets/image.rs @@ -17,8 +17,6 @@ use epaint::{util::FloatOrd, RectShape}; /// - [`ImageSource::Bytes`] will also load the image using the [asynchronous loading process][`load`], but with lower latency. /// - [`ImageSource::Texture`] will use the provided texture. /// -/// To use a texture you already have with a simpler API, consider using [`RawImage`]. -/// /// See [`load`] for more information. #[must_use = "You should put this widget in an ui with `ui.add(widget);`"] #[derive(Debug, Clone)] @@ -477,8 +475,6 @@ pub enum ImageSource<'a> { /// /// The user is responsible for loading the texture, determining its size, /// and allocating a [`TextureId`] for it. - /// - /// Note that a simpler API for this exists in [`RawImage`]. Texture(SizedTexture), /// Load the image from some raw bytes. @@ -556,113 +552,6 @@ impl> From for ImageSource<'static> { } } -/// A widget which displays a sized texture. -#[must_use = "You should put this widget in an ui with `ui.add(widget);`"] -#[derive(Debug, Clone)] -pub struct RawImage { - texture: SizedTexture, - texture_options: TextureOptions, - image_options: ImageOptions, - sense: Sense, -} - -impl RawImage { - /// Load the image from some source. - pub fn new(texture: impl Into) -> Self { - Self { - texture: texture.into(), - texture_options: Default::default(), - image_options: Default::default(), - sense: Sense::hover(), - } - } - - /// Texture options used when creating the texture. - #[inline] - pub fn texture_options(mut self, texture_options: TextureOptions) -> Self { - self.texture_options = texture_options; - self - } - - /// Make the image respond to clicks and/or drags. - #[inline] - pub fn sense(mut self, sense: Sense) -> Self { - self.sense = sense; - self - } - - /// Select UV range. Default is (0,0) in top-left, (1,1) bottom right. - pub fn uv(mut self, uv: impl Into) -> Self { - self.image_options.uv = uv.into(); - self - } - - /// A solid color to put behind the image. Useful for transparent images. - pub fn bg_fill(mut self, bg_fill: impl Into) -> Self { - self.image_options.bg_fill = bg_fill.into(); - self - } - - /// Multiply image color with this. Default is WHITE (no tint). - pub fn tint(mut self, tint: impl Into) -> Self { - self.image_options.tint = tint.into(); - self - } - - /// Rotate the image about an origin by some angle - /// - /// Positive angle is clockwise. - /// Origin is a vector in normalized UV space ((0,0) in top-left, (1,1) bottom right). - /// - /// To rotate about the center you can pass `Vec2::splat(0.5)` as the origin. - /// - /// Due to limitations in the current implementation, - /// this will turn off rounding of the image. - pub fn rotate(mut self, angle: f32, origin: Vec2) -> Self { - self.image_options.rotation = Some((Rot2::from_angle(angle), origin)); - self.image_options.rounding = Rounding::ZERO; // incompatible with rotation - self - } - - /// Round the corners of the image. - /// - /// The default is no rounding ([`Rounding::ZERO`]). - /// - /// Due to limitations in the current implementation, - /// this will turn off any rotation of the image. - pub fn rounding(mut self, rounding: impl Into) -> Self { - self.image_options.rounding = rounding.into(); - if self.image_options.rounding != Rounding::ZERO { - self.image_options.rotation = None; // incompatible with rounding - } - self - } -} - -impl RawImage { - /// Returns the [`TextureId`] of the texture from which this image was created. - pub fn texture_id(&self) -> TextureId { - self.texture.id - } - - /// Returns the size of the texture from which this image was created. - pub fn size(&self) -> Vec2 { - self.texture.size - } - - pub fn paint_at(&self, ui: &mut Ui, rect: Rect) { - paint_image_at(ui, rect, &self.image_options, &self.texture); - } -} - -impl Widget for RawImage { - fn ui(self, ui: &mut Ui) -> Response { - let (rect, response) = ui.allocate_exact_size(self.size(), self.sense); - self.paint_at(ui, rect); - response - } -} - #[derive(Debug, Clone)] #[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))] pub struct ImageOptions { diff --git a/crates/egui/src/widgets/mod.rs b/crates/egui/src/widgets/mod.rs index 708fcd84304..875a8599c43 100644 --- a/crates/egui/src/widgets/mod.rs +++ b/crates/egui/src/widgets/mod.rs @@ -22,7 +22,7 @@ pub mod text_edit; pub use button::*; pub use drag_value::DragValue; pub use hyperlink::*; -pub use image::{Image, ImageFit, ImageOptions, ImageSize, ImageSource, RawImage}; +pub use image::{Image, ImageFit, ImageOptions, ImageSize, ImageSource}; pub use label::*; pub use progress_bar::ProgressBar; pub use selected_label::SelectableLabel; diff --git a/crates/egui_demo_app/Cargo.toml b/crates/egui_demo_app/Cargo.toml index 989539619a6..c3fec7453e3 100644 --- a/crates/egui_demo_app/Cargo.toml +++ b/crates/egui_demo_app/Cargo.toml @@ -36,6 +36,7 @@ chrono = { version = "0.4", default-features = false, features = [ eframe = { version = "0.22.0", path = "../eframe", default-features = false } egui = { version = "0.22.0", path = "../egui", features = [ "extra_debug_asserts", + "log", ] } egui_demo_lib = { version = "0.22.0", path = "../egui_demo_lib", features = [ "chrono", diff --git a/crates/egui_glium/examples/native_texture.rs b/crates/egui_glium/examples/native_texture.rs index 94977cd6176..eb5a956f464 100644 --- a/crates/egui_glium/examples/native_texture.rs +++ b/crates/egui_glium/examples/native_texture.rs @@ -30,8 +30,7 @@ fn main() { egui::SidePanel::left("my_side_panel").show(egui_ctx, |ui| { if ui .add(egui::Button::image_and_text( - texture_id, - button_image_size, + (texture_id, button_image_size), "Quit", )) .clicked() From b955a4fa5501643fd1d4c137f0de8b60fcb110cf Mon Sep 17 00:00:00 2001 From: jprochazk <1665677+jprochazk@users.noreply.github.com> Date: Wed, 13 Sep 2023 10:34:55 +0200 Subject: [PATCH 08/32] remove unused doc link --- crates/egui/src/ui.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/egui/src/ui.rs b/crates/egui/src/ui.rs index 362715a986c..afceeca7bfd 100644 --- a/crates/egui/src/ui.rs +++ b/crates/egui/src/ui.rs @@ -1581,7 +1581,7 @@ impl Ui { /// from a file with a statically known path, unless you really want to /// load it at runtime instead! /// - /// See also [`crate::Image`], [`crate::ImageSource`] and [`Self::raw_image`]. + /// See also [`crate::Image`], [`crate::ImageSource`]. #[inline] pub fn image<'a>(&mut self, source: impl Into>) -> Response { Image::new(source).ui(self) From 6589fdf965371a423885ef8bf04040d3c87f8f9d Mon Sep 17 00:00:00 2001 From: jprochazk <1665677+jprochazk@users.noreply.github.com> Date: Wed, 13 Sep 2023 10:45:52 +0200 Subject: [PATCH 09/32] add style option to disable image spinners --- crates/egui/src/style.rs | 8 +++++ crates/egui/src/widgets/image.rs | 52 ++++++++++++++++++++++++-------- 2 files changed, 47 insertions(+), 13 deletions(-) diff --git a/crates/egui/src/style.rs b/crates/egui/src/style.rs index e7a2c76e433..69185dbe916 100644 --- a/crates/egui/src/style.rs +++ b/crates/egui/src/style.rs @@ -207,6 +207,9 @@ pub struct Style { /// /// This only affects a few egui widgets. pub explanation_tooltips: bool, + + /// Show a spinner when loading an image. + pub image_loading_spinners: bool, } impl Style { @@ -738,6 +741,7 @@ impl Default for Style { animation_time: 1.0 / 12.0, debug: Default::default(), explanation_tooltips: false, + image_loading_spinners: true, } } } @@ -990,6 +994,7 @@ impl Style { animation_time, debug, explanation_tooltips, + image_loading_spinners, } = self; visuals.light_dark_radio_buttons(ui); @@ -1057,6 +1062,9 @@ impl Style { "Show explanatory text when hovering DragValue:s and other egui widgets", ); + ui.checkbox(image_loading_spinners, "Image loading spinners") + .on_hover_text("Show a spinner when an Image is loading"); + ui.vertical_centered(|ui| reset_button(ui, self)); } } diff --git a/crates/egui/src/widgets/image.rs b/crates/egui/src/widgets/image.rs index 4b516e3c0bb..f15b8056df3 100644 --- a/crates/egui/src/widgets/image.rs +++ b/crates/egui/src/widgets/image.rs @@ -26,6 +26,7 @@ pub struct Image<'a> { image_options: ImageOptions, sense: Sense, size: ImageSize, + show_spinner: Option, } impl<'a> Image<'a> { @@ -50,6 +51,7 @@ impl<'a> Image<'a> { image_options: Default::default(), sense: Sense::hover(), size, + show_spinner: None, } } @@ -221,6 +223,15 @@ impl<'a> Image<'a> { } self } + + /// Show a spinner when the image is loading. + /// + /// By default this uses the value of [`Style::image_loading_spinners`]. + #[inline] + pub fn show_spinner(mut self, show: bool) -> Self { + self.show_spinner = Some(show); + self + } } impl<'a> Image<'a> { @@ -294,21 +305,36 @@ impl<'a> Widget for Image<'a> { self.paint_at(ui, rect, &texture); response } - Ok(TexturePoll::Pending { size }) => match size { - Some(size) => { - let size = self.calculate_size(ui.available_size(), size); - ui.allocate_ui(size, |ui| { - ui.with_layout(Layout::centered_and_justified(Direction::TopDown), |ui| { - ui.spinner() - .on_hover_text(format!("Loading {:?}…", self.uri())) + Ok(TexturePoll::Pending { size }) => { + let spinner = |ui: &mut Ui| { + let show_spinner = self + .show_spinner + .unwrap_or(ui.style().image_loading_spinners); + if show_spinner { + ui.spinner() + .on_hover_text(format!("Loading {:?}…", self.uri())) + } else { + ui.allocate_response( + Vec2::splat(ui.style().spacing.interact_size.y), + Sense::hover(), + ) + } + }; + + match size { + Some(size) => { + let size = self.calculate_size(ui.available_size(), size); + ui.allocate_ui(size, |ui| { + ui.with_layout( + Layout::centered_and_justified(Direction::TopDown), + spinner, + ) }) - }) - .response + .response + } + None => spinner(ui), } - None => ui - .spinner() - .on_hover_text(format!("Loading {:?}…", self.uri())), - }, + } Err(err) => ui .colored_label(ui.visuals().error_fg_color, "⚠") .on_hover_text(err.to_string()), From cd64d2e42f4c1840b92bbb68da55bb60a7749c43 Mon Sep 17 00:00:00 2001 From: jprochazk <1665677+jprochazk@users.noreply.github.com> Date: Wed, 13 Sep 2023 11:01:35 +0200 Subject: [PATCH 10/32] use `Into` instead of `Into` to allow configuring the underlying image --- crates/egui/src/ui.rs | 4 ++-- crates/egui/src/widgets/button.rs | 11 ++++------- crates/egui/src/widgets/image.rs | 6 ++++++ 3 files changed, 12 insertions(+), 9 deletions(-) diff --git a/crates/egui/src/ui.rs b/crates/egui/src/ui.rs index afceeca7bfd..4886e2d7b45 100644 --- a/crates/egui/src/ui.rs +++ b/crates/egui/src/ui.rs @@ -2198,13 +2198,13 @@ impl Ui { #[inline] pub fn menu_image_button<'a, R>( &mut self, - image_source: impl Into>, + image: impl Into>, add_contents: impl FnOnce(&mut Ui) -> R, ) -> InnerResponse> { if let Some(menu_state) = self.menu_state.clone() { menu::submenu_button(self, menu_state, String::new(), add_contents) } else { - menu::menu_image_button(self, ImageButton::new(image_source), add_contents) + menu::menu_image_button(self, ImageButton::new(image), add_contents) } } } diff --git a/crates/egui/src/widgets/button.rs b/crates/egui/src/widgets/button.rs index 9f973a556b2..e53f85565c1 100644 --- a/crates/egui/src/widgets/button.rs +++ b/crates/egui/src/widgets/button.rs @@ -56,12 +56,9 @@ impl<'a> Button<'a> { /// Creates a button with an image to the left of the text. The size of the image as displayed is defined by the provided size. #[allow(clippy::needless_pass_by_value)] - pub fn image_and_text( - image_source: impl Into>, - text: impl Into, - ) -> Self { + pub fn image_and_text(image: impl Into>, text: impl Into) -> Self { Self { - image: Some(Image::new(image_source)), + image: Some(image.into()), ..Self::new(text) } } @@ -494,9 +491,9 @@ pub struct ImageButton<'a> { } impl<'a> ImageButton<'a> { - pub fn new(source: impl Into>) -> Self { + pub fn new(image: impl Into>) -> Self { Self { - image: Image::new(source), + image: image.into(), sense: Sense::click(), frame: true, selected: false, diff --git a/crates/egui/src/widgets/image.rs b/crates/egui/src/widgets/image.rs index f15b8056df3..96ae77c0c9d 100644 --- a/crates/egui/src/widgets/image.rs +++ b/crates/egui/src/widgets/image.rs @@ -234,6 +234,12 @@ impl<'a> Image<'a> { } } +impl<'a, T: Into>> From for Image<'a> { + fn from(value: T) -> Self { + Image::new(value) + } +} + impl<'a> Image<'a> { /// Returns the size the image will occupy in the final UI. #[inline] From d40834e3f30c58d1a8427a3b44c8cf3ce1c77aeb Mon Sep 17 00:00:00 2001 From: jprochazk <1665677+jprochazk@users.noreply.github.com> Date: Wed, 13 Sep 2023 11:39:53 +0200 Subject: [PATCH 11/32] propagate `image_options` through `ImageButton` --- crates/egui/src/widgets/button.rs | 6 +++--- crates/egui/src/widgets/image.rs | 5 +++++ 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/crates/egui/src/widgets/button.rs b/crates/egui/src/widgets/button.rs index e53f85565c1..5a67f11e6ea 100644 --- a/crates/egui/src/widgets/button.rs +++ b/crates/egui/src/widgets/button.rs @@ -574,9 +574,9 @@ impl<'a> ImageButton<'a> { .align_size_within_rect(texture.size, rect.shrink2(padding)); // let image_rect = image_rect.expand2(expansion); // can make it blurry, so let's not let image_options = ImageOptions { - rounding, - ..Default::default() - }; // apply rounding to the image + rounding, // apply rounding to the image + ..self.image.image_options().clone() + }; crate::widgets::image::paint_image_at(ui, image_rect, &image_options, texture); // Draw frame outline: diff --git a/crates/egui/src/widgets/image.rs b/crates/egui/src/widgets/image.rs index 96ae77c0c9d..075c75a322c 100644 --- a/crates/egui/src/widgets/image.rs +++ b/crates/egui/src/widgets/image.rs @@ -255,6 +255,11 @@ impl<'a> Image<'a> { } } + #[inline] + pub fn image_options(&self) -> &ImageOptions { + &self.image_options + } + #[inline] pub fn source(&self) -> &ImageSource<'a> { &self.source From 6d09e81f73cc0c3c8b0a233df562d4d42a61f7e3 Mon Sep 17 00:00:00 2001 From: jprochazk <1665677+jprochazk@users.noreply.github.com> Date: Wed, 13 Sep 2023 11:44:16 +0200 Subject: [PATCH 12/32] calculate image size properly in `Button` --- crates/egui/src/widgets/button.rs | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/crates/egui/src/widgets/button.rs b/crates/egui/src/widgets/button.rs index 5a67f11e6ea..dbe7186458c 100644 --- a/crates/egui/src/widgets/button.rs +++ b/crates/egui/src/widgets/button.rs @@ -151,10 +151,15 @@ impl Widget for Button<'_> { image, } = self; - let image_size = image + let image_size = match image .as_ref() - .and_then(|image| image.size()) - .unwrap_or(Vec2::splat(0.0)); + .and_then(|image| Some((image, image.load(ui).ok()?))) + { + Some((image, TexturePoll::Ready { texture })) => { + image.calculate_size(ui.available_size(), texture.size) + } + _ => Vec2::splat(0.0), + }; let frame = frame.unwrap_or_else(|| ui.visuals().button_frame); From c273826a22fc422fc262eb24a8da4d018771ae7f Mon Sep 17 00:00:00 2001 From: jprochazk <1665677+jprochazk@users.noreply.github.com> Date: Wed, 13 Sep 2023 11:54:52 +0200 Subject: [PATCH 13/32] properly calculate size in `ImageButton` --- crates/egui/src/widgets/button.rs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/crates/egui/src/widgets/button.rs b/crates/egui/src/widgets/button.rs index dbe7186458c..dafcd3de1af 100644 --- a/crates/egui/src/widgets/button.rs +++ b/crates/egui/src/widgets/button.rs @@ -596,7 +596,10 @@ impl<'a> ImageButton<'a> { impl<'a> Widget for ImageButton<'a> { fn ui(self, ui: &mut Ui) -> Response { match self.image.load(ui) { - Ok(TexturePoll::Ready { texture }) => self.show(ui, &texture), + Ok(TexturePoll::Ready { mut texture }) => { + texture.size = self.image.calculate_size(ui.available_size(), texture.size); + self.show(ui, &texture) + } Ok(TexturePoll::Pending { .. }) => ui .spinner() .on_hover_text(format!("Loading {:?}…", self.image.uri())), From 238584cd707b868e741ad6417a0c7b99b89f8530 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20Proch=C3=A1zka?= <1665677+jprochazk@users.noreply.github.com> Date: Wed, 13 Sep 2023 13:09:34 +0200 Subject: [PATCH 14/32] Update crates/egui/src/widgets/image.rs Co-authored-by: Emil Ernerfeldt --- crates/egui/src/widgets/image.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/egui/src/widgets/image.rs b/crates/egui/src/widgets/image.rs index 075c75a322c..7161370f324 100644 --- a/crates/egui/src/widgets/image.rs +++ b/crates/egui/src/widgets/image.rs @@ -37,7 +37,7 @@ impl<'a> Image<'a> { // User is probably expecting their texture to have // the exact size of the provided `SizedTexture`. ImageSize { - maintain_aspect_ratio: false, + maintain_aspect_ratio: true, max_size: None, fit: ImageFit::Exact(tex.size), } From a6774f70c07a612f8aedf9f2f6c33d1fdae950d8 Mon Sep 17 00:00:00 2001 From: jprochazk <1665677+jprochazk@users.noreply.github.com> Date: Wed, 13 Sep 2023 13:16:11 +0200 Subject: [PATCH 15/32] improve no image loaders error message --- crates/egui/src/load.rs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/crates/egui/src/load.rs b/crates/egui/src/load.rs index 50fde707a23..2b420bedae3 100644 --- a/crates/egui/src/load.rs +++ b/crates/egui/src/load.rs @@ -85,7 +85,9 @@ pub enum LoadError { impl Display for LoadError { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { - LoadError::NoImageLoaders => f.write_str("no image loaders are installed"), + LoadError::NoImageLoaders => f.write_str( + "No image loaders are installed. If you're trying to load some images \ + for the first time, follow the steps outlined in https://docs.rs/egui/latest/egui/load/index.html"), LoadError::NotSupported => f.write_str("not supported"), LoadError::Custom(message) => f.write_str(message), } From 9071a6504ef00443e3c95233c5b4af72395d1b9b Mon Sep 17 00:00:00 2001 From: jprochazk <1665677+jprochazk@users.noreply.github.com> Date: Wed, 13 Sep 2023 13:19:59 +0200 Subject: [PATCH 16/32] add `size()` helper to `TexturePoll` --- crates/egui/src/load.rs | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/crates/egui/src/load.rs b/crates/egui/src/load.rs index 2b420bedae3..2bd6c63225b 100644 --- a/crates/egui/src/load.rs +++ b/crates/egui/src/load.rs @@ -354,7 +354,7 @@ pub trait ImageLoader { } /// A texture with a known size. -#[derive(Clone, Debug, PartialEq, Eq)] +#[derive(Clone, Copy, Debug, PartialEq, Eq)] pub struct SizedTexture { pub id: TextureId, pub size: Vec2, @@ -397,7 +397,7 @@ impl<'a> From<&'a TextureHandle> for SizedTexture { /// This is similar to [`std::task::Poll`], but the `Pending` variant /// contains an optional `size`, which may be used during layout to /// pre-allocate space the image. -#[derive(Clone)] +#[derive(Clone, Copy)] pub enum TexturePoll { /// Texture is loading. Pending { @@ -409,6 +409,15 @@ pub enum TexturePoll { Ready { texture: SizedTexture }, } +impl TexturePoll { + pub fn size(self) -> Option { + match self { + TexturePoll::Pending { size } => size, + TexturePoll::Ready { texture } => Some(texture.size), + } + } +} + pub type TextureLoadResult = Result; /// Represents a loader capable of loading a full texture. From 8e5e1e05f5e139042a4006c5b09cfe8eab2a6e53 Mon Sep 17 00:00:00 2001 From: jprochazk <1665677+jprochazk@users.noreply.github.com> Date: Wed, 13 Sep 2023 13:20:15 +0200 Subject: [PATCH 17/32] try get size from poll in `Button` --- crates/egui/src/widgets/button.rs | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/crates/egui/src/widgets/button.rs b/crates/egui/src/widgets/button.rs index dafcd3de1af..00a443ed5bb 100644 --- a/crates/egui/src/widgets/button.rs +++ b/crates/egui/src/widgets/button.rs @@ -153,11 +153,9 @@ impl Widget for Button<'_> { let image_size = match image .as_ref() - .and_then(|image| Some((image, image.load(ui).ok()?))) + .and_then(|image| Some((image, image.load(ui).ok()?.size()?))) { - Some((image, TexturePoll::Ready { texture })) => { - image.calculate_size(ui.available_size(), texture.size) - } + Some((image, image_size)) => image.calculate_size(ui.available_size(), image_size), _ => Vec2::splat(0.0), }; From dfe266b736ade980640cb0c08da78c8b7f81919b Mon Sep 17 00:00:00 2001 From: jprochazk <1665677+jprochazk@users.noreply.github.com> Date: Wed, 13 Sep 2023 13:21:50 +0200 Subject: [PATCH 18/32] add `paint_at` to `Spinner` --- crates/egui/src/widgets/spinner.rs | 27 +++++++++++++++------------ 1 file changed, 15 insertions(+), 12 deletions(-) diff --git a/crates/egui/src/widgets/spinner.rs b/crates/egui/src/widgets/spinner.rs index f2b253bc6a6..75b1edf3bde 100644 --- a/crates/egui/src/widgets/spinner.rs +++ b/crates/egui/src/widgets/spinner.rs @@ -1,4 +1,4 @@ -use epaint::{emath::lerp, vec2, Color32, Pos2, Shape, Stroke}; +use epaint::{emath::lerp, vec2, Color32, Pos2, Rect, Shape, Stroke}; use crate::{Response, Sense, Ui, Widget}; @@ -31,21 +31,14 @@ impl Spinner { self.color = Some(color.into()); self } -} - -impl Widget for Spinner { - fn ui(self, ui: &mut Ui) -> Response { - let size = self - .size - .unwrap_or_else(|| ui.style().spacing.interact_size.y); - let color = self - .color - .unwrap_or_else(|| ui.visuals().strong_text_color()); - let (rect, response) = ui.allocate_exact_size(vec2(size, size), Sense::hover()); + pub fn paint_at(&self, ui: &mut Ui, rect: Rect) { if ui.is_rect_visible(rect) { ui.ctx().request_repaint(); + let color = self + .color + .unwrap_or_else(|| ui.visuals().strong_text_color()); let radius = (rect.height() / 2.0) - 2.0; let n_points = 20; let time = ui.input(|i| i.time); @@ -61,6 +54,16 @@ impl Widget for Spinner { ui.painter() .add(Shape::line(points, Stroke::new(3.0, color))); } + } +} + +impl Widget for Spinner { + fn ui(self, ui: &mut Ui) -> Response { + let size = self + .size + .unwrap_or_else(|| ui.style().spacing.interact_size.y); + let (rect, response) = ui.allocate_exact_size(vec2(size, size), Sense::hover()); + self.paint_at(ui, rect); response } From 35576c0ad5023ba0e76b82cbf8bc56758014003d Mon Sep 17 00:00:00 2001 From: jprochazk <1665677+jprochazk@users.noreply.github.com> Date: Wed, 13 Sep 2023 13:28:32 +0200 Subject: [PATCH 19/32] use `Spinner::paint_at` and hover on image button response --- crates/egui/src/widgets/button.rs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/crates/egui/src/widgets/button.rs b/crates/egui/src/widgets/button.rs index 00a443ed5bb..e26945ab24a 100644 --- a/crates/egui/src/widgets/button.rs +++ b/crates/egui/src/widgets/button.rs @@ -193,7 +193,7 @@ impl Widget for Button<'_> { } desired_size = desired_size.at_least(min_size); - let (rect, response) = ui.allocate_at_least(desired_size, sense); + let (rect, mut response) = ui.allocate_at_least(desired_size, sense); response.widget_info(|| WidgetInfo::labeled(WidgetType::Button, text.text())); if ui.is_rect_visible(rect) { @@ -245,12 +245,12 @@ impl Widget for Button<'_> { image.paint_at(ui, image_rect, &texture); } Ok(TexturePoll::Pending { .. }) => { - ui.add(Spinner::new().size(image_rect.size().min_elem())) - .on_hover_text(format!("Loading {:?}…", image.uri())); + Spinner::new().paint_at(ui, image_rect); + response = response.on_hover_text(format!("Loading {:?}…", image.uri())); } Err(err) => { - ui.colored_label(ui.visuals().error_fg_color, "⚠") - .on_hover_text(err.to_string()); + ui.colored_label(ui.visuals().error_fg_color, "⚠"); + response = response.on_hover_text(err.to_string()); } }; } From 4daa2568f3d0f6f5355321fa7aebe4436ef68af4 Mon Sep 17 00:00:00 2001 From: jprochazk <1665677+jprochazk@users.noreply.github.com> Date: Wed, 13 Sep 2023 13:29:14 +0200 Subject: [PATCH 20/32] `show_spinner` -> `show_loading_spinner` --- crates/egui/src/widgets/image.rs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/crates/egui/src/widgets/image.rs b/crates/egui/src/widgets/image.rs index 7161370f324..1c018f75e40 100644 --- a/crates/egui/src/widgets/image.rs +++ b/crates/egui/src/widgets/image.rs @@ -26,7 +26,7 @@ pub struct Image<'a> { image_options: ImageOptions, sense: Sense, size: ImageSize, - show_spinner: Option, + show_loading_spinner: Option, } impl<'a> Image<'a> { @@ -51,7 +51,7 @@ impl<'a> Image<'a> { image_options: Default::default(), sense: Sense::hover(), size, - show_spinner: None, + show_loading_spinner: None, } } @@ -228,8 +228,8 @@ impl<'a> Image<'a> { /// /// By default this uses the value of [`Style::image_loading_spinners`]. #[inline] - pub fn show_spinner(mut self, show: bool) -> Self { - self.show_spinner = Some(show); + pub fn show_loading_spinner(mut self, show: bool) -> Self { + self.show_loading_spinner = Some(show); self } } @@ -319,7 +319,7 @@ impl<'a> Widget for Image<'a> { Ok(TexturePoll::Pending { size }) => { let spinner = |ui: &mut Ui| { let show_spinner = self - .show_spinner + .show_loading_spinner .unwrap_or(ui.style().image_loading_spinners); if show_spinner { ui.spinner() From 87f2411be771b126415b497e2b24225c3c3b809c Mon Sep 17 00:00:00 2001 From: jprochazk <1665677+jprochazk@users.noreply.github.com> Date: Wed, 13 Sep 2023 13:33:07 +0200 Subject: [PATCH 21/32] avoid `allocate_ui` in `Image` when painting spinner --- crates/egui/src/widgets/image.rs | 35 ++++++++------------------------ 1 file changed, 8 insertions(+), 27 deletions(-) diff --git a/crates/egui/src/widgets/image.rs b/crates/egui/src/widgets/image.rs index 1c018f75e40..079db13cefa 100644 --- a/crates/egui/src/widgets/image.rs +++ b/crates/egui/src/widgets/image.rs @@ -317,34 +317,15 @@ impl<'a> Widget for Image<'a> { response } Ok(TexturePoll::Pending { size }) => { - let spinner = |ui: &mut Ui| { - let show_spinner = self - .show_loading_spinner - .unwrap_or(ui.style().image_loading_spinners); - if show_spinner { - ui.spinner() - .on_hover_text(format!("Loading {:?}…", self.uri())) - } else { - ui.allocate_response( - Vec2::splat(ui.style().spacing.interact_size.y), - Sense::hover(), - ) - } - }; - - match size { - Some(size) => { - let size = self.calculate_size(ui.available_size(), size); - ui.allocate_ui(size, |ui| { - ui.with_layout( - Layout::centered_and_justified(Direction::TopDown), - spinner, - ) - }) - .response - } - None => spinner(ui), + let size = size.unwrap_or_else(|| Vec2::splat(ui.style().spacing.interact_size.y)); + let response = ui.allocate_response(size, Sense::hover()); + let show_spinner = self + .show_loading_spinner + .unwrap_or(ui.style().image_loading_spinners); + if show_spinner { + Spinner::new().paint_at(ui, response.rect); } + response.on_hover_text(format!("Loading {:?}…", self.uri())) } Err(err) => ui .colored_label(ui.visuals().error_fg_color, "⚠") From c2cd018e1269ded1ae86c94ce84ee1c4abae6755 Mon Sep 17 00:00:00 2001 From: jprochazk <1665677+jprochazk@users.noreply.github.com> Date: Wed, 13 Sep 2023 13:36:42 +0200 Subject: [PATCH 22/32] make icon smaller + remove old texture --- crates/egui_demo_lib/assets/icon.png | Bin 17166 -> 2642 bytes .../egui_demo_lib/src/demo/widget_gallery.rs | 6 ++---- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/crates/egui_demo_lib/assets/icon.png b/crates/egui_demo_lib/assets/icon.png index cf1e6c3ebdb52209943a9affac3b463b2c85e717..87f15e746e42df190f84317c76a22f036c50a234 100644 GIT binary patch literal 2642 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 MVrYS>(szsd4{n6D`v3p{ literal 17166 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 Date: Wed, 13 Sep 2023 14:00:40 +0200 Subject: [PATCH 23/32] add `load_and_calculate_size` + expose `paint_image_at` --- crates/egui/src/widgets/button.rs | 9 +++------ crates/egui/src/widgets/image.rs | 5 +++++ crates/egui/src/widgets/mod.rs | 2 +- 3 files changed, 9 insertions(+), 7 deletions(-) diff --git a/crates/egui/src/widgets/button.rs b/crates/egui/src/widgets/button.rs index e26945ab24a..ef029078330 100644 --- a/crates/egui/src/widgets/button.rs +++ b/crates/egui/src/widgets/button.rs @@ -151,13 +151,10 @@ impl Widget for Button<'_> { image, } = self; - let image_size = match image + let image_size = image .as_ref() - .and_then(|image| Some((image, image.load(ui).ok()?.size()?))) - { - Some((image, image_size)) => image.calculate_size(ui.available_size(), image_size), - _ => Vec2::splat(0.0), - }; + .and_then(|image| image.load_and_calculate_size(ui, ui.available_size())) + .unwrap_or(Vec2::ZERO); let frame = frame.unwrap_or_else(|| ui.visuals().button_frame); diff --git a/crates/egui/src/widgets/image.rs b/crates/egui/src/widgets/image.rs index 079db13cefa..1f423c925b0 100644 --- a/crates/egui/src/widgets/image.rs +++ b/crates/egui/src/widgets/image.rs @@ -247,6 +247,11 @@ impl<'a> Image<'a> { self.size.get(available_size, image_size) } + pub fn load_and_calculate_size(&self, ui: &mut Ui, available_size: Vec2) -> Option { + let image_size = self.load(ui).ok()?.size()?; + Some(self.size.get(available_size, image_size)) + } + #[inline] pub fn size(&self) -> Option { match &self.source { diff --git a/crates/egui/src/widgets/mod.rs b/crates/egui/src/widgets/mod.rs index 875a8599c43..eff7a2515a5 100644 --- a/crates/egui/src/widgets/mod.rs +++ b/crates/egui/src/widgets/mod.rs @@ -22,7 +22,7 @@ pub mod text_edit; pub use button::*; pub use drag_value::DragValue; pub use hyperlink::*; -pub use image::{Image, ImageFit, ImageOptions, ImageSize, ImageSource}; +pub use image::{paint_image_at, Image, ImageFit, ImageOptions, ImageSize, ImageSource}; pub use label::*; pub use progress_bar::ProgressBar; pub use selected_label::SelectableLabel; From 5ee02a47ee1d1952b961403fd1e11cd929719817 Mon Sep 17 00:00:00 2001 From: jprochazk <1665677+jprochazk@users.noreply.github.com> Date: Wed, 13 Sep 2023 14:03:09 +0200 Subject: [PATCH 24/32] update `egui_plot` to paint image in the right place --- crates/egui_plot/src/items/mod.rs | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/crates/egui_plot/src/items/mod.rs b/crates/egui_plot/src/items/mod.rs index 6d1e78795c5..7e71de92b40 100644 --- a/crates/egui_plot/src/items/mod.rs +++ b/crates/egui_plot/src/items/mod.rs @@ -1234,12 +1234,18 @@ impl PlotItem for PlotImage { Rect::from_two_pos(left_top_screen, right_bottom_screen) }; let screen_rotation = -*rotation as f32; - ui.add( - Image::from_texture((*texture_id, image_screen_rect.size())) - .bg_fill(*bg_fill) - .tint(*tint) - .uv(*uv) - .rotate(screen_rotation, Vec2::splat(0.5)), + + egui::paint_image_at( + ui, + image_screen_rect, + &ImageOptions { + uv: *uv, + bg_fill: *bg_fill, + tint: *tint, + rotation: Some((Rot2::from_angle(screen_rotation), Vec2::splat(0.5))), + rounding: Rounding::ZERO, + }, + &(*texture_id, image_screen_rect.size()).into(), ); if *highlight { let center = image_screen_rect.center(); From c6c0f27becd6fd6f1e9588fa6263670980e30f4b Mon Sep 17 00:00:00 2001 From: Emil Ernerfeldt Date: Wed, 13 Sep 2023 15:32:52 +0200 Subject: [PATCH 25/32] Add helpers for painting an ImageSource directly --- crates/egui/src/widgets/button.rs | 26 +++--- crates/egui/src/widgets/image.rs | 142 +++++++++++++++++++++-------- crates/egui/src/widgets/spinner.rs | 2 +- 3 files changed, 118 insertions(+), 52 deletions(-) diff --git a/crates/egui/src/widgets/button.rs b/crates/egui/src/widgets/button.rs index ef029078330..6c834fd8a35 100644 --- a/crates/egui/src/widgets/button.rs +++ b/crates/egui/src/widgets/button.rs @@ -237,19 +237,19 @@ impl Widget for Button<'_> { ), image_size, ); - match image.load(ui) { - Ok(TexturePoll::Ready { texture }) => { - image.paint_at(ui, image_rect, &texture); - } - Ok(TexturePoll::Pending { .. }) => { - Spinner::new().paint_at(ui, image_rect); - response = response.on_hover_text(format!("Loading {:?}…", image.uri())); - } - Err(err) => { - ui.colored_label(ui.visuals().error_fg_color, "⚠"); - response = response.on_hover_text(err.to_string()); - } - }; + let tlr = image.load(ui); + let show_loading_spinner = image + .show_loading_spinner + .unwrap_or(ui.style().image_loading_spinners); + widgets::image::paint_texture_load_result( + ui, + &tlr, + image_rect, + show_loading_spinner, + image.image_options(), + ); + response = + widgets::image::texture_load_result_response(image.source(), &tlr, response); } } diff --git a/crates/egui/src/widgets/image.rs b/crates/egui/src/widgets/image.rs index 1f423c925b0..e34fb35aa34 100644 --- a/crates/egui/src/widgets/image.rs +++ b/crates/egui/src/widgets/image.rs @@ -26,7 +26,7 @@ pub struct Image<'a> { image_options: ImageOptions, sense: Sense, size: ImageSize, - show_loading_spinner: Option, + pub(crate) show_loading_spinner: Option, } impl<'a> Image<'a> { @@ -275,11 +275,7 @@ impl<'a> Image<'a> { /// This will return `` for [`ImageSource::Texture`]. #[inline] pub fn uri(&self) -> &str { - match &self.source { - ImageSource::Bytes(uri, _) | ImageSource::Uri(uri) => uri, - // Note: texture source is never in "loading" state - ImageSource::Texture(_) => "", - } + self.source.uri().unwrap_or("") } /// Load the image from its [`Image::source`], returning the resulting [`SizedTexture`]. @@ -288,22 +284,10 @@ impl<'a> Image<'a> { /// /// May fail if they underlying [`Context::try_load_texture`] call fails. pub fn load(&self, ui: &Ui) -> TextureLoadResult { - match self.source.clone() { - ImageSource::Texture(texture) => Ok(TexturePoll::Ready { texture }), - ImageSource::Uri(uri) => ui.ctx().try_load_texture( - uri.as_ref(), - self.texture_options, - self.size.hint(ui.available_size()), - ), - ImageSource::Bytes(uri, bytes) => { - ui.ctx().include_bytes(uri.clone(), bytes); - ui.ctx().try_load_texture( - uri.as_ref(), - self.texture_options, - self.size.hint(ui.available_size()), - ) - } - } + let size_hint = self.size.hint(ui.available_size()); + self.source + .clone() + .load(ui.ctx(), self.texture_options, size_hint) } #[inline] @@ -315,22 +299,27 @@ impl<'a> Image<'a> { impl<'a> Widget for Image<'a> { fn ui(self, ui: &mut Ui) -> Response { match self.load(ui) { - Ok(TexturePoll::Ready { texture }) => { - let size = self.calculate_size(ui.available_size(), texture.size); - let (rect, response) = ui.allocate_exact_size(size, self.sense); - self.paint_at(ui, rect, &texture); - response - } - Ok(TexturePoll::Pending { size }) => { - let size = size.unwrap_or_else(|| Vec2::splat(ui.style().spacing.interact_size.y)); - let response = ui.allocate_response(size, Sense::hover()); - let show_spinner = self - .show_loading_spinner - .unwrap_or(ui.style().image_loading_spinners); - if show_spinner { - Spinner::new().paint_at(ui, response.rect); + Ok(texture_poll) => { + let texture_size = texture_poll.size(); + let texture_size = + texture_size.unwrap_or_else(|| Vec2::splat(ui.style().spacing.interact_size.y)); + let ui_size = self.calculate_size(ui.available_size(), texture_size); + let (rect, response) = ui.allocate_exact_size(ui_size, self.sense); + match texture_poll { + TexturePoll::Ready { texture } => { + self.paint_at(ui, rect, &texture); + response + } + TexturePoll::Pending { .. } => { + let show_spinner = self + .show_loading_spinner + .unwrap_or(ui.style().image_loading_spinners); + if show_spinner { + Spinner::new().paint_at(ui, response.rect); + } + response.on_hover_text(format!("Loading {:?}…", self.uri())) + } } - response.on_hover_text(format!("Loading {:?}…", self.uri())) } Err(err) => ui .colored_label(ui.visuals().error_fg_color, "⚠") @@ -514,6 +503,83 @@ pub enum ImageSource<'a> { Bytes(Cow<'static, str>, Bytes), } +impl<'a> ImageSource<'a> { + /// # Errors + /// Failure to load the texture. + pub fn load( + self, + ctx: &Context, + texture_options: TextureOptions, + size_hint: SizeHint, + ) -> TextureLoadResult { + match self { + Self::Texture(texture) => Ok(TexturePoll::Ready { texture }), + Self::Uri(uri) => ctx.try_load_texture(uri.as_ref(), texture_options, size_hint), + Self::Bytes(uri, bytes) => { + ctx.include_bytes(uri.clone(), bytes); + ctx.try_load_texture(uri.as_ref(), texture_options, size_hint) + } + } + } + + /// Get the `uri` that this image was constructed from. + /// + /// This will return `None` for [`Self::Texture`]. + pub fn uri(&self) -> Option<&str> { + match self { + ImageSource::Bytes(uri, _) | ImageSource::Uri(uri) => Some(uri), + ImageSource::Texture(_) => None, + } + } +} + +pub fn paint_texture_load_result( + ui: &Ui, + tlr: &TextureLoadResult, + rect: Rect, + show_loading_spinner: bool, + options: &ImageOptions, +) { + match tlr { + Ok(TexturePoll::Ready { texture }) => { + paint_image_at(ui, rect, options, texture); + } + Ok(TexturePoll::Pending { .. }) => { + if show_loading_spinner { + Spinner::new().paint_at(ui, rect); + } + } + Err(_) => { + let font_id = TextStyle::Body.resolve(ui.style()); + ui.painter().text( + rect.center(), + Align2::CENTER_CENTER, + "⚠", + font_id, + ui.visuals().error_fg_color, + ); + } + } +} + +pub fn texture_load_result_response( + source: &ImageSource<'_>, + tlr: &TextureLoadResult, + response: Response, +) -> Response { + match tlr { + Ok(TexturePoll::Ready { .. }) => response, + Ok(TexturePoll::Pending { .. }) => { + if let Some(uri) = source.uri() { + response.on_hover_text(format!("Loading {uri}…")) + } else { + response.on_hover_text("Loading image…") + } + } + Err(err) => response.on_hover_text(err.to_string()), + } +} + impl<'a> From<&'a str> for ImageSource<'a> { #[inline] fn from(value: &'a str) -> Self { @@ -620,7 +686,7 @@ impl Default for ImageOptions { } /// Paint a `SizedTexture` as an image according to some `ImageOptions` at a given `rect`. -pub fn paint_image_at(ui: &mut Ui, rect: Rect, options: &ImageOptions, texture: &SizedTexture) { +pub fn paint_image_at(ui: &Ui, rect: Rect, options: &ImageOptions, texture: &SizedTexture) { if !ui.is_rect_visible(rect) { return; } diff --git a/crates/egui/src/widgets/spinner.rs b/crates/egui/src/widgets/spinner.rs index 75b1edf3bde..688c9759eda 100644 --- a/crates/egui/src/widgets/spinner.rs +++ b/crates/egui/src/widgets/spinner.rs @@ -32,7 +32,7 @@ impl Spinner { self } - pub fn paint_at(&self, ui: &mut Ui, rect: Rect) { + pub fn paint_at(&self, ui: &Ui, rect: Rect) { if ui.is_rect_visible(rect) { ui.ctx().request_repaint(); From f6df497006b3b0c615f942aaa37073fc4c37ce55 Mon Sep 17 00:00:00 2001 From: Emil Ernerfeldt Date: Wed, 13 Sep 2023 16:01:06 +0200 Subject: [PATCH 26/32] Use max_size=INF as default --- crates/egui/src/widgets/image.rs | 70 ++++++++----------- crates/egui_demo_app/src/apps/image_viewer.rs | 21 ++---- 2 files changed, 34 insertions(+), 57 deletions(-) diff --git a/crates/egui/src/widgets/image.rs b/crates/egui/src/widgets/image.rs index e34fb35aa34..8c684452d8d 100644 --- a/crates/egui/src/widgets/image.rs +++ b/crates/egui/src/widgets/image.rs @@ -38,7 +38,7 @@ impl<'a> Image<'a> { // the exact size of the provided `SizedTexture`. ImageSize { maintain_aspect_ratio: true, - max_size: None, + max_size: Vec2::INFINITY, fit: ImageFit::Exact(tex.size), } } else { @@ -91,10 +91,7 @@ impl<'a> Image<'a> { /// No matter what the image is scaled to, it will never exceed this limit. #[inline] pub fn max_width(mut self, width: f32) -> Self { - match self.size.max_size.as_mut() { - Some(max_size) => max_size.x = width, - None => self.size.max_size = Some(Vec2::new(width, f32::INFINITY)), - } + self.size.max_size.x = width; self } @@ -103,10 +100,7 @@ impl<'a> Image<'a> { /// No matter what the image is scaled to, it will never exceed this limit. #[inline] pub fn max_height(mut self, height: f32) -> Self { - match self.size.max_size.as_mut() { - Some(max_size) => max_size.y = height, - None => self.size.max_size = Some(Vec2::new(f32::INFINITY, height)), - } + self.size.max_size.y = height; self } @@ -114,7 +108,7 @@ impl<'a> Image<'a> { /// /// No matter what the image is scaled to, it will never exceed this limit. #[inline] - pub fn max_size(mut self, size: Option) -> Self { + pub fn max_size(mut self, size: Vec2) -> Self { self.size.max_size = size; self } @@ -342,7 +336,7 @@ pub struct ImageSize { /// Determines the maximum size of the image. /// /// Defaults to `None` - pub max_size: Option, + pub max_size: Vec2, /// Determines how the image should shrink/expand/stretch/etc. to fit within its allocated space. /// @@ -368,6 +362,16 @@ pub enum ImageFit { Exact(Vec2), } +impl ImageFit { + pub fn resolve(self, available_size: Vec2, image_size: Vec2) -> Vec2 { + match self { + ImageFit::Original(scale) => image_size * scale.unwrap_or(1.0), + ImageFit::Fraction(fract) => available_size * fract, + ImageFit::Exact(size) => size, + } + } +} + impl ImageSize { fn hint(&self, available_size: Vec2) -> SizeHint { if self.maintain_aspect_ratio { @@ -380,10 +384,7 @@ impl ImageSize { ImageFit::Exact(size) => size, }; - let fit = match self.max_size { - Some(extent) => fit.min(extent), - None => fit, - }; + let fit = fit.min(self.max_size); // `inf` on an axis means "any value" match (fit.x.is_finite(), fit.y.is_finite()) { @@ -395,35 +396,27 @@ impl ImageSize { } fn get(&self, available_size: Vec2, image_size: Vec2) -> Vec2 { + let max_size = self.max_size; match self.fit { ImageFit::Original(scale) => { let image_size = image_size * scale.unwrap_or(1.0); - if let Some(available_size) = self.max_size { - if image_size.x < available_size.x && image_size.y < available_size.y { - return image_size; - } - - if self.maintain_aspect_ratio { - let ratio_x = available_size.x / image_size.x; - let ratio_y = available_size.y / image_size.y; - let ratio = if ratio_x < ratio_y { ratio_x } else { ratio_y }; - let ratio = if ratio.is_infinite() { 1.0 } else { ratio }; + if image_size.x <= max_size.x && image_size.y <= max_size.y { + image_size + } else if self.maintain_aspect_ratio { + let ratio_x = max_size.x / image_size.x; + let ratio_y = max_size.y / image_size.y; + let ratio = if ratio_x < ratio_y { ratio_x } else { ratio_y }; + let ratio = if ratio.is_infinite() { 1.0 } else { ratio }; - return Vec2::new(image_size.x * ratio, image_size.y * ratio); - } else { - return image_size.min(available_size); - } + Vec2::new(image_size.x * ratio, image_size.y * ratio) + } else { + image_size.min(max_size) } - - image_size } ImageFit::Fraction(fract) => { let available_size = available_size * fract; - let available_size = match self.max_size { - Some(max_size) => available_size.min(max_size), - None => available_size, - }; + let available_size = available_size.min(max_size); if self.maintain_aspect_ratio { let ratio_x = available_size.x / image_size.x; @@ -438,10 +431,7 @@ impl ImageSize { } ImageFit::Exact(size) => { let available_size = size; - let available_size = match self.max_size { - Some(max_size) => available_size.min(max_size), - None => available_size, - }; + let available_size = available_size.min(max_size); if self.maintain_aspect_ratio { let ratio_x = available_size.x / image_size.x; @@ -462,7 +452,7 @@ impl Default for ImageSize { #[inline] fn default() -> Self { Self { - max_size: None, + max_size: Vec2::INFINITY, fit: ImageFit::Fraction(Vec2::new(1.0, 1.0)), maintain_aspect_ratio: true, } diff --git a/crates/egui_demo_app/src/apps/image_viewer.rs b/crates/egui_demo_app/src/apps/image_viewer.rs index f1929b9101d..fde398076f9 100644 --- a/crates/egui_demo_app/src/apps/image_viewer.rs +++ b/crates/egui_demo_app/src/apps/image_viewer.rs @@ -13,7 +13,7 @@ pub struct ImageViewer { chosen_fit: ChosenFit, fit: ImageFit, maintain_aspect_ratio: bool, - max_size: Option, + max_size: Vec2, } #[derive(Clone, Copy, PartialEq, Eq)] @@ -43,7 +43,7 @@ impl Default for ImageViewer { chosen_fit: ChosenFit::Fraction, fit: ImageFit::Fraction(Vec2::splat(1.0)), maintain_aspect_ratio: true, - max_size: None, + max_size: Vec2::splat(2048.0), } } } @@ -173,21 +173,8 @@ impl eframe::App for ImageViewer { // max size ui.add_space(5.0); ui.label("The calculated size will not exceed the maximum size"); - let had_max_size = self.max_size.is_some(); - let mut has_max_size = had_max_size; - ui.checkbox(&mut has_max_size, "Max size"); - match (had_max_size, has_max_size) { - (true, false) => self.max_size = None, - (false, true) => { - self.max_size = Some(ui.available_size()); - } - (true, true) | (false, false) => {} - } - - if let Some(max_size) = self.max_size.as_mut() { - ui.add(Slider::new(&mut max_size.x, 0.0..=2048.0).text("width")); - ui.add(Slider::new(&mut max_size.y, 0.0..=2048.0).text("height")); - } + ui.add(Slider::new(&mut self.max_size.x, 0.0..=2048.0).text("width")); + ui.add(Slider::new(&mut self.max_size.y, 0.0..=2048.0).text("height")); // aspect ratio ui.add_space(5.0); From 9af8b7766c9c3af9cd6392a34284727c7d544611 Mon Sep 17 00:00:00 2001 From: Emil Ernerfeldt Date: Wed, 13 Sep 2023 16:01:24 +0200 Subject: [PATCH 27/32] Use new API in WidgetGallery --- .../egui_demo_lib/src/demo/widget_gallery.rs | 18 ++++-------------- 1 file changed, 4 insertions(+), 14 deletions(-) diff --git a/crates/egui_demo_lib/src/demo/widget_gallery.rs b/crates/egui_demo_lib/src/demo/widget_gallery.rs index 1b34134025f..19cfadfcecb 100644 --- a/crates/egui_demo_lib/src/demo/widget_gallery.rs +++ b/crates/egui_demo_lib/src/demo/widget_gallery.rs @@ -21,9 +21,6 @@ pub struct WidgetGallery { #[cfg(feature = "chrono")] #[cfg_attr(feature = "serde", serde(skip))] date: Option, - - #[cfg_attr(feature = "serde", serde(skip))] - texture: Option, } impl Default for WidgetGallery { @@ -39,7 +36,6 @@ impl Default for WidgetGallery { animate_progress_bar: false, #[cfg(feature = "chrono")] date: None, - texture: None, } } } @@ -111,14 +107,8 @@ impl WidgetGallery { animate_progress_bar, #[cfg(feature = "chrono")] date, - texture, } = self; - let texture: &egui::TextureHandle = texture.get_or_insert_with(|| { - ui.ctx() - .load_texture("example", egui::ColorImage::example(), Default::default()) - }); - ui.add(doc_link_label("Label", "label,heading")); ui.label("Welcome to the widget gallery!"); ui.end_row(); @@ -206,16 +196,16 @@ impl WidgetGallery { ui.color_edit_button_srgba(color); ui.end_row(); - let img_size = 16.0 * texture.size_vec2() / texture.size_vec2().y; - ui.add(doc_link_label("Image", "Image")); let egui_icon = egui::include_image!("../../assets/icon.png"); - ui.add(egui::Image::new(egui_icon).max_size(Some(egui::Vec2::splat(32.0)))); + ui.add(egui::Image::new(egui_icon.clone())); ui.end_row(); ui.add(doc_link_label("ImageButton", "ImageButton")); if ui - .add(egui::ImageButton::new((texture.id(), img_size))) + .add(egui::ImageButton::new( + egui::Image::from(egui_icon).max_size(egui::Vec2::splat(16.0)), + )) .clicked() { *boolean = !*boolean; From 5cdbeffc7e7cd8bb757b35e763a88da1a64e4836 Mon Sep 17 00:00:00 2001 From: Emil Ernerfeldt Date: Wed, 13 Sep 2023 16:01:34 +0200 Subject: [PATCH 28/32] Make egui_demo_app work by default --- crates/egui_demo_app/Cargo.toml | 3 ++- crates/egui_demo_app/src/wrap_app.rs | 1 - 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/crates/egui_demo_app/Cargo.toml b/crates/egui_demo_app/Cargo.toml index c3fec7453e3..422513364d3 100644 --- a/crates/egui_demo_app/Cargo.toml +++ b/crates/egui_demo_app/Cargo.toml @@ -46,8 +46,9 @@ log = { version = "0.4", features = ["std"] } # Optional dependencies: bytemuck = { version = "1.7.1", optional = true } -egui_extras = { version = "0.22.0", optional = true, path = "../egui_extras", features = [ +egui_extras = { version = "0.22.0", path = "../egui_extras", features = [ "log", + "image", ] } rfd = { version = "0.11", optional = true } diff --git a/crates/egui_demo_app/src/wrap_app.rs b/crates/egui_demo_app/src/wrap_app.rs index c1a07e735be..079446277f9 100644 --- a/crates/egui_demo_app/src/wrap_app.rs +++ b/crates/egui_demo_app/src/wrap_app.rs @@ -165,7 +165,6 @@ pub struct WrapApp { impl WrapApp { pub fn new(_cc: &eframe::CreationContext<'_>) -> Self { - #[cfg(feature = "image_viewer")] egui_extras::loaders::install(&_cc.egui_ctx); #[allow(unused_mut)] From e6fae9a9721dbaa8abe992e7a70e2fd1efea5792 Mon Sep 17 00:00:00 2001 From: Emil Ernerfeldt Date: Wed, 13 Sep 2023 16:04:01 +0200 Subject: [PATCH 29/32] Remove Option from scale --- crates/egui/src/widgets/image.rs | 18 +++++++++--------- crates/egui_demo_app/src/apps/image_viewer.rs | 8 ++++---- 2 files changed, 13 insertions(+), 13 deletions(-) diff --git a/crates/egui/src/widgets/image.rs b/crates/egui/src/widgets/image.rs index 8c684452d8d..0af4af41ee7 100644 --- a/crates/egui/src/widgets/image.rs +++ b/crates/egui/src/widgets/image.rs @@ -120,14 +120,14 @@ impl<'a> Image<'a> { self } - /// Fit the image to its original size. + /// Fit the image to its original size with some scaling. /// /// This will cause the image to overflow if it is larger than the available space. /// /// If [`Image::max_size`] is set, this is guaranteed to never exceed that limit. #[inline] - pub fn fit_to_original_size(mut self, scale: Option) -> Self { - self.size.fit = ImageFit::Original(scale); + pub fn fit_to_original_size(mut self, scale: f32) -> Self { + self.size.fit = ImageFit::Original { scale }; self } @@ -352,8 +352,8 @@ pub struct ImageSize { #[derive(Debug, Clone, Copy)] #[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))] pub enum ImageFit { - /// Fit the image to its original size, optionally scaling it by some factor. - Original(Option), + /// Fit the image to its original size, scaled by some factor. + Original { scale: f32 }, /// Fit the image to a fraction of the available size. Fraction(Vec2), @@ -365,7 +365,7 @@ pub enum ImageFit { impl ImageFit { pub fn resolve(self, available_size: Vec2, image_size: Vec2) -> Vec2 { match self { - ImageFit::Original(scale) => image_size * scale.unwrap_or(1.0), + ImageFit::Original { scale } => image_size * scale, ImageFit::Fraction(fract) => available_size * fract, ImageFit::Exact(size) => size, } @@ -379,7 +379,7 @@ impl ImageSize { }; let fit = match self.fit { - ImageFit::Original(scale) => return SizeHint::Scale(scale.unwrap_or(1.0).ord()), + ImageFit::Original { scale } => return SizeHint::Scale(scale.ord()), ImageFit::Fraction(fract) => available_size * fract, ImageFit::Exact(size) => size, }; @@ -398,8 +398,8 @@ impl ImageSize { fn get(&self, available_size: Vec2, image_size: Vec2) -> Vec2 { let max_size = self.max_size; match self.fit { - ImageFit::Original(scale) => { - let image_size = image_size * scale.unwrap_or(1.0); + ImageFit::Original { scale } => { + let image_size = image_size * scale; if image_size.x <= max_size.x && image_size.y <= max_size.y { image_size diff --git a/crates/egui_demo_app/src/apps/image_viewer.rs b/crates/egui_demo_app/src/apps/image_viewer.rs index fde398076f9..98f80f727c0 100644 --- a/crates/egui_demo_app/src/apps/image_viewer.rs +++ b/crates/egui_demo_app/src/apps/image_viewer.rs @@ -160,10 +160,10 @@ impl eframe::App for ImageViewer { ui.add(Slider::new(&mut fract.y, 0.0..=1.0).text("height")); } ChosenFit::OriginalSize => { - if !matches!(self.fit, ImageFit::Original(_)) { - self.fit = ImageFit::Original(Some(1.0)); + if !matches!(self.fit, ImageFit::Original { .. }) { + self.fit = ImageFit::Original { scale: 1.0 }; } - let ImageFit::Original(Some(scale)) = &mut self.fit else { + let ImageFit::Original{scale} = &mut self.fit else { unreachable!() }; ui.add(Slider::new(scale, 0.1..=4.0).text("scale")); @@ -196,7 +196,7 @@ impl eframe::App for ImageViewer { }); image = image.rotate(angle, origin); match self.fit { - ImageFit::Original(scale) => image = image.fit_to_original_size(scale), + ImageFit::Original { scale } => image = image.fit_to_original_size(scale), ImageFit::Fraction(fract) => image = image.fit_to_fraction(fract), ImageFit::Exact(size) => image = image.fit_to_exact_size(size), } From a69092b18889611bdbd7642aa2c2ea667d2cc01e Mon Sep 17 00:00:00 2001 From: Emil Ernerfeldt Date: Wed, 13 Sep 2023 16:12:11 +0200 Subject: [PATCH 30/32] Refactor ImageSize --- crates/egui/src/widgets/image.rs | 53 +++++++++++--------------------- 1 file changed, 18 insertions(+), 35 deletions(-) diff --git a/crates/egui/src/widgets/image.rs b/crates/egui/src/widgets/image.rs index 0af4af41ee7..0008dde3326 100644 --- a/crates/egui/src/widgets/image.rs +++ b/crates/egui/src/widgets/image.rs @@ -400,54 +400,37 @@ impl ImageSize { match self.fit { ImageFit::Original { scale } => { let image_size = image_size * scale; - if image_size.x <= max_size.x && image_size.y <= max_size.y { image_size - } else if self.maintain_aspect_ratio { - let ratio_x = max_size.x / image_size.x; - let ratio_y = max_size.y / image_size.y; - let ratio = if ratio_x < ratio_y { ratio_x } else { ratio_y }; - let ratio = if ratio.is_infinite() { 1.0 } else { ratio }; - - Vec2::new(image_size.x * ratio, image_size.y * ratio) } else { - image_size.min(max_size) + scale_to_fit(image_size, max_size, self.maintain_aspect_ratio) } } ImageFit::Fraction(fract) => { - let available_size = available_size * fract; - let available_size = available_size.min(max_size); - - if self.maintain_aspect_ratio { - let ratio_x = available_size.x / image_size.x; - let ratio_y = available_size.y / image_size.y; - let ratio = if ratio_x < ratio_y { ratio_x } else { ratio_y }; - let ratio = if ratio.is_infinite() { 1.0 } else { ratio }; - - return Vec2::new(image_size.x * ratio, image_size.y * ratio); - } - - available_size + let scale_to_size = (available_size * fract).min(max_size); + scale_to_fit(image_size, scale_to_size, self.maintain_aspect_ratio) } ImageFit::Exact(size) => { - let available_size = size; - let available_size = available_size.min(max_size); - - if self.maintain_aspect_ratio { - let ratio_x = available_size.x / image_size.x; - let ratio_y = available_size.y / image_size.y; - let ratio = if ratio_x < ratio_y { ratio_x } else { ratio_y }; - let ratio = if ratio.is_infinite() { 1.0 } else { ratio }; - - return Vec2::new(image_size.x * ratio, image_size.y * ratio); - } - - available_size + let scale_to_size = size.min(max_size); + scale_to_fit(image_size, scale_to_size, self.maintain_aspect_ratio) } } } } +// TODO: unit-tests +fn scale_to_fit(image_size: Vec2, available_size: Vec2, maintain_aspect_ratio: bool) -> Vec2 { + if maintain_aspect_ratio { + let ratio_x = available_size.x / image_size.x; + let ratio_y = available_size.y / image_size.y; + let ratio = if ratio_x < ratio_y { ratio_x } else { ratio_y }; + let ratio = if ratio.is_finite() { ratio } else { 1.0 }; + image_size * ratio + } else { + available_size + } +} + impl Default for ImageSize { #[inline] fn default() -> Self { From 682d4059154aa887767bff7c3a42656a0a93f8d8 Mon Sep 17 00:00:00 2001 From: Emil Ernerfeldt Date: Wed, 13 Sep 2023 16:14:23 +0200 Subject: [PATCH 31/32] Fix docstring --- crates/egui/src/widgets/image.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/egui/src/widgets/image.rs b/crates/egui/src/widgets/image.rs index 0008dde3326..d8686c75931 100644 --- a/crates/egui/src/widgets/image.rs +++ b/crates/egui/src/widgets/image.rs @@ -335,7 +335,7 @@ pub struct ImageSize { /// Determines the maximum size of the image. /// - /// Defaults to `None` + /// Defaults to `Vec2::INFINITY` (no limit). pub max_size: Vec2, /// Determines how the image should shrink/expand/stretch/etc. to fit within its allocated space. From 9d3ba1b10ac9a99fca4b133bace6aa3c97911314 Mon Sep 17 00:00:00 2001 From: Emil Ernerfeldt Date: Wed, 13 Sep 2023 16:16:01 +0200 Subject: [PATCH 32/32] Small refactor --- crates/egui/src/widgets/image.rs | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/crates/egui/src/widgets/image.rs b/crates/egui/src/widgets/image.rs index d8686c75931..163e264e492 100644 --- a/crates/egui/src/widgets/image.rs +++ b/crates/egui/src/widgets/image.rs @@ -396,23 +396,27 @@ impl ImageSize { } fn get(&self, available_size: Vec2, image_size: Vec2) -> Vec2 { - let max_size = self.max_size; - match self.fit { + let Self { + maintain_aspect_ratio, + max_size, + fit, + } = *self; + match fit { ImageFit::Original { scale } => { let image_size = image_size * scale; if image_size.x <= max_size.x && image_size.y <= max_size.y { image_size } else { - scale_to_fit(image_size, max_size, self.maintain_aspect_ratio) + scale_to_fit(image_size, max_size, maintain_aspect_ratio) } } ImageFit::Fraction(fract) => { let scale_to_size = (available_size * fract).min(max_size); - scale_to_fit(image_size, scale_to_size, self.maintain_aspect_ratio) + scale_to_fit(image_size, scale_to_size, maintain_aspect_ratio) } ImageFit::Exact(size) => { let scale_to_size = size.min(max_size); - scale_to_fit(image_size, scale_to_size, self.maintain_aspect_ratio) + scale_to_fit(image_size, scale_to_size, maintain_aspect_ratio) } } }