Swipe up in develop for the photo roll

Develop opens one photograph. The grid handed over a path and nothing
else, so `index` and `total` were pinned to "1 of 1" on the way in and the
only route to the next frame was back to the library, find your place,
tap again. Fine once; intolerable through a set of forty, which is the
situation the develop view exists for.

The roll is the grid's already-loaded window along the foot of the
canvas. Swipe up to bring it out, swipe down to put it away — the sheet
gesture, already in the hands of anyone who has used a phone — and a
handle is drawn at the edge so the gesture is discoverable rather than
folklore, and so a pointer, which has no swipe to make, has a way in.

`SwipeGestureHandler` wraps the strip rather than sitting over or under
it, which is what it is built for: it delays a press the way a Flickable
does, forwards it to the children if no swipe develops and claims it once
one does, so a tap reaches the thumbnail and a drag does not. It covers
only the band along the bottom — above that, a drag still belongs to the
photograph, for panning and for the crop.

Picking goes through the same path a cell click does, so the outgoing
edit is persisted before the next image loads. The strip marks what is
open and scrolls to keep the mark in view.

The position readout now says where in the *library* the open photograph
sits rather than "1 of 1". Set after the open rather than before, since
the generic open path resets it — and given as the library ordinal, not
the row in the loaded window, which is an artefact of how much has been
paged in and would jump about as the window moves.
This commit is contained in:
2026-08-21 21:10:44 +02:00
parent c72f197880
commit 6ae0af3f72
3 changed files with 252 additions and 4 deletions
+48 -3
View File
@@ -3452,6 +3452,20 @@ fn place_cursor(
window.set_library_cursor(at as i32);
}
/// Say where in the library the photograph now open sits.
///
/// **After `on_open_image`, never before.** The generic open path resets the
/// readout to "1 of 1" on its way in, because until the photo roll existed the
/// grid really did hand develop a single path with nothing to walk to. It has
/// a window of them now, so the honest answer is the ordinal in the library —
/// not the row in the loaded window, which is an artefact of how much has been
/// paged in and would jump about as the window moves.
fn report_position(window: &AppWindow, ctl: &Rc<LibraryController>, row: usize) {
let offset = *ctl.offset.borrow();
window.set_index((offset + row) as i32);
window.set_total(window.get_library_total());
}
/// Connect the grid's callbacks.
pub fn wire<F>(
window: &AppWindow,
@@ -3487,12 +3501,41 @@ pub fn wire<F>(
// "‹ Library" button comes back here.
if let Some(w) = weak.upgrade() {
w.set_show_library(false);
// Which cell the develop view is now showing, so the photo
// roll opens marking it rather than marking nothing.
w.set_library_roll_current(i);
}
on_open_image(path);
if let Some(w) = weak.upgrade() {
report_position(&w, &ctl, i as usize);
}
}
});
}
// TRACES: FR-UI-4
// A photograph chosen from the photo roll.
//
// The same row-to-path lookup a cell click does, without the selection
// rules: the roll is a way of moving between photographs, not of building
// a set, so there is no modified press to honour and no reason to leave
// develop. `open_from_library` persists the outgoing edit before it loads
// the next one, which is what makes this safe to fire repeatedly.
{
let weak = window.as_weak();
let ctl = ctl.clone();
let on_open_image = on_open_image.clone();
window.on_library_roll_pick(move |i| {
let Some(w) = weak.upgrade() else { return };
let Some(path) = ctl.paths.borrow().get(i as usize).cloned() else {
return;
};
w.set_library_roll_current(i);
on_open_image(path);
report_position(&w, &ctl, i as usize);
});
}
// --- the keyboard cursor (FR-CULL-4) -----------------------------------
//
// Walking the grid with the arrows, and opening with `Return`. Together
@@ -3524,12 +3567,14 @@ pub fn wire<F>(
// wrong photograph is worse than opening none.
let Some(cursor) = coll.cursor() else { return };
let offset = *ctl.offset.borrow();
let path = cursor
.checked_sub(offset)
.and_then(|row| ctl.paths.borrow().get(row).cloned());
let row = cursor.checked_sub(offset);
let path = row.and_then(|row| ctl.paths.borrow().get(row).cloned());
if let Some(path) = path {
w.set_show_library(false);
// As on a click: the roll marks what is open.
w.set_library_roll_current(row.unwrap_or(0) as i32);
on_open(path);
report_position(&w, &ctl, row.unwrap_or(0));
}
});
}