From c2529503fd73d32288af938b07fdb518be3dcc09 Mon Sep 17 00:00:00 2001 From: Nathaniel Ledford Date: Thu, 29 Oct 2026 12:17:28 -0400 Subject: [PATCH] LR-BEHAV-DIALOGS: the Move confirm counts restored photos apart from moved ones --- crates/ui-egui/src/dialogs.rs | 18 ++++++++++++- crates/ui-egui/src/dialogs/import.rs | 7 +++++ crates/ui-egui/src/tests/dialog_copy.rs | 36 +++++++++++++++++++++++++ docs/whats-new.md | 4 ++- 4 files changed, 63 insertions(+), 2 deletions(-) diff --git a/crates/ui-egui/src/dialogs.rs b/crates/ui-egui/src/dialogs.rs index b565166d9..4d6f19a7a 100644 --- a/crates/ui-egui/src/dialogs.rs +++ b/crates/ui-egui/src/dialogs.rs @@ -330,7 +330,7 @@ fn cancel_label(dlg: &Dialog, sam: &SamState) -> &'static str { /// The label of a dialog's confirm button (empty: it has none). fn ok_label(app: &LightcraftApp, dlg: &Dialog, informational: bool, sam: &SamState) -> String { match dlg { - Dialog::Import { opts } => photos_ok_label(if opts.copy && opts.move_files { "Move" } else { "Import" }, opts.selected_paths().len()), + Dialog::Import { opts } => import_ok_label(opts), Dialog::Export { .. } => photos_ok_label("Export", app.session.selection.ids.len().max(1)), Dialog::SamModel { .. } => sam_ok_label(dlg, sam).to_string(), _ => fixed_ok_label(app, dlg, informational).to_string(), @@ -342,6 +342,22 @@ fn photos_ok_label(verb: &str, n: usize) -> String { crate::i18n::tr_format!("{verb} {n} Photo{}", crate::i18n::plural_s(n), verb = crate::i18n::tr(verb), n = n) } +/// The import review's confirm: "Import 3 Photos", or with Move "Move 3 Photos". A checked file whose photo is in +/// Recently Deleted is restored, not moved (its files stay where they are), so Move counts those apart: +/// "Move 2 Photos · Restore 1 Photo", or "Restore 1 Photo" when nothing is left to move. +fn import_ok_label(opts: &crate::import::ImportDialog) -> String { + let n = opts.selected_paths().len(); + if !(opts.copy && opts.move_files) { + return photos_ok_label("Import", n); + } + let restored = opts.restored_count(); + match (n - restored, restored) { + (_, 0) => photos_ok_label("Move", n), + (0, r) => photos_ok_label("Restore", r), + (m, r) => format!("{} · {}", photos_ok_label("Move", m), photos_ok_label("Restore", r)), + } +} + /// The confirm labels that are one fixed phrase. fn fixed_ok_label(app: &LightcraftApp, dlg: &Dialog, informational: bool) -> &'static str { match dlg { diff --git a/crates/ui-egui/src/dialogs/import.rs b/crates/ui-egui/src/dialogs/import.rs index 8425c3ec9..be4b78bb8 100644 --- a/crates/ui-egui/src/dialogs/import.rs +++ b/crates/ui-egui/src/dialogs/import.rs @@ -196,6 +196,13 @@ impl ImportDialog { } self.last_clicked = Some(i); } + /// How many of the checked files come back as the photo Recently Deleted holds: the import restores + /// those without copying or moving their files (only "Import as new" imports them like any other file). + pub fn restored_count(&self) -> usize { + let restoring = self.on_deleted != "fresh"; + let checked = self.checked.iter().enumerate().filter(|(i, on)| **on && self.importable(*i)); + checked.filter(|(i, _)| restoring && self.is_trashed(*i)).count() + } pub fn selected_paths(&self) -> Vec { self.candidates .iter() diff --git a/crates/ui-egui/src/tests/dialog_copy.rs b/crates/ui-egui/src/tests/dialog_copy.rs index 0e45d5af7..7983c3052 100644 --- a/crates/ui-egui/src/tests/dialog_copy.rs +++ b/crates/ui-egui/src/tests/dialog_copy.rs @@ -131,6 +131,42 @@ fn export_confirm_says_how_many_photos() { } } +/// With Move, a checked file whose photo is in Recently Deleted is restored, not moved: the confirm counts it apart. +#[test] +fn the_move_confirm_counts_restored_photos_apart_from_moved_ones() { + let mut h = demo(); + let review = |copy: bool, move_files: bool, on_deleted: &str, checked: [bool; 3]| { + let candidates = (0..3).map(|i| lightcraft_engine::import::ImportCandidate { path: format!("img{i}.jpg"), ..Default::default() }).collect(); + let mut opts = crate::import::ImportDialog::new(candidates); + opts.copy = copy; + opts.move_files = move_files; + opts.trashed = vec![true, false, false]; + opts.on_deleted = on_deleted.into(); + opts.checked = checked.to_vec(); + Dialog::Import { opts: Box::new(opts) } + }; + for (copy, move_files, on_deleted, checked, want) in [ + (true, true, "restore", [true, true, true], "Move 2 Photos · Restore 1 Photo"), + (true, true, "restore", [true, true, false], "Move 1 Photo · Restore 1 Photo"), + (true, true, "restore", [true, false, false], "Restore 1 Photo"), + (true, true, "restore", [false, true, true], "Move 2 Photos"), + (true, true, "fresh", [true, true, true], "Move 3 Photos"), + // "Leave them" still restores a file that was checked by hand: the engine's default + (true, true, "", [true, true, true], "Move 2 Photos · Restore 1 Photo"), + (true, false, "restore", [true, true, true], "Import 3 Photos"), + ] { + show(&mut h, review(copy, move_files, on_deleted, checked)); + // the wide review sits over the photo grid: its text (view tab, file names) is painted inside the button's rectangle too, + // so the label is what the rectangle's text ends with, and it names each verb as often as `want` does + let label = confirm_label(&h); + let said = |verb: &str| (label.matches(verb).count(), want.matches(verb).count()); + let what = format!("copy {copy}, move {move_files}, {on_deleted}, {checked:?}: {label:?}"); + assert!(label.ends_with(want), "{what}"); + assert_eq!(said("Move").0, said("Move").1, "{what}"); + assert_eq!(said("Restore").0, said("Restore").1, "{what}"); + } +} + #[test] fn an_unusable_model_file_only_closes() { let mut h = demo(); diff --git a/docs/whats-new.md b/docs/whats-new.md index d8c79602e..1fd05bb56 100644 --- a/docs/whats-new.md +++ b/docs/whats-new.md @@ -162,7 +162,9 @@ preset, Rename for photos, albums, keywords, people and presets, Create Stacks for Auto-Stack, Change Time for Capture Time, Copy and Paste for settings, and Export 12 Photos in the Export dialog. Rename Photos now explains what its Tags button lists and what happens on disk, instead of pointing at the button. Contact Sheet, Auto-Stack and - Slideshow Export no longer say "1 photos" or "1 stacks". + Slideshow Export no longer say "1 photos" or "1 stacks". The Import review's Move button counts photos that are + in Recently Deleted apart, since they are restored and their files stay where they are: "Move 2 Photos · Restore 1 + Photo". ### Sharper resampling in HDR and panorama merges - Merged photos (HDR and panorama) are resampled with a sharper filter (Lanczos-3 instead of bilinear). Moving a