Choose folders in the platform's dialogue, not by typing a path
Every folder the desktop asked for was a text field: the library folder at launch, an import's source and second copy, a preset folder brought over from Lightroom. A typed path is how a destination silently becomes a new folder nobody meant — one wrong letter three levels down and the write succeeds somewhere the photographer will never look — and a field cannot make the folder that is not there yet. They now open the platform's own dialogue through rfd: the XDG desktop portal on Linux, the common item dialogue on Windows. The portal rather than GTK because it reaches the user's files from inside the Flatpak and needs no GTK in a Slint application, and it draws whichever desktop's chooser is running, "New folder" included. It is awaited on Slint's event loop (spawn_local), so the window keeps drawing while it is open, and parented to the window so it opens over it. PathRow shows what is chosen, read-only, beside the button. Android has no filesystem dialogue — only SAF, which returns document trees, not paths — so there the same rows stay typed fields (Pickers.local-paths). The launch screen keeps the folder used last on screen with "Open folder" beside it, so reopening is one press. Presets get two buttons, a folder and a single .xmp file, because no platform dialogue picks "a file or a folder" in one go.
This commit is contained in:
@@ -394,10 +394,28 @@ where
|
||||
let context = context.clone();
|
||||
window.on_import_card_path_changed(move |text| {
|
||||
let Some(w) = weak.upgrade() else { return };
|
||||
*ctl.card.borrow_mut() = text.to_string();
|
||||
ctl.selected.set(-1);
|
||||
survey(&w, &ctl, &context);
|
||||
render(&w, &ctl);
|
||||
set_card(&w, &ctl, &context, text.to_string());
|
||||
});
|
||||
}
|
||||
|
||||
{
|
||||
let weak = window.as_weak();
|
||||
let ctl = ctl.clone();
|
||||
let context = context.clone();
|
||||
window.on_import_choose_card(move || {
|
||||
let Some(w) = weak.upgrade() else { return };
|
||||
let start = ctl.card.borrow().clone();
|
||||
let (weak, ctl, context) = (weak.clone(), ctl.clone(), context.clone());
|
||||
crate::folder_dialog::ask(
|
||||
&w,
|
||||
"Import from",
|
||||
crate::folder_dialog::Pick::Folder,
|
||||
Some(&start),
|
||||
move |path| {
|
||||
let Some(w) = weak.upgrade() else { return };
|
||||
set_card(&w, &ctl, &context, path.to_string_lossy().into_owned());
|
||||
},
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -482,6 +500,27 @@ fn wire_options(window: &AppWindow, ctl: &Rc<ImportController>) {
|
||||
render(&w, &ctl);
|
||||
});
|
||||
}
|
||||
|
||||
{
|
||||
let weak = window.as_weak();
|
||||
let ctl = ctl.clone();
|
||||
window.on_import_choose_backup(move || {
|
||||
let Some(w) = weak.upgrade() else { return };
|
||||
let start = ctl.settings.settings.borrow().import.backup.trim().to_string();
|
||||
let (weak, ctl) = (weak.clone(), ctl.clone());
|
||||
crate::folder_dialog::ask(
|
||||
&w,
|
||||
"Second copy in",
|
||||
crate::folder_dialog::Pick::Folder,
|
||||
Some(&start),
|
||||
move |path| {
|
||||
let Some(w) = weak.upgrade() else { return };
|
||||
ctl.edit(|s| s.backup = path.to_string_lossy().into_owned());
|
||||
render(&w, &ctl);
|
||||
},
|
||||
);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/// Running.
|
||||
@@ -519,6 +558,20 @@ fn wire_running<C, F>(
|
||||
}
|
||||
}
|
||||
|
||||
/// Take a source the user named — typed, or chosen in the dialogue — rather
|
||||
/// than one from the volume list, and count what is on it.
|
||||
fn set_card(
|
||||
w: &AppWindow,
|
||||
ctl: &Rc<ImportController>,
|
||||
context: &Rc<impl Fn() -> Option<Context>>,
|
||||
path: String,
|
||||
) {
|
||||
*ctl.card.borrow_mut() = path;
|
||||
ctl.selected.set(-1);
|
||||
survey(w, ctl, context);
|
||||
render(w, ctl);
|
||||
}
|
||||
|
||||
/// Count what is on the card, without copying anything.
|
||||
///
|
||||
/// Runs on the UI thread, which is defensible only because it is a directory
|
||||
|
||||
Reference in New Issue
Block a user