diff --git a/Cargo.lock b/Cargo.lock
index 740fcac..f163543 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -4206,8 +4206,8 @@ dependencies = [
"self_update",
"serde",
"serde_json",
- "strum 0.25.0",
- "strum_macros 0.25.3",
+ "strum 0.26.1",
+ "strum_macros 0.26.1",
"thiserror",
"tiff",
"tiny-skia 0.9.1",
@@ -5014,9 +5014,9 @@ dependencies = [
[[package]]
name = "rfd"
-version = "0.12.1"
+version = "0.13.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "3c9e7b57df6e8472152674607f6cc68aa14a748a3157a857a94f516e11aeacc2"
+checksum = "c0d8ab342bcc5436e04d3a4c1e09e17d74958bfaddf8d5fad6f85607df0f994f"
dependencies = [
"block",
"dispatch",
@@ -5630,11 +5630,11 @@ dependencies = [
[[package]]
name = "strum"
-version = "0.25.0"
+version = "0.26.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "290d54ea6f91c969195bdbcd7442c8c2a2ba87da8bf60a7ee86a235d4bc1e125"
+checksum = "723b93e8addf9aa965ebe2d11da6d7540fa2283fcea14b3371ff055f7ba13f5f"
dependencies = [
- "strum_macros 0.25.3",
+ "strum_macros 0.26.1",
]
[[package]]
@@ -5652,9 +5652,9 @@ dependencies = [
[[package]]
name = "strum_macros"
-version = "0.25.3"
+version = "0.26.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "23dc1fa9ac9c169a78ba62f0b841814b7abae11bdd047b9c58f893439e309ea0"
+checksum = "7a3417fc93d76740d974a01654a09777cb500428cc874ca9f45edfe0c4d4cd18"
dependencies = [
"heck",
"proc-macro2",
diff --git a/Cargo.toml b/Cargo.toml
index f5c24e5..d045446 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -47,13 +47,13 @@ rand = "0.8"
rand_chacha = "0.3"
rayon = "1.7"
resvg = "0.33.0"
-rfd = {version = "0.12", optional = true}
+rfd = {version = "0.13", optional = true}
rgb = "0.8"
self_update = {version = "0.39", default-features = false, features = ["rustls"], optional = true}
serde = {version = "1.0", features = ["derive"]}
serde_json = "1.0"
-strum = {version = "0.25", features = ["derive"]}
-strum_macros = "0.25"
+strum = {version = "0.26", features = ["derive"]}
+strum_macros = "0.26"
tiny-skia = "0.9"
turbojpeg = {version = "0.5", features = ["image"], optional = true}
usvg = "0.33.0"
@@ -81,7 +81,7 @@ webp-animation = { version = "0.9.0", features = ["static"] }
heif = ["libheif-rs"]
avif_native = ["avif-decode"]
dav1d = ["libavif-image"]
-default = ["turbo", "file_open", "avif_native", "update"]
+default = ["turbo", "avif_native", "file_open", "update"]
file_open = ["rfd"]
turbo = ["turbojpeg"]
update = ["self_update"]
diff --git a/res/logo.svg b/res/logo.svg
index f195707..546b1f1 100644
--- a/res/logo.svg
+++ b/res/logo.svg
@@ -1,358 +1 @@
-
-
+
\ No newline at end of file
diff --git a/res/net.gif b/res/net.gif
index e24547a..3656b7b 100644
Binary files a/res/net.gif and b/res/net.gif differ
diff --git a/res/oculante.png b/res/oculante.png
index 1ff2a91..65fcf17 100644
Binary files a/res/oculante.png and b/res/oculante.png differ
diff --git a/res/premult.png b/res/premult.png
index f2afbcd..2b2cc0f 100644
Binary files a/res/premult.png and b/res/premult.png differ
diff --git a/res/screenshot_1.png b/res/screenshot_1.png
index d296729..7650ae4 100644
Binary files a/res/screenshot_1.png and b/res/screenshot_1.png differ
diff --git a/res/screenshot_exif.png b/res/screenshot_exif.png
index c485c40..3c62a71 100644
Binary files a/res/screenshot_exif.png and b/res/screenshot_exif.png differ
diff --git a/src/appstate.rs b/src/appstate.rs
index 93cfe3a..5d7d13e 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..d44f88c
--- /dev/null
+++ b/src/filebrowser.rs
@@ -0,0 +1,267 @@
+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_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 open = true;
+
+ egui::Window::new(if save { "Save" } else { "Open" })
+ .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| {
+ 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.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()
+ {
+ *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 {
+ 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();
+ }
+ });
+ }
+ None => {
+ ui.label("no contents");
+ }
+ });
+ ui.spacing();
+ ui.separator();
+
+ 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();
+ }
+ }
+ });
+
+ ui.ctx()
+ .data_mut(|w| w.insert_temp(Id::new("FBFILENAME"), filename.clone()));
+ if ui.button("Save").clicked() {
+ callback(&path.join(filename));
+ }
+ }
+ });
+ });
+}
diff --git a/src/image_editing.rs b/src/image_editing.rs
index 2e80877..9399b45 100644
--- a/src/image_editing.rs
+++ b/src/image_editing.rs
@@ -1,10 +1,12 @@
use std::collections::HashMap;
use std::fmt;
use std::num::NonZeroU32;
-use std::path::{Path, PathBuf};
+use std::path::Path;
use crate::paint::PaintStroke;
use crate::ui::EguiExt;
+#[cfg(not(feature = "file_open"))]
+use crate::{filebrowser, SUPPORTED_EXTENSIONS};
use anyhow::Result;
use evalexpr::*;
@@ -224,11 +226,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)
@@ -259,34 +256,64 @@ 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_modal(
+ false,
+ SUPPORTED_EXTENSIONS,
+ |p| {
+ *lut_name = p.to_string_lossy().to_string();
+ x.mark_changed();
+ },
+ ui.ctx(),
+ );
+ }
+ }
+
#[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 ce394cf..cf86c5a 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -31,6 +31,8 @@ use utils::*;
mod appstate;
mod image_loader;
use appstate::*;
+#[cfg(not(feature = "file_open"))]
+mod filebrowser;
pub mod ktx2_loader;
// mod events;
@@ -196,7 +198,7 @@ fn init(gfx: &mut Graphics, plugins: &mut Plugins) -> OculanteState {
Err(e) => {
warn!("Settings failed to load: {e}. This may happen after application updates. Generating a fresh file.");
state.persistent_settings = Default::default();
- state.persistent_settings.save();
+ state.persistent_settings.save_blocking();
}
}
@@ -420,11 +422,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)
@@ -719,7 +726,6 @@ fn drawe(app: &mut App, gfx: &mut Graphics, plugins: &mut Plugins, state: &mut O
state.persistent_settings.last_open_directory = dir.to_path_buf();
}
state.current_path = Some(p);
- _ = state.persistent_settings.save();
}
// check if a new texture has been sent
@@ -815,6 +821,9 @@ fn drawe(app: &mut App, gfx: &mut Graphics, plugins: &mut Plugins, state: &mut O
FrameSource::Animation => {
state.redraw = true;
}
+ FrameSource::CompareResult => {
+ state.redraw = false;
+ }
}
if let Some(tex) = &mut state.current_texture {
@@ -964,6 +973,23 @@ 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_modal(
+ false,
+ SUPPORTED_EXTENSIONS,
+ |p| {
+ let _ = state.load_channel.0.clone().send(p.to_path_buf());
+ },
+ ctx,
+ );
+ }
+ }
+
if !state.persistent_settings.zen_mode {
egui::TopBottomPanel::top("menu")
.min_height(30.)
diff --git a/src/settings.rs b/src/settings.rs
index c665fcd..74f8f99 100644
--- a/src/settings.rs
+++ b/src/settings.rs
@@ -97,7 +97,7 @@ impl PersistentSettings {
}
// save settings in a thread so we don't block
- pub fn save(&self) {
+ pub fn save_threaded(&self) {
let settings = self.clone();
std::thread::spawn(move || {
_ = save(&settings);
@@ -112,7 +112,7 @@ impl PersistentSettings {
fn save(s: &PersistentSettings) -> Result<()> {
let local_dir = dirs::data_local_dir().ok_or(anyhow!("Can't get local dir"))?;
let f = File::create(local_dir.join(".oculante"))?;
- Ok(serde_json::to_writer_pretty(f, s)?)
+ Ok(serde_json::to_writer(f, s)?)
}
pub fn set_system_theme(ctx: &Context) {
diff --git a/src/ui.rs b/src/ui.rs
index 9509b03..5292c76 100644
--- a/src/ui.rs
+++ b/src/ui.rs
@@ -1,5 +1,3 @@
-#[cfg(feature = "file_open")]
-use crate::browse_for_image_path;
use crate::{
appstate::{ImageGeometry, Message, OculanteState},
image_editing::{process_pixels, Channel, GradientStop, ImageOperation, ScaleFilter},
@@ -12,7 +10,10 @@ use crate::{
load_image_from_path, next_image, prev_image, send_extended_info, set_title, solo_channel,
toggle_fullscreen, unpremult, ColorChannel, ImageExt,
},
+ FrameSource,
};
+#[cfg(not(feature = "file_open"))]
+use crate::{filebrowser, SUPPORTED_EXTENSIONS};
const ICON_SIZE: f32 = 24.;
@@ -177,14 +178,12 @@ impl EguiExt for Ui {
}
}
-
/// Proof-of-concept funtion to draw texture completely with egui
#[allow(unused)]
pub fn image_ui(ctx: &Context, state: &mut OculanteState, gfx: &mut Graphics) {
if let Some(texture) = &state.current_texture {
let tex_id = gfx.egui_register_texture(texture);
-
let image_rect = Rect::from_center_size(
Pos2::new(
state.image_geometry.offset.x
@@ -207,9 +206,8 @@ pub fn image_ui(ctx: &Context, state: &mut OculanteState, gfx: &mut Graphics) {
tex_id.id,
image_rect,
Rect::from_min_max(pos2(0.0, 0.0), pos2(1.0, 1.0)),
- Color32::WHITE
+ Color32::WHITE,
);
-
}
// state.image_geometry.scale;
@@ -385,22 +383,17 @@ pub fn info_ui(ctx: &Context, state: &mut OculanteState, gfx: &mut Graphics) {
for (path, geo) in compare_list {
if ui.selectable_label(p==&path, path.file_name().map(|f| f.to_string_lossy().to_string()).unwrap_or_default().to_string()).clicked(){
state.image_geometry = geo.clone();
- state.is_loaded = false;
- state.current_image = None;
state
.player
- .load(&path, state.message_channel.0.clone());
+ .load_advanced(&path, Some(FrameSource::CompareResult), state.message_channel.0.clone());
state.current_path = Some(path);
- state.persistent_settings.keep_view = true;
}
}
if ui.button("Clear").clicked() {
state.compare_list.clear();
}
}
- if state.is_loaded {
- state.persistent_settings.keep_view = false;
- }
+
});
});
@@ -1166,34 +1159,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);
@@ -1263,6 +1228,55 @@ pub fn edit_ui(app: &mut App, ctx: &Context, state: &mut OculanteState, gfx: &mu
}
}
+ #[cfg(not(feature = "file_open"))]
+ if state.current_image.is_some() {
+ if ui.button(format!("{FLOPPY_DISK} Save as...")).clicked() {
+ ui.ctx().memory_mut(|w| w.open_popup(Id::new("SAVE")));
+
+ }
+
+
+ if ctx.memory(|w| w.is_popup_open(Id::new("SAVE"))) {
+
+
+ let msg_sender = state.message_channel.0.clone();
+
+ filebrowser::browse_modal(
+ true,
+ &["png", "jpg", "bmp", "webp", "tif", "tga"],
+ |p| {
+ 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) = &state.image_info {
+ debug!("Extended image info present");
+
+ // before doing anything, make sure we have raw exif data
+ if info.raw_exif.is_some() {
+ if let Err(e) = fix_exif(&p, info.raw_exif.clone()) {
+ error!("{e}");
+ } else {
+ info!("Saved EXIF.");
+ _ = msg_sender.send(Message::Info("Exif metadata was saved to file".into()));
+ }
+ } else {
+ debug!("No raw exif");
+ }
+ }
+ }
+ Err(e) => {
+ _ = msg_sender.send(Message::err(&format!("Error: Could not save: {e}")));
+ }
+ }
+ },
+ ctx,
+ );
+ }
+ }
+
if let Some(p) = &state.current_path {
let text = if p
// .with_extension(&state.edit_state.export_extension)
@@ -1796,12 +1810,29 @@ 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_modal(
+ false,
+ SUPPORTED_EXTENSIONS,
+ |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;
diff --git a/src/utils.rs b/src/utils.rs
index 5e3c2c3..9d0dd24 100644
--- a/src/utils.rs
+++ b/src/utils.rs
@@ -190,6 +190,7 @@ pub struct Player {
}
impl Player {
+ /// Create a new Player
pub fn new(image_sender: Sender, cache_size: usize, max_texture_size: u32) -> Player {
let (stop_sender, _): (Sender<()>, Receiver<()>) = mpsc::channel();
Player {
@@ -226,14 +227,23 @@ impl Player {
}
}
- pub fn load(&mut self, img_location: &Path, message_sender: Sender) {
+ pub fn load_advanced(
+ &mut self,
+ img_location: &Path,
+ forced_frame_source: Option,
+ message_sender: Sender,
+ ) {
debug!("Stopping player on load");
self.stop();
let (stop_sender, stop_receiver): (Sender<()>, Receiver<()>) = mpsc::channel();
self.stop_sender = stop_sender;
if let Some(cached_image) = self.cache.get(img_location) {
- _ = self.image_sender.send(Frame::new_still(cached_image));
+ let mut frame = Frame::new_still(cached_image);
+ if let Some(fs) = forced_frame_source {
+ frame.source = fs;
+ }
+ _ = self.image_sender.send(frame);
info!("Cache hit for {}", img_location.display());
return;
}
@@ -244,6 +254,7 @@ impl Player {
message_sender,
stop_receiver,
self.max_texture_size,
+ forced_frame_source,
);
if let Ok(meta) = std::fs::metadata(img_location) {
@@ -253,6 +264,10 @@ impl Player {
}
}
+ pub fn load(&mut self, img_location: &Path, message_sender: Sender) {
+ self.load_advanced(img_location, None, message_sender);
+ }
+
pub fn stop(&self) {
_ = self.stop_sender.send(());
}
@@ -264,6 +279,7 @@ pub fn send_image_threaded(
message_sender: Sender,
stop_receiver: Receiver<()>,
max_texture_size: u32,
+ forced_frame_source: Option,
) {
let loc = img_location.to_owned();
@@ -278,7 +294,10 @@ pub fn send_image_threaded(
// .send(Frame::new_reset(f.buffer.clone()));
let mut first = true;
- for f in frame_receiver.iter() {
+ for mut f in frame_receiver.iter() {
+ if let Some(ref fs) = forced_frame_source {
+ f.source = fs.clone();
+ }
if stop_receiver.try_recv().is_ok() {
info!("Stopped from receiver.");
return;
@@ -304,6 +323,7 @@ pub fn send_image_threaded(
);
let mut frame = f;
+
let op = ImageOperation::Resize {
dimensions: new_dimensions,
aspect: true,
@@ -372,6 +392,7 @@ pub enum FrameSource {
AnimationStart,
Still,
EditResult,
+ CompareResult,
}
/// A single frame
@@ -795,9 +816,12 @@ pub fn compare_next(state: &mut OculanteState) {
state.image_geometry = geo.clone();
state.is_loaded = false;
state.current_image = None;
- state.player.load(path, state.message_channel.0.clone());
+ state.player.load_advanced(
+ path,
+ Some(FrameSource::CompareResult),
+ state.message_channel.0.clone(),
+ );
state.current_path = Some(path.clone());
- state.persistent_settings.keep_view = true;
}
}
}
diff --git a/tests/frstvisuals-lmV1g1UbdhQ-unsplash.jpg b/tests/frstvisuals-lmV1g1UbdhQ-unsplash.jpg
index 8741370..8c610e8 100644
Binary files a/tests/frstvisuals-lmV1g1UbdhQ-unsplash.jpg and b/tests/frstvisuals-lmV1g1UbdhQ-unsplash.jpg differ
diff --git a/tests/gray_8bpp.png b/tests/gray_8bpp.png
index 4cbc694..360e4fc 100644
Binary files a/tests/gray_8bpp.png and b/tests/gray_8bpp.png differ
diff --git a/tests/high.png b/tests/high.png
index 9467c2a..b261b06 100644
Binary files a/tests/high.png and b/tests/high.png differ
diff --git a/tests/johnny_automatic_lobster.svg b/tests/johnny_automatic_lobster.svg
index dfd6917..44effcf 100644
--- a/tests/johnny_automatic_lobster.svg
+++ b/tests/johnny_automatic_lobster.svg
@@ -1,312 +1 @@
-
-
-
-
-]>
-
+
\ No newline at end of file
diff --git a/tests/large.png b/tests/large.png
index ec33748..cf23d00 100644
Binary files a/tests/large.png and b/tests/large.png differ
diff --git a/tests/pngtest_16bit.png b/tests/pngtest_16bit.png
index 3830893..93b8792 100644
Binary files a/tests/pngtest_16bit.png and b/tests/pngtest_16bit.png differ
diff --git a/tests/rust.png b/tests/rust.png
index 85d0003..5a73c90 100644
Binary files a/tests/rust.png and b/tests/rust.png differ
diff --git a/tests/test.jpg b/tests/test.jpg
index 86c7a08..76b4455 100644
Binary files a/tests/test.jpg and b/tests/test.jpg differ
diff --git a/tests/test.png b/tests/test.png
index da15d8b..9bcd22c 100644
Binary files a/tests/test.png and b/tests/test.png differ
diff --git a/tests/unpremult.png b/tests/unpremult.png
index 1a98a87..4f7b67c 100644
Binary files a/tests/unpremult.png and b/tests/unpremult.png differ
diff --git a/tests/wide.png b/tests/wide.png
index ca9cb2b..f9408b9 100644
Binary files a/tests/wide.png and b/tests/wide.png differ