1
0
mirror of https://github.com/emilk/egui.git synced 2026-09-02 06:40:06 -04:00

Improved texture loading (#3315)

* rework loading around `Arc<Loaders>`

* use `Bytes` instead of splitting api

* remove unwraps in `texture_handle`

* make `FileLoader` optional under `file` feature

* hide http load error stack trace from UI

* implement image fit

* support more image sources

* center spinner if we know size ahead of time

* allocate final size for spinner

* improve image format guessing

* remove `ui.image`, `Image`, add `RawImage`

* deprecate `RetainedImage`

* `image2` -> `image`

* add viewer example

* update `examples/image` + remove `svg` and `download_image` exapmles

* fix lints and tests

* fix doc link

* add image controls to `images` example

* add more `From` str-like types

* add api to forget all images

* fix max size

* do not scale original size unless necessary

* fix doc link

* add more docs for `Image` and `RawImage`

* make paint_at `pub`

* update `ImageButton` to use new `Image` API

* fix double rendering

* `SizeHint::Original` -> `Scale` + remove `Option` wrapper

* Update crates/egui/src/load.rs

Co-authored-by: Emil Ernerfeldt <emil.ernerfeldt@gmail.com>

* remove special `None` value for `forget`

* Update crates/egui/src/load.rs

Co-authored-by: Emil Ernerfeldt <emil.ernerfeldt@gmail.com>

* add more examples to `ui.image` + add `include_image` macro

* Update crates/egui/src/ui.rs

Co-authored-by: Emil Ernerfeldt <emil.ernerfeldt@gmail.com>

* update `menu_image_button` to use `ImageSource`

* `OrderedFloat::get` -> `into_inner`

* derive `Eq` on `SizedTexture`

* add `id` to loaders + `is_installed` check

* move `images` to demo + simplify `images` example

* log trace when installing loaders

* fix lint

* fix doc link

* add more documentation

* more `egui_extras::loaders` docs

* Update examples/images/src/main.rs

Co-authored-by: Emil Ernerfeldt <emil.ernerfeldt@gmail.com>

* update `images` example screenshots + readme

* remove unused `rfd` from `images` example

* Update crates/egui_extras/src/loaders/ehttp_loader.rs

Co-authored-by: Emil Ernerfeldt <emil.ernerfeldt@gmail.com>

* add `must_use` on `Image` and `RawImage`

* document `loaders::install` multiple call safety

* Update crates/egui_extras/Cargo.toml

Co-authored-by: Emil Ernerfeldt <emil.ernerfeldt@gmail.com>

* reshuffle `is_loader_installed`

* make `include_image` produce `ImageSource` + update docs

* update `include_image` docs

* remove `None` mentions from loader `forget`

* inline `From` texture id + size for `SizedTexture`

* add warning about statically known path

* change image load error + use in image button

* add `.size()` to `Image`

* Update crates/egui_demo_app/Cargo.toml

Co-authored-by: Emil Ernerfeldt <emil.ernerfeldt@gmail.com>

* add explanations to image viewer ui

---------

Co-authored-by: Emil Ernerfeldt <emil.ernerfeldt@gmail.com>
This commit is contained in:
Jan Procházka
2023-09-12 10:39:17 +02:00
committed by GitHub
parent dbcf15b49e
commit 2bc6814acc
47 changed files with 1563 additions and 770 deletions

View File

@@ -1,3 +1,5 @@
#![allow(deprecated)]
use egui_extras::RetainedImage;
use poll_promise::Promise;

View File

@@ -0,0 +1,217 @@
use egui::emath::Rot2;
use egui::panel::Side;
use egui::panel::TopBottomSide;
use egui::ImageFit;
use egui::Slider;
use egui::Vec2;
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
pub struct ImageViewer {
current_uri: String,
uri_edit_text: String,
image_options: egui::ImageOptions,
chosen_fit: ChosenFit,
fit: ImageFit,
maintain_aspect_ratio: bool,
max_size: Option<Vec2>,
}
#[derive(Clone, Copy, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
enum ChosenFit {
ExactSize,
Fraction,
OriginalSize,
}
impl ChosenFit {
fn as_str(&self) -> &'static str {
match self {
ChosenFit::ExactSize => "exact size",
ChosenFit::Fraction => "fraction",
ChosenFit::OriginalSize => "original size",
}
}
}
impl Default for ImageViewer {
fn default() -> Self {
Self {
current_uri: "https://picsum.photos/seed/1.759706314/1024".to_owned(),
uri_edit_text: "https://picsum.photos/seed/1.759706314/1024".to_owned(),
image_options: egui::ImageOptions::default(),
chosen_fit: ChosenFit::Fraction,
fit: ImageFit::Fraction(Vec2::splat(1.0)),
maintain_aspect_ratio: true,
max_size: None,
}
}
}
impl eframe::App for ImageViewer {
fn update(&mut self, ctx: &egui::Context, _: &mut eframe::Frame) {
egui::TopBottomPanel::new(TopBottomSide::Top, "url bar").show(ctx, |ui| {
ui.horizontal_centered(|ui| {
ui.label("URI:");
ui.text_edit_singleline(&mut self.uri_edit_text);
if ui.small_button("").clicked() {
ctx.forget_image(&self.current_uri);
self.uri_edit_text = self.uri_edit_text.trim().to_owned();
self.current_uri = self.uri_edit_text.clone();
};
#[cfg(not(target_arch = "wasm32"))]
if ui.button("file…").clicked() {
if let Some(path) = rfd::FileDialog::new().pick_file() {
self.uri_edit_text = format!("file://{}", path.display());
self.current_uri = self.uri_edit_text.clone();
}
}
});
});
egui::SidePanel::new(Side::Left, "controls").show(ctx, |ui| {
// uv
ui.label("UV");
ui.add(Slider::new(&mut self.image_options.uv.min.x, 0.0..=1.0).text("min x"));
ui.add(Slider::new(&mut self.image_options.uv.min.y, 0.0..=1.0).text("min y"));
ui.add(Slider::new(&mut self.image_options.uv.max.x, 0.0..=1.0).text("max x"));
ui.add(Slider::new(&mut self.image_options.uv.max.y, 0.0..=1.0).text("max y"));
// rotation
ui.add_space(2.0);
let had_rotation = self.image_options.rotation.is_some();
let mut has_rotation = had_rotation;
ui.checkbox(&mut has_rotation, "Rotation");
match (had_rotation, has_rotation) {
(true, false) => self.image_options.rotation = None,
(false, true) => {
self.image_options.rotation =
Some((Rot2::from_angle(0.0), Vec2::new(0.5, 0.5)));
}
(true, true) | (false, false) => {}
}
if let Some((rot, origin)) = self.image_options.rotation.as_mut() {
let mut angle = rot.angle();
ui.label("angle");
ui.drag_angle(&mut angle);
*rot = Rot2::from_angle(angle);
ui.add(Slider::new(&mut origin.x, 0.0..=1.0).text("origin x"));
ui.add(Slider::new(&mut origin.y, 0.0..=1.0).text("origin y"));
}
// bg_fill
ui.add_space(2.0);
ui.label("Background color");
ui.color_edit_button_srgba(&mut self.image_options.bg_fill);
// tint
ui.add_space(2.0);
ui.label("Tint");
ui.color_edit_button_srgba(&mut self.image_options.tint);
// fit
ui.add_space(10.0);
ui.label(
"The chosen fit will determine how the image tries to fill the available space",
);
egui::ComboBox::from_label("Fit")
.selected_text(self.chosen_fit.as_str())
.show_ui(ui, |ui| {
ui.selectable_value(
&mut self.chosen_fit,
ChosenFit::ExactSize,
ChosenFit::ExactSize.as_str(),
);
ui.selectable_value(
&mut self.chosen_fit,
ChosenFit::Fraction,
ChosenFit::Fraction.as_str(),
);
ui.selectable_value(
&mut self.chosen_fit,
ChosenFit::OriginalSize,
ChosenFit::OriginalSize.as_str(),
);
});
match self.chosen_fit {
ChosenFit::ExactSize => {
if !matches!(self.fit, ImageFit::Exact(_)) {
self.fit = ImageFit::Exact(Vec2::splat(128.0));
}
let ImageFit::Exact(size) = &mut self.fit else { unreachable!() };
ui.add(Slider::new(&mut size.x, 0.0..=2048.0).text("width"));
ui.add(Slider::new(&mut size.y, 0.0..=2048.0).text("height"));
}
ChosenFit::Fraction => {
if !matches!(self.fit, ImageFit::Fraction(_)) {
self.fit = ImageFit::Fraction(Vec2::splat(1.0));
}
let ImageFit::Fraction(fract) = &mut self.fit else { unreachable!() };
ui.add(Slider::new(&mut fract.x, 0.0..=1.0).text("width"));
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));
}
let ImageFit::Original(Some(scale)) = &mut self.fit else { unreachable!() };
ui.add(Slider::new(scale, 0.1..=4.0).text("scale"));
}
}
// 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"));
}
// aspect ratio
ui.add_space(5.0);
ui.label("Aspect ratio is maintained by scaling both sides as necessary");
ui.checkbox(&mut self.maintain_aspect_ratio, "Maintain aspect ratio");
});
egui::CentralPanel::default().show(ctx, |ui| {
egui::ScrollArea::new([true, true]).show(ui, |ui| {
let mut image = egui::Image::from_uri(&self.current_uri);
image = image.uv(self.image_options.uv);
image = image.bg_fill(self.image_options.bg_fill);
image = image.tint(self.image_options.tint);
let (angle, origin) = self
.image_options
.rotation
.map_or((0.0, Vec2::splat(0.5)), |(rot, origin)| {
(rot.angle(), origin)
});
image = image.rotate(angle, origin);
match self.fit {
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),
}
image = image.maintain_aspect_ratio(self.maintain_aspect_ratio);
image = image.max_size(self.max_size);
ui.add_sized(ui.available_size(), image);
});
});
}
}

View File

@@ -9,6 +9,12 @@ mod fractal_clock;
#[cfg(feature = "http")]
mod http_app;
#[cfg(feature = "image_viewer")]
mod image_viewer;
#[cfg(feature = "image_viewer")]
pub use image_viewer::ImageViewer;
#[cfg(all(feature = "glow", not(feature = "wgpu")))]
pub use custom3d_glow::Custom3d;

View File

@@ -82,6 +82,8 @@ enum Anchor {
EasyMarkEditor,
#[cfg(feature = "http")]
Http,
#[cfg(feature = "image_viewer")]
ImageViewer,
Clock,
#[cfg(any(feature = "glow", feature = "wgpu"))]
Custom3d,
@@ -142,6 +144,8 @@ pub struct State {
easy_mark_editor: EasyMarkApp,
#[cfg(feature = "http")]
http: crate::apps::HttpApp,
#[cfg(feature = "image_viewer")]
image_viewer: crate::apps::ImageViewer,
clock: FractalClockApp,
color_test: ColorTestApp,
@@ -161,6 +165,9 @@ 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)]
let mut slf = Self {
state: State::default(),
@@ -204,6 +211,12 @@ impl WrapApp {
Anchor::Clock,
&mut self.state.clock as &mut dyn eframe::App,
),
#[cfg(feature = "image_viewer")]
(
"🖼️ Image Viewer",
Anchor::ImageViewer,
&mut self.state.image_viewer as &mut dyn eframe::App,
),
];
#[cfg(any(feature = "glow", feature = "wgpu"))]