egui/crates/egui_demo_app/src/apps/image_viewer.rs

231 lines
8.9 KiB
Rust

use egui::ImageFit;
use egui::Slider;
use egui::Vec2;
use egui::emath::Rot2;
use egui::panel::Side;
use egui::panel::TopBottomSide;
#[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: Vec2,
alt_text: String,
}
#[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 {
Self::ExactSize => "exact size",
Self::Fraction => "fraction",
Self::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: Vec2::splat(2048.0),
alt_text: "My Image".to_owned(),
}
}
}
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| {
let label = ui.label("URI:");
ui.text_edit_singleline(&mut self.uri_edit_text)
.labelled_by(label.id);
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()
&& 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.horizontal(|ui| {
ui.color_edit_button_srgba(&mut self.image_options.bg_fill);
ui.label("Background color");
});
// tint
ui.add_space(2.0);
ui.horizontal(|ui| {
ui.color_edit_button_srgba(&mut self.image_options.tint);
ui.label("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 { scale: 1.0 };
}
let ImageFit::Original { 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");
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);
ui.label("Aspect ratio is maintained by scaling both sides as necessary");
ui.checkbox(&mut self.maintain_aspect_ratio, "Maintain aspect ratio");
// alt text
ui.add_space(5.0);
ui.label("Alt text");
ui.text_edit_singleline(&mut self.alt_text);
// forget all images
if ui.button("Forget all images").clicked() {
ui.ctx().forget_all_images();
}
});
egui::CentralPanel::default().show(ctx, |ui| {
egui::ScrollArea::both().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);
if !self.alt_text.is_empty() {
image = image.alt_text(&self.alt_text);
}
ui.add_sized(ui.available_size(), image);
});
});
}
}