diff --git a/Cargo.toml b/Cargo.toml index bc61c6f..3396efe 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -80,7 +80,7 @@ basis-universal = "0.3.1" heif = ["libheif-rs"] avif_native = ["avif-decode"] dav1d = ["libavif-image"] -default = ["turbo", "file_open", "avif_native", "update"] +default = ["turbo", "avif_native", "update"] file_open = ["rfd"] turbo = ["turbojpeg"] update = ["self_update"] diff --git a/src/appstate.rs b/src/appstate.rs index 93cfe3a..4203fad 100644 --- a/src/appstate.rs +++ b/src/appstate.rs @@ -88,6 +88,7 @@ pub struct OculanteState { pub redraw: bool, pub first_start: bool, pub toasts: Toasts, + pub filebrowser_id: Option } impl OculanteState { @@ -147,6 +148,7 @@ impl Default for OculanteState { redraw: Default::default(), first_start: true, toasts: Toasts::default().with_anchor(egui_notify::Anchor::BottomLeft), + filebrowser_id: None, } } } diff --git a/src/filebrowser.rs b/src/filebrowser.rs new file mode 100644 index 0000000..4ba95bf --- /dev/null +++ b/src/filebrowser.rs @@ -0,0 +1,179 @@ +use super::utils::SUPPORTED_EXTENSIONS; +use anyhow::{Context, Result}; +use dirs; +use egui_phosphor::variants::regular::*; +use notan::egui::{self, *}; +use std::io::Write; + +use std::{ + fs::{self, read_to_string, File}, + path::{Path, PathBuf}, +}; + +fn load_recent_dir() -> Result { + Ok(PathBuf::from(read_to_string( + dirs::cache_dir() + .context("Can't get temp dir")? + .join(".efd_history"), + )?)) +} + +fn save_recent_dir(p: &Path) -> Result<()> { + let p = if p.is_file() { + p.parent().context("Can't get parent")?.to_path_buf() + } else { + p.to_path_buf() + }; + + let mut f = File::create( + dirs::cache_dir() + .context("Can't get temp dir")? + .join(".efd_history"), + )?; + write!(f, "{}", p.to_string_lossy())?; + Ok(()) +} + +pub fn browse)>(save: bool, 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 open = true; + + egui::Window::new("Browse") + .anchor(Align2::CENTER_CENTER, [0.0, 0.0]) + .collapsible(false) + .open(&mut open) + .resizable(true) + .default_width(500.) + .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.vertical(|ui| { + if ui.button(ARROW_BEND_LEFT_UP).clicked() { + if let Some(d) = path.parent() { + let p = d.to_path_buf(); + path = p; + } + } + + egui::ScrollArea::new([false, true]) + // .max_width(500.) + .min_scrolled_height(500.) + .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(), + ) + }) + { + 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(); + } + } 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()); + callback(Some(&de.path())); + // self.result = Some(de.path().to_path_buf()); + } + } + ui.end_row(); + } + }); + } + None => { + ui.label("no contents"); + } + }); + }); + }); + + ctx.data_mut(|w| w.insert_temp(Id::new("FBPATH"), path)); + }); + if !open { + ctx.memory_mut(|w| w.close_popup()); + } +} diff --git a/src/image_editing.rs b/src/image_editing.rs index 2e80877..bc52edd 100644 --- a/src/image_editing.rs +++ b/src/image_editing.rs @@ -3,6 +3,7 @@ use std::fmt; use std::num::NonZeroU32; use std::path::{Path, PathBuf}; +use crate::filebrowser; use crate::paint::PaintStroke; use crate::ui::EguiExt; @@ -259,6 +260,27 @@ impl ImageOperation { } }); + #[cfg(not(feature = "file_open"))] + { + if ui.button("Load lut").clicked() { + ui.ctx().memory_mut(|w| w.open_popup(Id::new("LUT"))); + } + + if ui.ctx().memory(|w| w.is_popup_open(Id::new("LUT"))) { + filebrowser::browse( + false, + |p| { + if let Some(p) = p { + *lut_name = p.to_string_lossy().to_string(); + } + ui.ctx().memory_mut(|w| w.close_popup()); + x.mark_changed(); + }, + ui.ctx(), + ); + } + } + #[cfg(feature = "file_open")] if ui .button("Load from disk") @@ -281,6 +303,7 @@ impl ImageOperation { } x.mark_changed(); } + ui.label("Find more LUTs here:"); ui.hyperlink_to( diff --git a/src/main.rs b/src/main.rs index a5392f7..a76d2c9 100644 --- a/src/main.rs +++ b/src/main.rs @@ -31,6 +31,7 @@ use utils::*; mod appstate; mod image_loader; use appstate::*; +mod filebrowser; pub mod ktx2_loader; // mod events; @@ -418,11 +419,16 @@ fn event(app: &mut App, state: &mut OculanteState, evt: Event) { } } } - #[cfg(feature = "file_open")] if key_pressed(app, state, Browse) { state.redraw = true; + #[cfg(feature = "file_open")] browse_for_image_path(state); + #[cfg(not(feature = "file_open"))] + { + state.filebrowser_id = Some("OPEN_SHORTCUT".into()); + } } + if key_pressed(app, state, NextImage) { if state.is_loaded { next_image(state) @@ -712,7 +718,7 @@ fn drawe(app: &mut App, gfx: &mut Graphics, plugins: &mut Plugins, state: &mut O if let Ok(p) = state.load_channel.1.try_recv() { state.is_loaded = false; state.current_image = None; - state.player.load(&p, state.message_channel.0.clone()); + state.player.load(&p, state.message_channel.0.clone()); if let Some(dir) = p.parent() { state.persistent_settings.last_open_directory = dir.to_path_buf(); } @@ -964,6 +970,25 @@ fn drawe(app: &mut App, gfx: &mut Graphics, plugins: &mut Plugins, state: &mut O // ctx.request_repaint_after(Duration::from_secs(1)); state.toasts.show(ctx); + if let Some(id) = state.filebrowser_id.take() { + ctx.memory_mut(|w| w.open_popup(Id::new(id))); + } + #[cfg(not(feature = "file_open"))] + { + if ctx.memory(|w| w.is_popup_open(Id::new("OPEN_SHORTCUT"))) { + filebrowser::browse( + false, + |p| { + if let Some(p) = p { + let _ = state.load_channel.0.clone().send(p.to_path_buf()); + } + ctx.memory_mut(|w| w.close_popup()); + }, + ctx, + ); + } + } + if !state.persistent_settings.zen_mode { egui::TopBottomPanel::top("menu") .min_height(30.) diff --git a/src/ui.rs b/src/ui.rs index fb10de2..baec8f9 100644 --- a/src/ui.rs +++ b/src/ui.rs @@ -1,5 +1,4 @@ -#[cfg(feature = "file_open")] -use crate::browse_for_image_path; +use crate::{filebrowser}; use crate::{ appstate::{ImageGeometry, Message, OculanteState}, image_editing::{process_pixels, Channel, GradientStop, ImageOperation, ScaleFilter}, @@ -1789,14 +1788,34 @@ pub fn main_menu(ui: &mut Ui, state: &mut OculanteState, app: &mut App, gfx: &mu // ui.label("Channels"); - #[cfg(feature = "file_open")] + if unframed_button(FOLDER, ui) .on_hover_text("Browse for image") .clicked() { - browse_for_image_path(state) + #[cfg(feature = "file_open")] + crate::browse_for_image_path(state); + #[cfg(not(feature = "file_open"))] + ui.ctx().memory_mut(|w| w.open_popup(Id::new("OPEN"))); + } + + #[cfg(not(feature = "file_open"))] + { + if ui.ctx().memory(|w| w.is_popup_open(Id::new("OPEN"))) { + filebrowser::browse( + false, + |p| { + if let Some(p) = p { + let _ = state.load_channel.0.clone().send(p.to_path_buf()); + } + ui.ctx().memory_mut(|w| w.close_popup()); + }, + ui.ctx(), + ); + } } + let mut changed_channels = false; if key_pressed(app, state, RedChannel) {