Add Escape key shortcut and subfolder prompt for folder drops

This commit is contained in:
2026-03-06 13:45:06 +02:00
parent 5428214d6c
commit ea596e01fe
2 changed files with 139 additions and 7 deletions

View File

@@ -16,6 +16,10 @@ pub fn build_images_page(state: &AppState) -> adw::NavigationPage {
stack.set_visible_child_name("empty");
// Session-level remembered subfolder choice (None = not yet asked)
let subfolder_choice: std::rc::Rc<std::cell::RefCell<Option<bool>>> =
std::rc::Rc::new(std::cell::RefCell::new(None));
// Set up drag-and-drop on the entire page
let drop_target = gtk::DropTarget::new(gtk::gio::File::static_type(), gtk::gdk::DragAction::COPY);
drop_target.set_types(&[gtk::gio::File::static_type()]);
@@ -24,16 +28,69 @@ pub fn build_images_page(state: &AppState) -> adw::NavigationPage {
let loaded_files = state.loaded_files.clone();
let excluded = state.excluded_files.clone();
let stack_ref = stack.clone();
drop_target.connect_drop(move |_target, value, _x, _y| {
let subfolder_choice = subfolder_choice.clone();
drop_target.connect_drop(move |target, value, _x, _y| {
if let Ok(file) = value.get::<gtk::gio::File>()
&& let Some(path) = file.path()
{
if path.is_dir() {
let mut files = loaded_files.borrow_mut();
add_images_from_dir(&path, &mut files);
let count = files.len();
drop(files);
update_loaded_ui(&stack_ref, &loaded_files, &excluded, count);
let has_subdirs = has_subfolders(&path);
if !has_subdirs {
// No subfolders - just load top-level images
let mut files = loaded_files.borrow_mut();
add_images_flat(&path, &mut files);
let count = files.len();
drop(files);
update_loaded_ui(&stack_ref, &loaded_files, &excluded, count);
} else {
let choice = *subfolder_choice.borrow();
match choice {
Some(true) => {
// Remembered: include subfolders
let mut files = loaded_files.borrow_mut();
add_images_from_dir(&path, &mut files);
let count = files.len();
drop(files);
update_loaded_ui(&stack_ref, &loaded_files, &excluded, count);
}
Some(false) => {
// Remembered: top-level only
let mut files = loaded_files.borrow_mut();
add_images_flat(&path, &mut files);
let count = files.len();
drop(files);
update_loaded_ui(&stack_ref, &loaded_files, &excluded, count);
}
None => {
// Not yet asked - add top-level now, then prompt
let mut files = loaded_files.borrow_mut();
add_images_flat(&path, &mut files);
let count = files.len();
drop(files);
update_loaded_ui(&stack_ref, &loaded_files, &excluded, count);
// Show dialog asynchronously
let loaded_files = loaded_files.clone();
let excluded = excluded.clone();
let stack_ref = stack_ref.clone();
let subfolder_choice = subfolder_choice.clone();
let dir_path = path.clone();
let window = target.widget()
.and_then(|w| w.root())
.and_then(|r| r.downcast::<gtk::Window>().ok());
gtk::glib::idle_add_local_once(move || {
show_subfolder_prompt(
window.as_ref(),
&dir_path,
&loaded_files,
&excluded,
&stack_ref,
&subfolder_choice,
);
});
}
}
}
return true;
} else if is_image_file(&path) {
let mut files = loaded_files.borrow_mut();
@@ -79,6 +136,81 @@ fn add_images_from_dir(dir: &std::path::Path, files: &mut Vec<std::path::PathBuf
}
}
/// Add only top-level images from a directory (no recursion into subfolders)
fn add_images_flat(dir: &std::path::Path, files: &mut Vec<std::path::PathBuf>) {
if let Ok(entries) = std::fs::read_dir(dir) {
for entry in entries.flatten() {
let path = entry.path();
if path.is_file() && is_image_file(&path) && !files.contains(&path) {
files.push(path);
}
}
}
}
/// Check if a directory contains any subdirectories
fn has_subfolders(dir: &std::path::Path) -> bool {
if let Ok(entries) = std::fs::read_dir(dir) {
for entry in entries.flatten() {
if entry.path().is_dir() {
return true;
}
}
}
false
}
/// Add only the images from subfolders (not top-level, since those were already added)
fn add_images_from_subdirs(dir: &std::path::Path, files: &mut Vec<std::path::PathBuf>) {
if let Ok(entries) = std::fs::read_dir(dir) {
for entry in entries.flatten() {
let path = entry.path();
if path.is_dir() {
add_images_from_dir(&path, files);
}
}
}
}
fn show_subfolder_prompt(
window: Option<&gtk::Window>,
dir: &std::path::Path,
loaded_files: &std::rc::Rc<std::cell::RefCell<Vec<std::path::PathBuf>>>,
excluded: &std::rc::Rc<std::cell::RefCell<std::collections::HashSet<std::path::PathBuf>>>,
stack: &gtk::Stack,
subfolder_choice: &std::rc::Rc<std::cell::RefCell<Option<bool>>>,
) {
let dialog = adw::AlertDialog::builder()
.heading("Include subfolders?")
.body("This folder contains subfolders. Would you like to include images from subfolders too?")
.build();
dialog.add_response("no", "Top-level Only");
dialog.add_response("yes", "Include Subfolders");
dialog.set_default_response(Some("yes"));
dialog.set_response_appearance("yes", adw::ResponseAppearance::Suggested);
let loaded_files = loaded_files.clone();
let excluded = excluded.clone();
let stack = stack.clone();
let subfolder_choice = subfolder_choice.clone();
let dir = dir.to_path_buf();
dialog.connect_response(None, move |_dialog, response| {
let include_subdirs = response == "yes";
*subfolder_choice.borrow_mut() = Some(include_subdirs);
if include_subdirs {
let mut files = loaded_files.borrow_mut();
add_images_from_subdirs(&dir, &mut files);
let count = files.len();
drop(files);
update_loaded_ui(&stack, &loaded_files, &excluded, count);
}
});
if let Some(win) = window {
dialog.present(Some(win));
}
}
fn update_loaded_ui(
stack: &gtk::Stack,
loaded_files: &std::rc::Rc<std::cell::RefCell<Vec<std::path::PathBuf>>>,