From 224f7c3f194f8b7797123364eb95815684120c88 Mon Sep 17 00:00:00 2001 From: Vitaliy Grabovets Date: Fri, 30 Jun 2023 12:11:53 +0300 Subject: [PATCH 01/73] add favourite hotkey --- .gitignore | 3 ++- src/main.rs | 3 +++ src/shortcuts.rs | 2 ++ 3 files changed, 7 insertions(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index db0e80df..0ef2ba6c 100644 --- a/.gitignore +++ b/.gitignore @@ -10,4 +10,5 @@ shortcuts.txt perf.* flamegraph.svg .notes -TODO \ No newline at end of file +TODO +.idea diff --git a/src/main.rs b/src/main.rs index 9aafb208..e11cacbe 100644 --- a/src/main.rs +++ b/src/main.rs @@ -354,6 +354,9 @@ fn event(app: &mut App, state: &mut OculanteState, evt: Event) { if key_pressed(app, state, ZoomFive) { set_zoom(5.0, None, state); } + if key_pressed(app, state, Favourite) { + debug!("Favourite"); + } if key_pressed(app, state, Quit) { // std::process::exit(0) diff --git a/src/shortcuts.rs b/src/shortcuts.rs index fc63a4c9..06e79a20 100644 --- a/src/shortcuts.rs +++ b/src/shortcuts.rs @@ -43,6 +43,7 @@ pub enum InputEvent { Browse, Quit, ZenMode, + Favourite, } pub type Shortcuts = HashMap; @@ -129,6 +130,7 @@ impl ShortcutExt for Shortcuts { .add_key(InputEvent::LosslessRotateLeft, "LBracket") .add_key(InputEvent::LosslessRotateRight, "RBracket") .add_key(InputEvent::ZenMode, "Z") + .add_key(InputEvent::Favourite, "J") // .add_key(InputEvent::Browse, "F1") // FIXME: As Shortcuts is a HashMap, only the newer key-sequence will be registered .add_keys(InputEvent::Browse, &["LControl", "O"]) .add_keys(InputEvent::PanRight, &["LShift", "Right"]) From 1660c24bf2bc1ec3337c010074c3a9ffdd660435 Mon Sep 17 00:00:00 2001 From: Vitaliy Grabovets Date: Fri, 30 Jun 2023 12:24:14 +0300 Subject: [PATCH 02/73] add_to_favourites function --- src/main.rs | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/main.rs b/src/main.rs index e11cacbe..8d0cf717 100644 --- a/src/main.rs +++ b/src/main.rs @@ -355,7 +355,7 @@ fn event(app: &mut App, state: &mut OculanteState, evt: Event) { set_zoom(5.0, None, state); } if key_pressed(app, state, Favourite) { - debug!("Favourite"); + add_to_favourites(state); } if key_pressed(app, state, Quit) { @@ -1070,3 +1070,9 @@ fn set_zoom(scale: f32, from_center: Option>, state: &mut OculanteS ); state.image_geometry.scale = scale; } + +fn add_to_favourites(state: &OculanteState) { + if let Some(img_path) = &state.current_path { + debug!("Favourite img: {}", img_path.to_string_lossy()); + } +} From 41325d9f057f336503a67455f79ad29ac4c71efd Mon Sep 17 00:00:00 2001 From: Vitaliy Grabovets Date: Fri, 30 Jun 2023 12:39:21 +0300 Subject: [PATCH 03/73] write favourites to a file --- src/main.rs | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/src/main.rs b/src/main.rs index 8d0cf717..42890cd5 100644 --- a/src/main.rs +++ b/src/main.rs @@ -12,6 +12,8 @@ use notan::draw::*; use notan::egui::{self, *}; use notan::prelude::*; use shortcuts::key_pressed; +use std::fs::OpenOptions; +use std::io::Write; use std::path::PathBuf; use std::sync::mpsc; pub mod cache; @@ -1073,6 +1075,12 @@ fn set_zoom(scale: f32, from_center: Option>, state: &mut OculanteS fn add_to_favourites(state: &OculanteState) { if let Some(img_path) = &state.current_path { - debug!("Favourite img: {}", img_path.to_string_lossy()); + let mut file = OpenOptions::new() + .append(true) + .create(true) + .open("/tmp/favourites.txt") + .expect("Unable to open file"); + + writeln!(file, "{}", img_path.to_string_lossy()).expect("Unable to write data"); } } From cc2cdf21041d3b544daceff41696c66883f04dc9 Mon Sep 17 00:00:00 2001 From: Vitaliy Grabovets Date: Sat, 1 Jul 2023 00:28:13 +0300 Subject: [PATCH 04/73] save favourites file in image parent folder --- src/main.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/main.rs b/src/main.rs index 42890cd5..102d0afc 100644 --- a/src/main.rs +++ b/src/main.rs @@ -14,6 +14,7 @@ use notan::prelude::*; use shortcuts::key_pressed; use std::fs::OpenOptions; use std::io::Write; +use std::path::Path; use std::path::PathBuf; use std::sync::mpsc; pub mod cache; @@ -1078,7 +1079,7 @@ fn add_to_favourites(state: &OculanteState) { let mut file = OpenOptions::new() .append(true) .create(true) - .open("/tmp/favourites.txt") + .open(img_path.parent().unwrap().join(Path::new("favourites.txt"))) .expect("Unable to open file"); writeln!(file, "{}", img_path.to_string_lossy()).expect("Unable to write data"); From aef13e8a05be934373d268ff8474223e455d2873 Mon Sep 17 00:00:00 2001 From: Vitaliy Grabovets Date: Sat, 1 Jul 2023 21:29:31 +0300 Subject: [PATCH 05/73] browse for folder path --- src/main.rs | 14 ++++++++++++++ src/ui.rs | 12 ++++++++++-- 2 files changed, 24 insertions(+), 2 deletions(-) diff --git a/src/main.rs b/src/main.rs index 102d0afc..32bca7ff 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1041,6 +1041,20 @@ fn browse_for_image_path(state: &mut OculanteState) { } } +fn browse_for_folder_path(state: &mut OculanteState) { + let start_directory = &state.persistent_settings.last_open_directory; + + let folder_dialog_result = rfd::FileDialog::new() + .set_directory(start_directory) + .pick_folder(); + + if let Some(folder_path) = folder_dialog_result { + debug!("Selected Folder Path = {:?}", folder_path); + state.persistent_settings.last_open_directory = folder_path.to_path_buf(); + _ = state.persistent_settings.save(); + } +} + // Make sure offset is restricted to window size so we don't offset to infinity fn limit_offset(app: &mut App, state: &mut OculanteState) { let window_size = app.window().size(); diff --git a/src/ui.rs b/src/ui.rs index 05f3c075..f10aa662 100644 --- a/src/ui.rs +++ b/src/ui.rs @@ -1,5 +1,5 @@ #[cfg(feature = "file_open")] -use crate::browse_for_image_path; +use crate::{browse_for_folder_path, browse_for_image_path}; use crate::{ appstate::{ImageGeometry, OculanteState}, image_editing::{process_pixels, Channel, ImageOperation, ScaleFilter}, @@ -1786,13 +1786,21 @@ pub fn main_menu(ui: &mut Ui, state: &mut OculanteState, app: &mut App, gfx: &mu } #[cfg(feature = "file_open")] - if unframed_button("🗁", ui) + if unframed_button("📄", ui) .on_hover_text("Browse for image") .clicked() { browse_for_image_path(state) } + #[cfg(feature = "file_open")] + if unframed_button("🗁", ui) + .on_hover_text("Browse for folder") + .clicked() + { + browse_for_folder_path(state) + } + ui.scope(|ui| { // ui.style_mut().override_text_style = Some(egui::TextStyle::Heading); // maybe override font size? From 6ef061f36d6dd6d7d14ab7f258fea8a88a0720a4 Mon Sep 17 00:00:00 2001 From: Vitaliy Grabovets Date: Sun, 2 Jul 2023 21:24:24 +0300 Subject: [PATCH 06/73] iterate over files in the folder --- src/main.rs | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/src/main.rs b/src/main.rs index 32bca7ff..a05be739 100644 --- a/src/main.rs +++ b/src/main.rs @@ -12,7 +12,7 @@ use notan::draw::*; use notan::egui::{self, *}; use notan::prelude::*; use shortcuts::key_pressed; -use std::fs::OpenOptions; +use std::fs; use std::io::Write; use std::path::Path; use std::path::PathBuf; @@ -1052,6 +1052,13 @@ fn browse_for_folder_path(state: &mut OculanteState) { debug!("Selected Folder Path = {:?}", folder_path); state.persistent_settings.last_open_directory = folder_path.to_path_buf(); _ = state.persistent_settings.save(); + + for file in fs::read_dir(folder_path).expect("Could not read directory") { + let file = file.unwrap().path(); + if is_ext_compatible(file.as_path()) { + debug!("file: {:?}", file); + } + } } } @@ -1090,7 +1097,7 @@ fn set_zoom(scale: f32, from_center: Option>, state: &mut OculanteS fn add_to_favourites(state: &OculanteState) { if let Some(img_path) = &state.current_path { - let mut file = OpenOptions::new() + let mut file = fs::OpenOptions::new() .append(true) .create(true) .open(img_path.parent().unwrap().join(Path::new("favourites.txt"))) From f0e6583d03d1994726c22d00c4dca91ead52ca57 Mon Sep 17 00:00:00 2001 From: Vitaliy Grabovets Date: Sun, 2 Jul 2023 22:05:40 +0300 Subject: [PATCH 07/73] shuffle files --- src/main.rs | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/src/main.rs b/src/main.rs index a05be739..6cb4aacb 100644 --- a/src/main.rs +++ b/src/main.rs @@ -11,6 +11,7 @@ use notan::app::Event; use notan::draw::*; use notan::egui::{self, *}; use notan::prelude::*; +use rand::seq::SliceRandom; use shortcuts::key_pressed; use std::fs; use std::io::Write; @@ -1049,16 +1050,21 @@ fn browse_for_folder_path(state: &mut OculanteState) { .pick_folder(); if let Some(folder_path) = folder_dialog_result { - debug!("Selected Folder Path = {:?}", folder_path); state.persistent_settings.last_open_directory = folder_path.to_path_buf(); _ = state.persistent_settings.save(); + let mut files: Vec = Vec::new(); + for file in fs::read_dir(folder_path).expect("Could not read directory") { let file = file.unwrap().path(); if is_ext_compatible(file.as_path()) { - debug!("file: {:?}", file); + files.push(file); } } + + let mut rng = rand::thread_rng(); + files.shuffle(&mut rng); + debug!("files: {:?}", files); } } From da2d96075b1ab068475e4aeeee99de91426b7bef Mon Sep 17 00:00:00 2001 From: Vitaliy Grabovets Date: Mon, 3 Jul 2023 16:39:06 +0300 Subject: [PATCH 08/73] use provided get_image_filenames_for_directory --- src/main.rs | 21 +++++++++++---------- src/scrubber.rs | 32 +++++++++++++++++++------------- 2 files changed, 30 insertions(+), 23 deletions(-) diff --git a/src/main.rs b/src/main.rs index 6cb4aacb..4e760e97 100644 --- a/src/main.rs +++ b/src/main.rs @@ -11,7 +11,7 @@ use notan::app::Event; use notan::draw::*; use notan::egui::{self, *}; use notan::prelude::*; -use rand::seq::SliceRandom; +use scrubber::get_image_filenames_for_directory; use shortcuts::key_pressed; use std::fs; use std::io::Write; @@ -1053,18 +1053,19 @@ fn browse_for_folder_path(state: &mut OculanteState) { state.persistent_settings.last_open_directory = folder_path.to_path_buf(); _ = state.persistent_settings.save(); - let mut files: Vec = Vec::new(); + let files = get_image_filenames_for_directory(folder_path.as_path(), true) + .unwrap_or_default(); - for file in fs::read_dir(folder_path).expect("Could not read directory") { - let file = file.unwrap().path(); - if is_ext_compatible(file.as_path()) { - files.push(file); - } + if files.is_empty() { + debug!("no supported files in the folder"); + return; } - let mut rng = rand::thread_rng(); - files.shuffle(&mut rng); - debug!("files: {:?}", files); + state.is_loaded = false; + state.current_image = None; + state + .player + .load(&files[0], state.message_channel.0.clone()); } } diff --git a/src/scrubber.rs b/src/scrubber.rs index 8dbd3c74..2506d971 100644 --- a/src/scrubber.rs +++ b/src/scrubber.rs @@ -1,6 +1,7 @@ use crate::utils::is_ext_compatible; use anyhow::{bail, Context, Result}; use log::debug; +use rand::seq::SliceRandom; use std::path::{Path, PathBuf}; #[derive(Debug, Default)] @@ -12,7 +13,7 @@ pub struct Scrubber { impl Scrubber { pub fn new(path: &Path) -> Self { - let entries = get_image_filenames_for_directory(path).unwrap_or_default(); + let entries = get_image_filenames_for_directory(path, false).unwrap_or_default(); let index = entries.iter().position(|p| p == path).unwrap_or_default(); Self { index, @@ -61,7 +62,7 @@ impl Scrubber { // Get sorted list of files in a folder // TODO: Should probably return an Result instead, but am too lazy to figure out + handle a dedicated error type here // TODO: Cache this result, instead of doing it each time we need to fetch another file from the folder -pub fn get_image_filenames_for_directory(folder_path: &Path) -> Result> { +pub fn get_image_filenames_for_directory(folder_path: &Path, randomize: bool) -> Result> { let mut folder_path = folder_path.to_path_buf(); if folder_path.is_file() { folder_path = folder_path @@ -78,16 +79,21 @@ pub fn get_image_filenames_for_directory(folder_path: &Path) -> Result>(); - dir_files.sort_unstable_by(|a, b| { - lexical_sort::natural_lexical_cmp( - &a.file_name() - .map(|f| f.to_string_lossy()) - .unwrap_or_default(), - &b.file_name() - .map(|f| f.to_string_lossy()) - .unwrap_or_default(), - ) - }); + if randomize { + let mut rng = rand::thread_rng(); + dir_files.shuffle(&mut rng); + } else { + dir_files.sort_unstable_by(|a, b| { + lexical_sort::natural_lexical_cmp( + &a.file_name() + .map(|f| f.to_string_lossy()) + .unwrap_or_default(), + &b.file_name() + .map(|f| f.to_string_lossy()) + .unwrap_or_default(), + ) + }); + } return Ok(dir_files); } @@ -98,7 +104,7 @@ pub fn find_first_image_in_directory(folder_path: &PathBuf) -> Result { if !folder_path.is_dir() { bail!("This is not a folder"); }; - get_image_filenames_for_directory(folder_path).map(|x| { + get_image_filenames_for_directory(folder_path, false).map(|x| { x.first() .cloned() .context("Folder does not have any supported images in it") From d844c3d8d5731010638c03a095b61fcd1c816301 Mon Sep 17 00:00:00 2001 From: Vitaliy Grabovets Date: Mon, 3 Jul 2023 17:10:32 +0300 Subject: [PATCH 09/73] use scrubber --- src/main.rs | 18 +++++++----------- 1 file changed, 7 insertions(+), 11 deletions(-) diff --git a/src/main.rs b/src/main.rs index 4e760e97..80318ebf 100644 --- a/src/main.rs +++ b/src/main.rs @@ -11,7 +11,6 @@ use notan::app::Event; use notan::draw::*; use notan::egui::{self, *}; use notan::prelude::*; -use scrubber::get_image_filenames_for_directory; use shortcuts::key_pressed; use std::fs; use std::io::Write; @@ -24,7 +23,7 @@ pub mod settings; pub mod shortcuts; #[cfg(feature = "turbo")] use crate::image_editing::lossless_tx; -use crate::scrubber::find_first_image_in_directory; +use crate::scrubber::{find_first_image_in_directory, Scrubber}; use crate::shortcuts::InputEvent::*; mod utils; use utils::*; @@ -1050,22 +1049,19 @@ fn browse_for_folder_path(state: &mut OculanteState) { .pick_folder(); if let Some(folder_path) = folder_dialog_result { - state.persistent_settings.last_open_directory = folder_path.to_path_buf(); + state.persistent_settings.last_open_directory = folder_path.clone(); _ = state.persistent_settings.save(); - let files = get_image_filenames_for_directory(folder_path.as_path(), true) - .unwrap_or_default(); - - if files.is_empty() { - debug!("no supported files in the folder"); - return; - } + state.scrubber = Scrubber::new(folder_path.as_path()); + let current_path = state.scrubber.next(); state.is_loaded = false; state.current_image = None; state .player - .load(&files[0], state.message_channel.0.clone()); + .load(current_path.as_path(), state.message_channel.0.clone()); + + state.current_path = Some(current_path); } } From 3f82c70d4cb0b4f66175c1f85abe6697a642cacb Mon Sep 17 00:00:00 2001 From: Vitaliy Grabovets Date: Mon, 3 Jul 2023 17:17:52 +0300 Subject: [PATCH 10/73] "randomize" argument for scrubber --- src/main.rs | 4 ++-- src/scrubber.rs | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/main.rs b/src/main.rs index 80318ebf..cb21981d 100644 --- a/src/main.rs +++ b/src/main.rs @@ -661,7 +661,7 @@ fn drawe(app: &mut App, gfx: &mut Graphics, plugins: &mut Plugins, state: &mut O // fill image sequence if let Some(p) = &state.current_path { - state.scrubber = scrubber::Scrubber::new(p); + state.scrubber = scrubber::Scrubber::new(p, false); state.scrubber.wrap = state.persistent_settings.wrap_folder; // debug!("{:#?} from {}", &state.scrubber, p.display()); @@ -1052,7 +1052,7 @@ fn browse_for_folder_path(state: &mut OculanteState) { state.persistent_settings.last_open_directory = folder_path.clone(); _ = state.persistent_settings.save(); - state.scrubber = Scrubber::new(folder_path.as_path()); + state.scrubber = Scrubber::new(folder_path.as_path(), true); let current_path = state.scrubber.next(); state.is_loaded = false; diff --git a/src/scrubber.rs b/src/scrubber.rs index 2506d971..ecf3e8a5 100644 --- a/src/scrubber.rs +++ b/src/scrubber.rs @@ -12,8 +12,8 @@ pub struct Scrubber { } impl Scrubber { - pub fn new(path: &Path) -> Self { - let entries = get_image_filenames_for_directory(path, false).unwrap_or_default(); + pub fn new(path: &Path, randomize: bool) -> Self { + let entries = get_image_filenames_for_directory(path, randomize).unwrap_or_default(); let index = entries.iter().position(|p| p == path).unwrap_or_default(); Self { index, From bc5b4dff7640b1b58cf4d04911dc473a63d66c89 Mon Sep 17 00:00:00 2001 From: Vitaliy Grabovets Date: Tue, 4 Jul 2023 17:33:14 +0300 Subject: [PATCH 11/73] walk files --- Cargo.lock | 1 + Cargo.toml | 1 + src/appstate.rs | 2 ++ src/main.rs | 22 +++++++++++++--------- src/scrubber.rs | 34 ++++++++++++++++++++++++---------- 5 files changed, 41 insertions(+), 19 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 00d59044..b49b7ac2 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3189,6 +3189,7 @@ dependencies = [ "tiny-skia 0.9.1", "turbojpeg", "usvg", + "walkdir", "webbrowser", "windres", "winres", diff --git a/Cargo.toml b/Cargo.toml index e877da08..9121edd7 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -60,6 +60,7 @@ webbrowser = "0.8" tiff = "0.9" jxl-oxide = "0.3.0" zune-png = "0.2" +walkdir = "2.3.3" [features] avif_native = ["avif-decode"] diff --git a/src/appstate.rs b/src/appstate.rs index e081e9fc..9bf343a7 100644 --- a/src/appstate.rs +++ b/src/appstate.rs @@ -64,6 +64,7 @@ pub struct OculanteState { pub scrubber: Scrubber, pub checker_texture: Option, pub animation_mode: bool, + pub folder_selected: bool, } impl OculanteState { @@ -114,6 +115,7 @@ impl Default for OculanteState { scrubber: Default::default(), checker_texture: Default::default(), animation_mode: Default::default(), + folder_selected: Default::default(), } } } diff --git a/src/main.rs b/src/main.rs index cb21981d..4e8d8127 100644 --- a/src/main.rs +++ b/src/main.rs @@ -660,14 +660,16 @@ fn drawe(app: &mut App, gfx: &mut Graphics, plugins: &mut Plugins, state: &mut O set_title(app, state); // fill image sequence - if let Some(p) = &state.current_path { - state.scrubber = scrubber::Scrubber::new(p, false); - state.scrubber.wrap = state.persistent_settings.wrap_folder; - - // debug!("{:#?} from {}", &state.scrubber, p.display()); - if !state.persistent_settings.recent_images.contains(p) { - state.persistent_settings.recent_images.insert(0, p.clone()); - state.persistent_settings.recent_images.truncate(10); + if !state.folder_selected { + if let Some(p) = &state.current_path { + state.scrubber = scrubber::Scrubber::new(p, false, false); + state.scrubber.wrap = state.persistent_settings.wrap_folder; + + // debug!("{:#?} from {}", &state.scrubber, p.display()); + if !state.persistent_settings.recent_images.contains(p) { + state.persistent_settings.recent_images.insert(0, p.clone()); + state.persistent_settings.recent_images.truncate(10); + } } } @@ -1028,6 +1030,7 @@ fn browse_for_image_path(state: &mut OculanteState) { if let Some(file_path) = file_dialog_result { debug!("Selected File Path = {:?}", file_path); + state.folder_selected = false; state.is_loaded = false; state.current_image = None; state @@ -1051,8 +1054,9 @@ fn browse_for_folder_path(state: &mut OculanteState) { if let Some(folder_path) = folder_dialog_result { state.persistent_settings.last_open_directory = folder_path.clone(); _ = state.persistent_settings.save(); + state.folder_selected = true; - state.scrubber = Scrubber::new(folder_path.as_path(), true); + state.scrubber = Scrubber::new(folder_path.as_path(), true, true); let current_path = state.scrubber.next(); state.is_loaded = false; diff --git a/src/scrubber.rs b/src/scrubber.rs index ecf3e8a5..7c970552 100644 --- a/src/scrubber.rs +++ b/src/scrubber.rs @@ -3,6 +3,7 @@ use anyhow::{bail, Context, Result}; use log::debug; use rand::seq::SliceRandom; use std::path::{Path, PathBuf}; +use walkdir::WalkDir; #[derive(Debug, Default)] pub struct Scrubber { @@ -12,8 +13,9 @@ pub struct Scrubber { } impl Scrubber { - pub fn new(path: &Path, randomize: bool) -> Self { - let entries = get_image_filenames_for_directory(path, randomize).unwrap_or_default(); + pub fn new(path: &Path, randomize: bool, walk_files: bool) -> Self { + let entries = get_image_filenames_for_directory(path, randomize, walk_files) + .unwrap_or_default(); let index = entries.iter().position(|p| p == path).unwrap_or_default(); Self { index, @@ -62,7 +64,7 @@ impl Scrubber { // Get sorted list of files in a folder // TODO: Should probably return an Result instead, but am too lazy to figure out + handle a dedicated error type here // TODO: Cache this result, instead of doing it each time we need to fetch another file from the folder -pub fn get_image_filenames_for_directory(folder_path: &Path, randomize: bool) -> Result> { +pub fn get_image_filenames_for_directory(folder_path: &Path, randomize: bool, walk_files: bool) -> Result> { let mut folder_path = folder_path.to_path_buf(); if folder_path.is_file() { folder_path = folder_path @@ -70,14 +72,26 @@ pub fn get_image_filenames_for_directory(folder_path: &Path, randomize: bool) -> .map(|p| p.to_path_buf()) .context("Can't get parent")?; } - let info = std::fs::read_dir(folder_path)?; + + let mut dir_files: Vec; + + if walk_files { + dir_files = WalkDir::new(folder_path) + .into_iter() + .filter_map(|v| v.ok()) + .filter(|x| is_ext_compatible(x.path())) + .map(|x| x.into_path()) + .collect::>(); + } else { + let info = std::fs::read_dir(folder_path)?; + dir_files = info + .flat_map(|x| x) + .map(|x| x.path()) + .filter(|x| is_ext_compatible(x)) + .collect::>(); + } // TODO: Are symlinks handled correctly? - let mut dir_files = info - .flat_map(|x| x) - .map(|x| x.path()) - .filter(|x| is_ext_compatible(x)) - .collect::>(); if randomize { let mut rng = rand::thread_rng(); @@ -104,7 +118,7 @@ pub fn find_first_image_in_directory(folder_path: &PathBuf) -> Result { if !folder_path.is_dir() { bail!("This is not a folder"); }; - get_image_filenames_for_directory(folder_path, false).map(|x| { + get_image_filenames_for_directory(folder_path, false, false).map(|x| { x.first() .cloned() .context("Folder does not have any supported images in it") From 1ff54757971ff0fbfced606bce7f22103bed2834 Mon Sep 17 00:00:00 2001 From: Vitaliy Grabovets Date: Wed, 5 Jul 2023 17:48:58 +0300 Subject: [PATCH 12/73] fix folder that is used to save favourites file --- src/appstate.rs | 2 +- src/main.rs | 13 +++++++++---- 2 files changed, 10 insertions(+), 5 deletions(-) diff --git a/src/appstate.rs b/src/appstate.rs index 9bf343a7..0a564fe2 100644 --- a/src/appstate.rs +++ b/src/appstate.rs @@ -64,7 +64,7 @@ pub struct OculanteState { pub scrubber: Scrubber, pub checker_texture: Option, pub animation_mode: bool, - pub folder_selected: bool, + pub folder_selected: Option, } impl OculanteState { diff --git a/src/main.rs b/src/main.rs index 4e8d8127..a1d1794a 100644 --- a/src/main.rs +++ b/src/main.rs @@ -660,7 +660,7 @@ fn drawe(app: &mut App, gfx: &mut Graphics, plugins: &mut Plugins, state: &mut O set_title(app, state); // fill image sequence - if !state.folder_selected { + if state.folder_selected.is_none() { if let Some(p) = &state.current_path { state.scrubber = scrubber::Scrubber::new(p, false, false); state.scrubber.wrap = state.persistent_settings.wrap_folder; @@ -1030,7 +1030,7 @@ fn browse_for_image_path(state: &mut OculanteState) { if let Some(file_path) = file_dialog_result { debug!("Selected File Path = {:?}", file_path); - state.folder_selected = false; + state.folder_selected = None; state.is_loaded = false; state.current_image = None; state @@ -1054,7 +1054,7 @@ fn browse_for_folder_path(state: &mut OculanteState) { if let Some(folder_path) = folder_dialog_result { state.persistent_settings.last_open_directory = folder_path.clone(); _ = state.persistent_settings.save(); - state.folder_selected = true; + state.folder_selected = Option::from(folder_path.clone()); state.scrubber = Scrubber::new(folder_path.as_path(), true, true); let current_path = state.scrubber.next(); @@ -1107,7 +1107,12 @@ fn add_to_favourites(state: &OculanteState) { let mut file = fs::OpenOptions::new() .append(true) .create(true) - .open(img_path.parent().unwrap().join(Path::new("favourites.txt"))) + .open( + state.folder_selected + .as_ref() + .unwrap_or(&img_path.parent().unwrap().to_path_buf()) + .join(Path::new("favourites.txt")) + ) .expect("Unable to open file"); writeln!(file, "{}", img_path.to_string_lossy()).expect("Unable to write data"); From bfdf9ea68d4ca25275d34f41259500e2c3d6bd7c Mon Sep 17 00:00:00 2001 From: Vitaliy Grabovets Date: Thu, 6 Jul 2023 17:08:15 +0300 Subject: [PATCH 13/73] strip prefix --- src/main.rs | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/main.rs b/src/main.rs index a1d1794a..1f42e447 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1115,6 +1115,12 @@ fn add_to_favourites(state: &OculanteState) { ) .expect("Unable to open file"); - writeln!(file, "{}", img_path.to_string_lossy()).expect("Unable to write data"); + writeln!( + file, + "{}", + img_path.strip_prefix(state.folder_selected.as_ref().unwrap().as_path()) + .unwrap() + .to_string_lossy() + ).expect("Unable to write data"); } } From 05499022d01eca2ca80125444bbe32d39ae0b106 Mon Sep 17 00:00:00 2001 From: Vitaliy Grabovets Date: Sat, 8 Jul 2023 22:51:27 +0300 Subject: [PATCH 14/73] update github actions --- .github/workflows/rust-clippy.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/rust-clippy.yml b/.github/workflows/rust-clippy.yml index f0d6e404..ca87ebd1 100644 --- a/.github/workflows/rust-clippy.yml +++ b/.github/workflows/rust-clippy.yml @@ -11,10 +11,10 @@ name: rust-clippy analyze on: push: - branches: [ "master" ] + branches: [ "master", "custom" ] pull_request: # The branches below must be a subset of the branches above - branches: [ "master" ] + branches: [ "master", "custom" ] schedule: - cron: '28 16 * * 6' From 713917f2849288826f6e7865240d1b1c5f6b2bed Mon Sep 17 00:00:00 2001 From: Vitaliy Grabovets Date: Sat, 8 Jul 2023 23:11:54 +0300 Subject: [PATCH 15/73] fix cargo check --- src/main.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/main.rs b/src/main.rs index c4142876..ce15ccd6 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1060,6 +1060,7 @@ fn browse_for_image_path(state: &mut OculanteState) { } } +#[cfg(feature = "file_open")] fn browse_for_folder_path(state: &mut OculanteState) { let start_directory = &state.persistent_settings.last_open_directory; From 03086f7afa329eda73b61bf77214e079d5aa9c8b Mon Sep 17 00:00:00 2001 From: Vitaliy Grabovets Date: Sat, 8 Jul 2023 23:29:07 +0300 Subject: [PATCH 16/73] remove aur-publish --- .github/workflows/release.yml | 28 ++++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index d4eef77f..4e1fa35d 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -233,17 +233,17 @@ jobs: with: release_id: ${{ needs.release_job.outputs.release_id }} - aur-publish: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v2 - - name: Publish AUR package - uses: KSXGitHub/github-actions-deploy-aur@v2.7.0 - with: - pkgname: oculante - pkgbuild: ./PKGBUILD - commit_username: ${{ secrets.AUR_USERNAME }} - commit_email: ${{ secrets.AUR_EMAIL }} - ssh_private_key: ${{ secrets.AUR_SSH_PRIVATE_KEY }} - commit_message: Update oculante AUR package - ssh_keyscan_types: rsa,dsa,ecdsa,ed25519 \ No newline at end of file +# aur-publish: +# runs-on: ubuntu-latest +# steps: +# - uses: actions/checkout@v2 +# - name: Publish AUR package +# uses: KSXGitHub/github-actions-deploy-aur@v2.7.0 +# with: +# pkgname: oculante +# pkgbuild: ./PKGBUILD +# commit_username: ${{ secrets.AUR_USERNAME }} +# commit_email: ${{ secrets.AUR_EMAIL }} +# ssh_private_key: ${{ secrets.AUR_SSH_PRIVATE_KEY }} +# commit_message: Update oculante AUR package +# ssh_keyscan_types: rsa,dsa,ecdsa,ed25519 From 61a29a0725458bae82d646461780eed5fdce4804 Mon Sep 17 00:00:00 2001 From: Vitaliy Grabovets Date: Sat, 8 Jul 2023 23:38:59 +0300 Subject: [PATCH 17/73] v0.6.68-dev1 --- Cargo.lock | 2 +- Cargo.toml | 2 +- PKGBUILD | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 5d720f5f..3dd7c0b2 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3192,7 +3192,7 @@ dependencies = [ [[package]] name = "oculante" -version = "0.6.68" +version = "0.6.68-dev1" dependencies = [ "anyhow", "arboard", diff --git a/Cargo.toml b/Cargo.toml index a17e6fa6..75dd29eb 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -11,7 +11,7 @@ license = "MIT" name = "oculante" readme = "README.md" repository = "https://github.com/woelper/oculante/" -version = "0.6.68" +version = "0.6.68-dev1" [package.metadata.bundle] icon = ["res/oculante.png"] diff --git a/PKGBUILD b/PKGBUILD index 3e5cfbad..9973c666 100644 --- a/PKGBUILD +++ b/PKGBUILD @@ -1,6 +1,6 @@ # Maintainer: Johann Woelper pkgname=oculante -pkgver=0.6.68 +pkgver=0.6.68-dev1 pkgrel=1 depends=('aom' 'libwebp' 'expat' 'freetype2' 'gtk3' 'cairo') makedepends=('rust' 'cargo' 'tar' 'nasm' 'cmake') From 7532358bb990f9bfe5be3f6822c5fb921e4236a5 Mon Sep 17 00:00:00 2001 From: Vitaliy Grabovets Date: Sun, 9 Jul 2023 12:53:03 +0300 Subject: [PATCH 18/73] v0.6.68-dev2 --- .github/workflows/release.yml | 22 +++++++++++----------- Cargo.toml | 2 +- PKGBUILD | 4 ++-- 3 files changed, 14 insertions(+), 14 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 4e1fa35d..f6e67bca 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -21,7 +21,7 @@ jobs: id: create_release uses: actions/create-release@v1 env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + GITHUB_TOKEN: ${{ secrets.TOKEN }} with: tag_name: ${{ github.ref }} release_name: Release ${{ github.ref }} @@ -114,7 +114,7 @@ jobs: id: upload-release-asset-lin-classic uses: actions/upload-release-asset@v1 env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + GITHUB_TOKEN: ${{ secrets.TOKEN }} with: upload_url: ${{ needs.release_job.outputs.upload_url }} asset_path: target/release/oculante @@ -126,7 +126,7 @@ jobs: id: upload-release-arm uses: actions/upload-release-asset@v1 env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + GITHUB_TOKEN: ${{ secrets.TOKEN }} with: upload_url: ${{ needs.release_job.outputs.upload_url }} asset_path: oculante_armv7_minimal.zip @@ -138,7 +138,7 @@ jobs: id: upload-release-arm64 uses: actions/upload-release-asset@v1 env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + GITHUB_TOKEN: ${{ secrets.TOKEN }} with: upload_url: ${{ needs.release_job.outputs.upload_url }} asset_path: oculante_aarch64_minimal.zip @@ -150,7 +150,7 @@ jobs: id: upload-release-asset-lin-classic-zip uses: actions/upload-release-asset@v1 env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + GITHUB_TOKEN: ${{ secrets.TOKEN }} with: upload_url: ${{ needs.release_job.outputs.upload_url }} asset_path: oculante_linux.zip @@ -162,7 +162,7 @@ jobs: id: upload-release-asset-lin-classic-zip-min uses: actions/upload-release-asset@v1 env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + GITHUB_TOKEN: ${{ secrets.TOKEN }} with: upload_url: ${{ needs.release_job.outputs.upload_url }} asset_path: oculante_linux_minimal.zip @@ -174,7 +174,7 @@ jobs: id: upload-release-asset-lin-classic-deb uses: actions/upload-release-asset@v1 env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + GITHUB_TOKEN: ${{ secrets.TOKEN }} with: upload_url: ${{ needs.release_job.outputs.upload_url }} asset_path: oculante.deb @@ -186,7 +186,7 @@ jobs: id: upload-release-asset-mac uses: actions/upload-release-asset@v1 env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + GITHUB_TOKEN: ${{ secrets.TOKEN }} with: upload_url: ${{ needs.release_job.outputs.upload_url }} asset_path: oculante_app.zip @@ -198,7 +198,7 @@ jobs: id: upload-release-asset-bin uses: actions/upload-release-asset@v1 env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + GITHUB_TOKEN: ${{ secrets.TOKEN }} with: upload_url: ${{ needs.release_job.outputs.upload_url }} asset_path: target/release/oculante @@ -210,7 +210,7 @@ jobs: id: upload-release-asset-windows-classic uses: actions/upload-release-asset@v1 env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + GITHUB_TOKEN: ${{ secrets.TOKEN }} with: upload_url: ${{ needs.release_job.outputs.upload_url }} asset_path: target/release/oculante.exe @@ -229,7 +229,7 @@ jobs: - name: Publish uses: eregon/publish-release@v1 env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + GITHUB_TOKEN: ${{ secrets.TOKEN }} with: release_id: ${{ needs.release_job.outputs.release_id }} diff --git a/Cargo.toml b/Cargo.toml index 75dd29eb..cd2e5c30 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -11,7 +11,7 @@ license = "MIT" name = "oculante" readme = "README.md" repository = "https://github.com/woelper/oculante/" -version = "0.6.68-dev1" +version = "0.6.68-dev2" [package.metadata.bundle] icon = ["res/oculante.png"] diff --git a/PKGBUILD b/PKGBUILD index 9973c666..b8cc2b3c 100644 --- a/PKGBUILD +++ b/PKGBUILD @@ -1,6 +1,6 @@ # Maintainer: Johann Woelper pkgname=oculante -pkgver=0.6.68-dev1 +pkgver=0.6.68-dev2 pkgrel=1 depends=('aom' 'libwebp' 'expat' 'freetype2' 'gtk3' 'cairo') makedepends=('rust' 'cargo' 'tar' 'nasm' 'cmake') @@ -23,4 +23,4 @@ package() { install -Dm644 res/oculante.desktop "${pkgdir}/usr/share/applications/${pkgname}.desktop" install -Dm644 LICENSE -t "${pkgdir}/usr/share/licenses/${pkgname}" install -Dm644 README.md -t "${pkgdir}/usr/share/doc/${pkgname}" -} \ No newline at end of file +} From f83f65358f4eaa67904859d0a8a431b0b8c61e17 Mon Sep 17 00:00:00 2001 From: Vitaliy Grabovets Date: Sun, 9 Jul 2023 23:07:30 +0300 Subject: [PATCH 19/73] read favourites file --- .github/workflows/ubuntu.yml | 2 +- .github/workflows/windows.yml | 2 +- Cargo.lock | 16 ++++++++++++--- Cargo.toml | 1 + PKGBUILD | 2 +- src/main.rs | 17 ++++++++++++---- src/scrubber.rs | 38 +++++++++++++++++++++++++++++++---- 7 files changed, 64 insertions(+), 14 deletions(-) diff --git a/.github/workflows/ubuntu.yml b/.github/workflows/ubuntu.yml index 457a043f..e533c7d3 100644 --- a/.github/workflows/ubuntu.yml +++ b/.github/workflows/ubuntu.yml @@ -5,7 +5,7 @@ jobs: check: strategy: matrix: - os: [ubuntu-latest, ubuntu-20.04] + os: [ubuntu-20.04] runs-on: ${{ matrix.os }} steps: - uses: actions/checkout@v2 diff --git a/.github/workflows/windows.yml b/.github/workflows/windows.yml index edc9d621..64b374fb 100644 --- a/.github/workflows/windows.yml +++ b/.github/workflows/windows.yml @@ -5,7 +5,7 @@ jobs: check: strategy: matrix: - os: [windows-latest, windows-2019] + os: [windows-latest] runs-on: ${{ matrix.os }} steps: - uses: actions/checkout@v2 diff --git a/Cargo.lock b/Cargo.lock index 3dd7c0b2..ff4dab1b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -217,7 +217,7 @@ checksum = "6f6ca6f0c18c02c2fbfc119df551b8aeb8a385f6d5980f1475ba0255f1e97f1e" dependencies = [ "anyhow", "arrayvec 0.7.4", - "itertools", + "itertools 0.10.5", "log", "nom", "num-rational", @@ -2039,6 +2039,15 @@ dependencies = [ "either", ] +[[package]] +name = "itertools" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1c173a5686ce8bfa551b3563d0c2170bf24ca44da99c7ca4bfdab5418c3fe57" +dependencies = [ + "either", +] + [[package]] name = "itoa" version = "1.0.8" @@ -3192,7 +3201,7 @@ dependencies = [ [[package]] name = "oculante" -version = "0.6.68-dev1" +version = "0.6.68-dev2" dependencies = [ "anyhow", "arboard", @@ -3209,6 +3218,7 @@ dependencies = [ "gif", "gif-dispose", "image", + "itertools 0.11.0", "jxl-oxide", "kamadak-exif", "lexical-sort", @@ -3714,7 +3724,7 @@ dependencies = [ "cc", "cfg-if 1.0.0", "interpolate_name", - "itertools", + "itertools 0.10.5", "libc", "libfuzzer-sys", "log", diff --git a/Cargo.toml b/Cargo.toml index cd2e5c30..d8df0e2e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -32,6 +32,7 @@ fast_image_resize = "2.7" gif = "0.12" gif-dispose = "4" image = "0.24" +itertools = "0.11.0" kamadak-exif = "0.5" lexical-sort = "0.3" libavif-image = {version = "0.10", optional = true} diff --git a/PKGBUILD b/PKGBUILD index b8cc2b3c..3d10ecd7 100644 --- a/PKGBUILD +++ b/PKGBUILD @@ -23,4 +23,4 @@ package() { install -Dm644 res/oculante.desktop "${pkgdir}/usr/share/applications/${pkgname}.desktop" install -Dm644 LICENSE -t "${pkgdir}/usr/share/licenses/${pkgname}" install -Dm644 README.md -t "${pkgdir}/usr/share/doc/${pkgname}" -} +} \ No newline at end of file diff --git a/src/main.rs b/src/main.rs index ce15ccd6..b9cbb0b2 100644 --- a/src/main.rs +++ b/src/main.rs @@ -2,6 +2,7 @@ use clap::Arg; use clap::Command; +use itertools::Itertools; use log::debug; use log::error; use log::info; @@ -48,6 +49,7 @@ mod image_editing; pub mod paint; pub const FONT: &[u8; 309828] = include_bytes!("../res/fonts/Inter-Regular.ttf"); +const FAVOURITES_FILE: &str = "favourites.txt"; #[notan_main] fn main() -> Result<(), String> { @@ -663,7 +665,7 @@ fn drawe(app: &mut App, gfx: &mut Graphics, plugins: &mut Plugins, state: &mut O // fill image sequence if state.folder_selected.is_none() { if let Some(p) = &state.current_path { - state.scrubber = scrubber::Scrubber::new(p, false, false); + state.scrubber = scrubber::Scrubber::new(p, None, false, false); state.scrubber.wrap = state.persistent_settings.wrap_folder; // debug!("{:#?} from {}", &state.scrubber, p.display()); @@ -1073,7 +1075,12 @@ fn browse_for_folder_path(state: &mut OculanteState) { _ = state.persistent_settings.save(); state.folder_selected = Option::from(folder_path.clone()); - state.scrubber = Scrubber::new(folder_path.as_path(), true, true); + state.scrubber = Scrubber::new( + folder_path.as_path(), + Some(FAVOURITES_FILE), + true, + true, + ); let current_path = state.scrubber.next(); state.is_loaded = false; @@ -1128,7 +1135,7 @@ fn add_to_favourites(state: &OculanteState) { state.folder_selected .as_ref() .unwrap_or(&img_path.parent().unwrap().to_path_buf()) - .join(Path::new("favourites.txt")) + .join(Path::new(FAVOURITES_FILE)) ) .expect("Unable to open file"); @@ -1137,7 +1144,9 @@ fn add_to_favourites(state: &OculanteState) { "{}", img_path.strip_prefix(state.folder_selected.as_ref().unwrap().as_path()) .unwrap() - .to_string_lossy() + .components() + .map(|component| component.as_os_str().to_str().unwrap()) + .join("\t") ).expect("Unable to write data"); } } diff --git a/src/scrubber.rs b/src/scrubber.rs index 7c970552..04d43b5d 100644 --- a/src/scrubber.rs +++ b/src/scrubber.rs @@ -2,6 +2,8 @@ use crate::utils::is_ext_compatible; use anyhow::{bail, Context, Result}; use log::debug; use rand::seq::SliceRandom; +use std::collections::HashSet; +use std::io::{BufRead, BufReader}; use std::path::{Path, PathBuf}; use walkdir::WalkDir; @@ -13,8 +15,8 @@ pub struct Scrubber { } impl Scrubber { - pub fn new(path: &Path, randomize: bool, walk_files: bool) -> Self { - let entries = get_image_filenames_for_directory(path, randomize, walk_files) + pub fn new(path: &Path, favourites_file: Option<&str>, randomize: bool, walk_files: bool) -> Self { + let entries = get_image_filenames_for_directory(path, favourites_file, randomize, walk_files) .unwrap_or_default(); let index = entries.iter().position(|p| p == path).unwrap_or_default(); Self { @@ -64,7 +66,7 @@ impl Scrubber { // Get sorted list of files in a folder // TODO: Should probably return an Result instead, but am too lazy to figure out + handle a dedicated error type here // TODO: Cache this result, instead of doing it each time we need to fetch another file from the folder -pub fn get_image_filenames_for_directory(folder_path: &Path, randomize: bool, walk_files: bool) -> Result> { +pub fn get_image_filenames_for_directory(folder_path: &Path, favourites_file: Option<&str>, randomize: bool, walk_files: bool) -> Result> { let mut folder_path = folder_path.to_path_buf(); if folder_path.is_file() { folder_path = folder_path @@ -73,6 +75,22 @@ pub fn get_image_filenames_for_directory(folder_path: &Path, randomize: bool, wa .context("Can't get parent")?; } + let mut _favourites: HashSet = Default::default(); + + if let Some(favourites_file) = favourites_file { + let favourites_path = folder_path.join(Path::new(favourites_file)); + if favourites_path.exists() { + let file = std::fs::File::open(favourites_path)?; + let reader = BufReader::new(file); + _favourites = reader + .lines() + .filter_map(|line| line.ok()) + .map(|file_str| folder_path.join(join_path_parts(file_str))) + .filter(|file| file.exists()) + .collect(); + } + } + let mut dir_files: Vec; if walk_files { @@ -91,6 +109,8 @@ pub fn get_image_filenames_for_directory(folder_path: &Path, randomize: bool, wa .collect::>(); } + debug!("number of files: {}", dir_files.len()); + // TODO: Are symlinks handled correctly? if randomize { @@ -118,9 +138,19 @@ pub fn find_first_image_in_directory(folder_path: &PathBuf) -> Result { if !folder_path.is_dir() { bail!("This is not a folder"); }; - get_image_filenames_for_directory(folder_path, false, false).map(|x| { + get_image_filenames_for_directory(folder_path, None, false, false).map(|x| { x.first() .cloned() .context("Folder does not have any supported images in it") })? } + +fn join_path_parts(path_with_tabs: String) -> PathBuf { + let mut path = PathBuf::new(); + + for part in path_with_tabs.split("\t") { + path.push(part); + } + + path +} From 88121e94f448ec933362bbb60be04be13028c8ba Mon Sep 17 00:00:00 2001 From: Vitaliy Grabovets Date: Mon, 10 Jul 2023 17:07:01 +0300 Subject: [PATCH 20/73] intersperse files with favourites --- .github/workflows/release.yml | 2 +- Cargo.lock | 2 +- Cargo.toml | 2 +- PKGBUILD | 2 +- src/main.rs | 3 +- src/scrubber.rs | 75 ++++++++++++++++++++++++++++++----- 6 files changed, 71 insertions(+), 15 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index f6e67bca..b9c926bf 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -34,7 +34,7 @@ jobs: strategy: fail-fast: false matrix: - os: [ubuntu-20.04, windows-2019, macOS-latest, ubuntu-latest] + os: [ubuntu-20.04, windows-2019] rust: [stable] steps: - uses: actions/checkout@v2 diff --git a/Cargo.lock b/Cargo.lock index ff4dab1b..654e78e7 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3201,7 +3201,7 @@ dependencies = [ [[package]] name = "oculante" -version = "0.6.68-dev2" +version = "0.6.68-dev3" dependencies = [ "anyhow", "arboard", diff --git a/Cargo.toml b/Cargo.toml index d8df0e2e..c799e550 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -11,7 +11,7 @@ license = "MIT" name = "oculante" readme = "README.md" repository = "https://github.com/woelper/oculante/" -version = "0.6.68-dev2" +version = "0.6.68-dev3" [package.metadata.bundle] icon = ["res/oculante.png"] diff --git a/PKGBUILD b/PKGBUILD index 3d10ecd7..c4d997f4 100644 --- a/PKGBUILD +++ b/PKGBUILD @@ -1,6 +1,6 @@ # Maintainer: Johann Woelper pkgname=oculante -pkgver=0.6.68-dev2 +pkgver=0.6.68-dev3 pkgrel=1 depends=('aom' 'libwebp' 'expat' 'freetype2' 'gtk3' 'cairo') makedepends=('rust' 'cargo' 'tar' 'nasm' 'cmake') diff --git a/src/main.rs b/src/main.rs index b9cbb0b2..44b2eeeb 100644 --- a/src/main.rs +++ b/src/main.rs @@ -665,7 +665,7 @@ fn drawe(app: &mut App, gfx: &mut Graphics, plugins: &mut Plugins, state: &mut O // fill image sequence if state.folder_selected.is_none() { if let Some(p) = &state.current_path { - state.scrubber = scrubber::Scrubber::new(p, None, false, false); + state.scrubber = scrubber::Scrubber::new(p, None, false, false, None); state.scrubber.wrap = state.persistent_settings.wrap_folder; // debug!("{:#?} from {}", &state.scrubber, p.display()); @@ -1080,6 +1080,7 @@ fn browse_for_folder_path(state: &mut OculanteState) { Some(FAVOURITES_FILE), true, true, + Some(3), ); let current_path = state.scrubber.next(); diff --git a/src/scrubber.rs b/src/scrubber.rs index 04d43b5d..259a57bf 100644 --- a/src/scrubber.rs +++ b/src/scrubber.rs @@ -15,8 +15,20 @@ pub struct Scrubber { } impl Scrubber { - pub fn new(path: &Path, favourites_file: Option<&str>, randomize: bool, walk_files: bool) -> Self { - let entries = get_image_filenames_for_directory(path, favourites_file, randomize, walk_files) + pub fn new( + path: &Path, + favourites_file: Option<&str>, + randomize: bool, + walk_files: bool, + intersperse_with_favs_every_n: Option, + ) -> Self { + let entries = get_image_filenames_for_directory( + path, + favourites_file, + randomize, + walk_files, + intersperse_with_favs_every_n, + ) .unwrap_or_default(); let index = entries.iter().position(|p| p == path).unwrap_or_default(); Self { @@ -66,7 +78,13 @@ impl Scrubber { // Get sorted list of files in a folder // TODO: Should probably return an Result instead, but am too lazy to figure out + handle a dedicated error type here // TODO: Cache this result, instead of doing it each time we need to fetch another file from the folder -pub fn get_image_filenames_for_directory(folder_path: &Path, favourites_file: Option<&str>, randomize: bool, walk_files: bool) -> Result> { +pub fn get_image_filenames_for_directory( + folder_path: &Path, + favourites_file: Option<&str>, + randomize: bool, + walk_files: bool, + intersperse_with_favs_every_n: Option, +) -> Result> { let mut folder_path = folder_path.to_path_buf(); if folder_path.is_file() { folder_path = folder_path @@ -75,14 +93,14 @@ pub fn get_image_filenames_for_directory(folder_path: &Path, favourites_file: Op .context("Can't get parent")?; } - let mut _favourites: HashSet = Default::default(); + let mut favourites: HashSet = Default::default(); if let Some(favourites_file) = favourites_file { let favourites_path = folder_path.join(Path::new(favourites_file)); if favourites_path.exists() { let file = std::fs::File::open(favourites_path)?; let reader = BufReader::new(file); - _favourites = reader + favourites = reader .lines() .filter_map(|line| line.ok()) .map(|file_str| folder_path.join(join_path_parts(file_str))) @@ -97,8 +115,8 @@ pub fn get_image_filenames_for_directory(folder_path: &Path, favourites_file: Op dir_files = WalkDir::new(folder_path) .into_iter() .filter_map(|v| v.ok()) - .filter(|x| is_ext_compatible(x.path())) - .map(|x| x.into_path()) + .map(|entry| entry.into_path()) + .filter(|x| is_ext_compatible(x)) .collect::>(); } else { let info = std::fs::read_dir(folder_path)?; @@ -109,13 +127,14 @@ pub fn get_image_filenames_for_directory(folder_path: &Path, favourites_file: Op .collect::>(); } - debug!("number of files: {}", dir_files.len()); - // TODO: Are symlinks handled correctly? + let mut favourites: Vec = favourites.into_iter().collect(); + if randomize { let mut rng = rand::thread_rng(); dir_files.shuffle(&mut rng); + favourites.shuffle(&mut rng); } else { dir_files.sort_unstable_by(|a, b| { lexical_sort::natural_lexical_cmp( @@ -129,6 +148,10 @@ pub fn get_image_filenames_for_directory(folder_path: &Path, favourites_file: Op }); } + if let Some(every_n) = intersperse_with_favs_every_n { + dir_files = insert_after_every(dir_files, favourites, every_n); + } + debug!("number of files: {}", dir_files.len()); return Ok(dir_files); } @@ -138,7 +161,14 @@ pub fn find_first_image_in_directory(folder_path: &PathBuf) -> Result { if !folder_path.is_dir() { bail!("This is not a folder"); }; - get_image_filenames_for_directory(folder_path, None, false, false).map(|x| { + get_image_filenames_for_directory( + folder_path, + None, + false, + false, + None, + ) + .map(|x| { x.first() .cloned() .context("Folder does not have any supported images in it") @@ -154,3 +184,28 @@ fn join_path_parts(path_with_tabs: String) -> PathBuf { path } + +fn insert_after_every(main_vector: Vec, other_vector: Vec, after: usize) -> Vec { + let mut result = Vec::with_capacity(main_vector.len() + other_vector.len()); + let mut other_vector_i = 0; + let other_vector_set: HashSet = other_vector.clone().into_iter().collect(); + + for (i, element) in main_vector.into_iter().enumerate() { + if other_vector_set.contains(&element) { + continue + } + + result.push(element); + if other_vector_i < other_vector.len() && (i + 1) % after == 0 { + result.push(other_vector[other_vector_i].clone()); + other_vector_i += 1; + } + } + + while other_vector_i < other_vector.len() { + result.push(other_vector[other_vector_i].clone()); + other_vector_i += 1; + } + + result +} From a8358bfcd446a76192e112d0b0bbf5577d1b696a Mon Sep 17 00:00:00 2001 From: Vitaliy <22475959+vgrabovets@users.noreply.github.com> Date: Fri, 14 Jul 2023 18:52:58 +0300 Subject: [PATCH 21/73] Slideshow (#2) * tokio async * use enigo * use eager loop * remove redundant crates * add favourites icon to file name * do not save favourites several times --- src/appstate.rs | 5 ++++ src/main.rs | 65 +++++++++++++++++++++++++++++------------------- src/scrubber.rs | 17 +++++++------ src/shortcuts.rs | 2 ++ src/utils.rs | 4 +++ 5 files changed, 60 insertions(+), 33 deletions(-) diff --git a/src/appstate.rs b/src/appstate.rs index f5aee785..ebf93d82 100644 --- a/src/appstate.rs +++ b/src/appstate.rs @@ -10,6 +10,7 @@ use notan::{egui::epaint::ahash::HashMap, prelude::Texture, AppState}; use std::{ path::PathBuf, sync::mpsc::{self, Receiver, Sender}, + time::Instant, }; #[derive(Debug, Clone)] @@ -85,6 +86,8 @@ pub struct OculanteState { pub checker_texture: Option, pub animation_mode: bool, pub folder_selected: Option, + pub toggle_slideshow: bool, + pub slideshow_time: Instant, pub first_start: bool } @@ -141,6 +144,8 @@ impl Default for OculanteState { checker_texture: Default::default(), animation_mode: Default::default(), folder_selected: Default::default(), + toggle_slideshow: false, + slideshow_time: Instant::now(), first_start: true, } } diff --git a/src/main.rs b/src/main.rs index 44b2eeeb..b3499db1 100644 --- a/src/main.rs +++ b/src/main.rs @@ -18,6 +18,7 @@ use std::io::Write; use std::path::Path; use std::path::PathBuf; use std::sync::mpsc; +use std::time::{Duration, Instant}; pub mod cache; pub mod scrubber; pub mod settings; @@ -26,6 +27,7 @@ pub mod shortcuts; use crate::image_editing::lossless_tx; use crate::scrubber::{find_first_image_in_directory, Scrubber}; use crate::shortcuts::InputEvent::*; +use crate::utils::set_title; mod utils; use utils::*; mod appstate; @@ -81,17 +83,17 @@ fn main() -> Result<(), String> { #[cfg(target_os = "linux")] { - window_config = window_config.lazy_loop(true).vsync(true).high_dpi(true); + window_config = window_config.lazy_loop(false).vsync(true).high_dpi(true); } #[cfg(target_os = "netbsd")] { - window_config = window_config.lazy_loop(true).vsync(true); + window_config = window_config.lazy_loop(false).vsync(true); } #[cfg(target_os = "macos")] { - window_config = window_config.lazy_loop(true).vsync(true).high_dpi(true); + window_config = window_config.lazy_loop(false).vsync(true).high_dpi(true); } #[cfg(target_os = "macos")] @@ -360,7 +362,10 @@ fn event(app: &mut App, state: &mut OculanteState, evt: Event) { set_zoom(5.0, None, state); } if key_pressed(app, state, Favourite) { - add_to_favourites(state); + add_to_favourites(app, state); + } + if key_pressed(app, state, ToggleSlideshow) { + state.toggle_slideshow = !state.toggle_slideshow; } if key_pressed(app, state, Quit) { state.persistent_settings.save_blocking(); @@ -646,6 +651,10 @@ fn update(app: &mut App, state: &mut OculanteState) { } state.first_start = false; + if state.toggle_slideshow && state.is_loaded && state.slideshow_time.elapsed() >= Duration::from_secs(2) { + next_image(state); + state.slideshow_time = Instant::now(); + } } fn drawe(app: &mut App, gfx: &mut Graphics, plugins: &mut Plugins, state: &mut OculanteState) { @@ -797,7 +806,6 @@ fn drawe(app: &mut App, gfx: &mut Graphics, plugins: &mut Plugins, state: &mut O state.image_geometry.offset = window_size / 2.0 - (img_size * state.image_geometry.scale) / 2.0; - debug!("Image has been reset."); state.reset_image = false; } // app.window().request_frame(); @@ -1127,27 +1135,32 @@ fn set_zoom(scale: f32, from_center: Option>, state: &mut OculanteS state.image_geometry.scale = scale; } -fn add_to_favourites(state: &OculanteState) { +fn add_to_favourites(app: &mut App, state: &mut OculanteState) { if let Some(img_path) = &state.current_path { - let mut file = fs::OpenOptions::new() - .append(true) - .create(true) - .open( - state.folder_selected - .as_ref() - .unwrap_or(&img_path.parent().unwrap().to_path_buf()) - .join(Path::new(FAVOURITES_FILE)) - ) - .expect("Unable to open file"); - - writeln!( - file, - "{}", - img_path.strip_prefix(state.folder_selected.as_ref().unwrap().as_path()) - .unwrap() - .components() - .map(|component| component.as_os_str().to_str().unwrap()) - .join("\t") - ).expect("Unable to write data"); + if !state.scrubber.favourites.contains(img_path) { + let mut file = fs::OpenOptions::new() + .append(true) + .create(true) + .open( + state.folder_selected + .as_ref() + .unwrap_or(&img_path.parent().unwrap().to_path_buf()) + .join(Path::new(FAVOURITES_FILE)) + ) + .expect("Unable to open file"); + + writeln!( + file, + "{}", + img_path.strip_prefix(state.folder_selected.as_ref().unwrap().as_path()) + .unwrap() + .components() + .map(|component| component.as_os_str().to_str().unwrap()) + .join("\t") + ).expect("Unable to write data"); + + state.scrubber.favourites.insert(img_path.clone()); + set_title(app, state); + } } } diff --git a/src/scrubber.rs b/src/scrubber.rs index 259a57bf..c53c3680 100644 --- a/src/scrubber.rs +++ b/src/scrubber.rs @@ -3,6 +3,7 @@ use anyhow::{bail, Context, Result}; use log::debug; use rand::seq::SliceRandom; use std::collections::HashSet; +use std::default::Default; use std::io::{BufRead, BufReader}; use std::path::{Path, PathBuf}; use walkdir::WalkDir; @@ -12,6 +13,7 @@ pub struct Scrubber { pub index: usize, pub entries: Vec, pub wrap: bool, + pub favourites: HashSet, } impl Scrubber { @@ -22,7 +24,7 @@ impl Scrubber { walk_files: bool, intersperse_with_favs_every_n: Option, ) -> Self { - let entries = get_image_filenames_for_directory( + let (entries, favourites) = get_image_filenames_for_directory( path, favourites_file, randomize, @@ -35,6 +37,7 @@ impl Scrubber { index, entries, wrap: true, + favourites, } } pub fn next(&mut self) -> PathBuf { @@ -84,7 +87,7 @@ pub fn get_image_filenames_for_directory( randomize: bool, walk_files: bool, intersperse_with_favs_every_n: Option, -) -> Result> { +) -> Result<(Vec, HashSet)> { let mut folder_path = folder_path.to_path_buf(); if folder_path.is_file() { folder_path = folder_path @@ -129,12 +132,12 @@ pub fn get_image_filenames_for_directory( // TODO: Are symlinks handled correctly? - let mut favourites: Vec = favourites.into_iter().collect(); + let mut favourites_vec: Vec = favourites.clone().into_iter().collect(); if randomize { let mut rng = rand::thread_rng(); dir_files.shuffle(&mut rng); - favourites.shuffle(&mut rng); + favourites_vec.shuffle(&mut rng); } else { dir_files.sort_unstable_by(|a, b| { lexical_sort::natural_lexical_cmp( @@ -149,10 +152,10 @@ pub fn get_image_filenames_for_directory( } if let Some(every_n) = intersperse_with_favs_every_n { - dir_files = insert_after_every(dir_files, favourites, every_n); + dir_files = insert_after_every(dir_files, favourites_vec, every_n); } debug!("number of files: {}", dir_files.len()); - return Ok(dir_files); + return Ok((dir_files, favourites)); } /// Find first valid image from the directory @@ -168,7 +171,7 @@ pub fn find_first_image_in_directory(folder_path: &PathBuf) -> Result { false, None, ) - .map(|x| { + .map(|(x, _)| { x.first() .cloned() .context("Folder does not have any supported images in it") diff --git a/src/shortcuts.rs b/src/shortcuts.rs index 06e79a20..b1950344 100644 --- a/src/shortcuts.rs +++ b/src/shortcuts.rs @@ -44,6 +44,7 @@ pub enum InputEvent { Quit, ZenMode, Favourite, + ToggleSlideshow, } pub type Shortcuts = HashMap; @@ -131,6 +132,7 @@ impl ShortcutExt for Shortcuts { .add_key(InputEvent::LosslessRotateRight, "RBracket") .add_key(InputEvent::ZenMode, "Z") .add_key(InputEvent::Favourite, "J") + .add_key(InputEvent::ToggleSlideshow, "Space") // .add_key(InputEvent::Browse, "F1") // FIXME: As Shortcuts is a HashMap, only the newer key-sequence will be registered .add_keys(InputEvent::Browse, &["LControl", "O"]) .add_keys(InputEvent::PanRight, &["LShift", "Right"]) diff --git a/src/utils.rs b/src/utils.rs index 17e32339..3371ebfa 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -672,6 +672,10 @@ pub fn set_title(app: &mut App, state: &mut OculanteState) { 10, ); + if state.scrubber.favourites.contains(p.as_path()) { + title_string.push_str("🔖"); + } + if state.persistent_settings.zen_mode { title_string.push_str(&format!( " '{}' to disable zen mode", From 94984ae9c2210fc179a3ee5278ef7e89bacb6f22 Mon Sep 17 00:00:00 2001 From: Vitaliy Grabovets Date: Fri, 14 Jul 2023 21:06:58 +0300 Subject: [PATCH 22/73] v0.6.68-dev4 --- Cargo.lock | 2 +- Cargo.toml | 2 +- PKGBUILD | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 9b4ecf68..7e0f9d2f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3201,7 +3201,7 @@ dependencies = [ [[package]] name = "oculante" -version = "0.6.68-dev3" +version = "0.6.68-dev4" dependencies = [ "anyhow", "arboard", diff --git a/Cargo.toml b/Cargo.toml index 2b3737a1..dc1e4504 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -11,7 +11,7 @@ license = "MIT" name = "oculante" readme = "README.md" repository = "https://github.com/woelper/oculante/" -version = "0.6.68-dev3" +version = "0.6.68-dev4" [package.metadata.bundle] icon = ["res/oculante.png"] diff --git a/PKGBUILD b/PKGBUILD index c4d997f4..8858fd33 100644 --- a/PKGBUILD +++ b/PKGBUILD @@ -1,6 +1,6 @@ # Maintainer: Johann Woelper pkgname=oculante -pkgver=0.6.68-dev3 +pkgver=0.6.68-dev4 pkgrel=1 depends=('aom' 'libwebp' 'expat' 'freetype2' 'gtk3' 'cairo') makedepends=('rust' 'cargo' 'tar' 'nasm' 'cmake') From 7e5eefdea83ee30c638152f4b989899fbd53d798 Mon Sep 17 00:00:00 2001 From: Vitaliy Grabovets Date: Sat, 15 Jul 2023 22:45:25 +0300 Subject: [PATCH 23/73] slideshow settings --- src/main.rs | 9 ++++++--- src/scrubber.rs | 21 ++++++++++++++++----- src/settings.rs | 4 ++++ src/ui.rs | 21 ++++++++++++++++++++- src/utils.rs | 1 - 5 files changed, 46 insertions(+), 10 deletions(-) diff --git a/src/main.rs b/src/main.rs index b3499db1..3b5ddf18 100644 --- a/src/main.rs +++ b/src/main.rs @@ -651,7 +651,10 @@ fn update(app: &mut App, state: &mut OculanteState) { } state.first_start = false; - if state.toggle_slideshow && state.is_loaded && state.slideshow_time.elapsed() >= Duration::from_secs(2) { + if state.toggle_slideshow + && state.is_loaded + && state.slideshow_time.elapsed() >= Duration::from_secs(state.persistent_settings.slideshow_delay) + { next_image(state); state.slideshow_time = Instant::now(); } @@ -674,7 +677,7 @@ fn drawe(app: &mut App, gfx: &mut Graphics, plugins: &mut Plugins, state: &mut O // fill image sequence if state.folder_selected.is_none() { if let Some(p) = &state.current_path { - state.scrubber = scrubber::Scrubber::new(p, None, false, false, None); + state.scrubber = scrubber::Scrubber::new(p, None, false, false, 0); state.scrubber.wrap = state.persistent_settings.wrap_folder; // debug!("{:#?} from {}", &state.scrubber, p.display()); @@ -1088,7 +1091,7 @@ fn browse_for_folder_path(state: &mut OculanteState) { Some(FAVOURITES_FILE), true, true, - Some(3), + state.persistent_settings.add_fav_every_n, ); let current_path = state.scrubber.next(); diff --git a/src/scrubber.rs b/src/scrubber.rs index c53c3680..8f0d3586 100644 --- a/src/scrubber.rs +++ b/src/scrubber.rs @@ -22,7 +22,7 @@ impl Scrubber { favourites_file: Option<&str>, randomize: bool, walk_files: bool, - intersperse_with_favs_every_n: Option, + intersperse_with_favs_every_n: usize, ) -> Self { let (entries, favourites) = get_image_filenames_for_directory( path, @@ -76,6 +76,17 @@ impl Scrubber { pub fn len(&mut self) -> usize { self.entries.len() } + + pub fn re_initialize(&mut self, intersperse_with_favs_every_n: usize) { + let entries_wo_favourites: Vec = self.entries + .iter() + .filter(|element| !self.favourites.contains(*element)) + .map(|element| element.clone()) + .collect(); + + let favourites_vec: Vec = self.favourites.clone().into_iter().collect(); + self.entries = insert_after_every(entries_wo_favourites, favourites_vec, intersperse_with_favs_every_n); + } } // Get sorted list of files in a folder @@ -86,7 +97,7 @@ pub fn get_image_filenames_for_directory( favourites_file: Option<&str>, randomize: bool, walk_files: bool, - intersperse_with_favs_every_n: Option, + intersperse_with_favs_every_n: usize, ) -> Result<(Vec, HashSet)> { let mut folder_path = folder_path.to_path_buf(); if folder_path.is_file() { @@ -151,8 +162,8 @@ pub fn get_image_filenames_for_directory( }); } - if let Some(every_n) = intersperse_with_favs_every_n { - dir_files = insert_after_every(dir_files, favourites_vec, every_n); + if intersperse_with_favs_every_n > 0 { + dir_files = insert_after_every(dir_files, favourites_vec, intersperse_with_favs_every_n); } debug!("number of files: {}", dir_files.len()); return Ok((dir_files, favourites)); @@ -169,7 +180,7 @@ pub fn find_first_image_in_directory(folder_path: &PathBuf) -> Result { None, false, false, - None, + 0, ) .map(|(x, _)| { x.first() diff --git a/src/settings.rs b/src/settings.rs index 88d4bf31..0a458a7e 100644 --- a/src/settings.rs +++ b/src/settings.rs @@ -18,6 +18,8 @@ pub struct PersistentSettings { pub keep_view: bool, /// How many images to keep in cache pub max_cache: usize, + pub slideshow_delay: u64, + pub add_fav_every_n: usize, pub show_scrub_bar: bool, pub wrap_folder: bool, /// Whether to keep the image edit stack @@ -48,6 +50,8 @@ impl Default for PersistentSettings { shortcuts: Shortcuts::default_keys(), keep_view: Default::default(), max_cache: 30, + slideshow_delay: 3, + add_fav_every_n: 3, show_scrub_bar: Default::default(), wrap_folder: true, keep_edits: Default::default(), diff --git a/src/ui.rs b/src/ui.rs index 46dbc83e..5e63fa6d 100644 --- a/src/ui.rs +++ b/src/ui.rs @@ -509,11 +509,30 @@ pub fn settings_ui(app: &mut App, ctx: &Context, state: &mut OculanteState) { } }); + ui.end_row(); + + ui.horizontal(|ui| { + ui.label("Slideshow delay"); + ui + .add(egui::DragValue::new(&mut state.persistent_settings.slideshow_delay).clamp_range(1..=10)) + .on_hover_text("Slideshow delay, seconds") + }); + + ui.horizontal(|ui| { + ui.label("Add favourite every n slides"); + if ui + .add(egui::DragValue::new(&mut state.persistent_settings.add_fav_every_n).clamp_range(0..=25)) + .on_hover_text("Add favourite to slideshow every n slide") + .changed() + { + state.scrubber.re_initialize(state.persistent_settings.add_fav_every_n); + } + }); + if ui.link("Visit github repo").on_hover_text("Check out the source code, request a feature, submit a bug or leave a star if you like it!").clicked() { _ = webbrowser::open("https://github.com/woelper/oculante"); } - ui.vertical_centered_justified(|ui| { #[cfg(feature = "update")] diff --git a/src/utils.rs b/src/utils.rs index 3371ebfa..5b017484 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -148,7 +148,6 @@ impl Player { } pub fn load(&mut self, img_location: &Path, message_sender: Sender) { - debug!("Stopping player on load"); self.stop(); let (stop_sender, stop_receiver): (Sender<()>, Receiver<()>) = mpsc::channel(); self.stop_sender = stop_sender; From 0431796c5186d75cab6ac46ae5b211fe1a38128d Mon Sep 17 00:00:00 2001 From: Vitaliy Grabovets Date: Sat, 15 Jul 2023 22:58:33 +0300 Subject: [PATCH 24/73] refactor ui --- src/ui.rs | 614 ++++++++++++++++++++++++++---------------------------- 1 file changed, 295 insertions(+), 319 deletions(-) diff --git a/src/ui.rs b/src/ui.rs index 5e63fa6d..801826b8 100644 --- a/src/ui.rs +++ b/src/ui.rs @@ -27,11 +27,13 @@ use notan::{ }; use std::{collections::HashSet, ops::RangeInclusive, path::PathBuf, time::Instant}; use strum::IntoEnumIterator; + const PANEL_WIDTH: f32 = 240.0; const PANEL_WIDGET_OFFSET: f32 = 10.0; #[cfg(feature = "turbo")] use crate::image_editing::{cropped_range, lossless_tx}; + pub trait EguiExt { fn label_i(&mut self, _text: &str) -> Response { unimplemented!() @@ -73,7 +75,7 @@ impl EguiExt for Ui { RichText::new(description).color(ui.style().visuals.noninteractive().text_color()), ); }) - .response + .response } /// Draw a justified icon from a string starting with an emoji @@ -96,7 +98,7 @@ impl EguiExt for Ui { } r }) - .inner + .inner } fn slider_styled( @@ -128,9 +130,9 @@ impl EguiExt for Ui { ui.monospace(format!("{:.0}", value.to_f64())); r }) - .inner + .inner }) - .inner + .inner } fn slider_timeline( @@ -170,9 +172,9 @@ impl EguiExt for Ui { )); r }) - .inner + .inner }) - .inner + .inner } } @@ -187,257 +189,246 @@ pub fn info_ui(ctx: &Context, state: &mut OculanteState, gfx: &mut Graphics) { } egui::SidePanel::left("side_panel") - .max_width(PANEL_WIDTH) - .min_width(PANEL_WIDTH/2.) - .show(ctx, |ui| { - - - egui::ScrollArea::vertical().auto_shrink([false,true]) - .show(ui, |ui| { - if let Some(texture) = &state.current_texture { - // texture. - let tex_id = gfx.egui_register_texture(texture); - - // width of image widget - // let desired_width = ui.available_width() - ui.spacing().indent; - let desired_width = PANEL_WIDTH - PANEL_WIDGET_OFFSET; - - let scale = (desired_width / 8.) / texture.size().0; + .max_width(PANEL_WIDTH) + .min_width(PANEL_WIDTH / 2.) + .show(ctx, |ui| { + egui::ScrollArea::vertical().auto_shrink([false, true]) + .show(ui, |ui| { + if let Some(texture) = &state.current_texture { + // texture. + let tex_id = gfx.egui_register_texture(texture); - let uv_center = ( - state.cursor_relative.x / state.image_dimension.0 as f32, - (state.cursor_relative.y / state.image_dimension.1 as f32), - ); + // width of image widget + // let desired_width = ui.available_width() - ui.spacing().indent; + let desired_width = PANEL_WIDTH - PANEL_WIDGET_OFFSET; - egui::Grid::new("info").show(ui, |ui| { - ui.label_i("⬜ Size"); + let scale = (desired_width / 8.) / texture.size().0; - ui.label( - RichText::new(format!( - "{}x{}", - state.image_dimension.0, state.image_dimension.1 - )) - .monospace(), - ); - ui.end_row(); + let uv_center = ( + state.cursor_relative.x / state.image_dimension.0 as f32, + (state.cursor_relative.y / state.image_dimension.1 as f32), + ); + egui::Grid::new("info").show(ui, |ui| { + ui.label_i("⬜ Size"); - if let Some(path) = &state.current_path { - // make sure we truncate filenames - let max_chars = 20; - let file_name = path.file_name().unwrap_or_default().to_string_lossy(); - let skip_symbol = if file_name.chars().count() > max_chars {".."} else {""}; + ui.label( + RichText::new(format!( + "{}x{}", + state.image_dimension.0, state.image_dimension.1 + )) + .monospace(), + ); + ui.end_row(); - ui.label_i("🖻 File"); - ui.label( - RichText::new(format!( - "{skip_symbol}{}", - file_name.chars().rev().take(max_chars).collect::().chars().rev().collect::() - )) - // .monospace(), - ) - .on_hover_text(format!("{}", path.display())); - ui.end_row(); - } - ui.label_i("🌗 RGBA"); - ui.label( - RichText::new(disp_col(state.sampled_color)) - .monospace() - .background_color(Color32::from_rgba_unmultiplied(255, 255, 255, 6)), - ); - ui.end_row(); + if let Some(path) = &state.current_path { + // make sure we truncate filenames + let max_chars = 20; + let file_name = path.file_name().unwrap_or_default().to_string_lossy(); + let skip_symbol = if file_name.chars().count() > max_chars { ".." } else { "" }; + + ui.label_i("🖻 File"); + ui.label( + RichText::new(format!( + "{skip_symbol}{}", + file_name.chars().rev().take(max_chars).collect::().chars().rev().collect::() + )) + // .monospace(), + ) + .on_hover_text(format!("{}", path.display())); + ui.end_row(); + } - ui.label_i("🌗 RGBA"); - ui.label( - RichText::new(disp_col_norm(state.sampled_color, 255.)) - .monospace() - .background_color(Color32::from_rgba_unmultiplied(255, 255, 255, 6)), - ); - ui.end_row(); + ui.label_i("🌗 RGBA"); + ui.label( + RichText::new(disp_col(state.sampled_color)) + .monospace() + .background_color(Color32::from_rgba_unmultiplied(255, 255, 255, 6)), + ); + ui.end_row(); - ui.label_i("⊞ Pos"); - ui.label( - RichText::new(format!( - "{:.0},{:.0}", - state.cursor_relative.x, state.cursor_relative.y - )) - .monospace() - .background_color(Color32::from_rgba_unmultiplied(255, 255, 255, 6)), - ); - ui.end_row(); + ui.label_i("🌗 RGBA"); + ui.label( + RichText::new(disp_col_norm(state.sampled_color, 255.)) + .monospace() + .background_color(Color32::from_rgba_unmultiplied(255, 255, 255, 6)), + ); + ui.end_row(); - ui.label_i(" UV"); - ui.label( - RichText::new(format!("{:.3},{:.3}", uv_center.0, 1.0 - uv_center.1)) - .monospace() - .background_color(Color32::from_rgba_unmultiplied(255, 255, 255, 6)), - ); - ui.end_row(); - }); + ui.label_i("⊞ Pos"); + ui.label( + RichText::new(format!( + "{:.0},{:.0}", + state.cursor_relative.x, state.cursor_relative.y + )) + .monospace() + .background_color(Color32::from_rgba_unmultiplied(255, 255, 255, 6)), + ); + ui.end_row(); - // make sure aspect ratio is compensated for the square preview - let ratio = texture.size().0 / texture.size().1; - let uv_size = (scale, scale * ratio); + ui.label_i(" UV"); + ui.label( + RichText::new(format!("{:.3},{:.3}", uv_center.0, 1.0 - uv_center.1)) + .monospace() + .background_color(Color32::from_rgba_unmultiplied(255, 255, 255, 6)), + ); + ui.end_row(); + }); + // make sure aspect ratio is compensated for the square preview + let ratio = texture.size().0 / texture.size().1; + let uv_size = (scale, scale * ratio); - let preview_rect = ui - .add( - egui::Image::new(tex_id, egui::Vec2::splat(desired_width)).uv(egui::Rect::from_x_y_ranges( - uv_center.0 - uv_size.0..=uv_center.0 + uv_size.0, - uv_center.1 - uv_size.1..=uv_center.1 + uv_size.1, - )), - ) - .rect; + let preview_rect = ui + .add( + egui::Image::new(tex_id, egui::Vec2::splat(desired_width)).uv(egui::Rect::from_x_y_ranges( + uv_center.0 - uv_size.0..=uv_center.0 + uv_size.0, + uv_center.1 - uv_size.1..=uv_center.1 + uv_size.1, + )), + ) + .rect; - let stroke_color = Color32::from_white_alpha(240); - let bg_color = Color32::BLACK.linear_multiply(0.5); - ui.painter_at(preview_rect).line_segment( - [preview_rect.center_bottom(), preview_rect.center_top()], - Stroke::new(4., bg_color), - ); - ui.painter_at(preview_rect).line_segment( - [preview_rect.left_center(), preview_rect.right_center()], - Stroke::new(4., bg_color), - ); - ui.painter_at(preview_rect).line_segment( - [preview_rect.center_bottom(), preview_rect.center_top()], - Stroke::new(1., stroke_color), - ); - ui.painter_at(preview_rect).line_segment( - [preview_rect.left_center(), preview_rect.right_center()], - Stroke::new(1., stroke_color), - ); - } - ui.collapsing("Compare", |ui| { - ui.vertical_centered_justified(|ui| { - if let Some(p) = &(state.current_path).clone() { - if ui.button("Add/update current image").clicked() { - state.compare_list.insert(p.clone(), state.image_geometry.clone()); + let stroke_color = Color32::from_white_alpha(240); + let bg_color = Color32::BLACK.linear_multiply(0.5); + ui.painter_at(preview_rect).line_segment( + [preview_rect.center_bottom(), preview_rect.center_top()], + Stroke::new(4., bg_color), + ); + ui.painter_at(preview_rect).line_segment( + [preview_rect.left_center(), preview_rect.right_center()], + Stroke::new(4., bg_color), + ); + ui.painter_at(preview_rect).line_segment( + [preview_rect.center_bottom(), preview_rect.center_top()], + Stroke::new(1., stroke_color), + ); + ui.painter_at(preview_rect).line_segment( + [preview_rect.left_center(), preview_rect.right_center()], + Stroke::new(1., stroke_color), + ); } + ui.collapsing("Compare", |ui| { + ui.vertical_centered_justified(|ui| { + if let Some(p) = &(state.current_path).clone() { + if ui.button("Add/update current image").clicked() { + state.compare_list.insert(p.clone(), state.image_geometry.clone()); + } - let mut compare_list: Vec<(PathBuf, ImageGeometry)> = state.compare_list.clone().into_iter().collect(); - compare_list.sort_by(|a,b| a.0.cmp(&b.0)); - 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()); - 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; - } - }); - }); + let mut compare_list: Vec<(PathBuf, ImageGeometry)> = state.compare_list.clone().into_iter().collect(); + compare_list.sort_by(|a, b| a.0.cmp(&b.0)); + 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()); + 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; + } + }); + }); - ui.collapsing("Alpha tools", |ui| { - ui.vertical_centered_justified(|ui| { - if let Some(img) = &state.current_image { - if ui - .button("Show alpha bleed") - .on_hover_text("Highlight pixels with zero alpha and color information") - .clicked() - { - state.current_texture = highlight_bleed(img).to_texture(gfx); - } - if ui - .button("Show semi-transparent pixels") - .on_hover_text( - "Highlight pixels that are neither fully opaque nor fully transparent", - ) - .clicked() - { - state.current_texture = highlight_semitrans(img).to_texture(gfx); - } - if ui.button("Reset image").clicked() { - state.current_texture = img.to_texture(gfx); - } + ui.collapsing("Alpha tools", |ui| { + ui.vertical_centered_justified(|ui| { + if let Some(img) = &state.current_image { + if ui + .button("Show alpha bleed") + .on_hover_text("Highlight pixels with zero alpha and color information") + .clicked() + { + state.current_texture = highlight_bleed(img).to_texture(gfx); + } + if ui + .button("Show semi-transparent pixels") + .on_hover_text( + "Highlight pixels that are neither fully opaque nor fully transparent", + ) + .clicked() + { + state.current_texture = highlight_semitrans(img).to_texture(gfx); + } + if ui.button("Reset image").clicked() { + state.current_texture = img.to_texture(gfx); + } + } + }); + }); + // ui.add(egui::Slider::new(&mut state.tiling, 1..=10).text("Image tiling")); - } + ui.horizontal(|ui| { + ui.label("Tiling"); + ui.slider_styled(&mut state.tiling, 1..=10); + }); + advanced_ui(ui, state); }); - }); - // ui.add(egui::Slider::new(&mut state.tiling, 1..=10).text("Image tiling")); - - ui.horizontal(|ui| { - ui.label("Tiling"); - ui.slider_styled(&mut state.tiling, 1..=10); - }); - advanced_ui(ui, state); - }); - - - - - - }); } pub fn settings_ui(app: &mut App, ctx: &Context, state: &mut OculanteState) { let mut settings_enabled = state.settings_enabled; egui::Window::new("Preferences") - .anchor(Align2::CENTER_CENTER, [0.0, 0.0]) - .collapsible(false) - .open(&mut settings_enabled) - .resizable(true) - .default_width(600.) - .show(ctx, |ui| { - - #[cfg(debug_assertions)] - if ui.button("send test msg").clicked() { - state.send_message("Test"); - } + .anchor(Align2::CENTER_CENTER, [0.0, 0.0]) + .collapsible(false) + .open(&mut settings_enabled) + .resizable(true) + .default_width(600.) + .show(ctx, |ui| { + #[cfg(debug_assertions)] + if ui.button("send test msg").clicked() { + state.send_message("Test"); + } - egui::Grid::new("settings").num_columns(2).show(ui, |ui| { - ui.horizontal(|ui| { - if ui - .color_edit_button_srgb(&mut state.persistent_settings.accent_color) - .changed() - { - let mut style: egui::Style = (*ctx.style()).clone(); - style.visuals.selection.bg_fill = Color32::from_rgb( - state.persistent_settings.accent_color[0], - state.persistent_settings.accent_color[1], - state.persistent_settings.accent_color[2], - ); - ctx.set_style(style); - } - ui.label("Accent color"); - }); + egui::Grid::new("settings").num_columns(2).show(ui, |ui| { + ui.horizontal(|ui| { + if ui + .color_edit_button_srgb(&mut state.persistent_settings.accent_color) + .changed() + { + let mut style: egui::Style = (*ctx.style()).clone(); + style.visuals.selection.bg_fill = Color32::from_rgb( + state.persistent_settings.accent_color[0], + state.persistent_settings.accent_color[1], + state.persistent_settings.accent_color[2], + ); + ctx.set_style(style); + } + ui.label("Accent color"); + }); - ui.horizontal(|ui| { - ui.color_edit_button_srgb(&mut state.persistent_settings.background_color); - ui.label("Background color"); - }); + ui.horizontal(|ui| { + ui.color_edit_button_srgb(&mut state.persistent_settings.background_color); + ui.label("Background color"); + }); - ui.end_row(); + ui.end_row(); - ui + ui .checkbox(&mut state.persistent_settings.vsync, "Enable vsync") .on_hover_text( "Vsync reduces tearing and saves CPU. Toggling it off will make some operations such as panning/zooming more snappy. This needs a restart to take effect.", ); ui - .checkbox(&mut state.persistent_settings.show_scrub_bar, "Show index slider") - .on_hover_text( - "Enable an index slider to quickly scrub through lots of images", - ); - ui.end_row(); + .checkbox(&mut state.persistent_settings.show_scrub_bar, "Show index slider") + .on_hover_text( + "Enable an index slider to quickly scrub through lots of images", + ); + ui.end_row(); - if ui + if ui .checkbox(&mut state.persistent_settings.wrap_folder, "Wrap images at folder boundary") .on_hover_text( "When you move past the first or last image in a folder, should oculante continue or stop?", @@ -449,16 +440,16 @@ pub fn settings_ui(app: &mut App, ctx: &Context, state: &mut OculanteState) { ui.horizontal(|ui| { ui.label("Number of image to cache"); if ui - .add(egui::DragValue::new(&mut state.persistent_settings.max_cache).clamp_range(0..=10000)) + .add(egui::DragValue::new(&mut state.persistent_settings.max_cache).clamp_range(0..=10000)) - .on_hover_text( - "Keep this many images in memory for faster opening.", - ) - .changed() - { - state.player.cache.cache_size = state.persistent_settings.max_cache; - state.player.cache.clear(); - } + .on_hover_text( + "Keep this many images in memory for faster opening.", + ) + .changed() + { + state.player.cache.cache_size = state.persistent_settings.max_cache; + state.player.cache.clear(); + } }); ui.end_row(); @@ -485,30 +476,6 @@ pub fn settings_ui(app: &mut App, ctx: &Context, state: &mut OculanteState) { .on_hover_text( "Draw a small frame around the image. It is centered on the outmost pixel. This can be helpful on images with lots of transparency.", ); - ui.end_row(); - if ui.checkbox(&mut state.persistent_settings.zen_mode, "Turn on Zen mode").on_hover_text("Zen mode hides all UI and fits the image to the frame.").changed(){ - set_title(app, state); - } - - - } - - - ); - - ui.horizontal(|ui| { - ui.label("Configure window title"); - if ui - .text_edit_singleline(&mut state.persistent_settings.title_format) - .on_hover_text( - "Configure the title. Use {APP}, {VERSION}, {FULLPATH}, {FILENAME} and {RES} as placeholders.", - ) - .changed() - { - set_title(app, state); - } - }); - ui.end_row(); ui.horizontal(|ui| { @@ -529,29 +496,50 @@ pub fn settings_ui(app: &mut App, ctx: &Context, state: &mut OculanteState) { } }); - if ui.link("Visit github repo").on_hover_text("Check out the source code, request a feature, submit a bug or leave a star if you like it!").clicked() { - _ = webbrowser::open("https://github.com/woelper/oculante"); + ui.end_row(); + + if ui.checkbox(&mut state.persistent_settings.zen_mode, "Turn on Zen mode").on_hover_text("Zen mode hides all UI and fits the image to the frame.").changed() { + set_title(app, state); } + }, + ); - ui.vertical_centered_justified(|ui| { + ui.horizontal(|ui| { + ui.label("Configure window title"); + if ui + .text_edit_singleline(&mut state.persistent_settings.title_format) + .on_hover_text( + "Configure the title. Use {APP}, {VERSION}, {FULLPATH}, {FILENAME} and {RES} as placeholders.", + ) + .changed() + { + set_title(app, state); + } + }); - #[cfg(feature = "update")] - if ui.button("Check for updates").on_hover_text("Check and install update if available. You will need to restart the app to use the new version.").clicked() { - state.send_message("Checking for updates..."); - crate::update::update(Some(state.message_channel.0.clone())); - state.settings_enabled = false; - } + ui.end_row(); - if ui.button("Reset all settings").clicked() { - state.persistent_settings = Default::default(); - } - }); + if ui.link("Visit github repo").on_hover_text("Check out the source code, request a feature, submit a bug or leave a star if you like it!").clicked() { + _ = webbrowser::open("https://github.com/woelper/oculante"); + } - ui.collapsing("Keybindings",|ui| { - keybinding_ui(app, state, ui); - }); + ui.vertical_centered_justified(|ui| { + #[cfg(feature = "update")] + if ui.button("Check for updates").on_hover_text("Check and install update if available. You will need to restart the app to use the new version.").clicked() { + state.send_message("Checking for updates..."); + crate::update::update(Some(state.message_channel.0.clone())); + state.settings_enabled = false; + } + if ui.button("Reset all settings").clicked() { + state.persistent_settings = Default::default(); + } + }); + + ui.collapsing("Keybindings", |ui| { + keybinding_ui(app, state, ui); }); + }); state.settings_enabled = settings_enabled; } @@ -595,8 +583,8 @@ pub fn advanced_ui(ui: &mut Ui, state: &mut OculanteState) { .map(|(k, v)| [*k as f64, *v as f64]) .collect::(), ) - .stems(0.0) - .color(Color32::RED); + .stems(0.0) + .color(Color32::RED); let green_vals = Points::new( info.green_histogram @@ -604,8 +592,8 @@ pub fn advanced_ui(ui: &mut Ui, state: &mut OculanteState) { .map(|(k, v)| [*k as f64, *v as f64]) .collect::(), ) - .stems(0.0) - .color(Color32::GREEN); + .stems(0.0) + .color(Color32::GREEN); let blue_vals = Points::new( info.blue_histogram @@ -613,8 +601,8 @@ pub fn advanced_ui(ui: &mut Ui, state: &mut OculanteState) { .map(|(k, v)| [*k as f64, *v as f64]) .collect::(), ) - .stems(0.0) - .color(Color32::BLUE); + .stems(0.0) + .color(Color32::BLUE); Plot::new("histogram") .allow_zoom(false) @@ -691,7 +679,7 @@ pub fn edit_ui(app: &mut App, ctx: &Context, state: &mut OculanteState, gfx: &mu ui.label_i("➕ Filter"); let available_w_single_spacing = ui.available_width(); - // - ui.style().spacing.item_spacing.x; + // - ui.style().spacing.item_spacing.x; egui::ComboBox::from_id_source("Imageops") .selected_text("Select a filter to add...") @@ -851,7 +839,7 @@ pub fn edit_ui(app: &mut App, ctx: &Context, state: &mut OculanteState, gfx: &mu ui.end_row(); for (i, stroke) in - state.edit_state.paint_strokes.iter_mut().enumerate() + state.edit_state.paint_strokes.iter_mut().enumerate() { if stroke.is_empty() || stroke.committed { continue; @@ -988,10 +976,9 @@ pub fn edit_ui(app: &mut App, ctx: &Context, state: &mut OculanteState, gfx: &mu if !state.edit_state.pixel_op_stack.is_empty() { let ops = &state.edit_state.pixel_op_stack; process_pixels(&mut state.edit_state.result_pixel_op, ops); - } - info!( + info!( "Finished Pixel op stack in {} s", stamp.elapsed().as_secs_f32() ); @@ -1025,12 +1012,10 @@ pub fn edit_ui(app: &mut App, ctx: &Context, state: &mut OculanteState, gfx: &mu stamp.elapsed().as_secs_f32() ); - // let sender = state.texture_channel.0.clone(); - - // let f = Frame::new_edit(state.edit_state.result_pixel_op.clone()); - // _ = sender.send(f); - + // let sender = state.texture_channel.0.clone(); + // let f = Frame::new_edit(state.edit_state.result_pixel_op.clone()); + // _ = sender.send(f); } // render uncommitted strokes if destructive to speed up painting @@ -1131,17 +1116,17 @@ pub fn edit_ui(app: &mut App, ctx: &Context, state: &mut OculanteState, gfx: &mu .edit_state .result_pixel_op .save(&file_path) { - Ok(_) => { - state.send_message("Saved"); - state.current_path = Some(file_path); - set_title(app, state); - } - Err(e) => { - state.send_message_err(&format!("Error: Could not save: {e}")); - } + Ok(_) => { + state.send_message("Saved"); + state.current_path = Some(file_path); + set_title(app, state); + } + Err(e) => { + state.send_message_err(&format!("Error: Could not save: {e}")); } - state.toast_cooldown = 0.0; - ui.ctx().request_repaint(); + } + state.toast_cooldown = 0.0; + ui.ctx().request_repaint(); } } } @@ -1158,9 +1143,9 @@ pub fn edit_ui(app: &mut App, ctx: &Context, state: &mut OculanteState, gfx: &mu if ui.button(text).on_hover_text("Save the image. This will create a new file or overwrite.").clicked() { match state - .edit_state - .result_pixel_op - .save(p) { + .edit_state + .result_pixel_op + .save(p) { Ok(_) => { state.send_message("Saved"); } @@ -1180,9 +1165,7 @@ pub fn edit_ui(app: &mut App, ctx: &Context, state: &mut OculanteState, gfx: &mu if let Ok(f) = std::fs::File::create(parent.join(".oculante")) { _ = serde_json::to_writer_pretty(&f, &state.edit_state); } - } - } } }); @@ -1231,7 +1214,7 @@ pub fn unframed_button_colored(text: impl Into, is_colored: bool, ui: &m .heading() .color(ui.style().visuals.selection.bg_fill), ) - .frame(false), + .frame(false), ) } else { ui.add(egui::Button::new(RichText::new(text).heading()).frame(false)) @@ -1428,7 +1411,7 @@ fn jpg_lossless_ui(state: &mut OculanteState, ui: &mut Ui) { ..turbojpeg::Transform::default() }, ) - .is_ok() + .is_ok() { reload = true; } @@ -1442,7 +1425,7 @@ fn jpg_lossless_ui(state: &mut OculanteState, ui: &mut Ui) { ..turbojpeg::Transform::default() }, ) - .is_ok() + .is_ok() { reload = true; } @@ -1456,14 +1439,14 @@ fn jpg_lossless_ui(state: &mut OculanteState, ui: &mut Ui) { ..turbojpeg::Transform::default() }, ) - .is_ok() + .is_ok() { reload = true; } } }); - ui.columns(2,|col| { + ui.columns(2, |col| { if col[0].button("Flip H").clicked() { if lossless_tx( p, @@ -1472,7 +1455,7 @@ fn jpg_lossless_ui(state: &mut OculanteState, ui: &mut Ui) { ..turbojpeg::Transform::default() }, ) - .is_ok() + .is_ok() { reload = true; } @@ -1486,7 +1469,7 @@ fn jpg_lossless_ui(state: &mut OculanteState, ui: &mut Ui) { ..turbojpeg::Transform::default() }, ) - .is_ok() + .is_ok() { reload = true; } @@ -1494,9 +1477,6 @@ fn jpg_lossless_ui(state: &mut OculanteState, ui: &mut Ui) { }); ui.vertical_centered_justified(|ui| { - - - let crop_ops = state .edit_state .image_op_stack @@ -1519,7 +1499,6 @@ fn jpg_lossless_ui(state: &mut OculanteState, ui: &mut Ui) { } ui.add_enabled_ui(crop != ImageOperation::Crop([0, 0, 0, 0]), |ui| { - if ui .button("Crop") .on_hover_text("Crop according to values defined in the operator stack above") @@ -1559,7 +1538,7 @@ fn jpg_lossless_ui(state: &mut OculanteState, ui: &mut Ui) { }; } }); - }); + }); if reload { @@ -1700,7 +1679,7 @@ pub fn main_menu(ui: &mut Ui, state: &mut OculanteState, app: &mut App, gfx: &mu &channel.hotkey(&state.persistent_settings.shortcuts), ui, ) - .clicked() + .clicked() { changed_channels = true; } @@ -1724,7 +1703,6 @@ pub fn main_menu(ui: &mut Ui, state: &mut OculanteState, app: &mut App, gfx: &mu } - if state.current_path.is_some() { if tooltip( unframed_button("◀", ui), @@ -1732,7 +1710,7 @@ pub fn main_menu(ui: &mut Ui, state: &mut OculanteState, app: &mut App, gfx: &mu &lookup(&state.persistent_settings.shortcuts, &PreviousImage), ui, ) - .clicked() + .clicked() { prev_image(state) } @@ -1742,15 +1720,13 @@ pub fn main_menu(ui: &mut Ui, state: &mut OculanteState, app: &mut App, gfx: &mu &lookup(&state.persistent_settings.shortcuts, &NextImage), ui, ) - .clicked() + .clicked() { next_image(state) } } if state.current_image.is_some() { - - if tooltip( // ui.checkbox(&mut state.info_enabled, "ℹ Info"), ui.selectable_label(state.persistent_settings.info_enabled, "ℹ Info"), @@ -1758,7 +1734,7 @@ pub fn main_menu(ui: &mut Ui, state: &mut OculanteState, app: &mut App, gfx: &mu &lookup(&state.persistent_settings.shortcuts, &InfoMode), ui, ) - .clicked() + .clicked() { state.persistent_settings.info_enabled = !state.persistent_settings.info_enabled; send_extended_info( @@ -1774,7 +1750,7 @@ pub fn main_menu(ui: &mut Ui, state: &mut OculanteState, app: &mut App, gfx: &mu &lookup(&state.persistent_settings.shortcuts, &EditMode), ui, ) - .clicked() + .clicked() { state.persistent_settings.edit_enabled = !state.persistent_settings.edit_enabled; } @@ -1802,7 +1778,7 @@ pub fn main_menu(ui: &mut Ui, state: &mut OculanteState, app: &mut App, gfx: &mu &lookup(&state.persistent_settings.shortcuts, &AlwaysOnTop), ui, ) - .clicked() + .clicked() { state.always_on_top = !state.always_on_top; app.window().set_always_on_top(state.always_on_top); From d82ff25623ec3de9c7e7a94a389cf6d21080c7f5 Mon Sep 17 00:00:00 2001 From: Vitaliy Grabovets Date: Sat, 15 Jul 2023 23:16:24 +0300 Subject: [PATCH 25/73] send message: number of files loaded --- src/main.rs | 2 ++ src/shortcuts.rs | 1 - 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/src/main.rs b/src/main.rs index 3b5ddf18..099674dc 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1093,6 +1093,8 @@ fn browse_for_folder_path(state: &mut OculanteState) { true, state.persistent_settings.add_fav_every_n, ); + let number_of_files = state.scrubber.len(); + state.send_message(format!("number of files: {}", number_of_files).as_str()); let current_path = state.scrubber.next(); state.is_loaded = false; diff --git a/src/shortcuts.rs b/src/shortcuts.rs index b1950344..3c023a94 100644 --- a/src/shortcuts.rs +++ b/src/shortcuts.rs @@ -258,7 +258,6 @@ pub fn key_pressed(app: &mut App, state: &mut OculanteState, command: InputEvent for pressed in &app.keyboard.pressed { // debug!("{:?}", pressed); if format!("{:?}", pressed) == key { - debug!("Number of keys pressed: {}", app.keyboard.down.len()); debug!("Matched {:?} / {:?}", command, key); return true; } From 6fd96b452d84de6ac21de3e584b5efb4d2f48b34 Mon Sep 17 00:00:00 2001 From: Vitaliy Grabovets Date: Sat, 15 Jul 2023 23:53:50 +0300 Subject: [PATCH 26/73] get image by index --- src/main.rs | 20 ++++++++++++-------- src/scrubber.rs | 4 ++++ 2 files changed, 16 insertions(+), 8 deletions(-) diff --git a/src/main.rs b/src/main.rs index 099674dc..179fabad 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1094,16 +1094,20 @@ fn browse_for_folder_path(state: &mut OculanteState) { state.persistent_settings.add_fav_every_n, ); let number_of_files = state.scrubber.len(); - state.send_message(format!("number of files: {}", number_of_files).as_str()); - let current_path = state.scrubber.next(); + if number_of_files > 0 { + state.send_message(format!("number of files: {}", number_of_files).as_str()); + let current_path = state.scrubber.get(0).unwrap(); - state.is_loaded = false; - state.current_image = None; - state - .player - .load(current_path.as_path(), state.message_channel.0.clone()); + state.is_loaded = false; + state.current_image = None; + state + .player + .load(current_path.as_path(), state.message_channel.0.clone()); - state.current_path = Some(current_path); + state.current_path = Some(current_path); + } else { + state.send_message_err(format!("No supported image files in {:?}", folder_path).as_str()); + } } } diff --git a/src/scrubber.rs b/src/scrubber.rs index 8f0d3586..e8ccb63d 100644 --- a/src/scrubber.rs +++ b/src/scrubber.rs @@ -73,6 +73,10 @@ impl Scrubber { self.entries.get(self.index).cloned().unwrap_or_default() } + pub fn get(&mut self, index: usize) -> Option { + self.entries.get(index).cloned() + } + pub fn len(&mut self) -> usize { self.entries.len() } From b1125d37421a00b9eb6c142840feecbd08320fe6 Mon Sep 17 00:00:00 2001 From: Vitaliy Grabovets Date: Sun, 16 Jul 2023 00:23:07 +0300 Subject: [PATCH 27/73] additional messages --- src/main.rs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/main.rs b/src/main.rs index 179fabad..7068235b 100644 --- a/src/main.rs +++ b/src/main.rs @@ -990,7 +990,7 @@ fn drawe(app: &mut App, gfx: &mut Graphics, plugins: &mut Plugins, state: &mut O edit_ui(app, ctx, state, gfx); } - if !state.is_loaded { + if !state.is_loaded && !state.toggle_slideshow { egui::TopBottomPanel::bottom("loader").show_animated( ctx, state.current_path.is_some(), @@ -1170,6 +1170,8 @@ fn add_to_favourites(app: &mut App, state: &mut OculanteState) { state.scrubber.favourites.insert(img_path.clone()); set_title(app, state); + } else { + state.send_message_err(format!("{:?} is already favourite", img_path).as_str()); } } } From ff6cfa5d1c573bf0df018b621062ba5f4ace8857 Mon Sep 17 00:00:00 2001 From: Vitaliy Grabovets Date: Sun, 16 Jul 2023 00:24:31 +0300 Subject: [PATCH 28/73] v0.6.68-dev5 --- Cargo.lock | 2 +- Cargo.toml | 2 +- PKGBUILD | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 7e0f9d2f..8178595e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3201,7 +3201,7 @@ dependencies = [ [[package]] name = "oculante" -version = "0.6.68-dev4" +version = "0.6.68-dev5" dependencies = [ "anyhow", "arboard", diff --git a/Cargo.toml b/Cargo.toml index dc1e4504..01626d5e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -11,7 +11,7 @@ license = "MIT" name = "oculante" readme = "README.md" repository = "https://github.com/woelper/oculante/" -version = "0.6.68-dev4" +version = "0.6.68-dev5" [package.metadata.bundle] icon = ["res/oculante.png"] diff --git a/PKGBUILD b/PKGBUILD index 8858fd33..f4d0474c 100644 --- a/PKGBUILD +++ b/PKGBUILD @@ -1,6 +1,6 @@ # Maintainer: Johann Woelper pkgname=oculante -pkgver=0.6.68-dev4 +pkgver=0.6.68-dev5 pkgrel=1 depends=('aom' 'libwebp' 'expat' 'freetype2' 'gtk3' 'cairo') makedepends=('rust' 'cargo' 'tar' 'nasm' 'cmake') From 1dbc01311b0d0c0be8a63ddbe2c8cd747395bd02 Mon Sep 17 00:00:00 2001 From: Vitaliy Grabovets Date: Sun, 16 Jul 2023 12:49:50 +0300 Subject: [PATCH 29/73] draw a star for favourite images --- src/appstate.rs | 2 ++ src/main.rs | 23 ++++++++++++++++++++--- src/shortcuts.rs | 1 - src/utils.rs | 4 ---- 4 files changed, 22 insertions(+), 8 deletions(-) diff --git a/src/appstate.rs b/src/appstate.rs index ebf93d82..b6f94f4a 100644 --- a/src/appstate.rs +++ b/src/appstate.rs @@ -88,6 +88,7 @@ pub struct OculanteState { pub folder_selected: Option, pub toggle_slideshow: bool, pub slideshow_time: Instant, + pub current_image_is_favourite: bool, pub first_start: bool } @@ -146,6 +147,7 @@ impl Default for OculanteState { folder_selected: Default::default(), toggle_slideshow: false, slideshow_time: Instant::now(), + current_image_is_favourite: Default::default(), first_start: true, } } diff --git a/src/main.rs b/src/main.rs index 7068235b..537c5300 100644 --- a/src/main.rs +++ b/src/main.rs @@ -362,7 +362,7 @@ fn event(app: &mut App, state: &mut OculanteState, evt: Event) { set_zoom(5.0, None, state); } if key_pressed(app, state, Favourite) { - add_to_favourites(app, state); + add_to_favourites(state); } if key_pressed(app, state, ToggleSlideshow) { state.toggle_slideshow = !state.toggle_slideshow; @@ -674,6 +674,14 @@ fn drawe(app: &mut App, gfx: &mut Graphics, plugins: &mut Plugins, state: &mut O set_title(app, state); + if let Some(current_path) = &state.current_path { + if state.scrubber.favourites.contains(current_path) { + state.current_image_is_favourite = true; + } else { + state.current_image_is_favourite = false; + } + } + // fill image sequence if state.folder_selected.is_none() { if let Some(p) = &state.current_path { @@ -908,6 +916,15 @@ fn drawe(app: &mut App, gfx: &mut Graphics, plugins: &mut Plugins, state: &mut O // } } } + + if state.current_image_is_favourite { + draw.star(10, 80.0, 40.0) + .position(150.0, 480.0) + .fill_color(Color::PINK) + .fill() + .stroke_color(Color::PURPLE) + .stroke(6.0); + } } let egui_output = plugins.egui(|ctx| { @@ -1144,7 +1161,7 @@ fn set_zoom(scale: f32, from_center: Option>, state: &mut OculanteS state.image_geometry.scale = scale; } -fn add_to_favourites(app: &mut App, state: &mut OculanteState) { +fn add_to_favourites(state: &mut OculanteState) { if let Some(img_path) = &state.current_path { if !state.scrubber.favourites.contains(img_path) { let mut file = fs::OpenOptions::new() @@ -1169,7 +1186,7 @@ fn add_to_favourites(app: &mut App, state: &mut OculanteState) { ).expect("Unable to write data"); state.scrubber.favourites.insert(img_path.clone()); - set_title(app, state); + state.current_image_is_favourite = true; } else { state.send_message_err(format!("{:?} is already favourite", img_path).as_str()); } diff --git a/src/shortcuts.rs b/src/shortcuts.rs index 3c023a94..a0a3d781 100644 --- a/src/shortcuts.rs +++ b/src/shortcuts.rs @@ -249,7 +249,6 @@ pub fn key_pressed(app: &mut App, state: &mut OculanteState, command: InputEvent if format!("{:?}", dn) == key { debug!("REPEAT: Number of keys down: {}", app.keyboard.down.len()); debug!("Matched {:?} / {:?}", command, key); - debug!("d {}", app.system_timer.delta_f32()); return true; } } diff --git a/src/utils.rs b/src/utils.rs index 5b017484..1c9a0c8f 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -671,10 +671,6 @@ pub fn set_title(app: &mut App, state: &mut OculanteState) { 10, ); - if state.scrubber.favourites.contains(p.as_path()) { - title_string.push_str("🔖"); - } - if state.persistent_settings.zen_mode { title_string.push_str(&format!( " '{}' to disable zen mode", From f7dc73ed239757dc009b2c9172871af4c97f1e9f Mon Sep 17 00:00:00 2001 From: Vitaliy Grabovets Date: Sun, 16 Jul 2023 17:10:48 +0300 Subject: [PATCH 30/73] refactor star --- src/main.rs | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) diff --git a/src/main.rs b/src/main.rs index 537c5300..93f7a454 100644 --- a/src/main.rs +++ b/src/main.rs @@ -918,12 +918,12 @@ fn drawe(app: &mut App, gfx: &mut Graphics, plugins: &mut Plugins, state: &mut O } if state.current_image_is_favourite { - draw.star(10, 80.0, 40.0) - .position(150.0, 480.0) - .fill_color(Color::PINK) + draw.star(5, 20.0, 10.0) + .position(50.0, 60.0) + .fill_color(Color::YELLOW) .fill() - .stroke_color(Color::PURPLE) - .stroke(6.0); + .stroke_color(Color::ORANGE) + .stroke(3.0); } } @@ -1111,8 +1111,15 @@ fn browse_for_folder_path(state: &mut OculanteState) { state.persistent_settings.add_fav_every_n, ); let number_of_files = state.scrubber.len(); + let number_of_favs = state.scrubber.favourites.len(); if number_of_files > 0 { - state.send_message(format!("number of files: {}", number_of_files).as_str()); + state.send_message( + format!( + "number of files: {}, number of favourites: {}", + number_of_files, + number_of_favs, + ).as_str(), + ); let current_path = state.scrubber.get(0).unwrap(); state.is_loaded = false; From e8be8a3af3ab55fdaa274c14d25e46709c2f4918 Mon Sep 17 00:00:00 2001 From: Vitaliy Grabovets Date: Sun, 16 Jul 2023 17:33:20 +0300 Subject: [PATCH 31/73] open folder shortcut --- src/main.rs | 3 +++ src/shortcuts.rs | 2 ++ 2 files changed, 5 insertions(+) diff --git a/src/main.rs b/src/main.rs index 93f7a454..7042044c 100644 --- a/src/main.rs +++ b/src/main.rs @@ -418,6 +418,9 @@ fn event(app: &mut App, state: &mut OculanteState, evt: Event) { if key_pressed(app, state, Browse) { browse_for_image_path(state) } + if key_pressed(app, state, BrowseFolder) { + browse_for_folder_path(state) + } if key_pressed(app, state, NextImage) { if state.is_loaded { next_image(state) diff --git a/src/shortcuts.rs b/src/shortcuts.rs index a0a3d781..36766f42 100644 --- a/src/shortcuts.rs +++ b/src/shortcuts.rs @@ -41,6 +41,7 @@ pub enum InputEvent { Copy, Paste, Browse, + BrowseFolder, Quit, ZenMode, Favourite, @@ -135,6 +136,7 @@ impl ShortcutExt for Shortcuts { .add_key(InputEvent::ToggleSlideshow, "Space") // .add_key(InputEvent::Browse, "F1") // FIXME: As Shortcuts is a HashMap, only the newer key-sequence will be registered .add_keys(InputEvent::Browse, &["LControl", "O"]) + .add_keys(InputEvent::BrowseFolder, &["LControl", "LShift", "O"]) .add_keys(InputEvent::PanRight, &["LShift", "Right"]) .add_keys(InputEvent::PanLeft, &["LShift", "Left"]) .add_keys(InputEvent::PanDown, &["LShift", "Down"]) From 7c40c890c00d839288e50632222dc0507055a677 Mon Sep 17 00:00:00 2001 From: Vitaliy Grabovets Date: Sun, 16 Jul 2023 17:49:12 +0300 Subject: [PATCH 32/73] 0.6.68-dev6 --- Cargo.toml | 2 +- PKGBUILD | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 01626d5e..5aa4eaa3 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -11,7 +11,7 @@ license = "MIT" name = "oculante" readme = "README.md" repository = "https://github.com/woelper/oculante/" -version = "0.6.68-dev5" +version = "0.6.68-dev6" [package.metadata.bundle] icon = ["res/oculante.png"] diff --git a/PKGBUILD b/PKGBUILD index f4d0474c..f750a0ec 100644 --- a/PKGBUILD +++ b/PKGBUILD @@ -1,6 +1,6 @@ # Maintainer: Johann Woelper pkgname=oculante -pkgver=0.6.68-dev5 +pkgver=0.6.68-dev6 pkgrel=1 depends=('aom' 'libwebp' 'expat' 'freetype2' 'gtk3' 'cairo') makedepends=('rust' 'cargo' 'tar' 'nasm' 'cmake') @@ -23,4 +23,4 @@ package() { install -Dm644 res/oculante.desktop "${pkgdir}/usr/share/applications/${pkgname}.desktop" install -Dm644 LICENSE -t "${pkgdir}/usr/share/licenses/${pkgname}" install -Dm644 README.md -t "${pkgdir}/usr/share/doc/${pkgname}" -} \ No newline at end of file +} From ae54948b0f5f806d4661e8873341d1c21236f560 Mon Sep 17 00:00:00 2001 From: Vitaliy Grabovets Date: Sun, 16 Jul 2023 17:59:18 +0300 Subject: [PATCH 33/73] 0.6.68-dev7 --- Cargo.lock | 2 +- Cargo.toml | 2 +- PKGBUILD | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 8178595e..69cccbf7 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3201,7 +3201,7 @@ dependencies = [ [[package]] name = "oculante" -version = "0.6.68-dev5" +version = "0.6.68-dev7" dependencies = [ "anyhow", "arboard", diff --git a/Cargo.toml b/Cargo.toml index 5aa4eaa3..f459cc44 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -11,7 +11,7 @@ license = "MIT" name = "oculante" readme = "README.md" repository = "https://github.com/woelper/oculante/" -version = "0.6.68-dev6" +version = "0.6.68-dev7" [package.metadata.bundle] icon = ["res/oculante.png"] diff --git a/PKGBUILD b/PKGBUILD index f750a0ec..4c01eb59 100644 --- a/PKGBUILD +++ b/PKGBUILD @@ -1,6 +1,6 @@ # Maintainer: Johann Woelper pkgname=oculante -pkgver=0.6.68-dev6 +pkgver=0.6.68-dev7 pkgrel=1 depends=('aom' 'libwebp' 'expat' 'freetype2' 'gtk3' 'cairo') makedepends=('rust' 'cargo' 'tar' 'nasm' 'cmake') @@ -23,4 +23,4 @@ package() { install -Dm644 res/oculante.desktop "${pkgdir}/usr/share/applications/${pkgname}.desktop" install -Dm644 LICENSE -t "${pkgdir}/usr/share/licenses/${pkgname}" install -Dm644 README.md -t "${pkgdir}/usr/share/doc/${pkgname}" -} +} \ No newline at end of file From 670402ae257eba054b463d84aeeabaf2c7835510 Mon Sep 17 00:00:00 2001 From: Vitaliy Grabovets Date: Sun, 16 Jul 2023 18:02:47 +0300 Subject: [PATCH 34/73] 0.6.68-dev8 --- Cargo.lock | 2 +- Cargo.toml | 2 +- PKGBUILD | 2 +- src/main.rs | 1 + 4 files changed, 4 insertions(+), 3 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 69cccbf7..fcb9b6ff 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3201,7 +3201,7 @@ dependencies = [ [[package]] name = "oculante" -version = "0.6.68-dev7" +version = "0.6.68-dev8" dependencies = [ "anyhow", "arboard", diff --git a/Cargo.toml b/Cargo.toml index f459cc44..f64ff9a2 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -11,7 +11,7 @@ license = "MIT" name = "oculante" readme = "README.md" repository = "https://github.com/woelper/oculante/" -version = "0.6.68-dev7" +version = "0.6.68-dev8" [package.metadata.bundle] icon = ["res/oculante.png"] diff --git a/PKGBUILD b/PKGBUILD index 4c01eb59..f4a02218 100644 --- a/PKGBUILD +++ b/PKGBUILD @@ -1,6 +1,6 @@ # Maintainer: Johann Woelper pkgname=oculante -pkgver=0.6.68-dev7 +pkgver=0.6.68-dev8 pkgrel=1 depends=('aom' 'libwebp' 'expat' 'freetype2' 'gtk3' 'cairo') makedepends=('rust' 'cargo' 'tar' 'nasm' 'cmake') diff --git a/src/main.rs b/src/main.rs index 7042044c..c4130621 100644 --- a/src/main.rs +++ b/src/main.rs @@ -418,6 +418,7 @@ fn event(app: &mut App, state: &mut OculanteState, evt: Event) { if key_pressed(app, state, Browse) { browse_for_image_path(state) } + #[cfg(feature = "file_open")] if key_pressed(app, state, BrowseFolder) { browse_for_folder_path(state) } From 4c84bb87b1cae2bb005f4e529e16cf96d6760bda Mon Sep 17 00:00:00 2001 From: Vitaliy <22475959+vgrabovets@users.noreply.github.com> Date: Wed, 19 Jul 2023 16:22:21 +0300 Subject: [PATCH 35/73] SQLite (#3) * save favourites to sqlite * separate rusqlite dependencies for unix and windows * delete favourites * wip: db connection * add DB struct * wip * close DB connection --- Cargo.lock | 62 +++++++++++++++++++++++++++++++++ Cargo.toml | 6 ++++ src/appstate.rs | 3 ++ src/cache.rs | 7 ---- src/db.rs | 89 ++++++++++++++++++++++++++++++++++++++++++++++++ src/main.rs | 76 ++++++++++++++++++++--------------------- src/scrubber.rs | 66 ++++++++++++++--------------------- src/shortcuts.rs | 3 -- src/utils.rs | 1 - 9 files changed, 222 insertions(+), 91 deletions(-) create mode 100644 src/db.rs diff --git a/Cargo.lock b/Cargo.lock index fcb9b6ff..9bc9e467 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -53,6 +53,12 @@ dependencies = [ "memchr", ] +[[package]] +name = "allocator-api2" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0942ffc6dcaadf03badf6e6a2d0228460359d5e34b57ccdc720b7382dfbd5ec5" + [[package]] name = "any_ascii" version = "0.1.7" @@ -1108,6 +1114,18 @@ version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e88a8acf291dafb59c2d96e8f59828f3838bb1a70398823ade51a84de6a6deed" +[[package]] +name = "fallible-iterator" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4443176a9f2c162692bd3d352d745ef9413eec5782a80d8fd6f8a1ac692a07f7" + +[[package]] +name = "fallible-streaming-iterator" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7360491ce676a36bf9bb3c56c1aa791658183a54d2744120f27285738d90465a" + [[package]] name = "fallible_collections" version = "0.4.9" @@ -1790,6 +1808,19 @@ name = "hashbrown" version = "0.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2c6201b9ff9fd90a5a3bac2e56a830d0caa509576f0e503818ee82c181b3437a" +dependencies = [ + "ahash", + "allocator-api2", +] + +[[package]] +name = "hashlink" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "312f66718a2d7789ffef4f4b7b213138ed9f1eb3aa1d0d82fc99f88fb3ffd26f" +dependencies = [ + "hashbrown 0.14.0", +] [[package]] name = "heck" @@ -2355,6 +2386,16 @@ version = "0.2.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f7012b1bbb0719e1097c47611d3898568c546d597c2e74d66f6087edd5233ff4" +[[package]] +name = "libsqlite3-sys" +version = "0.26.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "afc22eff61b133b115c6e8c74e818c628d6d5e7a502afea6f64dee076dd94326" +dependencies = [ + "pkg-config", + "vcpkg", +] + [[package]] name = "libwebp-sys" version = "0.9.1" @@ -3236,6 +3277,7 @@ dependencies = [ "resvg", "rfd", "rgb", + "rusqlite", "self_update", "serde", "serde_json", @@ -3982,6 +4024,20 @@ dependencies = [ "xmlparser", ] +[[package]] +name = "rusqlite" +version = "0.29.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "549b9d036d571d42e6e85d1c1425e2ac83491075078ca9a15be021c56b1641f2" +dependencies = [ + "bitflags 2.3.3", + "fallible-iterator", + "fallible-streaming-iterator", + "hashlink", + "libsqlite3-sys", + "smallvec", +] + [[package]] name = "rust_hawktracer" version = "0.7.0" @@ -5007,6 +5063,12 @@ dependencies = [ "rust_hawktracer", ] +[[package]] +name = "vcpkg" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" + [[package]] name = "vec_map" version = "0.8.2" diff --git a/Cargo.toml b/Cargo.toml index f64ff9a2..d386d2a0 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -74,6 +74,12 @@ update = ["self_update"] [target.'cfg(target_os = "macos")'.dependencies] fruitbasket = "0.10.0" +[target.'cfg(unix)'.dependencies] +rusqlite = "0.29.0" + +[target.'cfg(windows)'.dependencies] +rusqlite = {version = "0.29.0", features = ["winsqlite3"]} + [target.'cfg(windows)'.build-dependencies] # this currently causes a link error LNK1356, check in the future if the problem was solved windres = "0.2" diff --git a/src/appstate.rs b/src/appstate.rs index b6f94f4a..b323d3b0 100644 --- a/src/appstate.rs +++ b/src/appstate.rs @@ -1,4 +1,5 @@ use crate::{ + db::DB, image_editing::EditState, scrubber::Scrubber, settings::PersistentSettings, @@ -89,6 +90,7 @@ pub struct OculanteState { pub toggle_slideshow: bool, pub slideshow_time: Instant, pub current_image_is_favourite: bool, + pub db: Option, pub first_start: bool } @@ -148,6 +150,7 @@ impl Default for OculanteState { toggle_slideshow: false, slideshow_time: Instant::now(), current_image_is_favourite: Default::default(), + db: None, first_start: true, } } diff --git a/src/cache.rs b/src/cache.rs index db3eea88..68c33cd9 100644 --- a/src/cache.rs +++ b/src/cache.rs @@ -5,7 +5,6 @@ use std::{ }; use image::RgbaImage; -use log::debug; #[derive(Debug)] pub struct Cache { @@ -46,12 +45,6 @@ impl Cache { key = p.clone(); } } - debug!( - "Cache limit hit, deleting oldest: {}, {}s old", - key.display(), - latest.elapsed().as_secs_f32() - ); - _ = self.data.remove(&key); } } diff --git a/src/db.rs b/src/db.rs new file mode 100644 index 00000000..1e3e6f47 --- /dev/null +++ b/src/db.rs @@ -0,0 +1,89 @@ +use std::collections::HashSet; +use std::path::{Path, PathBuf}; +use itertools::Itertools; +use log::debug; +use rusqlite::Connection; + +const FAVOURITES_DB: &str = "favourites.db"; + +#[derive(Debug)] +pub struct DB { + connection: Option, + folder: PathBuf, +} + +impl DB { + pub fn new(folder: &PathBuf) -> Self { + debug!("init new DB connection"); + let db_file_path = get_db_file(&folder); + let connection = Connection::open(db_file_path).expect("cannot open DB connection"); + connection.execute( + "create table if not exists favourites (path text primary key)", + (), + ).expect("cannot create table"); + let folder_out = folder.clone(); + + Self {connection: Some(connection), folder: folder_out} + } + + pub fn insert(&self, img_path: &PathBuf) { + let record = self.prepare_record(img_path); + debug!("insert {} to DB", record); + self.connection.as_ref().unwrap().execute( + "INSERT INTO favourites (path) values (?1)", + [record], + ).expect("cannot save record"); + } + + pub fn delete(&self, img_path: &PathBuf) { + let record = self.prepare_record(img_path); + debug!("delete {} from DB", record); + self.connection.as_ref().unwrap().execute( + "DELETE FROM favourites where path = (?1)", + [record], + ).expect("cannot delete record"); + } + + pub fn get_all(&self) -> HashSet { + debug!("run select * statement"); + let mut stmt = self.connection + .as_ref() + .unwrap() + .prepare("SELECT path from favourites") + .expect("cannot prepare query"); + + stmt + .query_map((), |row| { Ok(row.get(0)?) }) + .expect("cannot get data") + .map(|e| self.folder.join(self.join_path_parts(e.unwrap()))) + .filter(|file| file.exists()) + .collect() + } + + pub fn close(&mut self) { + debug!("close DB connection"); + self.connection.take().unwrap().close().expect("cannot close DB connection") + } + + fn prepare_record(&self, img_path: &PathBuf) -> String { + img_path.strip_prefix(self.folder.as_path()) + .unwrap() + .components() + .map(|component| component.as_os_str().to_str().unwrap()) + .join("\t") + } + + fn join_path_parts(&self, path_with_tabs: String) -> PathBuf { + let mut path = PathBuf::new(); + + for part in path_with_tabs.split("\t") { + path.push(part); + } + + path + } +} + +pub fn get_db_file(folder: &PathBuf) -> PathBuf { + folder.join(Path::new(FAVOURITES_DB)) +} diff --git a/src/main.rs b/src/main.rs index c4130621..c05f1ebf 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,8 +1,8 @@ #![windows_subsystem = "windows"] +use std::collections::HashSet; use clap::Arg; use clap::Command; -use itertools::Itertools; use log::debug; use log::error; use log::info; @@ -13,9 +13,6 @@ use notan::draw::*; use notan::egui::{self, *}; use notan::prelude::*; use shortcuts::key_pressed; -use std::fs; -use std::io::Write; -use std::path::Path; use std::path::PathBuf; use std::sync::mpsc; use std::time::{Duration, Instant}; @@ -45,13 +42,14 @@ mod ui; mod update; use ui::*; +use crate::db::{DB, get_db_file}; use crate::image_editing::EditState; +mod db; mod image_editing; pub mod paint; pub const FONT: &[u8; 309828] = include_bytes!("../res/fonts/Inter-Regular.ttf"); -const FAVOURITES_FILE: &str = "favourites.txt"; #[notan_main] fn main() -> Result<(), String> { @@ -61,7 +59,7 @@ fn main() -> Result<(), String> { // on debug builds, override log level #[cfg(debug_assertions)] { - std::env::set_var("RUST_LOG", "debug"); + std::env::set_var("RUST_LOG", "oculante=debug"); let _ = env_logger::try_init(); } @@ -193,8 +191,6 @@ fn init(gfx: &mut Graphics, plugins: &mut Plugins) -> OculanteState { gfx.limits().max_texture_size, ); - debug!("Image is: {:?}", maybe_img_location); - if let Some(ref location) = maybe_img_location { // Check if path is a directory or a file (and that it even exists) let mut start_img_location: Option = None; @@ -316,8 +312,6 @@ fn event(app: &mut App, state: &mut OculanteState, evt: Event) { } } Event::KeyDown { .. } => { - debug!("key down"); - // return; // pan image with keyboard let delta = 40.; @@ -369,6 +363,11 @@ fn event(app: &mut App, state: &mut OculanteState, evt: Event) { } if key_pressed(app, state, Quit) { state.persistent_settings.save_blocking(); + + if let Some(ref mut db) = state.db { + db.close(); + } + app.backend.exit(); } #[cfg(feature = "turbo")] @@ -506,11 +505,15 @@ fn event(app: &mut App, state: &mut OculanteState, evt: Event) { match evt { Event::Exit => { - info!("About to exit"); + debug!("About to exit"); // save position state.persistent_settings.window_geometry = (app.window().position(), app.window().size()); state.persistent_settings.save_blocking(); + + if let Some(ref mut db) = state.db { + db.close(); + } } Event::MouseWheel { delta_y, .. } => { if !state.pointer_over_ui { @@ -689,7 +692,7 @@ fn drawe(app: &mut App, gfx: &mut Graphics, plugins: &mut Plugins, state: &mut O // fill image sequence if state.folder_selected.is_none() { if let Some(p) = &state.current_path { - state.scrubber = scrubber::Scrubber::new(p, None, false, false, 0); + state.scrubber = scrubber::Scrubber::new(p, false, false, None, 0); state.scrubber.wrap = state.persistent_settings.wrap_folder; // debug!("{:#?} from {}", &state.scrubber, p.display()); @@ -738,7 +741,6 @@ fn drawe(app: &mut App, gfx: &mut Graphics, plugins: &mut Plugins, state: &mut O } } } else if let Some(parent) = p.parent() { - info!("Looking for {}", parent.join(".oculante").display()); if parent.join(".oculante").is_file() { info!("is file {}", parent.join(".oculante").display()); @@ -992,7 +994,6 @@ fn drawe(app: &mut App, gfx: &mut Graphics, plugins: &mut Plugins, state: &mut O // debug!("cooldown {}", state.toast_cooldown); if state.toast_cooldown > max_anim_len { - debug!("Setting message to none, timer reached."); state.message = None; } } @@ -1107,11 +1108,21 @@ fn browse_for_folder_path(state: &mut OculanteState) { _ = state.persistent_settings.save(); state.folder_selected = Option::from(folder_path.clone()); + let db_file = get_db_file(&folder_path); + + let favourites: Option>; + if db_file.exists() { + state.db = Option::from(DB::new(&folder_path)); + favourites = Option::from(state.db.as_ref().unwrap().get_all()); + } else { + favourites = None; + } + state.scrubber = Scrubber::new( - folder_path.as_path(), - Some(FAVOURITES_FILE), + &folder_path.as_path(), true, true, + favourites, state.persistent_settings.add_fav_every_n, ); let number_of_files = state.scrubber.len(); @@ -1119,7 +1130,7 @@ fn browse_for_folder_path(state: &mut OculanteState) { if number_of_files > 0 { state.send_message( format!( - "number of files: {}, number of favourites: {}", + "files: {}, favourites: {}", number_of_files, number_of_favs, ).as_str(), @@ -1174,32 +1185,19 @@ fn set_zoom(scale: f32, from_center: Option>, state: &mut OculanteS fn add_to_favourites(state: &mut OculanteState) { if let Some(img_path) = &state.current_path { - if !state.scrubber.favourites.contains(img_path) { - let mut file = fs::OpenOptions::new() - .append(true) - .create(true) - .open( - state.folder_selected - .as_ref() - .unwrap_or(&img_path.parent().unwrap().to_path_buf()) - .join(Path::new(FAVOURITES_FILE)) - ) - .expect("Unable to open file"); - - writeln!( - file, - "{}", - img_path.strip_prefix(state.folder_selected.as_ref().unwrap().as_path()) - .unwrap() - .components() - .map(|component| component.as_os_str().to_str().unwrap()) - .join("\t") - ).expect("Unable to write data"); + if state.db.is_none() { + state.db = Option::from(DB::new(state.folder_selected.as_ref().unwrap())); + } + if !state.scrubber.favourites.contains(img_path) { + state.db.as_ref().unwrap().insert(&img_path); state.scrubber.favourites.insert(img_path.clone()); state.current_image_is_favourite = true; + } else { - state.send_message_err(format!("{:?} is already favourite", img_path).as_str()); + state.db.as_ref().unwrap().delete(&img_path); + state.scrubber.favourites.remove(img_path); + state.current_image_is_favourite = false; } } } diff --git a/src/scrubber.rs b/src/scrubber.rs index e8ccb63d..aef70db4 100644 --- a/src/scrubber.rs +++ b/src/scrubber.rs @@ -4,7 +4,6 @@ use log::debug; use rand::seq::SliceRandom; use std::collections::HashSet; use std::default::Default; -use std::io::{BufRead, BufReader}; use std::path::{Path, PathBuf}; use walkdir::WalkDir; @@ -19,25 +18,34 @@ pub struct Scrubber { impl Scrubber { pub fn new( path: &Path, - favourites_file: Option<&str>, randomize: bool, walk_files: bool, + favourites: Option>, intersperse_with_favs_every_n: usize, ) -> Self { - let (entries, favourites) = get_image_filenames_for_directory( + let entries = get_image_filenames_for_directory( path, - favourites_file, randomize, walk_files, + &favourites, intersperse_with_favs_every_n, ) .unwrap_or_default(); let index = entries.iter().position(|p| p == path).unwrap_or_default(); + + let favourites_out: HashSet; + + if favourites.is_some() { + favourites_out = favourites.unwrap(); + } else { + favourites_out = Default::default(); + } + Self { index, entries, wrap: true, - favourites, + favourites: favourites_out, } } pub fn next(&mut self) -> PathBuf { @@ -98,11 +106,11 @@ impl Scrubber { // TODO: Cache this result, instead of doing it each time we need to fetch another file from the folder pub fn get_image_filenames_for_directory( folder_path: &Path, - favourites_file: Option<&str>, randomize: bool, walk_files: bool, + favourites: &Option>, intersperse_with_favs_every_n: usize, -) -> Result<(Vec, HashSet)> { +) -> Result> { let mut folder_path = folder_path.to_path_buf(); if folder_path.is_file() { folder_path = folder_path @@ -111,22 +119,6 @@ pub fn get_image_filenames_for_directory( .context("Can't get parent")?; } - let mut favourites: HashSet = Default::default(); - - if let Some(favourites_file) = favourites_file { - let favourites_path = folder_path.join(Path::new(favourites_file)); - if favourites_path.exists() { - let file = std::fs::File::open(favourites_path)?; - let reader = BufReader::new(file); - favourites = reader - .lines() - .filter_map(|line| line.ok()) - .map(|file_str| folder_path.join(join_path_parts(file_str))) - .filter(|file| file.exists()) - .collect(); - } - } - let mut dir_files: Vec; if walk_files { @@ -147,7 +139,7 @@ pub fn get_image_filenames_for_directory( // TODO: Are symlinks handled correctly? - let mut favourites_vec: Vec = favourites.clone().into_iter().collect(); + let mut favourites_vec: Vec = favourites.clone().unwrap_or_default().into_iter().collect(); if randomize { let mut rng = rand::thread_rng(); @@ -169,8 +161,7 @@ pub fn get_image_filenames_for_directory( if intersperse_with_favs_every_n > 0 { dir_files = insert_after_every(dir_files, favourites_vec, intersperse_with_favs_every_n); } - debug!("number of files: {}", dir_files.len()); - return Ok((dir_files, favourites)); + return Ok(dir_files); } /// Find first valid image from the directory @@ -181,40 +172,33 @@ pub fn find_first_image_in_directory(folder_path: &PathBuf) -> Result { }; get_image_filenames_for_directory( folder_path, - None, false, false, + &None, 0, ) - .map(|(x, _)| { + .map(|x| { x.first() .cloned() .context("Folder does not have any supported images in it") })? } -fn join_path_parts(path_with_tabs: String) -> PathBuf { - let mut path = PathBuf::new(); - - for part in path_with_tabs.split("\t") { - path.push(part); - } - - path -} - fn insert_after_every(main_vector: Vec, other_vector: Vec, after: usize) -> Vec { - let mut result = Vec::with_capacity(main_vector.len() + other_vector.len()); + let mut result = Vec::with_capacity(main_vector.len()); + let mut i = 0; let mut other_vector_i = 0; let other_vector_set: HashSet = other_vector.clone().into_iter().collect(); - for (i, element) in main_vector.into_iter().enumerate() { + for element in main_vector.into_iter() { if other_vector_set.contains(&element) { continue } result.push(element); - if other_vector_i < other_vector.len() && (i + 1) % after == 0 { + i += 1; + + if other_vector_i < other_vector.len() && i % after == 0 { result.push(other_vector[other_vector_i].clone()); other_vector_i += 1; } diff --git a/src/shortcuts.rs b/src/shortcuts.rs index 36766f42..f13d85b9 100644 --- a/src/shortcuts.rs +++ b/src/shortcuts.rs @@ -249,8 +249,6 @@ pub fn key_pressed(app: &mut App, state: &mut OculanteState, command: InputEvent { for (dn, _) in &app.keyboard.down { if format!("{:?}", dn) == key { - debug!("REPEAT: Number of keys down: {}", app.keyboard.down.len()); - debug!("Matched {:?} / {:?}", command, key); return true; } } @@ -259,7 +257,6 @@ pub fn key_pressed(app: &mut App, state: &mut OculanteState, command: InputEvent for pressed in &app.keyboard.pressed { // debug!("{:?}", pressed); if format!("{:?}", pressed) == key { - debug!("Matched {:?} / {:?}", command, key); return true; } } diff --git a/src/utils.rs b/src/utils.rs index 1c9a0c8f..3041a918 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -154,7 +154,6 @@ impl Player { if let Some(cached_image) = self.cache.get(img_location) { _ = self.image_sender.send(Frame::new_still(cached_image)); - info!("Cache hit for {}", img_location.display()); return; } From 89056de011cd2876162a3cae907db9c3aeb75b6d Mon Sep 17 00:00:00 2001 From: Vitaliy Grabovets Date: Wed, 19 Jul 2023 16:23:46 +0300 Subject: [PATCH 36/73] reinit scrubber only when n > 0 --- src/scrubber.rs | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/src/scrubber.rs b/src/scrubber.rs index aef70db4..41ada832 100644 --- a/src/scrubber.rs +++ b/src/scrubber.rs @@ -90,14 +90,10 @@ impl Scrubber { } pub fn re_initialize(&mut self, intersperse_with_favs_every_n: usize) { - let entries_wo_favourites: Vec = self.entries - .iter() - .filter(|element| !self.favourites.contains(*element)) - .map(|element| element.clone()) - .collect(); - - let favourites_vec: Vec = self.favourites.clone().into_iter().collect(); - self.entries = insert_after_every(entries_wo_favourites, favourites_vec, intersperse_with_favs_every_n); + if intersperse_with_favs_every_n > 0 { + let favourites_vec: Vec = self.favourites.clone().into_iter().collect(); + self.entries = insert_after_every(self.entries.clone(), favourites_vec, intersperse_with_favs_every_n); + } } } From f3cbc461b6225f837bfb74fba33cb17b3f28d71d Mon Sep 17 00:00:00 2001 From: Vitaliy Grabovets Date: Wed, 19 Jul 2023 17:02:00 +0300 Subject: [PATCH 37/73] save image path to clipboard --- Cargo.lock | 44 +++++++++++++++++++++++++++++++++++++++++++- Cargo.toml | 1 + src/main.rs | 7 +++++++ src/shortcuts.rs | 2 ++ 4 files changed, 53 insertions(+), 1 deletion(-) diff --git a/Cargo.lock b/Cargo.lock index 9bc9e467..4c5ad65a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -105,7 +105,7 @@ version = "3.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d6041616acea41d67c4a984709ddab1587fd0b10efe5cc563fee954d2f011854" dependencies = [ - "clipboard-win", + "clipboard-win 4.5.0", "core-graphics", "image", "log", @@ -526,6 +526,28 @@ dependencies = [ "os_str_bytes", ] +[[package]] +name = "clipboard" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "25a904646c0340239dcf7c51677b33928bf24fdf424b79a57909c0109075b2e7" +dependencies = [ + "clipboard-win 2.2.0", + "objc", + "objc-foundation", + "objc_id", + "x11-clipboard", +] + +[[package]] +name = "clipboard-win" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3a093d6fed558e5fe24c3dfc85a68bb68f1c824f440d3ba5aca189e2998786b" +dependencies = [ + "winapi", +] + [[package]] name = "clipboard-win" version = "4.5.0" @@ -3248,6 +3270,7 @@ dependencies = [ "arboard", "avif-decode", "clap", + "clipboard", "cmd_lib", "dds-rs", "dirs 5.0.1", @@ -5650,6 +5673,15 @@ dependencies = [ "winapi", ] +[[package]] +name = "x11-clipboard" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "89bd49c06c9eb5d98e6ba6536cf64ac9f7ee3a009b2f53996d405b3944f6bcea" +dependencies = [ + "xcb", +] + [[package]] name = "x11-dl" version = "2.21.0" @@ -5683,6 +5715,16 @@ dependencies = [ "nix 0.24.3", ] +[[package]] +name = "xcb" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e917a3f24142e9ff8be2414e36c649d47d6cc2ba81f16201cdef96e533e02de" +dependencies = [ + "libc", + "log", +] + [[package]] name = "xcursor" version = "0.3.4" diff --git a/Cargo.toml b/Cargo.toml index d386d2a0..f3a9ac57 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -23,6 +23,7 @@ anyhow = "1.0" arboard = "3.2" avif-decode = {version = "0.2", optional = true} clap = "3.2" +clipboard = "0.5.0" dds-rs = "0.7" dirs = "5.0" env_logger = "0.10" diff --git a/src/main.rs b/src/main.rs index c05f1ebf..8135776e 100644 --- a/src/main.rs +++ b/src/main.rs @@ -3,6 +3,7 @@ use std::collections::HashSet; use clap::Arg; use clap::Command; +use clipboard::{ClipboardContext, ClipboardProvider}; use log::debug; use log::error; use log::info; @@ -421,6 +422,12 @@ fn event(app: &mut App, state: &mut OculanteState, evt: Event) { if key_pressed(app, state, BrowseFolder) { browse_for_folder_path(state) } + if key_pressed(app, state, CopyImagePathToClipboard) { + if let Some(img_path) = &state.current_path { + let mut ctx: ClipboardContext = ClipboardProvider::new().expect("Cannot create Clipboard context"); + ctx.set_contents(img_path.to_string_lossy().to_string()).expect("Cannot set Clipboard context"); + } + } if key_pressed(app, state, NextImage) { if state.is_loaded { next_image(state) diff --git a/src/shortcuts.rs b/src/shortcuts.rs index f13d85b9..1cd022f6 100644 --- a/src/shortcuts.rs +++ b/src/shortcuts.rs @@ -45,6 +45,7 @@ pub enum InputEvent { Quit, ZenMode, Favourite, + CopyImagePathToClipboard, ToggleSlideshow, } @@ -137,6 +138,7 @@ impl ShortcutExt for Shortcuts { // .add_key(InputEvent::Browse, "F1") // FIXME: As Shortcuts is a HashMap, only the newer key-sequence will be registered .add_keys(InputEvent::Browse, &["LControl", "O"]) .add_keys(InputEvent::BrowseFolder, &["LControl", "LShift", "O"]) + .add_keys(InputEvent::CopyImagePathToClipboard, &["LControl", "LShift", "P"]) .add_keys(InputEvent::PanRight, &["LShift", "Right"]) .add_keys(InputEvent::PanLeft, &["LShift", "Left"]) .add_keys(InputEvent::PanDown, &["LShift", "Down"]) From fdeb7042c6cc772a6f06463fde35702db81b2237 Mon Sep 17 00:00:00 2001 From: Vitaliy Grabovets Date: Wed, 19 Jul 2023 22:35:27 +0300 Subject: [PATCH 38/73] v0.6.68-dev9 --- Cargo.toml | 2 +- PKGBUILD | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index f3a9ac57..6a8fb1a1 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -11,7 +11,7 @@ license = "MIT" name = "oculante" readme = "README.md" repository = "https://github.com/woelper/oculante/" -version = "0.6.68-dev8" +version = "0.6.68-dev9" [package.metadata.bundle] icon = ["res/oculante.png"] diff --git a/PKGBUILD b/PKGBUILD index f4a02218..9b24af09 100644 --- a/PKGBUILD +++ b/PKGBUILD @@ -1,6 +1,6 @@ # Maintainer: Johann Woelper pkgname=oculante -pkgver=0.6.68-dev8 +pkgver=0.6.68-dev9 pkgrel=1 depends=('aom' 'libwebp' 'expat' 'freetype2' 'gtk3' 'cairo') makedepends=('rust' 'cargo' 'tar' 'nasm' 'cmake') @@ -23,4 +23,4 @@ package() { install -Dm644 res/oculante.desktop "${pkgdir}/usr/share/applications/${pkgname}.desktop" install -Dm644 LICENSE -t "${pkgdir}/usr/share/licenses/${pkgname}" install -Dm644 README.md -t "${pkgdir}/usr/share/doc/${pkgname}" -} \ No newline at end of file +} From 80a520ffe33b21f9699d9c74c582b386bb97e408 Mon Sep 17 00:00:00 2001 From: Vitaliy Grabovets Date: Thu, 20 Jul 2023 11:59:51 +0300 Subject: [PATCH 39/73] when n=0 add favourites to the beginning --- Cargo.lock | 2 +- PKGBUILD | 2 +- src/scrubber.rs | 26 ++++++++++++++++---------- src/ui.rs | 9 ++++++--- src/utils.rs | 7 +++++++ 5 files changed, 31 insertions(+), 15 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 4c5ad65a..278c3f76 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3264,7 +3264,7 @@ dependencies = [ [[package]] name = "oculante" -version = "0.6.68-dev8" +version = "0.6.68-dev9" dependencies = [ "anyhow", "arboard", diff --git a/PKGBUILD b/PKGBUILD index 9b24af09..174afa65 100644 --- a/PKGBUILD +++ b/PKGBUILD @@ -23,4 +23,4 @@ package() { install -Dm644 res/oculante.desktop "${pkgdir}/usr/share/applications/${pkgname}.desktop" install -Dm644 LICENSE -t "${pkgdir}/usr/share/licenses/${pkgname}" install -Dm644 README.md -t "${pkgdir}/usr/share/doc/${pkgname}" -} +} \ No newline at end of file diff --git a/src/scrubber.rs b/src/scrubber.rs index 41ada832..9bd8666b 100644 --- a/src/scrubber.rs +++ b/src/scrubber.rs @@ -1,6 +1,5 @@ use crate::utils::is_ext_compatible; use anyhow::{bail, Context, Result}; -use log::debug; use rand::seq::SliceRandom; use std::collections::HashSet; use std::default::Default; @@ -77,7 +76,6 @@ impl Scrubber { if index < self.entries.len() { self.index = index; } - debug!("{:?}", self.entries.get(self.index)); self.entries.get(self.index).cloned().unwrap_or_default() } @@ -90,10 +88,8 @@ impl Scrubber { } pub fn re_initialize(&mut self, intersperse_with_favs_every_n: usize) { - if intersperse_with_favs_every_n > 0 { - let favourites_vec: Vec = self.favourites.clone().into_iter().collect(); - self.entries = insert_after_every(self.entries.clone(), favourites_vec, intersperse_with_favs_every_n); - } + let favourites_vec: Vec = self.favourites.clone().into_iter().collect(); + self.entries = insert_after_every(self.entries.clone(), favourites_vec, intersperse_with_favs_every_n); } } @@ -154,9 +150,7 @@ pub fn get_image_filenames_for_directory( }); } - if intersperse_with_favs_every_n > 0 { - dir_files = insert_after_every(dir_files, favourites_vec, intersperse_with_favs_every_n); - } + dir_files = insert_after_every(dir_files, favourites_vec, intersperse_with_favs_every_n); return Ok(dir_files); } @@ -182,9 +176,21 @@ pub fn find_first_image_in_directory(folder_path: &PathBuf) -> Result { fn insert_after_every(main_vector: Vec, other_vector: Vec, after: usize) -> Vec { let mut result = Vec::with_capacity(main_vector.len()); + let other_vector_set: HashSet = other_vector.clone().into_iter().collect(); + + if after == 0 { + result.extend_from_slice(&other_vector); + result.extend( + main_vector. + into_iter(). + filter(|element| !other_vector_set.contains(element)) + ); + return result; + } + let mut i = 0; let mut other_vector_i = 0; - let other_vector_set: HashSet = other_vector.clone().into_iter().collect(); + for element in main_vector.into_iter() { if other_vector_set.contains(&element) { diff --git a/src/ui.rs b/src/ui.rs index 801826b8..857941cf 100644 --- a/src/ui.rs +++ b/src/ui.rs @@ -8,8 +8,8 @@ use crate::{ shortcuts::{key_pressed, keypresses_as_string, lookup}, utils::{ clipboard_copy, disp_col, disp_col_norm, highlight_bleed, highlight_semitrans, - load_image_from_path, next_image, prev_image, send_extended_info, set_title, solo_channel, - toggle_fullscreen, unpremult, ColorChannel, ImageExt, + load_image_from_path, next_image, prev_image, reload_image, send_extended_info, set_title, + solo_channel, toggle_fullscreen, unpremult, ColorChannel, ImageExt, }, }; @@ -492,7 +492,10 @@ pub fn settings_ui(app: &mut App, ctx: &Context, state: &mut OculanteState) { .on_hover_text("Add favourite to slideshow every n slide") .changed() { - state.scrubber.re_initialize(state.persistent_settings.add_fav_every_n); + if state.current_path.is_some() { + state.scrubber.re_initialize(state.persistent_settings.add_fav_every_n); + reload_image(state); + } } }); diff --git a/src/utils.rs b/src/utils.rs index 3041a918..79041b84 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -647,6 +647,13 @@ pub fn next_image(state: &mut OculanteState) { } } +pub fn reload_image(state: &mut OculanteState) { + let img_path = state.scrubber.set(state.scrubber.index); + state.is_loaded = false; + state.current_path = Some(img_path.clone()); + state.player.load(img_path.as_path(), state.message_channel.0.clone()); +} + /// Set the window title pub fn set_title(app: &mut App, state: &mut OculanteState) { let p = state.current_path.clone().unwrap_or_default(); From 85045040797f81fb7702ce2c67a9096e2fe3faa0 Mon Sep 17 00:00:00 2001 From: Vitaliy Grabovets Date: Thu, 20 Jul 2023 14:40:46 +0300 Subject: [PATCH 40/73] zoom with right mouse button --- src/main.rs | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/src/main.rs b/src/main.rs index 8135776e..edf9afb7 100644 --- a/src/main.rs +++ b/src/main.rs @@ -568,7 +568,7 @@ fn event(app: &mut App, state: &mut OculanteState, evt: Event) { } } Event::MouseDown { button, .. } => { - state.drag_enabled = true; + // state.drag_enabled = true; match button { MouseButton::Left => { if !state.mouse_grab { @@ -578,6 +578,15 @@ fn event(app: &mut App, state: &mut OculanteState, evt: Event) { MouseButton::Middle => { state.drag_enabled = true; } + MouseButton::Right => { + if state.current_image.is_some() { + if state.image_geometry.scale < 1. { + set_zoom(1., None, state); + } else { + state.reset_image = true; + } + } + } _ => {} } } From de0d64d51c0752846bb24a4a433232fce09054b6 Mon Sep 17 00:00:00 2001 From: Vitaliy Grabovets Date: Thu, 20 Jul 2023 15:41:38 +0300 Subject: [PATCH 41/73] check that cursor is within image --- src/main.rs | 12 ++++++++++-- src/utils.rs | 20 ++++++++++++++++++++ 2 files changed, 30 insertions(+), 2 deletions(-) diff --git a/src/main.rs b/src/main.rs index edf9afb7..dee3f537 100644 --- a/src/main.rs +++ b/src/main.rs @@ -579,8 +579,16 @@ fn event(app: &mut App, state: &mut OculanteState, evt: Event) { state.drag_enabled = true; } MouseButton::Right => { - if state.current_image.is_some() { - if state.image_geometry.scale < 1. { + if state.current_image.is_some() + && !state.pointer_over_ui + && cursor_within_image( + state.cursor, + state.image_geometry.offset, + state.image_dimension, + state.image_geometry.scale, + ) + { + if state.image_geometry.offset[1] == 0. { set_zoom(1., None, state); } else { state.reset_image = true; diff --git a/src/utils.rs b/src/utils.rs index 79041b84..5c6718f9 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -727,3 +727,23 @@ pub fn toggle_zen_mode(state: &mut OculanteState, app: &mut App) { } set_title(app, state); } + +pub fn cursor_within_image( + cursor_position: Vector2, + img_offset: Vector2, + img_dims: (u32, u32), + img_scale: f32, +) -> bool { + let img_dims_scaled = (img_dims.0 as f32 * img_scale, img_dims.1 as f32 * img_scale); + let img_x = (img_offset[0], img_offset[0] + img_dims_scaled.0); + let img_y = (img_offset[1], img_offset[1] + img_dims_scaled.1); + + if img_x.0 <= cursor_position[0] + && cursor_position[0] <= img_x.1 + && img_y.0 <= cursor_position[1] + && cursor_position[1] <= img_y.1 + { + return true; + } + false +} From e573d39e68bfe230c5c3bb405cf7798bab0bdef6 Mon Sep 17 00:00:00 2001 From: Vitaliy Grabovets Date: Thu, 20 Jul 2023 17:14:51 +0300 Subject: [PATCH 42/73] delete all pressed keys when opening file dialog --- src/main.rs | 10 ++++++---- src/ui.rs | 4 ++-- 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/src/main.rs b/src/main.rs index dee3f537..a842cfe0 100644 --- a/src/main.rs +++ b/src/main.rs @@ -416,11 +416,11 @@ fn event(app: &mut App, state: &mut OculanteState, evt: Event) { } #[cfg(feature = "file_open")] if key_pressed(app, state, Browse) { - browse_for_image_path(state) + browse_for_image_path(state, app) } #[cfg(feature = "file_open")] if key_pressed(app, state, BrowseFolder) { - browse_for_folder_path(state) + browse_for_folder_path(state, app) } if key_pressed(app, state, CopyImagePathToClipboard) { if let Some(img_path) = &state.current_path { @@ -1094,7 +1094,8 @@ fn drawe(app: &mut App, gfx: &mut Graphics, plugins: &mut Plugins, state: &mut O // Show file browser to select image to load #[cfg(feature = "file_open")] -fn browse_for_image_path(state: &mut OculanteState) { +fn browse_for_image_path(state: &mut OculanteState, app: &mut App) { + app.keyboard.down = Default::default(); let start_directory = &state.persistent_settings.last_open_directory; let file_dialog_result = rfd::FileDialog::new() @@ -1120,7 +1121,8 @@ fn browse_for_image_path(state: &mut OculanteState) { } #[cfg(feature = "file_open")] -fn browse_for_folder_path(state: &mut OculanteState) { +fn browse_for_folder_path(state: &mut OculanteState, app: &mut App) { + app.keyboard.down = Default::default(); let start_directory = &state.persistent_settings.last_open_directory; let folder_dialog_result = rfd::FileDialog::new() diff --git a/src/ui.rs b/src/ui.rs index 857941cf..07f9f703 100644 --- a/src/ui.rs +++ b/src/ui.rs @@ -1792,7 +1792,7 @@ pub fn main_menu(ui: &mut Ui, state: &mut OculanteState, app: &mut App, gfx: &mu .on_hover_text("Browse for image") .clicked() { - browse_for_image_path(state) + browse_for_image_path(state, app) } #[cfg(feature = "file_open")] @@ -1800,7 +1800,7 @@ pub fn main_menu(ui: &mut Ui, state: &mut OculanteState, app: &mut App, gfx: &mu .on_hover_text("Browse for folder") .clicked() { - browse_for_folder_path(state) + browse_for_folder_path(state, app) } ui.scope(|ui| { From 36571bbe006b8dc36cffff19d4e9ac2966428bab Mon Sep 17 00:00:00 2001 From: Vitaliy Grabovets Date: Thu, 20 Jul 2023 17:38:41 +0300 Subject: [PATCH 43/73] use linear texture filter --- src/shortcuts.rs | 1 - src/utils.rs | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/src/shortcuts.rs b/src/shortcuts.rs index 1cd022f6..df6b2f17 100644 --- a/src/shortcuts.rs +++ b/src/shortcuts.rs @@ -186,7 +186,6 @@ pub fn key_pressed(app: &mut App, state: &mut OculanteState, command: InputEvent // early out if just one key is pressed, and it's a modifier if app.keyboard.alt() || app.keyboard.shift() || app.keyboard.ctrl() { if app.keyboard.down.len() == 1 { - debug!("just modifier down"); return false; } } diff --git a/src/utils.rs b/src/utils.rs index 5c6718f9..14a73106 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -533,7 +533,7 @@ impl ImageExt for RgbaImage { .with_mipmaps(true) .with_format(notan::prelude::TextureFormat::SRgba8) // .with_premultiplied_alpha() - .with_filter(TextureFilter::Linear, TextureFilter::Nearest) + .with_filter(TextureFilter::Linear, TextureFilter::Linear) // .with_wrap(TextureWrap::Clamp, TextureWrap::Clamp) .build() .ok() From 034342ce34cbc29efa7508d9fcd2575d7ef115c5 Mon Sep 17 00:00:00 2001 From: Vitaliy Grabovets Date: Thu, 20 Jul 2023 17:47:32 +0300 Subject: [PATCH 44/73] check that cursor is within image for mouse events --- src/main.rs | 44 +++++++++++++++++++++----------------------- 1 file changed, 21 insertions(+), 23 deletions(-) diff --git a/src/main.rs b/src/main.rs index a842cfe0..f1031629 100644 --- a/src/main.rs +++ b/src/main.rs @@ -568,34 +568,32 @@ fn event(app: &mut App, state: &mut OculanteState, evt: Event) { } } Event::MouseDown { button, .. } => { - // state.drag_enabled = true; - match button { - MouseButton::Left => { - if !state.mouse_grab { + if cursor_within_image( + state.cursor, + state.image_geometry.offset, + state.image_dimension, + state.image_geometry.scale, + ) { + match button { + MouseButton::Left => { + if !state.mouse_grab { + state.drag_enabled = true; + } + } + MouseButton::Middle => { state.drag_enabled = true; } - } - MouseButton::Middle => { - state.drag_enabled = true; - } - MouseButton::Right => { - if state.current_image.is_some() - && !state.pointer_over_ui - && cursor_within_image( - state.cursor, - state.image_geometry.offset, - state.image_dimension, - state.image_geometry.scale, - ) - { - if state.image_geometry.offset[1] == 0. { - set_zoom(1., None, state); - } else { - state.reset_image = true; + MouseButton::Right => { + if state.current_image.is_some() { + if state.image_geometry.offset[1] == 0. { + set_zoom(1., None, state); + } else { + state.reset_image = true; + } } } + _ => {} } - _ => {} } } Event::MouseUp { button, .. } => match button { From 387a8867892d4fc5b0033cac4238ed9a1165b047 Mon Sep 17 00:00:00 2001 From: Vitaliy Grabovets Date: Fri, 21 Jul 2023 16:54:01 +0300 Subject: [PATCH 45/73] replace message as soon as new arrives --- src/main.rs | 38 +++++++++++++++++++++++--------------- src/shortcuts.rs | 2 +- 2 files changed, 24 insertions(+), 16 deletions(-) diff --git a/src/main.rs b/src/main.rs index f1031629..8606c9b6 100644 --- a/src/main.rs +++ b/src/main.rs @@ -315,6 +315,14 @@ fn event(app: &mut App, state: &mut OculanteState, evt: Event) { Event::KeyDown { .. } => { // return; // pan image with keyboard + if state.key_grab { + state.send_message_err("Shortcuts don't work, key grab is active"); + } + + if state.message.is_some() { + state.message = None; + } + let delta = 40.; if key_pressed(app, state, PanRight) { state.image_geometry.offset.x += delta; @@ -426,6 +434,7 @@ fn event(app: &mut App, state: &mut OculanteState, evt: Event) { if let Some(img_path) = &state.current_path { let mut ctx: ClipboardContext = ClipboardProvider::new().expect("Cannot create Clipboard context"); ctx.set_contents(img_path.to_string_lossy().to_string()).expect("Cannot set Clipboard context"); + state.send_message(format!("path {:?} copied", img_path).as_str()); } } if key_pressed(app, state, NextImage) { @@ -660,24 +669,23 @@ fn update(app: &mut App, state: &mut OculanteState) { // Only receive messages if current one is cleared // debug!("cooldown {}", state.toast_cooldown); - if state.message.is_none() { - state.toast_cooldown = 0.; - - // check if a new message has been sent - if let Ok(msg) = state.message_channel.1.try_recv() { - debug!("Received message: {:?}", msg); - match msg { - Message::LoadError(_) => { - state.current_image = None; - state.is_loaded = true; - state.current_texture = None; - }, - _ => (), - } - state.message = Some(msg); + // check if a new message has been sent + if let Ok(msg) = state.message_channel.1.try_recv() { + debug!("Received message: {:?}", msg); + state.toast_cooldown = 0.; + match msg { + Message::LoadError(_) => { + state.current_image = None; + state.is_loaded = true; + state.current_texture = None; + }, + _ => (), } + + state.message = Some(msg); } + state.first_start = false; if state.toggle_slideshow diff --git a/src/shortcuts.rs b/src/shortcuts.rs index df6b2f17..12bcbcf4 100644 --- a/src/shortcuts.rs +++ b/src/shortcuts.rs @@ -138,7 +138,7 @@ impl ShortcutExt for Shortcuts { // .add_key(InputEvent::Browse, "F1") // FIXME: As Shortcuts is a HashMap, only the newer key-sequence will be registered .add_keys(InputEvent::Browse, &["LControl", "O"]) .add_keys(InputEvent::BrowseFolder, &["LControl", "LShift", "O"]) - .add_keys(InputEvent::CopyImagePathToClipboard, &["LControl", "LShift", "P"]) + .add_key(InputEvent::CopyImagePathToClipboard, "P") .add_keys(InputEvent::PanRight, &["LShift", "Right"]) .add_keys(InputEvent::PanLeft, &["LShift", "Left"]) .add_keys(InputEvent::PanDown, &["LShift", "Down"]) From b18790c8768886e14d8071d9b1cee5fe34ca55c4 Mon Sep 17 00:00:00 2001 From: Vitaliy Grabovets Date: Fri, 21 Jul 2023 23:13:43 +0300 Subject: [PATCH 46/73] collapse if statements --- src/ui.rs | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/src/ui.rs b/src/ui.rs index 07f9f703..d4cf3993 100644 --- a/src/ui.rs +++ b/src/ui.rs @@ -491,11 +491,10 @@ pub fn settings_ui(app: &mut App, ctx: &Context, state: &mut OculanteState) { .add(egui::DragValue::new(&mut state.persistent_settings.add_fav_every_n).clamp_range(0..=25)) .on_hover_text("Add favourite to slideshow every n slide") .changed() + && state.current_path.is_some() { - if state.current_path.is_some() { - state.scrubber.re_initialize(state.persistent_settings.add_fav_every_n); - reload_image(state); - } + state.scrubber.re_initialize(state.persistent_settings.add_fav_every_n); + reload_image(state); } }); From 05c21ecdad1db77a7f4a16bb282921a88fa72593 Mon Sep 17 00:00:00 2001 From: Vitaliy Grabovets Date: Sat, 22 Jul 2023 00:51:27 +0300 Subject: [PATCH 47/73] delete files --- Cargo.lock | 78 +++++++++++++++++++++++++++++++++++++++++++++++- Cargo.toml | 1 + src/appstate.rs | 19 ++++++++++++ src/main.rs | 9 ++++++ src/scrubber.rs | 15 ++++++++-- src/shortcuts.rs | 2 ++ src/ui.rs | 13 ++++---- src/utils.rs | 41 +++++++++++-------------- 8 files changed, 145 insertions(+), 33 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 523e1049..275645bb 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -59,6 +59,21 @@ version = "0.2.16" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0942ffc6dcaadf03badf6e6a2d0228460359d5e34b57ccdc720b7382dfbd5ec5" +[[package]] +name = "android-tzdata" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e999941b234f3131b00bc13c22d06e8c5ff726d1b6318ac7eb276997bbb4fef0" + +[[package]] +name = "android_system_properties" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311" +dependencies = [ + "libc", +] + [[package]] name = "any_ascii" version = "0.1.7" @@ -502,6 +517,18 @@ dependencies = [ "libc", ] +[[package]] +name = "chrono" +version = "0.4.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec837a71355b28f6556dbd569b37b3f363091c0bd4b2e735674521b4c5fd9bc5" +dependencies = [ + "android-tzdata", + "iana-time-zone", + "num-traits 0.2.15", + "winapi", +] + [[package]] name = "clap" version = "3.2.25" @@ -1952,6 +1979,29 @@ dependencies = [ "tokio-rustls", ] +[[package]] +name = "iana-time-zone" +version = "0.1.57" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2fad5b825842d2b38bd206f3e81d6957625fd7f0a361e345c30e01a0ae2dd613" +dependencies = [ + "android_system_properties", + "core-foundation-sys", + "iana-time-zone-haiku", + "js-sys", + "wasm-bindgen", + "windows 0.48.0", +] + +[[package]] +name = "iana-time-zone-haiku" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f" +dependencies = [ + "cc", +] + [[package]] name = "ident_case" version = "1.0.1" @@ -3308,6 +3358,7 @@ dependencies = [ "strum_macros", "tiff 0.9.0", "tiny-skia 0.9.1", + "trash", "turbojpeg", "usvg", "walkdir", @@ -3998,7 +4049,7 @@ dependencies = [ "wasm-bindgen", "wasm-bindgen-futures", "web-sys", - "windows", + "windows 0.44.0", ] [[package]] @@ -4865,6 +4916,22 @@ dependencies = [ "once_cell", ] +[[package]] +name = "trash" +version = "3.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af3663fb8f476d674b9c61d1d2796acec725bef6bec4b41402a904252a25971e" +dependencies = [ + "chrono", + "libc", + "log", + "objc", + "once_cell", + "scopeguard", + "url", + "windows 0.44.0", +] + [[package]] name = "try-lock" version = "0.2.4" @@ -5409,6 +5476,15 @@ dependencies = [ "windows-targets 0.42.2", ] +[[package]] +name = "windows" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e686886bc078bc1b0b600cac0147aadb815089b6e4da64016cbd754b6342700f" +dependencies = [ + "windows-targets 0.48.1", +] + [[package]] name = "windows-sys" version = "0.36.1" diff --git a/Cargo.toml b/Cargo.toml index 5d97b31b..047ea66e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -56,6 +56,7 @@ serde_json = "1.0" strum = {version = "0.25", features = ["derive"]} strum_macros = "0.25" tiny-skia = "0.9" +trash = "3.0.3" turbojpeg = {version = "0.5", features = ["image"], optional = true} usvg = "0.33.0" webbrowser = "0.8" diff --git a/src/appstate.rs b/src/appstate.rs index a81baaf7..374ad8d9 100644 --- a/src/appstate.rs +++ b/src/appstate.rs @@ -9,6 +9,7 @@ use image::RgbaImage; use nalgebra::Vector2; use notan::{egui::epaint::ahash::HashMap, prelude::Texture, AppState}; use std::{ + default::Default, path::PathBuf, sync::mpsc::{self, Receiver, Sender}, time::Instant, @@ -104,6 +105,24 @@ impl OculanteState { pub fn send_message_err(&self, msg: &str) { _ = self.message_channel.0.send(Message::err(msg)); } + + pub fn reload_image(&mut self) { + match self.scrubber.set(self.scrubber.index) { + Ok(img_path) => { + self.is_loaded = false; + self.current_path = Some(img_path.clone()); + self.player.load(img_path.as_path(), self.message_channel.0.clone()); + }, + Err(_) => { + self.reset(); + self.send_message_err("No images"); + } + } + } + + fn reset(&mut self) { + *self = OculanteState::default(); + } } impl Default for OculanteState { diff --git a/src/main.rs b/src/main.rs index f085ec23..5a315aac 100644 --- a/src/main.rs +++ b/src/main.rs @@ -17,6 +17,7 @@ use shortcuts::key_pressed; use std::path::PathBuf; use std::sync::mpsc; use std::time::{Duration, Instant}; +use trash; pub mod cache; pub mod scrubber; pub mod settings; @@ -370,6 +371,14 @@ fn event(app: &mut App, state: &mut OculanteState, evt: Event) { if key_pressed(app, state, ToggleSlideshow) { state.toggle_slideshow = !state.toggle_slideshow; } + if key_pressed(app, state, DeleteFile) { + if let Some(img_path) = &state.current_path { + trash::delete(img_path).expect("Cannot delete file"); + state.send_message(format!("file {:?} removed", img_path).as_str()); + state.scrubber.delete(img_path); + state.reload_image(); + } + } if key_pressed(app, state, Quit) { state.persistent_settings.save_blocking(); diff --git a/src/scrubber.rs b/src/scrubber.rs index 9bd8666b..b9119234 100644 --- a/src/scrubber.rs +++ b/src/scrubber.rs @@ -72,11 +72,17 @@ impl Scrubber { self.entries.get(self.index).cloned().unwrap_or_default() } - pub fn set(&mut self, index: usize) -> PathBuf { + pub fn set(&mut self, index: usize) -> Result { + if self.entries.is_empty() { + return Err("Scrubber has no entries".to_string()); + } + if index < self.entries.len() { self.index = index; + } else { + self.index = self.entries.len() - 1; } - self.entries.get(self.index).cloned().unwrap_or_default() + Ok(self.entries[self.index].clone()) } pub fn get(&mut self, index: usize) -> Option { @@ -91,6 +97,10 @@ impl Scrubber { let favourites_vec: Vec = self.favourites.clone().into_iter().collect(); self.entries = insert_after_every(self.entries.clone(), favourites_vec, intersperse_with_favs_every_n); } + + pub fn delete(&mut self, file: &PathBuf) { + self.entries.retain(|element| element != file); + } } // Get sorted list of files in a folder @@ -191,7 +201,6 @@ fn insert_after_every(main_vector: Vec, other_vector: Vec, aft let mut i = 0; let mut other_vector_i = 0; - for element in main_vector.into_iter() { if other_vector_set.contains(&element) { continue diff --git a/src/shortcuts.rs b/src/shortcuts.rs index 12bcbcf4..16b633fd 100644 --- a/src/shortcuts.rs +++ b/src/shortcuts.rs @@ -46,6 +46,7 @@ pub enum InputEvent { ZenMode, Favourite, CopyImagePathToClipboard, + DeleteFile, ToggleSlideshow, } @@ -135,6 +136,7 @@ impl ShortcutExt for Shortcuts { .add_key(InputEvent::ZenMode, "Z") .add_key(InputEvent::Favourite, "J") .add_key(InputEvent::ToggleSlideshow, "Space") + .add_key(InputEvent::DeleteFile, "Delete") // .add_key(InputEvent::Browse, "F1") // FIXME: As Shortcuts is a HashMap, only the newer key-sequence will be registered .add_keys(InputEvent::Browse, &["LControl", "O"]) .add_keys(InputEvent::BrowseFolder, &["LControl", "LShift", "O"]) diff --git a/src/ui.rs b/src/ui.rs index d4cf3993..6a7c57a4 100644 --- a/src/ui.rs +++ b/src/ui.rs @@ -8,8 +8,8 @@ use crate::{ shortcuts::{key_pressed, keypresses_as_string, lookup}, utils::{ clipboard_copy, disp_col, disp_col_norm, highlight_bleed, highlight_semitrans, - load_image_from_path, next_image, prev_image, reload_image, send_extended_info, set_title, - solo_channel, toggle_fullscreen, unpremult, ColorChannel, ImageExt, + load_image_from_path, next_image, prev_image, send_extended_info, set_title, solo_channel, + toggle_fullscreen, unpremult, ColorChannel, ImageExt, }, }; @@ -494,7 +494,7 @@ pub fn settings_ui(app: &mut App, ctx: &Context, state: &mut OculanteState) { && state.current_path.is_some() { state.scrubber.re_initialize(state.persistent_settings.add_fav_every_n); - reload_image(state); + state.reload_image(); } }); @@ -1559,9 +1559,10 @@ pub fn scrubber_ui(state: &mut OculanteState, ui: &mut Ui) { .slider_timeline(&mut state.scrubber.index, 0..=len) .changed() { - let p = state.scrubber.set(state.scrubber.index); - state.current_path = Some(p.clone()); - state.player.load(&p, state.message_channel.0.clone()); + if let Ok(p) = state.scrubber.set(state.scrubber.index) { + state.current_path = Some(p.clone()); + state.player.load(&p, state.message_channel.0.clone()); + } } } diff --git a/src/utils.rs b/src/utils.rs index 14a73106..ef58eb09 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -607,28 +607,30 @@ pub fn load_image_from_path(p: &Path, state: &mut OculanteState) { pub fn last_image(state: &mut OculanteState) { if let Some(img_location) = state.current_path.as_mut() { let last = state.scrubber.len().saturating_sub(1); - let next_img = state.scrubber.set(last); - // prevent reload if at last or first - if &next_img != img_location { - state.is_loaded = false; - *img_location = next_img; - state - .player - .load(img_location, state.message_channel.0.clone()); + if let Ok(next_img) = state.scrubber.set(last) { + // prevent reload if at last or first + if &next_img != img_location { + state.is_loaded = false; + *img_location = next_img; + state + .player + .load(img_location, state.message_channel.0.clone()); + } } } } pub fn first_image(state: &mut OculanteState) { if let Some(img_location) = state.current_path.as_mut() { - let next_img = state.scrubber.set(0); - // prevent reload if at last or first - if &next_img != img_location { - state.is_loaded = false; - *img_location = next_img; - state - .player - .load(img_location, state.message_channel.0.clone()); + if let Ok(next_img) = state.scrubber.set(0) { + // prevent reload if at last or first + if &next_img != img_location { + state.is_loaded = false; + *img_location = next_img; + state + .player + .load(img_location, state.message_channel.0.clone()); + } } } } @@ -647,13 +649,6 @@ pub fn next_image(state: &mut OculanteState) { } } -pub fn reload_image(state: &mut OculanteState) { - let img_path = state.scrubber.set(state.scrubber.index); - state.is_loaded = false; - state.current_path = Some(img_path.clone()); - state.player.load(img_path.as_path(), state.message_channel.0.clone()); -} - /// Set the window title pub fn set_title(app: &mut App, state: &mut OculanteState) { let p = state.current_path.clone().unwrap_or_default(); From c43012b270073dfc8a8d1227e9dd27bb09a9be86 Mon Sep 17 00:00:00 2001 From: Vitaliy Grabovets Date: Sat, 22 Jul 2023 01:15:26 +0300 Subject: [PATCH 48/73] fix zoom by right mouse button --- Cargo.lock | 7 +++++++ Cargo.toml | 1 + src/appstate.rs | 24 ++++++++++++++++++++++++ src/main.rs | 10 +++------- src/utils.rs | 20 -------------------- 5 files changed, 35 insertions(+), 27 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 275645bb..aa9f364a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3350,6 +3350,7 @@ dependencies = [ "resvg", "rfd", "rgb", + "round", "rusqlite", "self_update", "serde", @@ -4089,6 +4090,12 @@ dependencies = [ "svgtypes", ] +[[package]] +name = "round" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b02705db4fa872ae37e464fc803b7ffa574e6c4a5e112c52a82550f9dd63b657" + [[package]] name = "roxmltree" version = "0.18.0" diff --git a/Cargo.toml b/Cargo.toml index 047ea66e..3d219337 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -50,6 +50,7 @@ rayon = "1.7" resvg = "0.33.0" rfd = {version = "0.11", optional = true} rgb = "0.8" +round = "0.1.2" self_update = {version = "0.37", default-features = false, features = ["rustls"], optional = true} serde = {version = "1.0", features = ["derive"]} serde_json = "1.0" diff --git a/src/appstate.rs b/src/appstate.rs index 374ad8d9..7e5eaa1e 100644 --- a/src/appstate.rs +++ b/src/appstate.rs @@ -120,6 +120,30 @@ impl OculanteState { } } + pub fn cursor_within_image(& self) -> bool { + let img_dims_scaled = ( + self.image_dimension.0 as f32 * self.image_geometry.scale, + self.image_dimension.1 as f32 * self.image_geometry.scale, + ); + let img_x = ( + self.image_geometry.offset[0], + self.image_geometry.offset[0] + img_dims_scaled.0, + ); + let img_y = ( + self.image_geometry.offset[1], + self.image_geometry.offset[1] + img_dims_scaled.1, + ); + + if img_x.0 <= self.cursor[0] + && self.cursor[0] <= img_x.1 + && img_y.0 <= self.cursor[1] + && self.cursor[1] <= img_y.1 + { + return true; + } + false + } + fn reset(&mut self) { *self = OculanteState::default(); } diff --git a/src/main.rs b/src/main.rs index 5a315aac..d9fce66f 100644 --- a/src/main.rs +++ b/src/main.rs @@ -13,6 +13,7 @@ use notan::app::Event; use notan::draw::*; use notan::egui::{self, *}; use notan::prelude::*; +use round::round; use shortcuts::key_pressed; use std::path::PathBuf; use std::sync::mpsc; @@ -586,12 +587,7 @@ fn event(app: &mut App, state: &mut OculanteState, evt: Event) { } } Event::MouseDown { button, .. } => { - if cursor_within_image( - state.cursor, - state.image_geometry.offset, - state.image_dimension, - state.image_geometry.scale, - ) { + if state.cursor_within_image() { match button { MouseButton::Left => { if !state.mouse_grab { @@ -603,7 +599,7 @@ fn event(app: &mut App, state: &mut OculanteState, evt: Event) { } MouseButton::Right => { if state.current_image.is_some() { - if state.image_geometry.offset[1] == 0. { + if round(state.image_geometry.offset[1] as f64, 4) == 0. { set_zoom(1., None, state); } else { state.reset_image = true; diff --git a/src/utils.rs b/src/utils.rs index ef58eb09..d9da9e6c 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -722,23 +722,3 @@ pub fn toggle_zen_mode(state: &mut OculanteState, app: &mut App) { } set_title(app, state); } - -pub fn cursor_within_image( - cursor_position: Vector2, - img_offset: Vector2, - img_dims: (u32, u32), - img_scale: f32, -) -> bool { - let img_dims_scaled = (img_dims.0 as f32 * img_scale, img_dims.1 as f32 * img_scale); - let img_x = (img_offset[0], img_offset[0] + img_dims_scaled.0); - let img_y = (img_offset[1], img_offset[1] + img_dims_scaled.1); - - if img_x.0 <= cursor_position[0] - && cursor_position[0] <= img_x.1 - && img_y.0 <= cursor_position[1] - && cursor_position[1] <= img_y.1 - { - return true; - } - false -} From 3d2733af2833f9e18ae57aea853d71add2216405 Mon Sep 17 00:00:00 2001 From: Vitaliy Grabovets Date: Sat, 22 Jul 2023 17:28:26 +0300 Subject: [PATCH 49/73] show tooltip with image data --- src/appstate.rs | 2 ++ src/main.rs | 41 ++++++++++++++++++++++++++++++++++------- src/ui.rs | 9 +++++++++ 3 files changed, 45 insertions(+), 7 deletions(-) diff --git a/src/appstate.rs b/src/appstate.rs index 7e5eaa1e..99e84324 100644 --- a/src/appstate.rs +++ b/src/appstate.rs @@ -94,6 +94,7 @@ pub struct OculanteState { pub slideshow_time: Instant, pub current_image_is_favourite: bool, pub db: Option, + pub show_metadata_tooltip: bool, pub first_start: bool, } @@ -197,6 +198,7 @@ impl Default for OculanteState { slideshow_time: Instant::now(), current_image_is_favourite: Default::default(), db: None, + show_metadata_tooltip: false, first_start: true, } } diff --git a/src/main.rs b/src/main.rs index d9fce66f..70980212 100644 --- a/src/main.rs +++ b/src/main.rs @@ -18,7 +18,6 @@ use shortcuts::key_pressed; use std::path::PathBuf; use std::sync::mpsc; use std::time::{Duration, Instant}; -use trash; pub mod cache; pub mod scrubber; pub mod settings; @@ -53,6 +52,16 @@ mod image_editing; pub mod paint; pub const FONT: &[u8; 309828] = include_bytes!("../res/fonts/Inter-Regular.ttf"); +const STAR: ui::Star = ui::Star { + spikes: 5, + outer_radius: 20., + inner_radius: 10., + x: 50., + y: 60., + stroke: 3., +}; +const TOP_MENU_HEIGHT: f32 = 30.; + #[notan_main] fn main() -> Result<(), String> { @@ -595,7 +604,7 @@ fn event(app: &mut App, state: &mut OculanteState, evt: Event) { } } MouseButton::Middle => { - state.drag_enabled = true; + state.show_metadata_tooltip = !state.show_metadata_tooltip; } MouseButton::Right => { if state.current_image.is_some() { @@ -969,22 +978,40 @@ fn drawe(app: &mut App, gfx: &mut Graphics, plugins: &mut Plugins, state: &mut O } if state.current_image_is_favourite { - draw.star(5, 20.0, 10.0) - .position(50.0, 60.0) + draw.star(STAR.spikes, STAR.outer_radius, STAR.inner_radius) + .position(STAR.x, STAR.y) .fill_color(Color::YELLOW) .fill() .stroke_color(Color::ORANGE) - .stroke(3.0); + .stroke(STAR.stroke); } } let egui_output = plugins.egui(|ctx| { // the top menu bar + if state.show_metadata_tooltip && state.cursor_within_image() { + let pos_y = TOP_MENU_HEIGHT + 5. + if state.current_image_is_favourite {STAR.y} else {0.}; + + show_tooltip_at( + ctx, + egui::Id::new("my_tooltip"), + Some(pos2(10., pos_y)), + |ui| { + ui.label(format!( + "scale: {scale}\nwidth: {width}\nheight: {height}", + scale=round(state.image_geometry.scale as f64, 2), + width=state.image_dimension.0, + height=state.image_dimension.1, + )); + }, + ); + } + if !state.persistent_settings.zen_mode { egui::TopBottomPanel::top("menu") - .min_height(30.) - .default_height(30.) + .min_height(TOP_MENU_HEIGHT) + .default_height(TOP_MENU_HEIGHT) .show(ctx, |ui| { main_menu(ui, state, app, gfx); }); diff --git a/src/ui.rs b/src/ui.rs index 6a7c57a4..3bd81736 100644 --- a/src/ui.rs +++ b/src/ui.rs @@ -1908,3 +1908,12 @@ pub fn main_menu(ui: &mut Ui, state: &mut OculanteState, app: &mut App, gfx: &mu }); }); } + +pub struct Star { + pub spikes: u8, + pub outer_radius: f32, + pub inner_radius: f32, + pub x: f32, + pub y: f32, + pub stroke: f32, +} From 79ea794380bf489f072248a3949e0354699edf26 Mon Sep 17 00:00:00 2001 From: Vitaliy Grabovets Date: Sat, 22 Jul 2023 22:16:45 +0300 Subject: [PATCH 50/73] v0.6.69-dev1 --- Cargo.lock | 2 +- Cargo.toml | 2 +- PKGBUILD | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index aa9f364a..05b1a64d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3314,7 +3314,7 @@ dependencies = [ [[package]] name = "oculante" -version = "0.6.69" +version = "0.6.69-dev1" dependencies = [ "anyhow", "arboard", diff --git a/Cargo.toml b/Cargo.toml index 3d219337..bafcb3ba 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -11,7 +11,7 @@ license = "MIT" name = "oculante" readme = "README.md" repository = "https://github.com/woelper/oculante/" -version = "0.6.69" +version = "0.6.69-dev1" [package.metadata.bundle] icon = ["res/oculante.png"] diff --git a/PKGBUILD b/PKGBUILD index 272da2ea..9c0fccd0 100644 --- a/PKGBUILD +++ b/PKGBUILD @@ -1,6 +1,6 @@ # Maintainer: Johann Woelper pkgname=oculante -pkgver=0.6.69 +pkgver=0.6.69-dev1 pkgrel=1 depends=('aom' 'libwebp' 'expat' 'freetype2' 'gtk3' 'cairo') makedepends=('rust' 'cargo' 'tar' 'nasm' 'cmake') From aae69510b5bd09a508d12d17971035bbe65f505e Mon Sep 17 00:00:00 2001 From: Vitaliy Grabovets Date: Sun, 23 Jul 2023 20:53:11 +0300 Subject: [PATCH 51/73] fix image tooltip --- src/main.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main.rs b/src/main.rs index 70980212..84989433 100644 --- a/src/main.rs +++ b/src/main.rs @@ -990,7 +990,7 @@ fn drawe(app: &mut App, gfx: &mut Graphics, plugins: &mut Plugins, state: &mut O let egui_output = plugins.egui(|ctx| { // the top menu bar - if state.show_metadata_tooltip && state.cursor_within_image() { + if state.show_metadata_tooltip && !state.settings_enabled && state.cursor_within_image() { let pos_y = TOP_MENU_HEIGHT + 5. + if state.current_image_is_favourite {STAR.y} else {0.}; show_tooltip_at( From 19a730c9f8e6bbbd1b87e717982863ab1dd2074b Mon Sep 17 00:00:00 2001 From: Vitaliy Grabovets Date: Sun, 23 Jul 2023 21:13:13 +0300 Subject: [PATCH 52/73] state.toast_cooldown is an Instant --- src/appstate.rs | 4 ++-- src/main.rs | 8 +++----- src/ui.rs | 6 ++++-- 3 files changed, 9 insertions(+), 9 deletions(-) diff --git a/src/appstate.rs b/src/appstate.rs index 99e84324..6f4d22e7 100644 --- a/src/appstate.rs +++ b/src/appstate.rs @@ -82,7 +82,7 @@ pub struct OculanteState { pub always_on_top: bool, pub network_mode: bool, /// how long the toast message appears - pub toast_cooldown: f32, + pub toast_cooldown: Instant, /// data to transform image once fullscreen is entered/left pub fullscreen_offset: Option<(i32, i32)>, /// List of images to cycle through. Usually the current dir or dropped files @@ -188,7 +188,7 @@ impl Default for OculanteState { always_on_top: Default::default(), network_mode: Default::default(), window_size: Default::default(), - toast_cooldown: Default::default(), + toast_cooldown: Instant::now(), fullscreen_offset: Default::default(), scrubber: Default::default(), checker_texture: Default::default(), diff --git a/src/main.rs b/src/main.rs index 84989433..fcdf60d6 100644 --- a/src/main.rs +++ b/src/main.rs @@ -685,7 +685,7 @@ fn update(app: &mut App, state: &mut OculanteState) { // check if a new message has been sent if let Ok(msg) = state.message_channel.1.try_recv() { debug!("Received message: {:?}", msg); - state.toast_cooldown = 0.; + state.toast_cooldown = Instant::now(); match msg { Message::LoadError(_) => { state.current_image = None; @@ -990,7 +990,7 @@ fn drawe(app: &mut App, gfx: &mut Graphics, plugins: &mut Plugins, state: &mut O let egui_output = plugins.egui(|ctx| { // the top menu bar - if state.show_metadata_tooltip && !state.settings_enabled && state.cursor_within_image() { + if state.show_metadata_tooltip && !state.pointer_over_ui && state.cursor_within_image() { let pos_y = TOP_MENU_HEIGHT + 5. + if state.current_image_is_favourite {STAR.y} else {0.}; show_tooltip_at( @@ -1054,14 +1054,12 @@ fn drawe(app: &mut App, gfx: &mut Graphics, plugins: &mut Plugins, state: &mut O ui.ctx().request_repaint(); }, ); - let max_anim_len = 2.5; // using delta does not work with rfd // state.toast_cooldown += app.timer.delta_f32(); - state.toast_cooldown += 0.01; // debug!("cooldown {}", state.toast_cooldown); - if state.toast_cooldown > max_anim_len { + if state.toast_cooldown.elapsed() > Duration::from_secs(3u64) { state.message = None; } } diff --git a/src/ui.rs b/src/ui.rs index 3bd81736..9a1a86e8 100644 --- a/src/ui.rs +++ b/src/ui.rs @@ -1127,7 +1127,7 @@ pub fn edit_ui(app: &mut App, ctx: &Context, state: &mut OculanteState, gfx: &mu state.send_message_err(&format!("Error: Could not save: {e}")); } } - state.toast_cooldown = 0.0; + state.toast_cooldown = Instant::now(); ui.ctx().request_repaint(); } } @@ -1771,7 +1771,9 @@ pub fn main_menu(ui: &mut Ui, state: &mut OculanteState, app: &mut App, gfx: &mu // toggle_fullscreen(app, state); // } - if unframed_button("⛶", ui).clicked() { + if unframed_button("⛶", ui) + .on_hover_text("Fullscreen") + .clicked() { toggle_fullscreen(app, state); } From c1c3438da971dfc01e7ddbab72916fedc5b40340 Mon Sep 17 00:00:00 2001 From: Vitaliy Grabovets Date: Sun, 23 Jul 2023 21:15:44 +0300 Subject: [PATCH 53/73] 0.6.69-dev2 --- Cargo.lock | 2 +- Cargo.toml | 2 +- PKGBUILD | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 05b1a64d..7fcfa2df 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3314,7 +3314,7 @@ dependencies = [ [[package]] name = "oculante" -version = "0.6.69-dev1" +version = "0.6.69-dev2" dependencies = [ "anyhow", "arboard", diff --git a/Cargo.toml b/Cargo.toml index bafcb3ba..88391df1 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -11,7 +11,7 @@ license = "MIT" name = "oculante" readme = "README.md" repository = "https://github.com/woelper/oculante/" -version = "0.6.69-dev1" +version = "0.6.69-dev2" [package.metadata.bundle] icon = ["res/oculante.png"] diff --git a/PKGBUILD b/PKGBUILD index 9c0fccd0..7016c30c 100644 --- a/PKGBUILD +++ b/PKGBUILD @@ -1,6 +1,6 @@ # Maintainer: Johann Woelper pkgname=oculante -pkgver=0.6.69-dev1 +pkgver=0.6.69-dev2 pkgrel=1 depends=('aom' 'libwebp' 'expat' 'freetype2' 'gtk3' 'cairo') makedepends=('rust' 'cargo' 'tar' 'nasm' 'cmake') From 02e42f56a4eecf2a5dd92ec29f12b367d6f67384 Mon Sep 17 00:00:00 2001 From: Vitaliy Grabovets Date: Thu, 3 Aug 2023 22:48:03 +0300 Subject: [PATCH 54/73] Add functionality to read image paths from text file --- src/main.rs | 29 ++++++++++++++++++++++++----- src/scrubber.rs | 11 ++++++++++- src/ui.rs | 4 ++-- 3 files changed, 36 insertions(+), 8 deletions(-) diff --git a/src/main.rs b/src/main.rs index fcdf60d6..baf85e98 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,6 +1,5 @@ #![windows_subsystem = "windows"] -use std::collections::HashSet; use clap::Arg; use clap::Command; use clipboard::{ClipboardContext, ClipboardProvider}; @@ -15,7 +14,11 @@ use notan::egui::{self, *}; use notan::prelude::*; use round::round; use shortcuts::key_pressed; -use std::path::PathBuf; +use std::collections::HashSet; +use std::ffi::OsStr; +use std::fs::File; +use std::io::{self, BufRead}; +use std::path::{Path, PathBuf}; use std::sync::mpsc; use std::time::{Duration, Instant}; pub mod cache; @@ -1147,17 +1150,33 @@ fn browse_for_image_path(state: &mut OculanteState, app: &mut App) { .pick_file(); if let Some(file_path) = file_dialog_result { - debug!("Selected File Path = {:?}", file_path); + let mut current_path = file_path.clone(); state.folder_selected = None; + + if file_path.extension() == Some(OsStr::new("txt")) { + let file = File::open(&file_path).unwrap(); + let reader = io::BufReader::new(file); + let img_paths: Vec = reader + .lines() + .into_iter() + .filter_map(|line| {line.ok()}) + .map(|line| {Path::new(&line).to_path_buf()}) + .collect(); + + state.folder_selected = Some(file_path.parent().unwrap().to_path_buf()); + state.scrubber = Scrubber::new_from_entries(img_paths); + current_path = state.scrubber.get(0).unwrap(); + } + state.is_loaded = false; state.current_image = None; state .player - .load(&file_path, state.message_channel.0.clone()); + .load(¤t_path, state.message_channel.0.clone()); if let Some(dir) = file_path.parent() { state.persistent_settings.last_open_directory = dir.to_path_buf(); } - state.current_path = Some(file_path); + state.current_path = Some(current_path); _ = state.persistent_settings.save(); } } diff --git a/src/scrubber.rs b/src/scrubber.rs index b9119234..83a0d536 100644 --- a/src/scrubber.rs +++ b/src/scrubber.rs @@ -47,6 +47,16 @@ impl Scrubber { favourites: favourites_out, } } + + pub fn new_from_entries(entries: Vec) -> Self { + Self { + index: 0, + entries, + wrap: true, + favourites: Default::default(), + } + } + pub fn next(&mut self) -> PathBuf { self.index += 1; if self.index > self.entries.len().saturating_sub(1) { @@ -56,7 +66,6 @@ impl Scrubber { self.index = self.entries.len().saturating_sub(1); } } - // debug!("{:?}", self.entries.get(self.index)); self.entries.get(self.index).cloned().unwrap_or_default() } diff --git a/src/ui.rs b/src/ui.rs index 9a1a86e8..6fac8071 100644 --- a/src/ui.rs +++ b/src/ui.rs @@ -110,7 +110,7 @@ impl EguiExt for Ui { let color = ui.style().visuals.selection.bg_fill; // let color = Color32::RED; let available_width = ui.available_width() * 0.6; - let mut style = ui.style_mut(); + let style = ui.style_mut(); style.visuals.widgets.hovered.bg_fill = color; style.visuals.widgets.hovered.fg_stroke.width = 0.; @@ -144,7 +144,7 @@ impl EguiExt for Ui { let color = ui.style().visuals.selection.bg_fill; // let color = Color32::RED; let available_width = ui.available_width() * 1. - 60.; - let mut style = ui.style_mut(); + let style = ui.style_mut(); style.visuals.widgets.hovered.bg_fill = color; style.visuals.widgets.hovered.fg_stroke.width = 0.; From 0873960715f38a4bb499b3bb9afcfa813f202f4b Mon Sep 17 00:00:00 2001 From: Vitaliy Grabovets Date: Thu, 3 Aug 2023 23:47:32 +0300 Subject: [PATCH 55/73] Refactor logging and improve error messaging --- src/main.rs | 21 ++++++++++++++++----- src/utils.rs | 4 +++- 2 files changed, 19 insertions(+), 6 deletions(-) diff --git a/src/main.rs b/src/main.rs index baf85e98..11abfc0d 100644 --- a/src/main.rs +++ b/src/main.rs @@ -3,10 +3,7 @@ use clap::Arg; use clap::Command; use clipboard::{ClipboardContext, ClipboardProvider}; -use log::debug; -use log::error; -use log::info; -use log::warn; +use log::{LevelFilter, debug, error, info, warn}; use nalgebra::Vector2; use notan::app::Event; use notan::draw::*; @@ -17,7 +14,7 @@ use shortcuts::key_pressed; use std::collections::HashSet; use std::ffi::OsStr; use std::fs::File; -use std::io::{self, BufRead}; +use std::io::{self, BufRead, Write}; use std::path::{Path, PathBuf}; use std::sync::mpsc; use std::time::{Duration, Instant}; @@ -68,6 +65,19 @@ const TOP_MENU_HEIGHT: f32 = 30.; #[notan_main] fn main() -> Result<(), String> { + env_logger::builder() + .format(|buf, record| { + writeln!(buf, + "{}:{} {} - {}", + record.file().unwrap_or("unknown"), + record.line().unwrap_or(0), + record.level(), + record.args() + ) + }) + .filter(Some("oculante"), LevelFilter::Debug) + .init(); + if std::env::var("RUST_LOG").is_err() { std::env::set_var("RUST_LOG", "warning"); } @@ -694,6 +704,7 @@ fn update(app: &mut App, state: &mut OculanteState) { state.current_image = None; state.is_loaded = true; state.current_texture = None; + set_title(app, state); }, _ => (), } diff --git a/src/utils.rs b/src/utils.rs index d9da9e6c..64f9616c 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -266,7 +266,9 @@ pub fn send_image_threaded( } Err(e) => { error!("{e}"); - _ = message_sender.send(Message::LoadError(e.to_string())); + _ = message_sender.send( + Message::LoadError(format!("File {} does not exist", loc.display())) + ); } } }); From 871542ecd7c250a30dcc81e13f218451302a24d3 Mon Sep 17 00:00:00 2001 From: Vitaliy Grabovets Date: Fri, 4 Aug 2023 22:05:01 +0300 Subject: [PATCH 56/73] Add file size to image metadata tooltip --- src/appstate.rs | 2 ++ src/main.rs | 21 +++++++++++++++------ src/utils.rs | 16 ++++++++++++++++ 3 files changed, 33 insertions(+), 6 deletions(-) diff --git a/src/appstate.rs b/src/appstate.rs index 6f4d22e7..03ca5f25 100644 --- a/src/appstate.rs +++ b/src/appstate.rs @@ -57,6 +57,7 @@ pub struct OculanteState { pub cursor: Vector2, pub cursor_relative: Vector2, pub image_dimension: (u32, u32), + pub image_size: u64, pub sampled_color: [f32; 4], pub mouse_delta: Vector2, pub texture_channel: (Sender, Receiver), @@ -166,6 +167,7 @@ impl Default for OculanteState { cursor: Default::default(), cursor_relative: Default::default(), image_dimension: (0, 0), + image_size: 0, sampled_color: [0., 0., 0., 0.], player: Player::new(tx_channel.0.clone(), 20, 16384), texture_channel: tx_channel, diff --git a/src/main.rs b/src/main.rs index 11abfc0d..a2febdc2 100644 --- a/src/main.rs +++ b/src/main.rs @@ -13,7 +13,7 @@ use round::round; use shortcuts::key_pressed; use std::collections::HashSet; use std::ffi::OsStr; -use std::fs::File; +use std::fs::{self, File}; use std::io::{self, BufRead, Write}; use std::path::{Path, PathBuf}; use std::sync::mpsc; @@ -754,6 +754,14 @@ fn drawe(app: &mut App, gfx: &mut Graphics, plugins: &mut Plugins, state: &mut O } else { state.current_image_is_favourite = false; } + + match fs::metadata(current_path) { + Ok(metadata) => { + state.image_size = metadata.len(); + } + Err(_) => state.send_message_err("Couldn't get metadata"), + } + } // fill image sequence @@ -1005,7 +1013,7 @@ fn drawe(app: &mut App, gfx: &mut Graphics, plugins: &mut Plugins, state: &mut O // the top menu bar if state.show_metadata_tooltip && !state.pointer_over_ui && state.cursor_within_image() { - let pos_y = TOP_MENU_HEIGHT + 5. + if state.current_image_is_favourite {STAR.y} else {0.}; + let pos_y = TOP_MENU_HEIGHT + 5. + if state.current_image_is_favourite { STAR.y } else { 0. }; show_tooltip_at( ctx, @@ -1013,10 +1021,11 @@ fn drawe(app: &mut App, gfx: &mut Graphics, plugins: &mut Plugins, state: &mut O Some(pos2(10., pos_y)), |ui| { ui.label(format!( - "scale: {scale}\nwidth: {width}\nheight: {height}", - scale=round(state.image_geometry.scale as f64, 2), - width=state.image_dimension.0, - height=state.image_dimension.1, + "scale: {scale}\nwidth: {width}\nheight: {height}\nsize: {size}", + scale = round(state.image_geometry.scale as f64, 2), + width = state.image_dimension.0, + height = state.image_dimension.1, + size = format_bytes(state.image_size as f64), )); }, ); diff --git a/src/utils.rs b/src/utils.rs index 64f9616c..c8f7d7c4 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -724,3 +724,19 @@ pub fn toggle_zen_mode(state: &mut OculanteState, app: &mut App) { } set_title(app, state); } + +pub fn format_bytes(bytes: f64) -> String { + const KILOBYTE: f64 = 1000.; + const MEGABYTE: f64 = KILOBYTE * 1000.; + const GIGABYTE: f64 = MEGABYTE * 1000.; + + return if bytes < KILOBYTE { + format!("{} bytes", bytes) + } else if bytes < MEGABYTE { + format!("{:.1} KB", bytes / KILOBYTE) + } else if bytes < GIGABYTE { + format!("{:.1} MB", bytes / MEGABYTE) + } else { + format!("{:.1} GB", bytes / GIGABYTE) + } +} From 61e9d8bdfce88d561d1a161aaeadbc19620ea826 Mon Sep 17 00:00:00 2001 From: Vitaliy Grabovets Date: Sat, 5 Aug 2023 23:30:36 +0300 Subject: [PATCH 57/73] v0.6.69-dev3 --- Cargo.lock | 2 +- Cargo.toml | 2 +- PKGBUILD | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 7fcfa2df..468c944d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3314,7 +3314,7 @@ dependencies = [ [[package]] name = "oculante" -version = "0.6.69-dev2" +version = "0.6.69-dev3" dependencies = [ "anyhow", "arboard", diff --git a/Cargo.toml b/Cargo.toml index 88391df1..f7cacd37 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -11,7 +11,7 @@ license = "MIT" name = "oculante" readme = "README.md" repository = "https://github.com/woelper/oculante/" -version = "0.6.69-dev2" +version = "0.6.69-dev3" [package.metadata.bundle] icon = ["res/oculante.png"] diff --git a/PKGBUILD b/PKGBUILD index 7016c30c..d7e734a9 100644 --- a/PKGBUILD +++ b/PKGBUILD @@ -1,6 +1,6 @@ # Maintainer: Johann Woelper pkgname=oculante -pkgver=0.6.69-dev2 +pkgver=0.6.69-dev3 pkgrel=1 depends=('aom' 'libwebp' 'expat' 'freetype2' 'gtk3' 'cairo') makedepends=('rust' 'cargo' 'tar' 'nasm' 'cmake') From 0c45aac34075cbcbdb6fef7c84b91096e5863f68 Mon Sep 17 00:00:00 2001 From: Vitaliy Grabovets Date: Sun, 6 Aug 2023 12:55:00 +0300 Subject: [PATCH 58/73] Refactor code for readability and efficiency --- src/appstate.rs | 26 +++++++++----------------- src/db.rs | 12 ++++-------- src/main.rs | 9 ++++----- src/scrubber.rs | 1 - 4 files changed, 17 insertions(+), 31 deletions(-) diff --git a/src/appstate.rs b/src/appstate.rs index 03ca5f25..c34a973c 100644 --- a/src/appstate.rs +++ b/src/appstate.rs @@ -9,7 +9,6 @@ use image::RgbaImage; use nalgebra::Vector2; use notan::{egui::epaint::ahash::HashMap, prelude::Texture, AppState}; use std::{ - default::Default, path::PathBuf, sync::mpsc::{self, Receiver, Sender}, time::Instant, @@ -109,20 +108,17 @@ impl OculanteState { } pub fn reload_image(&mut self) { - match self.scrubber.set(self.scrubber.index) { - Ok(img_path) => { - self.is_loaded = false; - self.current_path = Some(img_path.clone()); - self.player.load(img_path.as_path(), self.message_channel.0.clone()); - }, - Err(_) => { - self.reset(); - self.send_message_err("No images"); - } + if let Ok(img_path) = self.scrubber.set(self.scrubber.index) { + self.is_loaded = false; + self.current_path = Some(img_path.clone()); + self.player.load(img_path.as_path(), self.message_channel.0.clone()); + } else { + self.reset(); + self.send_message_err("No images"); } } - pub fn cursor_within_image(& self) -> bool { + pub fn cursor_within_image(&self) -> bool { let img_dims_scaled = ( self.image_dimension.0 as f32 * self.image_geometry.scale, self.image_dimension.1 as f32 * self.image_geometry.scale, @@ -136,14 +132,10 @@ impl OculanteState { self.image_geometry.offset[1] + img_dims_scaled.1, ); - if img_x.0 <= self.cursor[0] + img_x.0 <= self.cursor[0] && self.cursor[0] <= img_x.1 && img_y.0 <= self.cursor[1] && self.cursor[1] <= img_y.1 - { - return true; - } - false } fn reset(&mut self) { diff --git a/src/db.rs b/src/db.rs index 1e3e6f47..4717fa2d 100644 --- a/src/db.rs +++ b/src/db.rs @@ -66,7 +66,7 @@ impl DB { } fn prepare_record(&self, img_path: &PathBuf) -> String { - img_path.strip_prefix(self.folder.as_path()) + img_path.strip_prefix(&self.folder) .unwrap() .components() .map(|component| component.as_os_str().to_str().unwrap()) @@ -74,13 +74,9 @@ impl DB { } fn join_path_parts(&self, path_with_tabs: String) -> PathBuf { - let mut path = PathBuf::new(); - - for part in path_with_tabs.split("\t") { - path.push(part); - } - - path + path_with_tabs.split("\t") + .into_iter() + .collect() } } diff --git a/src/main.rs b/src/main.rs index a2febdc2..8fac238e 100644 --- a/src/main.rs +++ b/src/main.rs @@ -3,7 +3,7 @@ use clap::Arg; use clap::Command; use clipboard::{ClipboardContext, ClipboardProvider}; -use log::{LevelFilter, debug, error, info, warn}; +use log::{debug, error, info, warn}; use nalgebra::Vector2; use notan::app::Event; use notan::draw::*; @@ -24,9 +24,8 @@ pub mod settings; pub mod shortcuts; #[cfg(feature = "turbo")] use crate::image_editing::lossless_tx; -use crate::scrubber::{find_first_image_in_directory, Scrubber}; +use crate::scrubber::{Scrubber, find_first_image_in_directory}; use crate::shortcuts::InputEvent::*; -use crate::utils::set_title; mod utils; use utils::*; mod appstate; @@ -44,10 +43,10 @@ mod ui; mod update; use ui::*; +mod db; use crate::db::{DB, get_db_file}; use crate::image_editing::EditState; -mod db; mod image_editing; pub mod paint; @@ -75,7 +74,7 @@ fn main() -> Result<(), String> { record.args() ) }) - .filter(Some("oculante"), LevelFilter::Debug) + .filter(Some("oculante"), log::LevelFilter::Debug) .init(); if std::env::var("RUST_LOG").is_err() { diff --git a/src/scrubber.rs b/src/scrubber.rs index 83a0d536..9613459a 100644 --- a/src/scrubber.rs +++ b/src/scrubber.rs @@ -2,7 +2,6 @@ use crate::utils::is_ext_compatible; use anyhow::{bail, Context, Result}; use rand::seq::SliceRandom; use std::collections::HashSet; -use std::default::Default; use std::path::{Path, PathBuf}; use walkdir::WalkDir; From e84374a94c718ec0b7fa5f90f5decd1ce9441145 Mon Sep 17 00:00:00 2001 From: Vitaliy Grabovets Date: Sun, 6 Aug 2023 17:28:35 +0300 Subject: [PATCH 59/73] refactor --- src/main.rs | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/src/main.rs b/src/main.rs index 8fac238e..59fd8607 100644 --- a/src/main.rs +++ b/src/main.rs @@ -698,14 +698,12 @@ fn update(app: &mut App, state: &mut OculanteState) { if let Ok(msg) = state.message_channel.1.try_recv() { debug!("Received message: {:?}", msg); state.toast_cooldown = Instant::now(); - match msg { - Message::LoadError(_) => { - state.current_image = None; - state.is_loaded = true; - state.current_texture = None; - set_title(app, state); - }, - _ => (), + + if let Message::LoadError(_) = msg { + state.current_image = None; + state.is_loaded = true; + state.current_texture = None; + set_title(app, state); } state.message = Some(msg); From 3b1fbb96f8fd376057bcfa5b4caf3181b8594301 Mon Sep 17 00:00:00 2001 From: Vitaliy Grabovets Date: Thu, 10 Aug 2023 18:00:18 +0300 Subject: [PATCH 60/73] Update asset naming and refactor code in main.rs --- .github/workflows/release.yml | 2 +- src/main.rs | 13 ++++--------- 2 files changed, 5 insertions(+), 10 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index b9c926bf..5b19e757 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -118,7 +118,7 @@ jobs: with: upload_url: ${{ needs.release_job.outputs.upload_url }} asset_path: target/release/oculante - asset_name: oculante_linux + asset_name: oculante_linux.AppImage asset_content_type: application/zip - name: Upload ARMv7 Release diff --git a/src/main.rs b/src/main.rs index 59fd8607..c5e5f1aa 100644 --- a/src/main.rs +++ b/src/main.rs @@ -15,7 +15,7 @@ use std::collections::HashSet; use std::ffi::OsStr; use std::fs::{self, File}; use std::io::{self, BufRead, Write}; -use std::path::{Path, PathBuf}; +use std::path::PathBuf; use std::sync::mpsc; use std::time::{Duration, Instant}; pub mod cache; @@ -746,11 +746,7 @@ fn drawe(app: &mut App, gfx: &mut Graphics, plugins: &mut Plugins, state: &mut O set_title(app, state); if let Some(current_path) = &state.current_path { - if state.scrubber.favourites.contains(current_path) { - state.current_image_is_favourite = true; - } else { - state.current_image_is_favourite = false; - } + state.current_image_is_favourite = state.scrubber.favourites.contains(current_path); match fs::metadata(current_path) { Ok(metadata) => { @@ -1175,9 +1171,8 @@ fn browse_for_image_path(state: &mut OculanteState, app: &mut App) { let reader = io::BufReader::new(file); let img_paths: Vec = reader .lines() - .into_iter() - .filter_map(|line| {line.ok()}) - .map(|line| {Path::new(&line).to_path_buf()}) + .filter_map(Result::ok) + .map(PathBuf::from) .collect(); state.folder_selected = Some(file_path.parent().unwrap().to_path_buf()); From 3439dc9cfcbf5d36ad3c459ec5f8bd8f507d8a7d Mon Sep 17 00:00:00 2001 From: Vitaliy Grabovets Date: Sat, 12 Aug 2023 13:06:20 +0300 Subject: [PATCH 61/73] Refactor code for simplicity and readability --- src/main.rs | 23 +++++++++++------------ src/scrubber.rs | 44 +++++++++++++++----------------------------- 2 files changed, 26 insertions(+), 41 deletions(-) diff --git a/src/main.rs b/src/main.rs index c5e5f1aa..12ae4bce 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1205,20 +1205,19 @@ fn browse_for_folder_path(state: &mut OculanteState, app: &mut App) { if let Some(folder_path) = folder_dialog_result { state.persistent_settings.last_open_directory = folder_path.clone(); _ = state.persistent_settings.save(); - state.folder_selected = Option::from(folder_path.clone()); + state.folder_selected = Some(folder_path.clone()); let db_file = get_db_file(&folder_path); - let favourites: Option>; - if db_file.exists() { - state.db = Option::from(DB::new(&folder_path)); - favourites = Option::from(state.db.as_ref().unwrap().get_all()); + let favourites: Option> = if db_file.exists() { + state.db = Some(DB::new(&folder_path)); + Some(state.db.as_ref().unwrap().get_all()) } else { - favourites = None; - } + None + }; state.scrubber = Scrubber::new( - &folder_path.as_path(), + folder_path.as_path(), true, true, favourites, @@ -1228,11 +1227,11 @@ fn browse_for_folder_path(state: &mut OculanteState, app: &mut App) { let number_of_favs = state.scrubber.favourites.len(); if number_of_files > 0 { state.send_message( - format!( + &format!( "files: {}, favourites: {}", number_of_files, number_of_favs, - ).as_str(), + ), ); let current_path = state.scrubber.get(0).unwrap(); @@ -1244,7 +1243,7 @@ fn browse_for_folder_path(state: &mut OculanteState, app: &mut App) { state.current_path = Some(current_path); } else { - state.send_message_err(format!("No supported image files in {:?}", folder_path).as_str()); + state.send_message_err(&format!("No supported image files in {:?}", folder_path)); } } } @@ -1285,7 +1284,7 @@ fn set_zoom(scale: f32, from_center: Option>, state: &mut OculanteS fn add_to_favourites(state: &mut OculanteState) { if let Some(img_path) = &state.current_path { if state.db.is_none() { - state.db = Option::from(DB::new(state.folder_selected.as_ref().unwrap())); + state.db = Some(DB::new(state.folder_selected.as_ref().unwrap())); } if !state.scrubber.favourites.contains(img_path) { diff --git a/src/scrubber.rs b/src/scrubber.rs index 9613459a..fdf5d337 100644 --- a/src/scrubber.rs +++ b/src/scrubber.rs @@ -30,14 +30,7 @@ impl Scrubber { ) .unwrap_or_default(); let index = entries.iter().position(|p| p == path).unwrap_or_default(); - - let favourites_out: HashSet; - - if favourites.is_some() { - favourites_out = favourites.unwrap(); - } else { - favourites_out = Default::default(); - } + let favourites_out: HashSet = favourites.unwrap_or_default(); Self { index, @@ -84,12 +77,7 @@ impl Scrubber { if self.entries.is_empty() { return Err("Scrubber has no entries".to_string()); } - - if index < self.entries.len() { - self.index = index; - } else { - self.index = self.entries.len() - 1; - } + self.index = std::cmp::min(index, self.entries.len() - 1); Ok(self.entries[self.index].clone()) } @@ -115,13 +103,13 @@ impl Scrubber { // TODO: Should probably return an Result instead, but am too lazy to figure out + handle a dedicated error type here // TODO: Cache this result, instead of doing it each time we need to fetch another file from the folder pub fn get_image_filenames_for_directory( - folder_path: &Path, + path: &Path, randomize: bool, walk_files: bool, favourites: &Option>, intersperse_with_favs_every_n: usize, ) -> Result> { - let mut folder_path = folder_path.to_path_buf(); + let mut folder_path = path.to_path_buf(); if folder_path.is_file() { folder_path = folder_path .parent() @@ -129,23 +117,21 @@ pub fn get_image_filenames_for_directory( .context("Can't get parent")?; } - let mut dir_files: Vec; - - if walk_files { - dir_files = WalkDir::new(folder_path) + let mut dir_files: Vec = if walk_files { + WalkDir::new(folder_path) .into_iter() - .filter_map(|v| v.ok()) - .map(|entry| entry.into_path()) + .filter_map(Result::ok) + .map(|entry| entry.path().to_path_buf()) .filter(|x| is_ext_compatible(x)) - .collect::>(); + .collect() } else { let info = std::fs::read_dir(folder_path)?; - dir_files = info - .flat_map(|x| x) - .map(|x| x.path()) + info + .filter_map(Result::ok) + .map(|entry| entry.path()) .filter(|x| is_ext_compatible(x)) - .collect::>(); - } + .collect() + }; // TODO: Are symlinks handled correctly? @@ -169,7 +155,7 @@ pub fn get_image_filenames_for_directory( } dir_files = insert_after_every(dir_files, favourites_vec, intersperse_with_favs_every_n); - return Ok(dir_files); + Ok(dir_files) } /// Find first valid image from the directory From fd5a7ae817c3785407074a6aa383aed83c6d5e3d Mon Sep 17 00:00:00 2001 From: Vitaliy Grabovets Date: Sat, 12 Aug 2023 22:48:29 +0300 Subject: [PATCH 62/73] Optimize scrubber code for unnecessary cloning --- src/scrubber.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/scrubber.rs b/src/scrubber.rs index fdf5d337..af26baeb 100644 --- a/src/scrubber.rs +++ b/src/scrubber.rs @@ -180,7 +180,7 @@ pub fn find_first_image_in_directory(folder_path: &PathBuf) -> Result { fn insert_after_every(main_vector: Vec, other_vector: Vec, after: usize) -> Vec { let mut result = Vec::with_capacity(main_vector.len()); - let other_vector_set: HashSet = other_vector.clone().into_iter().collect(); + let other_vector_set: HashSet = other_vector.iter().cloned().collect(); if after == 0 { result.extend_from_slice(&other_vector); From ef554f09d2e5e287138a8a283970da741bf76446 Mon Sep 17 00:00:00 2001 From: Vitaliy Grabovets Date: Sat, 12 Aug 2023 22:49:51 +0300 Subject: [PATCH 63/73] 0.6.69-dev4 --- Cargo.lock | 2 +- Cargo.toml | 2 +- PKGBUILD | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 468c944d..9003750b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3314,7 +3314,7 @@ dependencies = [ [[package]] name = "oculante" -version = "0.6.69-dev3" +version = "0.6.69-dev4" dependencies = [ "anyhow", "arboard", diff --git a/Cargo.toml b/Cargo.toml index f7cacd37..31c56b00 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -11,7 +11,7 @@ license = "MIT" name = "oculante" readme = "README.md" repository = "https://github.com/woelper/oculante/" -version = "0.6.69-dev3" +version = "0.6.69-dev4" [package.metadata.bundle] icon = ["res/oculante.png"] diff --git a/PKGBUILD b/PKGBUILD index d7e734a9..a254050a 100644 --- a/PKGBUILD +++ b/PKGBUILD @@ -1,6 +1,6 @@ # Maintainer: Johann Woelper pkgname=oculante -pkgver=0.6.69-dev3 +pkgver=0.6.69-dev4 pkgrel=1 depends=('aom' 'libwebp' 'expat' 'freetype2' 'gtk3' 'cairo') makedepends=('rust' 'cargo' 'tar' 'nasm' 'cmake') From d43fd66ba776498209431e25fdc0cf1fddd971d3 Mon Sep 17 00:00:00 2001 From: Vitaliy Grabovets Date: Sat, 12 Aug 2023 23:26:36 +0300 Subject: [PATCH 64/73] Update Rust actions and refactor code for error handling and readability --- .github/workflows/rust-clippy.yml | 2 +- src/db.rs | 14 ++++++-------- src/main.rs | 8 ++++---- src/utils.rs | 2 +- 4 files changed, 12 insertions(+), 14 deletions(-) diff --git a/.github/workflows/rust-clippy.yml b/.github/workflows/rust-clippy.yml index ca87ebd1..38e1937f 100644 --- a/.github/workflows/rust-clippy.yml +++ b/.github/workflows/rust-clippy.yml @@ -55,7 +55,7 @@ jobs: continue-on-error: true - name: Upload analysis results to GitHub - uses: github/codeql-action/upload-sarif@v1 + uses: github/codeql-action/upload-sarif@v2 with: sarif_file: rust-clippy-results.sarif wait-for-processing: true diff --git a/src/db.rs b/src/db.rs index 4717fa2d..9fdaa49b 100644 --- a/src/db.rs +++ b/src/db.rs @@ -15,7 +15,7 @@ pub struct DB { impl DB { pub fn new(folder: &PathBuf) -> Self { debug!("init new DB connection"); - let db_file_path = get_db_file(&folder); + let db_file_path = get_db_file(folder); let connection = Connection::open(db_file_path).expect("cannot open DB connection"); connection.execute( "create table if not exists favourites (path text primary key)", @@ -23,7 +23,7 @@ impl DB { ).expect("cannot create table"); let folder_out = folder.clone(); - Self {connection: Some(connection), folder: folder_out} + Self { connection: Some(connection), folder: folder_out } } pub fn insert(&self, img_path: &PathBuf) { @@ -53,7 +53,7 @@ impl DB { .expect("cannot prepare query"); stmt - .query_map((), |row| { Ok(row.get(0)?) }) + .query_map((), |row| { row.get(0) }) .expect("cannot get data") .map(|e| self.folder.join(self.join_path_parts(e.unwrap()))) .filter(|file| file.exists()) @@ -65,7 +65,7 @@ impl DB { self.connection.take().unwrap().close().expect("cannot close DB connection") } - fn prepare_record(&self, img_path: &PathBuf) -> String { + fn prepare_record(&self, img_path: &Path) -> String { img_path.strip_prefix(&self.folder) .unwrap() .components() @@ -74,12 +74,10 @@ impl DB { } fn join_path_parts(&self, path_with_tabs: String) -> PathBuf { - path_with_tabs.split("\t") - .into_iter() - .collect() + path_with_tabs.split('\t').collect() } } -pub fn get_db_file(folder: &PathBuf) -> PathBuf { +pub fn get_db_file(folder: &Path) -> PathBuf { folder.join(Path::new(FAVOURITES_DB)) } diff --git a/src/main.rs b/src/main.rs index 12ae4bce..2c49762f 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1171,7 +1171,7 @@ fn browse_for_image_path(state: &mut OculanteState, app: &mut App) { let reader = io::BufReader::new(file); let img_paths: Vec = reader .lines() - .filter_map(Result::ok) + .map_while(Result::ok) .map(PathBuf::from) .collect(); @@ -1204,7 +1204,7 @@ fn browse_for_folder_path(state: &mut OculanteState, app: &mut App) { if let Some(folder_path) = folder_dialog_result { state.persistent_settings.last_open_directory = folder_path.clone(); - _ = state.persistent_settings.save(); + state.persistent_settings.save(); state.folder_selected = Some(folder_path.clone()); let db_file = get_db_file(&folder_path); @@ -1288,12 +1288,12 @@ fn add_to_favourites(state: &mut OculanteState) { } if !state.scrubber.favourites.contains(img_path) { - state.db.as_ref().unwrap().insert(&img_path); + state.db.as_ref().unwrap().insert(img_path); state.scrubber.favourites.insert(img_path.clone()); state.current_image_is_favourite = true; } else { - state.db.as_ref().unwrap().delete(&img_path); + state.db.as_ref().unwrap().delete(img_path); state.scrubber.favourites.remove(img_path); state.current_image_is_favourite = false; } diff --git a/src/utils.rs b/src/utils.rs index c8f7d7c4..e795ce1e 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -730,7 +730,7 @@ pub fn format_bytes(bytes: f64) -> String { const MEGABYTE: f64 = KILOBYTE * 1000.; const GIGABYTE: f64 = MEGABYTE * 1000.; - return if bytes < KILOBYTE { + if bytes < KILOBYTE { format!("{} bytes", bytes) } else if bytes < MEGABYTE { format!("{:.1} KB", bytes / KILOBYTE) From 4f6162192930813817ae1f347b2493baa8f61d94 Mon Sep 17 00:00:00 2001 From: Vitaliy Grabovets Date: Sat, 12 Aug 2023 23:28:39 +0300 Subject: [PATCH 65/73] Simplify asset name in GitHub workflow --- .github/workflows/release.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 5b19e757..0f4caa62 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -118,7 +118,7 @@ jobs: with: upload_url: ${{ needs.release_job.outputs.upload_url }} asset_path: target/release/oculante - asset_name: oculante_linux.AppImage + asset_name: oculante.AppImage asset_content_type: application/zip - name: Upload ARMv7 Release From 93d3fccd5e584353b09eca8ab2cda9b805a6aaa7 Mon Sep 17 00:00:00 2001 From: Vitaliy Grabovets Date: Tue, 22 Aug 2023 12:35:39 +0300 Subject: [PATCH 66/73] v0.7.2-dev1 --- Cargo.lock | 2 +- Cargo.toml | 2 +- PKGBUILD | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index b0f00445..2754eddb 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3341,7 +3341,7 @@ dependencies = [ [[package]] name = "oculante" -version = "0.7.2" +version = "0.7.2-dev1" dependencies = [ "anyhow", "arboard", diff --git a/Cargo.toml b/Cargo.toml index d5c410ea..9fd60803 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -11,7 +11,7 @@ license = "MIT" name = "oculante" readme = "README.md" repository = "https://github.com/woelper/oculante/" -version = "0.7.2" +version = "0.7.2-dev1" [package.metadata.bundle] icon = ["res/oculante.png"] diff --git a/PKGBUILD b/PKGBUILD index 9812344a..d89795e5 100644 --- a/PKGBUILD +++ b/PKGBUILD @@ -1,6 +1,6 @@ # Maintainer: Johann Woelper pkgname=oculante -pkgver=0.7.2 +pkgver=0.7.2-dev1 pkgrel=1 depends=('aom' 'libwebp' 'expat' 'freetype2' 'gtk3' 'cairo') makedepends=('rust' 'cargo' 'tar' 'nasm' 'cmake') From 34801e672020c2a8007963368bd8496d4b77ba0c Mon Sep 17 00:00:00 2001 From: Vitaliy Grabovets Date: Fri, 25 Aug 2023 21:44:04 +0300 Subject: [PATCH 67/73] v0.7.3-dev1 --- Cargo.lock | 210 +++++++++++++++++++++++++++++++++++++++++++++++++++-- Cargo.toml | 2 +- PKGBUILD | 2 +- 3 files changed, 206 insertions(+), 8 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 4b61fe32..4f9496ce 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -53,6 +53,27 @@ dependencies = [ "memchr", ] +[[package]] +name = "allocator-api2" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0942ffc6dcaadf03badf6e6a2d0228460359d5e34b57ccdc720b7382dfbd5ec5" + +[[package]] +name = "android-tzdata" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e999941b234f3131b00bc13c22d06e8c5ff726d1b6318ac7eb276997bbb4fef0" + +[[package]] +name = "android_system_properties" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311" +dependencies = [ + "libc", +] + [[package]] name = "any_ascii" version = "0.1.7" @@ -99,7 +120,7 @@ version = "3.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d6041616acea41d67c4a984709ddab1587fd0b10efe5cc563fee954d2f011854" dependencies = [ - "clipboard-win", + "clipboard-win 4.5.0", "core-graphics", "image", "log", @@ -217,7 +238,7 @@ checksum = "6f6ca6f0c18c02c2fbfc119df551b8aeb8a385f6d5980f1475ba0255f1e97f1e" dependencies = [ "anyhow", "arrayvec 0.7.4", - "itertools", + "itertools 0.10.5", "log", "nom", "num-rational", @@ -497,6 +518,18 @@ dependencies = [ "libc", ] +[[package]] +name = "chrono" +version = "0.4.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec837a71355b28f6556dbd569b37b3f363091c0bd4b2e735674521b4c5fd9bc5" +dependencies = [ + "android-tzdata", + "iana-time-zone", + "num-traits 0.2.16", + "winapi", +] + [[package]] name = "clap" version = "3.2.25" @@ -521,6 +554,28 @@ dependencies = [ "os_str_bytes", ] +[[package]] +name = "clipboard" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "25a904646c0340239dcf7c51677b33928bf24fdf424b79a57909c0109075b2e7" +dependencies = [ + "clipboard-win 2.2.0", + "objc", + "objc-foundation", + "objc_id", + "x11-clipboard", +] + +[[package]] +name = "clipboard-win" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3a093d6fed558e5fe24c3dfc85a68bb68f1c824f440d3ba5aca189e2998786b" +dependencies = [ + "winapi", +] + [[package]] name = "clipboard-win" version = "4.5.0" @@ -1109,6 +1164,18 @@ version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e88a8acf291dafb59c2d96e8f59828f3838bb1a70398823ade51a84de6a6deed" +[[package]] +name = "fallible-iterator" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4443176a9f2c162692bd3d352d745ef9413eec5782a80d8fd6f8a1ac692a07f7" + +[[package]] +name = "fallible-streaming-iterator" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7360491ce676a36bf9bb3c56c1aa791658183a54d2744120f27285738d90465a" + [[package]] name = "fallible_collections" version = "0.4.9" @@ -1797,6 +1864,19 @@ name = "hashbrown" version = "0.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2c6201b9ff9fd90a5a3bac2e56a830d0caa509576f0e503818ee82c181b3437a" +dependencies = [ + "ahash", + "allocator-api2", +] + +[[package]] +name = "hashlink" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "312f66718a2d7789ffef4f4b7b213138ed9f1eb3aa1d0d82fc99f88fb3ffd26f" +dependencies = [ + "hashbrown 0.14.0", +] [[package]] name = "heck" @@ -1906,6 +1986,29 @@ dependencies = [ "tokio-rustls", ] +[[package]] +name = "iana-time-zone" +version = "0.1.57" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2fad5b825842d2b38bd206f3e81d6957625fd7f0a361e345c30e01a0ae2dd613" +dependencies = [ + "android_system_properties", + "core-foundation-sys", + "iana-time-zone-haiku", + "js-sys", + "wasm-bindgen", + "windows 0.48.0", +] + +[[package]] +name = "iana-time-zone-haiku" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f" +dependencies = [ + "cc", +] + [[package]] name = "ident_case" version = "1.0.1" @@ -2057,6 +2160,15 @@ dependencies = [ "either", ] +[[package]] +name = "itertools" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1c173a5686ce8bfa551b3563d0c2170bf24ca44da99c7ca4bfdab5418c3fe57" +dependencies = [ + "either", +] + [[package]] name = "itoa" version = "1.0.9" @@ -2364,6 +2476,16 @@ version = "0.2.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f7012b1bbb0719e1097c47611d3898568c546d597c2e74d66f6087edd5233ff4" +[[package]] +name = "libsqlite3-sys" +version = "0.26.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "afc22eff61b133b115c6e8c74e818c628d6d5e7a502afea6f64dee076dd94326" +dependencies = [ + "pkg-config", + "vcpkg", +] + [[package]] name = "libwebp-sys" version = "0.9.2" @@ -3219,12 +3341,13 @@ dependencies = [ [[package]] name = "oculante" -version = "0.7.3" +version = "0.7.3-dev1" dependencies = [ "anyhow", "arboard", "avif-decode", "clap", + "clipboard", "cmd_lib", "dds-rs", "dirs 5.0.1", @@ -3237,6 +3360,7 @@ dependencies = [ "gif-dispose", "image", "img-parts", + "itertools 0.11.0", "jxl-oxide", "kamadak-exif", "lexical-sort", @@ -3254,6 +3378,8 @@ dependencies = [ "resvg", "rfd", "rgb", + "round", + "rusqlite", "self_update", "serde", "serde_json", @@ -3261,8 +3387,10 @@ dependencies = [ "strum_macros", "tiff", "tiny-skia 0.9.1", + "trash", "turbojpeg", "usvg", + "walkdir", "webbrowser", "windres", "winres", @@ -3741,7 +3869,7 @@ dependencies = [ "cc", "cfg-if 1.0.0", "interpolate_name", - "itertools", + "itertools 0.10.5", "libc", "libfuzzer-sys", "log", @@ -3950,7 +4078,7 @@ dependencies = [ "wasm-bindgen", "wasm-bindgen-futures", "web-sys", - "windows", + "windows 0.44.0", ] [[package]] @@ -3990,6 +4118,12 @@ dependencies = [ "svgtypes", ] +[[package]] +name = "round" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b02705db4fa872ae37e464fc803b7ffa574e6c4a5e112c52a82550f9dd63b657" + [[package]] name = "roxmltree" version = "0.18.0" @@ -3999,6 +4133,20 @@ dependencies = [ "xmlparser", ] +[[package]] +name = "rusqlite" +version = "0.29.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "549b9d036d571d42e6e85d1c1425e2ac83491075078ca9a15be021c56b1641f2" +dependencies = [ + "bitflags 2.4.0", + "fallible-iterator", + "fallible-streaming-iterator", + "hashlink", + "libsqlite3-sys", + "smallvec", +] + [[package]] name = "rust_hawktracer" version = "0.7.0" @@ -4800,6 +4948,22 @@ dependencies = [ "once_cell", ] +[[package]] +name = "trash" +version = "3.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af3663fb8f476d674b9c61d1d2796acec725bef6bec4b41402a904252a25971e" +dependencies = [ + "chrono", + "libc", + "log", + "objc", + "once_cell", + "scopeguard", + "url", + "windows 0.44.0", +] + [[package]] name = "try-lock" version = "0.2.4" @@ -4848,7 +5012,7 @@ version = "1.6.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "97fee6b57c6a41524a810daee9286c02d7752c4253064d0b05472833a438f675" dependencies = [ - "cfg-if 0.1.10", + "cfg-if 1.0.0", "rand", "static_assertions", ] @@ -5021,6 +5185,12 @@ dependencies = [ "rust_hawktracer", ] +[[package]] +name = "vcpkg" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" + [[package]] name = "vec_map" version = "0.8.2" @@ -5325,6 +5495,15 @@ dependencies = [ "windows-targets 0.42.2", ] +[[package]] +name = "windows" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e686886bc078bc1b0b600cac0147aadb815089b6e4da64016cbd754b6342700f" +dependencies = [ + "windows-targets 0.48.5", +] + [[package]] name = "windows-sys" version = "0.36.1" @@ -5590,6 +5769,15 @@ dependencies = [ "winapi", ] +[[package]] +name = "x11-clipboard" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "89bd49c06c9eb5d98e6ba6536cf64ac9f7ee3a009b2f53996d405b3944f6bcea" +dependencies = [ + "xcb", +] + [[package]] name = "x11-dl" version = "2.21.0" @@ -5623,6 +5811,16 @@ dependencies = [ "nix 0.24.3", ] +[[package]] +name = "xcb" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e917a3f24142e9ff8be2414e36c649d47d6cc2ba81f16201cdef96e533e02de" +dependencies = [ + "libc", + "log", +] + [[package]] name = "xcursor" version = "0.3.4" diff --git a/Cargo.toml b/Cargo.toml index ff20d1dc..b330b1b4 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -11,7 +11,7 @@ license = "MIT" name = "oculante" readme = "README.md" repository = "https://github.com/woelper/oculante/" -version = "0.7.3" +version = "0.7.3-dev1" [package.metadata.bundle] icon = ["res/oculante.png"] diff --git a/PKGBUILD b/PKGBUILD index 8d909398..b0fb6dc0 100644 --- a/PKGBUILD +++ b/PKGBUILD @@ -1,6 +1,6 @@ # Maintainer: Johann Woelper pkgname=oculante -pkgver=0.7.3 +pkgver=0.7.3-dev1 pkgrel=1 depends=('aom' 'libwebp' 'expat' 'freetype2' 'gtk3' 'cairo') makedepends=('rust' 'cargo' 'tar' 'nasm' 'cmake') From b0776fe56df9e01e9f19163c9ef7e446eb8ed3db Mon Sep 17 00:00:00 2001 From: Vitaliy Grabovets Date: Sun, 17 Sep 2023 17:07:55 +0300 Subject: [PATCH 68/73] v0.7.5-dev1 --- Cargo.lock | 2 +- Cargo.toml | 2 +- PKGBUILD | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index fdd5ae64..0028d0d6 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3454,7 +3454,7 @@ dependencies = [ [[package]] name = "oculante" -version = "0.7.5" +version = "0.7.5-dev1" dependencies = [ "anyhow", "arboard", diff --git a/Cargo.toml b/Cargo.toml index 41b6c618..762b51a7 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -11,7 +11,7 @@ license = "MIT" name = "oculante" readme = "README.md" repository = "https://github.com/woelper/oculante/" -version = "0.7.5" +version = "0.7.5-dev1" [package.metadata.bundle] icon = ["res/oculante.png"] diff --git a/PKGBUILD b/PKGBUILD index 0743ee8c..38a84728 100644 --- a/PKGBUILD +++ b/PKGBUILD @@ -1,6 +1,6 @@ # Maintainer: Johann Woelper pkgname=oculante -pkgver=0.7.5 +pkgver=0.7.5-dev1 pkgrel=1 depends=('aom' 'libwebp' 'expat' 'freetype2' 'gtk3' 'cairo') makedepends=('rust' 'cargo' 'tar' 'nasm' 'cmake') From e25afa3a225c77d0e10a8d92e4124dd43b1d1a8d Mon Sep 17 00:00:00 2001 From: Vitaliy Grabovets Date: Mon, 18 Sep 2023 12:49:10 +0300 Subject: [PATCH 69/73] set lazy loop to false --- Cargo.lock | 2 +- Cargo.toml | 2 +- PKGBUILD | 2 +- src/main.rs | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 0028d0d6..d8d63478 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3454,7 +3454,7 @@ dependencies = [ [[package]] name = "oculante" -version = "0.7.5-dev1" +version = "0.7.5-dev2" dependencies = [ "anyhow", "arboard", diff --git a/Cargo.toml b/Cargo.toml index 762b51a7..d2856750 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -11,7 +11,7 @@ license = "MIT" name = "oculante" readme = "README.md" repository = "https://github.com/woelper/oculante/" -version = "0.7.5-dev1" +version = "0.7.5-dev2" [package.metadata.bundle] icon = ["res/oculante.png"] diff --git a/PKGBUILD b/PKGBUILD index 38a84728..cf142a76 100644 --- a/PKGBUILD +++ b/PKGBUILD @@ -1,6 +1,6 @@ # Maintainer: Johann Woelper pkgname=oculante -pkgver=0.7.5-dev1 +pkgver=0.7.5-dev2 pkgrel=1 depends=('aom' 'libwebp' 'expat' 'freetype2' 'gtk3' 'cairo') makedepends=('rust' 'cargo' 'tar' 'nasm' 'cmake') diff --git a/src/main.rs b/src/main.rs index ba2e181e..ca4a5d22 100644 --- a/src/main.rs +++ b/src/main.rs @@ -102,7 +102,7 @@ fn main() -> Result<(), String> { #[cfg(target_os = "windows")] { - window_config = window_config.set_lazy_loop(true).set_vsync(true).set_high_dpi(true); + window_config = window_config.set_lazy_loop(false).set_vsync(true).set_high_dpi(true); } #[cfg(target_os = "linux")] From f15509d21a9d4f5118f7e0972bb75f352e4edc80 Mon Sep 17 00:00:00 2001 From: Vitaliy Grabovets Date: Fri, 22 Sep 2023 00:21:08 +0300 Subject: [PATCH 70/73] delete_current_image function --- src/main.rs | 17 ++++++++++++----- src/ui.rs | 7 +++---- 2 files changed, 15 insertions(+), 9 deletions(-) diff --git a/src/main.rs b/src/main.rs index bb33ff37..a0fe9546 100644 --- a/src/main.rs +++ b/src/main.rs @@ -411,11 +411,8 @@ fn event(app: &mut App, state: &mut OculanteState, evt: Event) { state.toggle_slideshow = !state.toggle_slideshow; } if key_pressed(app, state, DeleteFile) { - if let Some(img_path) = &state.current_path { - trash::delete(img_path).expect("Cannot delete file"); - state.send_message(format!("file {:?} removed", img_path).as_str()); - state.scrubber.delete(img_path); - state.reload_image(); + if state.current_path.is_some() { + delete_current_image(state); } } if key_pressed(app, state, Quit) { @@ -1350,3 +1347,13 @@ fn add_to_favourites(state: &mut OculanteState) { } } } + +fn delete_current_image(state: &mut OculanteState) { + if state.current_path.is_some() { + let img_path = state.current_path.as_ref().unwrap(); + trash::delete(&img_path).expect("Cannot delete file"); + state.send_message(format!("file {:?} removed", img_path).as_str()); + state.scrubber.delete(&img_path); + state.reload_image(); + } +} diff --git a/src/ui.rs b/src/ui.rs index 4043177f..0178ecd3 100644 --- a/src/ui.rs +++ b/src/ui.rs @@ -1,5 +1,5 @@ #[cfg(feature = "file_open")] -use crate::{browse_for_folder_path, browse_for_image_path}; +use crate::{browse_for_folder_path, browse_for_image_path, delete_current_image}; use crate::{ appstate::{ImageGeometry, Message, OculanteState}, image_editing::{process_pixels, Channel, GradientStop, ImageOperation, ScaleFilter}, @@ -1888,7 +1888,7 @@ pub fn main_menu(ui: &mut Ui, state: &mut OculanteState, app: &mut App, gfx: &mu } #[cfg(not(target_os = "netbsd"))] - if let Some(p) = &state.current_path { + if state.current_path.is_some() { if tooltip( unframed_button_colored("🗑", state.always_on_top, ui), "Move file to trash", @@ -1897,8 +1897,7 @@ pub fn main_menu(ui: &mut Ui, state: &mut OculanteState, app: &mut App, gfx: &mu ) .clicked() { - _ = trash::delete(p); - state.send_message("Deleted image"); + delete_current_image(state); } } From af3ca962975454fdb4187adf50f5956dde7399c3 Mon Sep 17 00:00:00 2001 From: Vitaliy Grabovets Date: Fri, 22 Sep 2023 00:26:19 +0300 Subject: [PATCH 71/73] v0.7.6-dev1 --- Cargo.lock | 2 +- Cargo.toml | 2 +- PKGBUILD | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index c4cf2c57..c873db7c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3444,7 +3444,7 @@ dependencies = [ [[package]] name = "oculante" -version = "0.7.6" +version = "0.7.6-dev1" dependencies = [ "anyhow", "arboard", diff --git a/Cargo.toml b/Cargo.toml index 228b4a69..5678cff8 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -11,7 +11,7 @@ license = "MIT" name = "oculante" readme = "README.md" repository = "https://github.com/woelper/oculante/" -version = "0.7.6" +version = "0.7.6-dev1" [package.metadata.bundle] icon = ["res/oculante.png"] diff --git a/PKGBUILD b/PKGBUILD index 752abb94..fa220774 100644 --- a/PKGBUILD +++ b/PKGBUILD @@ -1,6 +1,6 @@ # Maintainer: Johann Woelper pkgname=oculante -pkgver=0.7.6 +pkgver=0.7.6-dev1 pkgrel=1 depends=('aom' 'libwebp' 'expat' 'freetype2' 'gtk3' 'cairo') makedepends=('rust' 'cargo' 'tar' 'nasm' 'cmake') From f10b23867e127208c3505e3024c429a94209ddd0 Mon Sep 17 00:00:00 2001 From: Vitaliy Grabovets Date: Fri, 22 Sep 2023 16:27:57 +0300 Subject: [PATCH 72/73] fix import; v0.7.6-dev2 --- Cargo.lock | 2 +- Cargo.toml | 2 +- PKGBUILD | 2 +- src/ui.rs | 4 ++-- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index c873db7c..d14cc6c7 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3444,7 +3444,7 @@ dependencies = [ [[package]] name = "oculante" -version = "0.7.6-dev1" +version = "0.7.6-dev2" dependencies = [ "anyhow", "arboard", diff --git a/Cargo.toml b/Cargo.toml index 5678cff8..d16a500a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -11,7 +11,7 @@ license = "MIT" name = "oculante" readme = "README.md" repository = "https://github.com/woelper/oculante/" -version = "0.7.6-dev1" +version = "0.7.6-dev2" [package.metadata.bundle] icon = ["res/oculante.png"] diff --git a/PKGBUILD b/PKGBUILD index fa220774..5068b96c 100644 --- a/PKGBUILD +++ b/PKGBUILD @@ -1,6 +1,6 @@ # Maintainer: Johann Woelper pkgname=oculante -pkgver=0.7.6-dev1 +pkgver=0.7.6-dev2 pkgrel=1 depends=('aom' 'libwebp' 'expat' 'freetype2' 'gtk3' 'cairo') makedepends=('rust' 'cargo' 'tar' 'nasm' 'cmake') diff --git a/src/ui.rs b/src/ui.rs index 0178ecd3..5407bfa9 100644 --- a/src/ui.rs +++ b/src/ui.rs @@ -1,10 +1,10 @@ #[cfg(feature = "file_open")] -use crate::{browse_for_folder_path, browse_for_image_path, delete_current_image}; +use crate::{browse_for_folder_path, browse_for_image_path}; use crate::{ appstate::{ImageGeometry, Message, OculanteState}, image_editing::{process_pixels, Channel, GradientStop, ImageOperation, ScaleFilter}, paint::PaintStroke, - set_zoom, + delete_current_image, set_zoom, settings::{set_system_theme, ColorTheme}, shortcuts::{key_pressed, keypresses_as_string, lookup}, utils::{ From 987d2a9fd0de4ab0960fa9f0c14c0e5c56a46258 Mon Sep 17 00:00:00 2001 From: Vitaliy Grabovets Date: Fri, 22 Sep 2023 16:32:46 +0300 Subject: [PATCH 73/73] refactor --- src/ui.rs | 18 ++++++++---------- 1 file changed, 8 insertions(+), 10 deletions(-) diff --git a/src/ui.rs b/src/ui.rs index 5407bfa9..c82dba77 100644 --- a/src/ui.rs +++ b/src/ui.rs @@ -1888,17 +1888,15 @@ pub fn main_menu(ui: &mut Ui, state: &mut OculanteState, app: &mut App, gfx: &mu } #[cfg(not(target_os = "netbsd"))] - if state.current_path.is_some() { - if tooltip( - unframed_button_colored("🗑", state.always_on_top, ui), - "Move file to trash", - &lookup(&state.persistent_settings.shortcuts, &DeleteFile), - ui, - ) + if state.current_path.is_some() && tooltip( + unframed_button_colored("🗑", state.always_on_top, ui), + "Move file to trash", + &lookup(&state.persistent_settings.shortcuts, &DeleteFile), + ui, + ) .clicked() - { - delete_current_image(state); - } + { + delete_current_image(state); } ui.add_space(ui.available_width() - 32.);