use crate::image::Image;
use crate::metadata;
use eframe::egui::{self, vec2, Rect, RichText, Widget};
use eframe::epaint::{Pos2, Vec2};
use std;
use std::path::PathBuf;
use std::thread::JoinHandle;
pub struct GalleryImageSizing {
pub zoom_factor: f32,
pub scroll_delta: Vec2,
pub should_maximize: bool,
pub has_maximized: bool,
}
pub struct GalleryImageFrame {
pub enabled: bool,
pub size_r: f32,
}
pub struct GalleryImage {
pub path: PathBuf,
pub name: String,
pub display_name: Option<String>,
scroll_pos: Pos2,
image: Option<Image>,
load_image_handle: Option<JoinHandle<Option<Image>>>,
output_profile: String,
display_metadata: Option<Vec<(String, String)>>,
pub prev_percentage_zoom: f32,
pub prev_available_size: Vec2,
pub prev_target_size: Vec2,
pub prev_cursor_pos_normalized: Vec2,
}
impl GalleryImage {
pub fn from_paths(
paths: &[PathBuf],
output_profile: &String,
) -> Vec<Self> {
paths
.iter()
.map(|p| Self {
path: p.clone(),
name: p
.file_name()
.unwrap_or_default()
.to_string_lossy()
.to_string(),
scroll_pos: Pos2::new(0.0, 0.0),
image: None,
load_image_handle: None,
output_profile: output_profile.to_owned(),
display_metadata: None,
display_name: None,
prev_percentage_zoom: 0.,
prev_available_size: vec2(0., 0.),
prev_target_size: vec2(0., 0.),
prev_cursor_pos_normalized: vec2(0., 0.),
})
.collect()
}
pub fn ui(
&mut self,
ui: &mut egui::Ui,
frame: &GalleryImageFrame,
sizing: &mut GalleryImageSizing,
) {
self.finish_img_loading();
let image = match &mut self.image {
Some(image) => image,
None => {
Self::display_loading_frame(ui);
return;
}
};
let texture = match image.get_texture(&self.name, ui) {
Some(t) => t,
None => {
Self::display_loading_frame(ui);
return;
}
};
let original_size = texture.size_vec2();
let mut target_size = texture.size_vec2();
let aspect_ratio = texture.aspect_ratio();
if ui.available_width() < target_size[0] {
target_size[0] = ui.available_width();
target_size[1] = target_size[0] / aspect_ratio;
}
if ui.available_height() < target_size[1] {
target_size[1] = ui.available_height();
target_size[0] = aspect_ratio * target_size[1];
}
self.prev_target_size = target_size;
self.prev_available_size = ui.available_size();
if sizing.should_maximize && !sizing.has_maximized {
sizing.has_maximized = true;
if self.prev_available_size.x / self.prev_available_size.y
> self.prev_target_size.x / self.prev_target_size.y
{
sizing.zoom_factor = self.prev_available_size.y / self.prev_target_size.y;
} else {
sizing.zoom_factor = self.prev_available_size.x / self.prev_target_size.x;
}
}
target_size[0] *= sizing.zoom_factor;
target_size[1] *= sizing.zoom_factor;
self.prev_percentage_zoom = target_size[0] * 100. / original_size[0];
let mut display_size = target_size;
if display_size[0] > ui.available_width() {
display_size[0] = ui.available_width();
}
if display_size[1] > ui.available_height() {
display_size[1] = ui.available_height();
}
let mut visible_rect = Rect {
min: Pos2 { x: 0.0, y: 0.0 },
max: Pos2 {
x: display_size.x,
y: display_size.y,
},
};
let out_bounds_y = target_size[1] - display_size[1];
let out_bounds_x = target_size[0] - display_size[0];
if out_bounds_y > 0.0 {
let remain_y = (target_size.y - display_size.y) / 2.0;
visible_rect.min.y = remain_y;
visible_rect.max.y = target_size.y - remain_y;
}
if out_bounds_x > 0.0 {
let remain_x = (target_size.x - display_size.x) / 2.0;
visible_rect.min.x = remain_x;
visible_rect.max.x = target_size.x - remain_x;
}
Self::update_panning_pos(
&mut self.scroll_pos,
&mut visible_rect,
&target_size,
&mut sizing.scroll_delta,
);
let visible_rect_normalized = Rect {
min: Pos2 {
x: visible_rect.min.x / target_size.x,
y: visible_rect.min.y / target_size.y,
},
max: Pos2 {
x: visible_rect.max.x / target_size.x,
y: visible_rect.max.y / target_size.y,
},
};
if frame.enabled {
let stroke = if display_size[0] > display_size[1] {
display_size[1] * frame.size_r
} else {
display_size[0] * frame.size_r
};
let aspect_ratio = display_size[0] / display_size[1];
display_size[0] -= stroke;
display_size[1] -= stroke / aspect_ratio;
let image = egui::Image::new(texture)
.fit_to_exact_size(vec2(display_size[0], display_size[1]))
.maintain_aspect_ratio(false)
.uv(visible_rect_normalized);
let offset_x = (ui.available_width() - (display_size[0] + stroke)) / 2.;
let offset_y = (ui.available_height() - (display_size[1] + stroke)) / 2.;
ui.painter().rect_filled(
Rect {
min: Pos2::new(offset_x, offset_y),
max: Pos2::new(
offset_x + display_size[0] + stroke,
offset_y + display_size[1] + stroke,
),
},
1.,
egui::Color32::WHITE,
);
ui.add(image);
} else {
egui::Image::new(texture)
.uv(visible_rect_normalized)
.fit_to_exact_size(vec2(display_size[0], display_size[1]))
.maintain_aspect_ratio(false)
.ui(ui);
}
}
fn update_panning_pos(
scroll_pos: &mut Pos2,
visible_rect: &mut Rect,
target_size: &Vec2,
scroll_delta: &mut Vec2,
) {
let free_space = Pos2::new(
target_size.x - (visible_rect.max.x - visible_rect.min.x),
target_size.y - (visible_rect.max.y - visible_rect.min.y),
);
scroll_delta.x *= -1.0;
scroll_delta.y *= -1.0;
if free_space.x != 0.0 {
scroll_pos.x += scroll_delta.x;
if scroll_pos.x > free_space.x / 2.0 {
scroll_pos.x = free_space.x / 2.0;
} else if scroll_pos.x < -free_space.x / 2.0 {
scroll_pos.x = -free_space.x / 2.0;
}
} else {
scroll_pos.x = 0.0;
}
if free_space.y != 0.0 {
scroll_pos.y += scroll_delta.y;
if scroll_pos.y > free_space.y / 2.0 {
scroll_pos.y = free_space.y / 2.0;
} else if scroll_pos.y < -free_space.y / 2.0 {
scroll_pos.y = -free_space.y / 2.0;
}
} else {
scroll_pos.y = 0.0;
}
visible_rect.min.y += scroll_pos.y;
visible_rect.max.y += scroll_pos.y;
visible_rect.min.x += scroll_pos.x;
visible_rect.max.x += scroll_pos.x;
}
pub fn finish_img_loading(&mut self) {
if self.load_image_handle.is_none() {
return;
};
let lih = self.load_image_handle.take().unwrap();
if lih.is_finished() {
match lih.join() {
Ok(image) => self.image = image,
Err(_) => println!("Failure joining load image thread"),
}
} else {
self.load_image_handle = Some(lih);
}
}
pub fn metadata_ui(&mut self, ui: &mut egui::Ui, tags_to_display: &Vec<String>) {
if let Some(img) = &mut self.image {
if self.display_metadata.is_none() {
let mut metadata: Vec<(String, String)> = vec![];
for tag in tags_to_display {
if let Some(value) = &img.metadata.get(tag) {
metadata.push((tag.to_string(), value.to_string()));
};
}
self.display_metadata = Some(metadata);
}
if let Some(metadata) = &self.display_metadata {
for md in metadata {
ui.horizontal(|ui| {
let text = RichText::new(format!("{}:", md.0)).strong();
ui.label(text);
ui.label(&md.1);
});
}
}
}
}
pub fn image_size(&self) -> Option<Vec2> {
if let Some(img) = &self.image {
if let Some(texture) = &img.texture {
return Some(texture.size_vec2());
}
}
None
}
pub fn unload(&mut self) {
if self.image.is_some() || self.load_image_handle.is_some() {
println!("{} -> Unloading image", self.name);
}
self.image = None;
self.load_image_handle = None;
}
pub fn load(&mut self) {
if self.load_image_handle.is_none() && self.image.is_none() {
println!("{} -> Loading image", self.name);
self.load_image_handle = Some(Image::load(
self.path.clone(),
None,
self.output_profile.clone(),
));
}
}
pub fn display_loading_frame(ui: &mut egui::Ui) {
let spinner_size = ui.available_height() / 3.;
let inner_margin_y = (ui.available_height() - spinner_size) / 2.;
let inner_margin_x = (ui.available_width() - spinner_size) / 2.;
egui::Frame::NONE
.inner_margin(epaint::Marginf {
left: inner_margin_x,
right: inner_margin_x,
top: inner_margin_y,
bottom: inner_margin_y,
})
.show(ui, |ui| ui.add(egui::Spinner::new().size(spinner_size)));
}
pub fn set_display_name(&mut self, format: &str) -> String {
if format.is_empty() {
self.display_name = Some(self.name.clone());
return self.name.clone();
}
if let Some(img) = &self.image {
let display_name =
metadata::Metadata::format_string_with_metadata(format, &img.metadata);
self.display_name = Some(display_name.clone());
display_name
} else {
String::new()
}
}
pub fn get_display_name(&mut self, format: String) -> String {
match &self.display_name {
Some(dn) => dn.clone(),
None => self.set_display_name(&format),
}
}
pub fn is_loading(&self) -> bool {
self.load_image_handle.is_some()
}
}