Skip to content

Commit

Permalink
"Final" image polish (#3342)
Browse files Browse the repository at this point in the history
* Improve the Image API a bit

* Improve image view demo

* Better names

* calculate -> calc
  • Loading branch information
emilk authored Sep 15, 2023
1 parent d7d222d commit 2bbceb8
Show file tree
Hide file tree
Showing 4 changed files with 53 additions and 37 deletions.
11 changes: 6 additions & 5 deletions crates/egui/src/widgets/button.rs
Original file line number Diff line number Diff line change
Expand Up @@ -187,7 +187,7 @@ impl Widget for Button<'_> {

let image_size = if let Some(image) = &image {
image
.load_and_calculate_size(ui, space_available_for_image)
.load_and_calc_size(ui, space_available_for_image)
.unwrap_or(space_available_for_image)
} else {
Vec2::ZERO
Expand Down Expand Up @@ -276,7 +276,7 @@ impl Widget for Button<'_> {
image_size,
);
cursor_x += image_size.x;
let tlr = image.load(ui);
let tlr = image.load_for_size(ui.ctx(), image_size);
widgets::image::paint_texture_load_result(
ui,
&tlr,
Expand Down Expand Up @@ -605,11 +605,12 @@ impl<'a> Widget for ImageButton<'a> {
Vec2::ZERO
};

let tlr = self.image.load(ui);
let texture_size = tlr.as_ref().ok().and_then(|t| t.size());
let available_size_for_image = ui.available_size() - 2.0 * padding;
let tlr = self.image.load_for_size(ui.ctx(), available_size_for_image);
let original_image_size = tlr.as_ref().ok().and_then(|t| t.size());
let image_size = self
.image
.calculate_size(ui.available_size() - 2.0 * padding, texture_size);
.calc_size(available_size_for_image, original_image_size);

let padded_size = image_size + 2.0 * padding;
let (rect, response) = ui.allocate_exact_size(padded_size, self.sense);
Expand Down
65 changes: 38 additions & 27 deletions crates/egui/src/widgets/image.rs
Original file line number Diff line number Diff line change
Expand Up @@ -239,14 +239,14 @@ impl<'a, T: Into<ImageSource<'a>>> From<T> for 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: Option<Vec2>) -> Vec2 {
let image_size = image_size.unwrap_or(Vec2::splat(24.0)); // Fallback for still-loading textures, or failure to load.
self.size.get(available_size, image_size)
pub fn calc_size(&self, available_size: Vec2, original_image_size: Option<Vec2>) -> Vec2 {
let original_image_size = original_image_size.unwrap_or(Vec2::splat(24.0)); // Fallback for still-loading textures, or failure to load.
self.size.calc_size(available_size, original_image_size)
}

pub fn load_and_calculate_size(&self, ui: &mut Ui, available_size: Vec2) -> Option<Vec2> {
let image_size = self.load(ui).ok()?.size()?;
Some(self.size.get(available_size, image_size))
pub fn load_and_calc_size(&self, ui: &mut Ui, available_size: Vec2) -> Option<Vec2> {
let image_size = self.load_for_size(ui.ctx(), available_size).ok()?.size()?;
Some(self.size.calc_size(available_size, image_size))
}

#[inline]
Expand All @@ -269,21 +269,23 @@ impl<'a> Image<'a> {

/// Load the image from its [`Image::source`], returning the resulting [`SizedTexture`].
///
/// The `available_size` is used as a hint when e.g. rendering an svg.
///
/// # Errors
/// May fail if they underlying [`Context::try_load_texture`] call fails.
pub fn load(&self, ui: &Ui) -> TextureLoadResult {
let size_hint = self.size.hint(ui.available_size());
pub fn load_for_size(&self, ctx: &Context, available_size: Vec2) -> TextureLoadResult {
let size_hint = self.size.hint(available_size);
self.source
.clone()
.load(ui.ctx(), self.texture_options, size_hint)
.load(ctx, self.texture_options, size_hint)
}

/// Paint the image in the given rectangle.
#[inline]
pub fn paint_at(&self, ui: &mut Ui, rect: Rect) {
paint_texture_load_result(
ui,
&self.load(ui),
&self.load_for_size(ui.ctx(), rect.size()),
rect,
self.show_loading_spinner,
&self.image_options,
Expand All @@ -293,9 +295,9 @@ impl<'a> Image<'a> {

impl<'a> Widget for Image<'a> {
fn ui(self, ui: &mut Ui) -> Response {
let tlr = self.load(ui);
let texture_size = tlr.as_ref().ok().and_then(|t| t.size());
let ui_size = self.calculate_size(ui.available_size(), texture_size);
let tlr = self.load_for_size(ui.ctx(), ui.available_size());
let original_image_size = tlr.as_ref().ok().and_then(|t| t.size());
let ui_size = self.calc_size(ui.available_size(), original_image_size);

let (rect, response) = ui.allocate_exact_size(ui_size, self.sense);
paint_texture_load_result(
Expand Down Expand Up @@ -364,37 +366,37 @@ impl ImageFit {
}

impl ImageSize {
/// Size hint for e.g. rasterizing an svg.
pub fn hint(&self, available_size: Vec2) -> SizeHint {
if self.maintain_aspect_ratio {
return SizeHint::Scale(1.0.ord());
};

let fit = match self.fit {
let size = match self.fit {
ImageFit::Original { scale } => return SizeHint::Scale(scale.ord()),
ImageFit::Fraction(fract) => available_size * fract,
ImageFit::Exact(size) => size,
};

let fit = fit.min(self.max_size);
let size = size.min(self.max_size);

// TODO(emilk): take pixels_per_point into account here!

// `inf` on an axis means "any value"
match (fit.x.is_finite(), fit.y.is_finite()) {
(true, true) => SizeHint::Size(fit.x.round() as u32, fit.y.round() as u32),
(true, false) => SizeHint::Width(fit.x.round() as u32),
(false, true) => SizeHint::Height(fit.y.round() as u32),
match (size.x.is_finite(), size.y.is_finite()) {
(true, true) => SizeHint::Size(size.x.round() as u32, size.y.round() as u32),
(true, false) => SizeHint::Width(size.x.round() as u32),
(false, true) => SizeHint::Height(size.y.round() as u32),
(false, false) => SizeHint::Scale(1.0.ord()),
}
}

pub fn get(&self, available_size: Vec2, image_size: Vec2) -> Vec2 {
/// Calculate the final on-screen size in points.
pub fn calc_size(&self, available_size: Vec2, original_image_size: Vec2) -> Vec2 {
let Self {
maintain_aspect_ratio,
max_size,
fit,
} = *self;
match fit {
ImageFit::Original { scale } => {
let image_size = image_size * scale;
let image_size = original_image_size * scale;
if image_size.x <= max_size.x && image_size.y <= max_size.y {
image_size
} else {
Expand All @@ -403,11 +405,11 @@ impl ImageSize {
}
ImageFit::Fraction(fract) => {
let scale_to_size = (available_size * fract).min(max_size);
scale_to_fit(image_size, scale_to_size, maintain_aspect_ratio)
scale_to_fit(original_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, maintain_aspect_ratio)
scale_to_fit(original_image_size, scale_to_size, maintain_aspect_ratio)
}
}
}
Expand Down Expand Up @@ -483,6 +485,15 @@ impl<'a> std::fmt::Debug for ImageSource<'a> {
}

impl<'a> ImageSource<'a> {
/// Size of the texture, if known.
#[inline]
pub fn texture_size(&self) -> Option<Vec2> {
match self {
ImageSource::Texture(texture) => Some(texture.size),
ImageSource::Uri(_) | ImageSource::Bytes(_, _) => None,
}
}

/// # Errors
/// Failure to load the texture.
pub fn load(
Expand Down
12 changes: 8 additions & 4 deletions crates/egui_demo_app/src/apps/image_viewer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -105,13 +105,17 @@ impl eframe::App for ImageViewer {

// bg_fill
ui.add_space(2.0);
ui.label("Background color");
ui.color_edit_button_srgba(&mut self.image_options.bg_fill);
ui.horizontal(|ui| {
ui.color_edit_button_srgba(&mut self.image_options.bg_fill);
ui.label("Background color");
});

// tint
ui.add_space(2.0);
ui.label("Tint");
ui.color_edit_button_srgba(&mut self.image_options.tint);
ui.horizontal(|ui| {
ui.color_edit_button_srgba(&mut self.image_options.tint);
ui.label("Tint");
});

// fit
ui.add_space(10.0);
Expand Down
2 changes: 1 addition & 1 deletion crates/egui_demo_app/src/wrap_app.rs
Original file line number Diff line number Diff line change
Expand Up @@ -212,7 +212,7 @@ impl WrapApp {
),
#[cfg(feature = "image_viewer")]
(
"🖼 Image Viewer",
"🖼 Image Viewer",
Anchor::ImageViewer,
&mut self.state.image_viewer as &mut dyn eframe::App,
),
Expand Down

0 comments on commit 2bbceb8

Please sign in to comment.