Files
DarkRoom/ui/dr-ui/ui/pickers.slint
T
dtourolle 6683c14b40 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.
2026-09-26 14:13:53 -04:00

73 lines
2.5 KiB
Plaintext

// Choosing a folder or a file by pointing at it, not by typing its path.
//
// See `folder_dialog.rs` for why the platform's dialogue rather than a field:
// a typed path is how a destination silently becomes a new folder nobody
// meant, and the dialogue offers "New folder" where this app could not.
import { Theme } from "theme.slint";
import { Button, Value } from "widgets.slint";
import { TextRow, FieldRow } from "controls.slint";
// What this build can ask the platform for. Set once from Rust at startup.
export global Pickers {
/// A dialogue that returns filesystem paths: true on the desktop. False on
/// Android, whose only chooser (SAF) returns document trees, not paths —
/// so a screen that needs a path keeps its field there.
in property <bool> local-paths: true;
}
// A path setting: what is chosen now, and the button that changes it.
//
// Read-only where the platform has a dialogue — the value is shown so it is
// clear what the button will change, but it is not a field, because a field is
// the thing this replaced. Where there is no dialogue (Android) it is the
// ordinary typed row it always was.
export component PathRow inherits VerticalLayout {
in property <string> label;
in property <string> hint;
in property <string> path;
/// Shown dimmed when nothing is chosen yet.
in property <string> placeholder;
in property <string> button-text: @tr("Choose…");
in property <bool> enabled: true;
in property <length> field-width: 320px;
/// The button, where there is a dialogue.
callback choose();
/// The typed path, where there is not.
callback typed(string);
spacing: 4px;
if Pickers.local-paths: FieldRow { label: root.label; hint: root.hint; }
if Pickers.local-paths: HorizontalLayout {
spacing: Theme.gap-sm;
Value {
text: root.path != "" ? root.path : root.placeholder;
placeholder: root.path == "";
overflow: elide;
vertical-alignment: center;
horizontal-stretch: 1;
min-width: 0px;
}
Button {
text: root.button-text;
enabled: root.enabled;
clicked => { root.choose(); }
}
}
if !Pickers.local-paths: TextRow {
label: root.label;
hint: root.hint;
text: root.path;
placeholder: root.placeholder;
field-width: root.field-width;
enabled: root.enabled;
accepted(t) => { root.typed(t); }
}
}