use crate::{
image_window::{create_image_preview, create_meta_viewer, ImageWindow},
metadata::ImageMetadata,
sidebar::create_sidebar,
};
use egui::{mutex::Mutex, Align2, DroppedFile, FontId, RichText, TextStyle};
use egui_extras::{image::load_image_bytes, RetainedImage};
use std::{cell::RefCell, io::Cursor, rc::Rc};
#[derive(Default)]
pub struct MetadataTool {
pub windows: Vec<ImageWindow>,
pub dropped_files: Vec<egui::DroppedFile>,
pub toasts: egui_toast::Toasts,
}
pub static GLOB_COPIED_METADATA: once_cell::sync::Lazy<Mutex<Option<ImageMetadata>>> =
once_cell::sync::Lazy::new(|| Mutex::new(None));
fn configure_text_styles(ctx: &egui::Context) {
use egui::FontFamily::{Monospace, Proportional};
let mut style = (*ctx.style()).clone();
style.text_styles = [
(TextStyle::Heading, FontId::new(25.0, Proportional)),
(TextStyle::Body, FontId::new(16.0, Proportional)),
(TextStyle::Monospace, FontId::new(12.0, Monospace)),
(TextStyle::Button, FontId::new(12.0, Proportional)),
(TextStyle::Small, FontId::new(8.0, Proportional)),
]
.into();
ctx.set_style(style);
}
impl MetadataTool {
#[must_use]
pub fn new(cc: &eframe::CreationContext<'_>) -> Self {
configure_text_styles(&cc.egui_ctx);
let mut fonts = egui::FontDefinitions::default();
egui_phosphor::add_to_fonts(&mut fonts, egui_phosphor::Variant::Regular);
cc.egui_ctx.set_fonts(fonts);
let toasts = egui_toast::Toasts::new()
.anchor(Align2::RIGHT_BOTTOM, (-10.0, -10.0)) .direction(egui::Direction::BottomUp);
Self {
windows: Vec::new(),
dropped_files: Vec::new(),
toasts,
}
}
fn preview_files_being_dropped(ctx: &egui::Context) {
use egui::{Color32, Id, LayerId, Order};
if ctx.input(|i| i.raw.hovered_files.is_empty()) {
return;
}
let text = ctx.input(|i| {
let mut text = "Dropping files:\n".to_owned();
for file in &i.raw.hovered_files {
if let Some(path) = &file.path {
text += &format!("\n{}", path.display());
} else if !file.mime.is_empty() {
text += &format!("\n{}", file.mime);
} else {
text += "\nImage";
}
}
text
});
let painter =
ctx.layer_painter(LayerId::new(Order::Foreground, Id::new("file_drop_target")));
let screen_rect = ctx.screen_rect().shrink(10.0);
painter.rect_filled(screen_rect, 0.0, Color32::from_black_alpha(70));
painter.text(
screen_rect.center(),
Align2::CENTER_CENTER,
text,
TextStyle::Heading.resolve(&ctx.style()),
Color32::WHITE,
);
}
fn load_file_contents(file: &DroppedFile) -> Option<Vec<u8>> {
if let Some(path) = &file.path {
if let Ok(mut file) = std::fs::File::open(path) {
let mut buffer = Vec::new();
if std::io::Read::read_to_end(&mut file, &mut buffer).is_ok() {
return Some(buffer);
}
}
} else if let Some(bytes) = &file.bytes {
return Some(bytes.clone().to_vec());
}
None
}
pub fn load_files_or_err(&mut self, ui: &mut egui::Ui) {
if !self.dropped_files.is_empty() {
ui.group(|ui| {
for file in &self.dropped_files {
if let Some(bytes) = Self::load_file_contents(file) {
if bytes.is_empty() {
return;
}
let mut buffer: Vec<u8> = Vec::new();
let mut writer = Cursor::new(&mut buffer);
let bytes_reader = Cursor::new(&bytes);
let mut i = match image::load_from_memory_with_format(
&bytes,
image::ImageFormat::Png,
) {
Ok(image) => image,
Err(e) => {
ui.colored_label(
egui::Color32::RED,
format!("Error loading {}: {e}", file.name),
);
continue;
}
};
if let Ok(raw_dmi) = dmi::RawDmi::load(bytes_reader) {
let new_mwin = ImageWindow {
id: uuid::Uuid::new_v4(),
img: {
let h = (ui.available_height() * 1.85) as u32;
let w = (ui.available_width() * 1.85) as u32;
i = i.resize(w, h, image::imageops::FilterType::Nearest);
i.write_to(&mut writer, image::ImageFormat::Png).unwrap();
Rc::new(
RetainedImage::from_color_image(
"img",
load_image_bytes(&buffer).unwrap(),
)
.with_options(egui::TextureOptions::NEAREST),
)
},
dmi: raw_dmi.clone(),
metadata: Rc::new(ImageMetadata::new(raw_dmi.chunk_ztxt, file)),
is_open: RefCell::new(true),
};
self.windows.push(new_mwin);
}
}
}
});
}
}
}
impl eframe::App for MetadataTool {
fn update(&mut self, ctx: &egui::Context, _frame: &mut eframe::Frame) {
let tst = RefCell::new(&mut self.toasts);
for mwindow in &self.windows {
egui::Window::new(&mwindow.metadata.file_name)
.id(mwindow.id.to_string().into())
.open(&mut mwindow.is_open.borrow_mut())
.show(ctx, |ui| {
create_meta_viewer(mwindow, ui, &mwindow.metadata, &tst);
create_image_preview(mwindow, ui, ctx);
});
}
create_sidebar(self, ctx);
Self::preview_files_being_dropped(ctx);
egui::CentralPanel::default().show(ctx, |ui| {
ui.centered_and_justified(|ui| {
ui.heading(RichText::new("Drag & drop file(s) here").strong())
});
});
ctx.input(|i| {
if !i.raw.dropped_files.is_empty() {
self.dropped_files = i.raw.dropped_files.clone();
}
});
self.windows.retain(|w| *w.is_open.try_borrow().unwrap());
self.toasts.show(ctx)
}
}