Make "Choose folder" an actual folder picker

It previously fetched the folder list and threw it away into a status
line — a button that looked like it worked and did not. Now it opens a
browsable picker: click a folder to descend, ".." to go back, "Use this
folder" to select, "Cancel" to leave the root unchanged.

Descends one level per click because that is what the backend supports:
Depth: infinity is frequently disabled server-side and prohibitively
expensive where it is not (ARCH §8.4).

The chosen root persists immediately on confirm, so it survives a crash
before the library is opened. Confirming at the account root is allowed —
a user may legitimately keep everything at the top level — and cancelling
leaves any previous selection untouched, which a test asserts.

Verified against nextcloud.tourolle.paris at both depths: 30 folders at
the root, 21 year-folders inside PhotosRaw.

19 launch tests, 38 in dr-ui.
This commit is contained in:
2026-08-09 15:50:40 +02:00
parent f630a3ff81
commit 2ca1716a29
10 changed files with 888 additions and 65 deletions
+37 -8
View File
@@ -173,12 +173,16 @@ fn is_supported(p: &Path) -> bool {
/// progress. The symptom is a slider that jumps on click but cannot be
/// dragged, because each move event destroys the thing that would deliver
/// the next one.
fn sync_rows(rows: &Rc<slint::VecModel<ParamRow>>, session: &Rc<RefCell<Option<DevelopSession>>>) {
fn sync_rows(
window: &AppWindow,
rows: &Rc<slint::VecModel<ParamRow>>,
session: &Rc<RefCell<Option<DevelopSession>>>,
) {
use slint::Model as _;
let current = match session.borrow().as_ref() {
Some(s) => s.rows(),
None => Vec::new(),
let (current, samples) = match session.borrow().as_ref() {
Some(s) => (s.rows(), s.curve_samples()),
None => (Vec::new(), Vec::new()),
};
if current.len() == rows.row_count() {
@@ -194,6 +198,10 @@ fn sync_rows(rows: &Rc<slint::VecModel<ParamRow>>, session: &Rc<RefCell<Option<D
// is correct here — there is no drag to preserve.
rows.set_vec(current);
}
// The drawn curve follows the points. Replacing this model wholesale is
// safe where replacing `rows` was not: nothing in it is a drag target.
window.set_curve_samples(slint::ModelRc::new(slint::VecModel::from(samples)));
}
/// TRACES: M-13 | M-14
@@ -304,9 +312,13 @@ pub fn run(paths: Vec<PathBuf>) -> Result<()> {
// this code names no operation (FR-DEV-3a).
match l.session {
Some(s) => {
rows.set_vec(s.rows());
window.set_adjust_enabled(true);
*session.borrow_mut() = Some(s);
// Through `sync_rows` rather than setting rows
// directly, so the curve's drawn shape is
// refreshed by the same path that refreshes the
// controls — one place to keep them in step.
sync_rows(window, &rows, &session);
redraw(window);
}
None => {
@@ -351,7 +363,7 @@ pub fn run(paths: Vec<PathBuf>) -> Result<()> {
if let Some(s) = session.borrow_mut().as_mut() {
s.set_param(op, param, value);
}
sync_rows(&rows, &session);
sync_rows(&w, &rows, &session);
redraw(&w);
});
}
@@ -365,7 +377,7 @@ pub fn run(paths: Vec<PathBuf>) -> Result<()> {
if let Some(s) = session.borrow_mut().as_mut() {
s.reset_param(op, param);
}
sync_rows(&rows, &session);
sync_rows(&w, &rows, &session);
redraw(&w);
});
}
@@ -379,7 +391,24 @@ pub fn run(paths: Vec<PathBuf>) -> Result<()> {
if let Some(s) = session.borrow_mut().as_mut() {
s.reset_all();
}
sync_rows(&rows, &session);
sync_rows(&w, &rows, &session);
redraw(&w);
});
}
{
// A curve is one control spanning many parameters, so resetting it
// clears all of them at once — resetting a single point would leave
// a shape the user did not ask for.
let weak = window.as_weak();
let session = session.clone();
let redraw = redraw.clone();
let rows = rows.clone();
window.on_curve_reset(move |op| {
let Some(w) = weak.upgrade() else { return };
if let Some(s) = session.borrow_mut().as_mut() {
s.reset_curve(op);
}
sync_rows(&w, &rows, &session);
redraw(&w);
});
}