diff --git a/src/appstate.rs b/src/appstate.rs index 4203fad..5d7d13e 100644 --- a/src/appstate.rs +++ b/src/appstate.rs @@ -88,7 +88,7 @@ pub struct OculanteState { pub redraw: bool, pub first_start: bool, pub toasts: Toasts, - pub filebrowser_id: Option + pub filebrowser_id: Option, } impl OculanteState { diff --git a/src/filebrowser.rs b/src/filebrowser.rs index d994615..d44f88c 100644 --- a/src/filebrowser.rs +++ b/src/filebrowser.rs @@ -1,4 +1,3 @@ -use super::utils::SUPPORTED_EXTENSIONS; use anyhow::{Context, Result}; use dirs; use egui_phosphor::variants::regular::*; @@ -34,15 +33,16 @@ fn save_recent_dir(p: &Path) -> Result<()> { Ok(()) } -pub fn browse_modal)>(save: bool, mut callback: F, ctx: &egui::Context) { +pub fn browse_modal( + save: bool, + filter: &[&str], + mut callback: F, + ctx: &egui::Context, +) { let mut path = ctx .data(|r| r.get_temp::(Id::new("FBPATH"))) .unwrap_or(load_recent_dir().unwrap_or_default()); - let mut filename = ctx - .data(|r| r.get_temp::(Id::new("FBFILENAME"))) - .unwrap_or(String::from("unnamed.png")); - let mut open = true; egui::Window::new(if save { "Save" } else { "Open" }) @@ -54,185 +54,214 @@ pub fn browse_modal)>(save: bool, mut callback: F, ctx .default_height(600.) // .auto_sized() .show(ctx, |ui| { - let d = fs::read_dir(&path).ok(); - ui.horizontal(|ui| { - ui.allocate_ui_with_layout( - Vec2::new(120., ui.available_height()), - Layout::top_down_justified(Align::LEFT), - |ui| { - if let Some(d) = dirs::desktop_dir() { - if ui.button(format!("{DESKTOP} Desktop")).clicked() { - path = d; - } - } - if let Some(d) = dirs::home_dir() { - if ui.button(format!("{HOUSE} Home")).clicked() { - path = d; - } - } - if let Some(d) = dirs::document_dir() { - if ui.button(format!("{FILE} Documents")).clicked() { - path = d; - } - } - if let Some(d) = dirs::download_dir() { - if ui.button(format!("{DOWNLOAD} Downloads")).clicked() { - path = d; - } - } - if let Some(d) = dirs::picture_dir() { - if ui.button(format!("{IMAGES} Pictures")).clicked() { - path = d; - } - } - }, - ); - ui.separator(); - - ui.vertical(|ui| { - if ui.button(ARROW_BEND_LEFT_UP).clicked() { - if let Some(d) = path.parent() { - let p = d.to_path_buf(); - path = p; - } + browse( + &mut path, + filter, + save, + |p| { + callback(p); + ctx.memory_mut(|w| w.close_popup()); + }, + ui, + ); + + if ui.button("Cancel").clicked() { + ui.ctx().memory_mut(|w| w.close_popup()); + } + + ctx.data_mut(|w| w.insert_temp(Id::new("FBPATH"), path)); + }); + if !open { + ctx.memory_mut(|w| w.close_popup()); + } +} + +pub fn browse( + path: &mut PathBuf, + filter: &[&str], + save: bool, + mut callback: F, + ui: &mut Ui, +) { + let mut filename = ui + .ctx() + .data(|r| r.get_temp::(Id::new("FBFILENAME"))) + .unwrap_or(String::from("unnamed.png")); + + let d = fs::read_dir(&path).ok(); + ui.horizontal(|ui| { + ui.allocate_ui_with_layout( + Vec2::new(120., ui.available_height()), + Layout::top_down_justified(Align::LEFT), + |ui| { + if let Some(d) = dirs::desktop_dir() { + if ui.button(format!("{DESKTOP} Desktop")).clicked() { + *path = d; + } + } + if let Some(d) = dirs::home_dir() { + if ui.button(format!("{HOUSE} Home")).clicked() { + *path = d; + } + } + if let Some(d) = dirs::document_dir() { + if ui.button(format!("{FILE} Documents")).clicked() { + *path = d; } + } + if let Some(d) = dirs::download_dir() { + if ui.button(format!("{DOWNLOAD} Downloads")).clicked() { + *path = d; + } + } + if let Some(d) = dirs::picture_dir() { + if ui.button(format!("{IMAGES} Pictures")).clicked() { + *path = d; + } + } + }, + ); + ui.separator(); - ui.separator(); - - egui::ScrollArea::new([false, true]) - // .max_width(500.) - .min_scrolled_height(200.) - .auto_shrink([true, false]) - .show(ui, |ui| match d { - Some(contents) => { - egui::Grid::new("browser") - .striped(true) - .num_columns(0) - .min_col_width(ui.available_width()) - .show(ui, |ui| { - for de in contents - .into_iter() - .flat_map(|x| x) - .filter(|de| { - !de.file_name().to_string_lossy().starts_with(".") - }) - .filter(|de| { - de.path().is_dir() - || SUPPORTED_EXTENSIONS.contains( - &de.path() - .extension() - .map(|ext| { - ext.to_string_lossy().to_string() - }) - .unwrap_or_default() - .to_lowercase() - .as_str(), - ) - }) + ui.vertical(|ui| { + if ui.button(ARROW_BEND_LEFT_UP).clicked() { + if let Some(d) = path.parent() { + let p = d.to_path_buf(); + *path = p; + } + } + + ui.separator(); + + egui::ScrollArea::new([false, true]) + .max_width(100.) + .min_scrolled_height(400.) + .auto_shrink([true, false]) + .show(ui, |ui| match d { + Some(contents) => { + egui::Grid::new("browser") + .striped(true) + .num_columns(0) + .min_col_width(ui.available_width()) + .show(ui, |ui| { + let mut entries = contents + .into_iter() + .flat_map(|x| x) + .filter(|de| !de.file_name().to_string_lossy().starts_with(".")) + .filter(|de| { + de.path().is_dir() + || filter.contains( + &de.path() + .extension() + .map(|ext| ext.to_string_lossy().to_string()) + .unwrap_or_default() + .to_lowercase() + .as_str(), + ) + }) + .collect::>(); + + entries.sort_by(|a, b| { + a.file_name() + .to_string_lossy() + .to_lowercase() + .cmp(&b.file_name().to_string_lossy().to_lowercase()) + }); + + for de in entries { + if de.path().is_dir() { + if ui + .add( + egui::Button::new(format!( + "{FOLDER} {}", + de.file_name() + .to_string_lossy() + .chars() + .take(50) + .collect::() + )) + .frame(false), + ) + .clicked() { - if de.path().is_dir() { - if ui - .add( - egui::Button::new(format!( - "{FOLDER} {}", - de.file_name() - .to_string_lossy() - .chars() - .take(50) - .collect::() - )) - .frame(false), - ) - .clicked() - { - path = de.path(); - } + *path = de.path(); + } + } else { + if ui + .add( + egui::Button::new(format!( + "{IMAGE_SQUARE} {}", + de.file_name() + .to_string_lossy() + .chars() + .take(50) + .collect::() + )) + .frame(false), + ) + .clicked() + { + _ = save_recent_dir(&de.path()); + if !save { + callback(&de.path()); } else { - if ui - .add( - egui::Button::new(format!( - "{IMAGE_SQUARE} {}", - de.file_name() - .to_string_lossy() - .chars() - .take(50) - .collect::() - )) - .frame(false), + filename = de + .path() + .to_path_buf() + .file_name() + .map(|f| f.to_string_lossy().to_string()) + .unwrap_or_default(); + ui.ctx().data_mut(|w| { + w.insert_temp( + Id::new("FBFILENAME"), + filename.clone(), ) - .clicked() - { - _ = save_recent_dir(&de.path()); - if !save { - callback(Some(&de.path())); - } else { - filename = de - .path() - .to_path_buf() - .file_name() - .map(|f| { - f.to_string_lossy().to_string() - }) - .unwrap_or_default(); - ui.ctx().data_mut(|w| { - w.insert_temp( - Id::new("FBFILENAME"), - filename.clone(), - ) - }); - } - // self.result = Some(de.path().to_path_buf()); - } + }); } - ui.end_row(); + // self.result = Some(de.path().to_path_buf()); } - }); - } - None => { - ui.label("no contents"); - } - }); - ui.spacing(); - ui.separator(); - - if !save { - if ui.button("Cancel").clicked() { - ctx.memory_mut(|w| w.close_popup()); - } + } + ui.end_row(); + } + }); } + None => { + ui.label("no contents"); + } + }); + ui.spacing(); + ui.separator(); - if save { - ui.horizontal(|ui| { - ui.label("Filename"); - if ui - .add( - egui::TextEdit::singleline(&mut filename) - .desired_width(f32::INFINITY), - ) - .changed() - { - ui.ctx().data_mut(|w| { - w.insert_temp(Id::new("FBFILENAME"), filename.clone()) - }); - } - }); - - ui.horizontal(|ui| { - if ui.button("Save").clicked() { - callback(Some(&path.join(filename))); - } - if ui.button("Cancel").clicked() { - ctx.memory_mut(|w| w.close_popup()); - } - }); + if save { + ui.horizontal(|ui| { + ui.label("Filename"); + ui.add( + egui::TextEdit::singleline(&mut filename) + .desired_width(ui.available_width() - 10.), + ); + }); + + ui.horizontal(|ui| { + let ext = Path::new(&filename) + .extension() + .map(|e| e.to_string_lossy().to_string()) + .unwrap_or_default(); + for f in filter { + if ui.selectable_label(&ext == f, f.to_string()).clicked() { + filename = Path::new(&filename) + .with_extension(f) + .to_string_lossy() + .to_string(); + } } }); - }); - ctx.data_mut(|w| w.insert_temp(Id::new("FBPATH"), path)); + ui.ctx() + .data_mut(|w| w.insert_temp(Id::new("FBFILENAME"), filename.clone())); + if ui.button("Save").clicked() { + callback(&path.join(filename)); + } + } }); - if !open { - ctx.memory_mut(|w| w.close_popup()); - } + }); } - diff --git a/src/image_editing.rs b/src/image_editing.rs index bca03b4..326ffcd 100644 --- a/src/image_editing.rs +++ b/src/image_editing.rs @@ -1,11 +1,11 @@ use std::collections::HashMap; use std::fmt; use std::num::NonZeroU32; -use std::path::{Path, PathBuf}; +use std::path::Path; -use crate::filebrowser; use crate::paint::PaintStroke; use crate::ui::EguiExt; +use crate::{filebrowser, SUPPORTED_EXTENSIONS}; use anyhow::Result; use evalexpr::*; @@ -225,11 +225,6 @@ impl ImageOperation { Self::Expression(expr) => ui.text_edit_singleline(expr), Self::LUT(lut_name) => { ui.scope(|ui| { - // let last_folder: &mut PathBuf = ui.ctx().data_mut(|w| w.get_temp_mut_or_default::(Id::new("lutsrc"))); - let last_folder: Option = ui - .ctx() - .data_mut(|w| w.get_persisted::(Id::new("lutsrc"))); - let mut x = ui.allocate_response(vec2(0.0, 0.0), Sense::click_and_drag()); ui.vertical(|ui| { let lut_fname = Path::new(lut_name) @@ -269,11 +264,9 @@ impl ImageOperation { if ui.ctx().memory(|w| w.is_popup_open(Id::new("LUT"))) { filebrowser::browse_modal( false, + SUPPORTED_EXTENSIONS, |p| { - if let Some(p) = p { - *lut_name = p.to_string_lossy().to_string(); - } - ui.ctx().memory_mut(|w| w.close_popup()); + *lut_name = p.to_string_lossy().to_string(); x.mark_changed(); }, ui.ctx(), @@ -282,34 +275,44 @@ impl ImageOperation { } #[cfg(feature = "file_open")] - if ui - .button("Load from disk") - .on_hover_ui(|ui| { - ui.label("Load Hald CLUT"); - }) - .clicked() { - if let Some(lut_file) = rfd::FileDialog::new() - .set_directory(last_folder.unwrap_or_default()) - .pick_file() + // let last_folder: &mut PathBuf = ui.ctx().data_mut(|w| w.get_temp_mut_or_default::(Id::new("lutsrc"))); + let last_folder: Option = ui.ctx().data_mut(|w| { + w.get_persisted::(Id::new("lutsrc")) + }); + if ui + .button("Load from disk") + .on_hover_ui(|ui| { + ui.label("Load Hald CLUT"); + }) + .clicked() { - *lut_name = lut_file.to_string_lossy().to_string(); - let parent = lut_file - .parent() - .map(|p| p.to_path_buf()) - .unwrap_or_default(); - ui.ctx() - .data_mut(|w| w.insert_persisted(Id::new("lutsrc"), parent)); + if let Some(lut_file) = rfd::FileDialog::new() + .set_directory(last_folder.unwrap_or_default()) + .pick_file() + { + *lut_name = lut_file.to_string_lossy().to_string(); + let parent = lut_file + .parent() + .map(|p| p.to_path_buf()) + .unwrap_or_default(); + ui.ctx().data_mut(|w| { + w.insert_persisted(Id::new("lutsrc"), parent) + }); + } + x.mark_changed(); } - x.mark_changed(); } ui.label("Find more LUTs here:"); - ui.hyperlink_to( - "Cédric Eberhardt's collection", - "https://github.com/cedeber/hald-clut", - ); + if ui + .link("Cédric Eberhardt's collection") + .on_hover_text("You can find more interesting LUTs here to use") + .clicked() + { + _ = webbrowser::open("https://github.com/cedeber/hald-clut"); + } }); x }) diff --git a/src/main.rs b/src/main.rs index 48c2ab7..628e669 100644 --- a/src/main.rs +++ b/src/main.rs @@ -978,11 +978,9 @@ fn drawe(app: &mut App, gfx: &mut Graphics, plugins: &mut Plugins, state: &mut O if ctx.memory(|w| w.is_popup_open(Id::new("OPEN_SHORTCUT"))) { filebrowser::browse_modal( false, + SUPPORTED_EXTENSIONS, |p| { - if let Some(p) = p { - let _ = state.load_channel.0.clone().send(p.to_path_buf()); - } - ctx.memory_mut(|w| w.close_popup()); + let _ = state.load_channel.0.clone().send(p.to_path_buf()); }, ctx, ); diff --git a/src/ui.rs b/src/ui.rs index 4735c4b..1d86a54 100644 --- a/src/ui.rs +++ b/src/ui.rs @@ -1,4 +1,3 @@ -use crate::{filebrowser}; use crate::{ appstate::{ImageGeometry, Message, OculanteState}, image_editing::{process_pixels, Channel, GradientStop, ImageOperation, ScaleFilter}, @@ -13,6 +12,7 @@ use crate::{ }, FrameSource, }; +use crate::{filebrowser, SUPPORTED_EXTENSIONS}; const ICON_SIZE: f32 = 24.; @@ -1158,34 +1158,6 @@ pub fn edit_ui(app: &mut App, ctx: &Context, state: &mut OculanteState, gfx: &mu } } - #[cfg(not(feature = "file_open"))] - ui.horizontal(|ui| { - ui.label("File:"); - if let Some(p) = &mut state.current_path { - if let Some(pstem) = p.file_stem() { - let mut stem = pstem.to_string_lossy().to_string(); - if ui.text_edit_singleline(&mut stem).changed() { - if let Some(parent) = p.parent() { - *p = parent - .join(stem) - .with_extension(&state.edit_state.export_extension); - } - } - } - } - egui::ComboBox::from_id_source("ext") - .selected_text(&state.edit_state.export_extension) - .width(ui.available_width()) - .show_ui(ui, |ui| { - for f in ["png", "jpg", "bmp", "webp", "tif", "tga"] { - ui.selectable_value( - &mut state.edit_state.export_extension, - f.to_string(), - f, - ); - } - }); - }); #[cfg(feature = "turbo")] jpg_lossless_ui(state, ui); @@ -1262,26 +1234,23 @@ pub fn edit_ui(app: &mut App, ctx: &Context, state: &mut OculanteState, gfx: &mu } + if ctx.memory(|w| w.is_popup_open(Id::new("SAVE"))) { - let start_directory = state.persistent_settings.last_open_directory.clone(); - let image_to_save = state.edit_state.result_pixel_op.clone(); let msg_sender = state.message_channel.0.clone(); - let err_sender = state.message_channel.0.clone(); - let image_info = state.image_info.clone(); filebrowser::browse_modal( true, + &["png", "jpg", "bmp", "webp", "tif", "tga"], |p| { - if let Some(p) = p { - match image_to_save + match state.edit_state.result_pixel_op .save(&p) { Ok(_) => { _ = msg_sender.send(Message::Saved(p.clone())); debug!("Saved to {}", p.display()); // Re-apply exif - if let Some(info) = &image_info { + if let Some(info) = &state.image_info { debug!("Extended image info present"); // before doing anything, make sure we have raw exif data @@ -1289,7 +1258,8 @@ pub fn edit_ui(app: &mut App, ctx: &Context, state: &mut OculanteState, gfx: &mu if let Err(e) = fix_exif(&p, info.raw_exif.clone()) { error!("{e}"); } else { - info!("Saved EXIF.") + info!("Saved EXIF."); + _ = msg_sender.send(Message::Info("Exif metadata was saved to file".into())); } } else { debug!("No raw exif"); @@ -1297,29 +1267,13 @@ pub fn edit_ui(app: &mut App, ctx: &Context, state: &mut OculanteState, gfx: &mu } } Err(e) => { - _ = err_sender.send(Message::err(&format!("Error: Could not save: {e}"))); + _ = msg_sender.send(Message::err(&format!("Error: Could not save: {e}"))); } - } - } - ctx.memory_mut(|w| w.close_popup()); }, ctx, ); } - - - - - - - - - - - ui.ctx().request_repaint(); - - } if let Some(p) = &state.current_path { @@ -1855,7 +1809,6 @@ pub fn main_menu(ui: &mut Ui, state: &mut OculanteState, app: &mut App, gfx: &mu // ui.label("Channels"); - if unframed_button(FOLDER, ui) .on_hover_text("Browse for image") .clicked() @@ -1871,10 +1824,9 @@ pub fn main_menu(ui: &mut Ui, state: &mut OculanteState, app: &mut App, gfx: &mu if ui.ctx().memory(|w| w.is_popup_open(Id::new("OPEN"))) { filebrowser::browse_modal( false, + SUPPORTED_EXTENSIONS, |p| { - if let Some(p) = p { - let _ = state.load_channel.0.clone().send(p.to_path_buf()); - } + let _ = state.load_channel.0.clone().send(p.to_path_buf()); ui.ctx().memory_mut(|w| w.close_popup()); }, ui.ctx(), @@ -1882,7 +1834,6 @@ pub fn main_menu(ui: &mut Ui, state: &mut OculanteState, app: &mut App, gfx: &mu } } - let mut changed_channels = false; if key_pressed(app, state, RedChannel) {