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:
@@ -187,6 +187,105 @@ pub(super) fn window_move(
|
||||
Some(desired)
|
||||
}
|
||||
|
||||
/// Where the loaded window has to move to so that it holds ordinal `at`, or
|
||||
/// `None` if it already does.
|
||||
///
|
||||
/// Placed by [`window_start`], a quarter in, as a scroll places it — so a walk
|
||||
/// that carries on in the same direction has loaded cells to move into rather
|
||||
/// than another reload on the very next step.
|
||||
pub(super) fn window_for(
|
||||
at: usize,
|
||||
offset: usize,
|
||||
loaded: usize,
|
||||
window_size: usize,
|
||||
total: usize,
|
||||
) -> Option<usize> {
|
||||
(at < offset || at >= offset + loaded).then(|| window_start(at, window_size, total))
|
||||
}
|
||||
|
||||
/// Load the window around library ordinal `at` if it is not already in it,
|
||||
/// and say which row of it `at` now is.
|
||||
///
|
||||
/// `None` when the window could not be brought there — an empty or shrinking
|
||||
/// library — which callers treat as "do nothing" rather than guess at a row.
|
||||
pub(super) fn bring_window_to(
|
||||
window: &AppWindow,
|
||||
ctl: &Rc<LibraryController>,
|
||||
at: usize,
|
||||
) -> Option<usize> {
|
||||
let loaded = ctl.paths.borrow().len();
|
||||
let size = *ctl.window.borrow();
|
||||
let total = window.global::<Library>().get_library_total().max(0) as usize;
|
||||
if let Some(offset) = window_for(at, *ctl.offset.borrow(), loaded, size, total) {
|
||||
*ctl.offset.borrow_mut() = offset;
|
||||
load_window(window, ctl);
|
||||
}
|
||||
// Re-read: `load_window` clamps the offset against the library's end, so
|
||||
// the window may not start where it was asked to.
|
||||
let offset = *ctl.offset.borrow();
|
||||
at.checked_sub(offset)
|
||||
.filter(|row| *row < ctl.paths.borrow().len())
|
||||
}
|
||||
|
||||
/// Record that the photograph at `row` of the loaded window is the one now
|
||||
/// open in develop, and mark it on the roll.
|
||||
pub(super) fn mark_open(window: &AppWindow, ctl: &LibraryController, row: usize) {
|
||||
ctl.roll_open.set(ctl.image_ids.borrow().get(row).copied());
|
||||
ctl.roll_left.set(false);
|
||||
window
|
||||
.global::<Library>()
|
||||
.set_library_roll_current(row as i32);
|
||||
}
|
||||
|
||||
/// Put the roll's mark back on the open photograph after the window was
|
||||
/// re-read.
|
||||
///
|
||||
/// **The mark is a row, and a reload changes what every row holds.** It used
|
||||
/// to be set when a photograph was opened and never again, so once the window
|
||||
/// moved — a step off its end, a background sync, a judgement under a filter —
|
||||
/// the roll marked whichever photograph had taken that row, the rating keys in
|
||||
/// develop judged it, and the next step walked on from it. Found by id in the
|
||||
/// window just read: a scan of one window, not a query.
|
||||
///
|
||||
/// Where it is found, `index` is corrected with it, since that ordinal is what
|
||||
/// the next step and the return to the grid start from. Where it is not, the
|
||||
/// mark comes off; and if its ordinal is still inside the window, it is a
|
||||
/// photograph that has left the grid rather than one the window has moved away
|
||||
/// from — see [`LibraryController::roll_left`].
|
||||
fn follow_open(window: &AppWindow, ctl: &LibraryController, offset: usize) {
|
||||
let Some(open) = ctl.roll_open.get() else {
|
||||
return;
|
||||
};
|
||||
let index = window.get_index().max(0) as usize;
|
||||
match locate_open(open, &ctl.image_ids.borrow(), offset, index) {
|
||||
Ok(row) => {
|
||||
ctl.roll_left.set(false);
|
||||
window
|
||||
.global::<Library>()
|
||||
.set_library_roll_current(row as i32);
|
||||
window.set_index((offset + row) as i32);
|
||||
}
|
||||
Err(left) => {
|
||||
ctl.roll_left.set(left);
|
||||
window.global::<Library>().set_library_roll_current(-1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Which row of a window starting at `offset` holds photograph `open`, or, if
|
||||
/// none does, whether it has left the grid: its last known ordinal `index` is
|
||||
/// still inside the window, so something else now sits there.
|
||||
pub(super) fn locate_open(
|
||||
open: i64,
|
||||
ids: &[i64],
|
||||
offset: usize,
|
||||
index: usize,
|
||||
) -> Result<usize, bool> {
|
||||
ids.iter()
|
||||
.position(|id| *id == open)
|
||||
.ok_or(index >= offset && index < offset + ids.len())
|
||||
}
|
||||
|
||||
/// Fill the model from the catalog and start fetching thumbnails.
|
||||
///
|
||||
/// Reads the window starting at the controller's current offset, which the
|
||||
@@ -399,6 +498,9 @@ pub(super) fn load_window(window: &AppWindow, ctl: &Rc<LibraryController>) {
|
||||
window
|
||||
.global::<Library>()
|
||||
.set_library_cells(slint::ModelRc::new(slint::VecModel::from(rows)));
|
||||
// The rows have just changed meaning, so the roll's mark — a row — has to
|
||||
// be found again rather than left pointing at whoever sits there now.
|
||||
follow_open(window, ctl, offset);
|
||||
|
||||
// The model is fresh, so the "in this many collections" badges are all zero
|
||||
// until refilled. One query for the whole window, not one per cell.
|
||||
|
||||
Reference in New Issue
Block a user