LR-BEHAV-DIALOGS: the Move confirm counts restored photos apart from moved ones

This commit is contained in:
Nathaniel Ledford
2026-10-29 12:22:43 -04:00
parent 4c1e996a64
commit c2529503fd
4 changed files with 63 additions and 2 deletions
+17 -1
View File
@@ -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 {
+7
View File
@@ -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<String> {
self.candidates
.iter()
+36
View File
@@ -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();
+3 -1
View File
@@ -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