Step along the roll by library ordinal, and keep its mark on the open photograph

In a library's develop view the arrows, space, A and D opened
`library-roll-pick(library-roll-current ± 1)`. `roll-current` is a row
of the loaded window, set when a photograph was opened and never again.
Two things followed on any library larger than one window:

- Holding D stopped dead at the end of the loaded window: the pick of a
  row past `ctl.paths` found no path and did nothing, a few screenfuls
  into a library of thousands. A stopped at its start the same way.
- Any reload of the window — a background sync, a judgement that drops
  the open frame out of a filter — left `roll-current` on a row that now
  held another photograph. The roll marked it, develop's rating keys
  judged it, and the next step walked on from it.

The keys now call `library-roll-step(delta)`, and Rust steps as
`move_cursor` does in the grid: from the open photograph's ordinal
(`index`, which `report_position` already keeps), clamped to the
library, loading the window around the target only when it lies
outside. The window-bringing half of `place_cursor` is shared for this
as `bring_window_to`, so the grid cursor and the roll move the window
the same way. Catalog reads stay proportional to the window: one
`load_window` per window crossed, none per step within it.

The open photograph is also remembered by image id. Every
`load_window` finds it again in the rows it just read (a scan of one
window, no query), puts `roll-current` and `index` back on it, or takes
the mark off when it is not there. When it is missing but its ordinal
is still inside the window, it has left the grid and its successor
moved up into its place, so the next step forward lands on that ordinal
instead of skipping the successor.

A click on the roll still reports a row; it is right because the cells
it is drawn from and `ctl.paths` are replaced together, and it now goes
through the same open path as a step.

Fixes #64.
This commit is contained in:
2026-09-25 23:00:55 -04:00
parent ce72fe49a0
commit a030bfd239
8 changed files with 422 additions and 101 deletions
+4 -4
View File
@@ -717,12 +717,12 @@ export component AppWindow inherits Window {
/// line, and a photograph opened from a library leaves that list empty —
/// so with a library open the arrows, space, A and D did nothing at all,
/// and only a click on the roll moved on. With a library the step is the
/// roll's own: open its neighbouring frame, exactly as clicking it would.
/// roll's own: open the neighbouring frame in grid order. By ordinal in
/// Rust, not by the roll's row, because the roll holds only the loaded
/// window and a row past its end picked nothing.
function step-photo(delta: int) {
if (Library.library-total > 0) {
if (Library.library-roll-current + delta >= 0) {
Library.library-roll-pick(Library.library-roll-current + delta);
}
Library.library-roll-step(delta);
} else if (delta > 0) {
root.next-image();
} else {
+7
View File
@@ -534,6 +534,8 @@ export global Library {
in property <int> library-scroll-token: 0;
/// Which row of the loaded window is the photograph currently open in
/// develop, so the roll can mark it. `-1` when it is not in the window.
/// Rust re-finds it on every reload, so it follows the photograph and not
/// the row.
in property <int> library-roll-current: -1;
/// Centre the roll on the open photograph the next time the strip settles.
///
@@ -546,6 +548,11 @@ export global Library {
/// A photograph was chosen from the roll: the row within the loaded
/// window, which is what a cell click reports too.
callback library-roll-pick(int);
/// Step along the roll by this many photographs, from the keyboard. Rust
/// steps from the open photograph's place in the library and loads the
/// window around the next one, so the walk does not stop at the edge of
/// what the roll happens to hold.
callback library-roll-step(int);
callback library-columns-changed();
callback library-scrolled(int);
/// How many cells the grid's viewport shows at once. Rust sizes the