Give a panorama a wide cell in the grid

A 4:1 composite drawn in one square cell is a strip a few pixels high.
A photograph about twice as wide as it is tall (1.9 and up) now spans
two columns, three from 2.9, with a thumbnail class of its own whose
long edge is sized for that width; on the tablet, or where the columns
are too few to put it beside anything, it takes the whole row.

Rows are computed in one place, library_ui::layout. The grid is a
lattice of slots: each cell is drawn at the slot Rust gives it, and a
wide cell that would not fit in what is left of a row starts the next
one, leaving the gap empty so the grid still reads in capture order.
The scrollbar spans the slots, a scroll reports a slot that the layout
turns back into an ordinal, and scrubs, restores and the cursor go
through the same conversion. Up and down step by rows through the
layout rather than by a row's worth of ordinals; left and right, a
shift-click's run, burst folding and the timeline are ordinal-based
and unchanged.

The window's own read carries each photograph's w and h, so the cells
know their shape with no query per cell. Where the wide ones sit in
the whole list is one query, run when what the grid lists changes or
when a window finds the layout out of date, and a library with no
panorama answers it from a partial index created on first use
(images_wide), not a schema bump. The merge makes the wide thumbnail
for a wide composite along with the others.
This commit is contained in:
2026-09-28 19:57:37 -04:00
parent ce5b7d72e3
commit ae4e1a0f07
13 changed files with 878 additions and 89 deletions
+41 -5
View File
@@ -27,7 +27,7 @@ use super::timeline::{
capture_time_at, catalog_span, note_place, resume_position, wire_timeline, write_place,
zoomed_span,
};
use super::window::{bring_window_to, load_window, mark_open, window_move};
use super::window::{bring_window_to, load_window, mark_open, show_cursor, window_move};
/// Walk the keyboard cursor through the library — the arrow keys.
///
@@ -109,7 +109,7 @@ fn place_cursor(
}
crate::collections_ui::select_row(window, coll, &ids, offset, row, false, extend);
window.global::<Library>().set_library_cursor(at as i32);
show_cursor(window, ctl, at);
}
/// Where a step of `delta` along the roll lands, as a library ordinal, from
@@ -419,6 +419,33 @@ fn wire_grid_cursor_and_zoom(
});
}
// TRACES: FR-MRG-6
// Up and down, by rows: with a panorama's cell in the grid a row is not
// a fixed number of photographs, so the layout says what is under the
// cursor that many rows on, and the step is the distance to it.
{
let weak = window.as_weak();
let ctl = ctl.clone();
let coll = coll_ctl.clone();
window
.global::<Library>()
.on_library_move_cursor_rows(move |rows, extend| {
let Some(w) = weak.upgrade() else { return };
let delta = match coll.cursor() {
Some(from) => {
let to = ctl.layout.borrow().step_rows(from, rows as isize);
to as i32 - from as i32
}
// The first press takes hold of the grid, whichever key.
None => rows,
};
if delta == 0 && coll.cursor().is_some() {
return;
}
move_cursor(&w, &ctl, &coll, delta, extend);
});
}
{
let weak = window.as_weak();
let ctl = ctl.clone();
@@ -635,9 +662,18 @@ fn wire_grid_sync_and_load(window: &AppWindow, ctl: &Rc<LibraryController>) {
let ctl = ctl.clone();
window
.global::<Library>()
.on_library_scrolled(move |first_visible| {
.on_library_scrolled(move |first_slot| {
let Some(w) = weak.upgrade() else { return };
let first_visible = first_visible.max(0) as usize;
// The grid reports the first slot of its first visible row;
// the photograph there is what everything below means
// (FR-MRG-6).
let first_slot = first_slot.max(0) as usize;
let total = w.global::<Library>().get_library_total().max(0) as usize;
let first_visible = ctl
.layout
.borrow()
.ordinal_at(first_slot)
.min(total.saturating_sub(1));
// **A report from a grid that is not on screen is not a scroll.**
//
@@ -693,7 +729,7 @@ fn wire_grid_sync_and_load(window: &AppWindow, ctl: &Rc<LibraryController>) {
// hand. Without this they each rebuilt against whatever `scroll-to`
// was last *set* to — a stale scrub, or zero — and landed there.
w.global::<Library>()
.set_library_scroll_to(first_visible as i32);
.set_library_scroll_to(first_slot as i32);
// Move the timeline marker with the view. Scrolling the grid is a
// way of moving through time just as scrubbing is, and a marker