Take the whole run a shift-click names, not the part that happens to be loaded
Shift-clicking two photographs selected only the cells between them that were in the loaded window. The grid is a window of a hundred or so over a library of twenty thousand, and `apply_press` resolved the range against that window — `ids[lo_row..=hi_row]`, clamped to what was there. Everything else in the run had no id anywhere in the UI, so it was silently dropped. The user cannot see that: the selection count is off screen along with the photographs, and the gesture only announces itself when the drop files a dozen images instead of two hundred. The two ends are *ordinals*, and only the catalog knows what lies between them. `read_ids_span` asks it, through the same predicates, the same rating filter and the same ordering the window itself is read with — an ordinal names a photograph only relative to an ordering, so a run taken through any other one is a run through a different library. That ordering is now a constant, `GRID_ORDER`, shared by the window, the trash's own order beside it, and the run: capture time first, with the file name breaking ties and nothing more. A card written by two cameras interleaves names that have nothing to do with each other, and what "everything between these two" means to a photographer is a stretch of an afternoon. The query is reached through a closure handed to `CollectionsController` at wiring time rather than a catalog handle, because the scope and the filter that bound the run belong to the grid's controller. `apply_press` stays a pure function of what it is given, which is what keeps the selection rules testable with no library open — and the tests pass a run that reads a plain slice. Where there is nothing to ask, the loaded window is still used: a poorer answer than the catalog's and a far better one than a gesture that appears to do nothing. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -590,6 +590,38 @@ impl LibraryController {
|
||||
.collect()
|
||||
}
|
||||
|
||||
/// TRACES: FR-CAT-5
|
||||
/// Catalog ids of grid rows `first..=last`, loaded or not.
|
||||
///
|
||||
/// The counterpart to [`Self::visible_ids`], and the reason both exist: a
|
||||
/// shift-click names a run by its two ends, and everything between them is
|
||||
/// usually off screen. Answered from the catalog, through the same scope,
|
||||
/// filter and ordering this controller reads its window with — so the run
|
||||
/// is the run the user can see themselves selecting, continued past the
|
||||
/// edge of what has been loaded.
|
||||
///
|
||||
/// Empty before a library is open, and empty if the query fails: a
|
||||
/// selection gesture is not worth an error dialog, and the caller keeps
|
||||
/// what was already selected.
|
||||
pub fn ids_in_span(&self, first: usize, last: usize) -> Vec<dr_types::ImageId> {
|
||||
let borrow = self.catalog.borrow();
|
||||
let Some(catalog) = borrow.as_ref() else {
|
||||
return Vec::new();
|
||||
};
|
||||
library::read_ids_span(
|
||||
catalog,
|
||||
*self.scope.borrow(),
|
||||
&self.filter.borrow(),
|
||||
self.viewing_trash.get(),
|
||||
first,
|
||||
last,
|
||||
)
|
||||
.unwrap_or_else(|e| {
|
||||
log::warn!("reading the selected range: {e}");
|
||||
Vec::new()
|
||||
})
|
||||
}
|
||||
|
||||
/// TRACES: FR-EXP-7
|
||||
/// Remote paths for a set of selected images, in the order they were given.
|
||||
///
|
||||
|
||||
Reference in New Issue
Block a user