Mark a burst in the grid, and let it be folded away
The counterpart to the grouping: where the signatures come from, and how a group reaches a cell. Signatures are computed from the 256px thumbnails dr-thumbs already holds -- vastly more resolution than a 9x8 reduction can use -- so a library that has been browsed, or that has synced somebody else's shards, has already paid for them and no RAW is decoded for this. The consequence is stated rather than hidden: an image with no thumbnail gets no signature and never joins a burst. That is self-correcting, and it is why the pass runs when the thumbnail sweep finishes rather than on a timer. Nothing happens at import and nothing happens at query time. The mark is drawn as a child of the cell's TouchArea, for the same reason the star strip is: a click on it must not also reach `cell-clicked` and throw the user into develop, and children are hit-tested before the element they sit in. It is never hidden on hover the way the stars are -- a collapsed burst stands in for frames that are not on screen, and something has to say so whether or not a pointer is nearby. Folding changes what the grid's *query* returns rather than what its cells draw, because the grid is a window over an ordered query and the frames a fold hides are mostly not loaded. So the predicate joins VISIBLE in every query that lists or counts cells -- the window, the header's count, the run a shift-click resolves, and the ordinal a scrub lands on -- under the discipline VISIBLE's own comment sets out: present in four places of five is worse than absent, because the counts disagree with the cells and neither looks wrong on its own. There is a test for exactly that. `the_window_read_walks_the_ordering_index` now includes the burst clause. It asserts on the query plan while holding its own copy of the query, so left alone it would have gone on reporting green against a query the grid no longer runs. If the clause costs `images_grid_order` and puts the sort back, that fails here rather than becoming jitter someone measures in six months. The pass keeps its own drain timer in a thread-local instead of taking fields on the library controller, so everything the feature needs to run lives in one file and the screen that starts it holds nothing. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -2214,6 +2214,11 @@ fn load_window(window: &AppWindow, ctl: &Rc<LibraryController>) {
|
||||
// window, not one per cell.
|
||||
rating: 0,
|
||||
flag: 0,
|
||||
// And by `bursts::sync_badges`, in one more query for the
|
||||
// window. Zero is "not in a burst", which is what almost every
|
||||
// photograph in a library is.
|
||||
burst_count: 0,
|
||||
burst_expanded: false,
|
||||
}
|
||||
})
|
||||
.collect();
|
||||
@@ -2246,6 +2251,9 @@ fn load_window(window: &AppWindow, ctl: &Rc<LibraryController>) {
|
||||
.collect();
|
||||
crate::collections_ui::sync_badges(window, catalog, &ids);
|
||||
sync_ratings(window, catalog, &ids);
|
||||
// How many frames each cell stands for, where it stands for several
|
||||
// (FR-CULL-5).
|
||||
crate::bursts::sync_badges(window, catalog, &ids);
|
||||
// The rebuilt cells all carry `selected: false`, but the selection itself
|
||||
// is a set of image ids and survives untouched. Without this the ticks
|
||||
// vanished on every scroll — the selection was still there and still acted
|
||||
@@ -3769,6 +3777,26 @@ fn start_thumbnail_sweep(window: &AppWindow, ctl: &Rc<LibraryController>) {
|
||||
if !offline {
|
||||
start_derived_sync(&w, &ctl_cb);
|
||||
}
|
||||
// And now every photograph in the library has a
|
||||
// thumbnail, which is the only moment all of its burst
|
||||
// signatures can be computed. See `bursts::start_pass`,
|
||||
// which owns the pass and everything it needs to drain
|
||||
// itself; what it wants from here is the paths and a
|
||||
// way to say the grid has something new to draw.
|
||||
if let Some((conn, _)) = ctl_cb.session.borrow().clone() {
|
||||
let weak_after = w.as_weak();
|
||||
let ctl_after = ctl_cb.clone();
|
||||
crate::bursts::start_pass(
|
||||
library::catalog_path(&conn.account),
|
||||
library::thumbs_dir(&conn.account),
|
||||
move |bursts| {
|
||||
let Some(w) = weak_after.upgrade() else { return };
|
||||
if bursts > 0 && w.get_show_library() {
|
||||
schedule_reload(&w, &ctl_after);
|
||||
}
|
||||
},
|
||||
);
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
@@ -4119,10 +4147,14 @@ fn capture_time_from_catalog(catalog: &Catalog, ordinal: usize) -> Option<i64> {
|
||||
catalog
|
||||
.connection()
|
||||
.query_row(
|
||||
"SELECT captured_at FROM images
|
||||
WHERE shadowed_by IS NULL AND captured_at IS NOT NULL
|
||||
ORDER BY captured_at
|
||||
LIMIT 1 OFFSET ?1",
|
||||
&format!(
|
||||
"SELECT captured_at FROM images
|
||||
WHERE shadowed_by IS NULL AND captured_at IS NOT NULL
|
||||
AND {}
|
||||
ORDER BY captured_at
|
||||
LIMIT 1 OFFSET ?1",
|
||||
dr_catalog::bursts::not_collapsed_away("images")
|
||||
),
|
||||
[ordinal as i64],
|
||||
|r| r.get::<_, i64>(0),
|
||||
)
|
||||
@@ -4153,13 +4185,21 @@ fn scrub_to(window: &AppWindow, ctl: &Rc<LibraryController>, when: i64) {
|
||||
// BY), so they never precede a dated one and the predicate below stays
|
||||
// a simple `<`. Shadowed rows are excluded here exactly as the grid
|
||||
// excludes them.
|
||||
//
|
||||
// A burst folded up occupies one row of the grid, so it must occupy one
|
||||
// row of this count as well: an ordinal taken over the unfolded library
|
||||
// would overshoot by every frame hidden earlier in it.
|
||||
catalog
|
||||
.connection()
|
||||
.query_row(
|
||||
"SELECT count(*) FROM images
|
||||
WHERE shadowed_by IS NULL
|
||||
AND captured_at IS NOT NULL
|
||||
AND captured_at < ?1",
|
||||
&format!(
|
||||
"SELECT count(*) FROM images
|
||||
WHERE shadowed_by IS NULL
|
||||
AND captured_at IS NOT NULL
|
||||
AND captured_at < ?1
|
||||
AND {}",
|
||||
dr_catalog::bursts::not_collapsed_away("images")
|
||||
),
|
||||
[when],
|
||||
|r| r.get::<_, i64>(0),
|
||||
)
|
||||
@@ -4953,6 +4993,34 @@ pub fn wire<F>(
|
||||
});
|
||||
}
|
||||
|
||||
// Fold a burst up, or open it out (FR-CULL-5). A reload rather than a repaint, because
|
||||
// it changes what the grid's query returns — see [`crate::bursts::toggle`].
|
||||
{
|
||||
let weak = window.as_weak();
|
||||
let ctl = ctl.clone();
|
||||
window.on_library_burst_toggled(move |row| {
|
||||
let Some(w) = weak.upgrade() else { return };
|
||||
let id = ctl
|
||||
.image_ids
|
||||
.borrow()
|
||||
.get(row as usize)
|
||||
.map(|id| dr_types::ImageId(*id as u64));
|
||||
let Some(id) = id else { return };
|
||||
|
||||
let changed = {
|
||||
let borrow = ctl.catalog.borrow();
|
||||
match borrow.as_ref() {
|
||||
Some(catalog) => crate::bursts::toggle(catalog, id),
|
||||
None => false,
|
||||
}
|
||||
};
|
||||
if changed {
|
||||
ctl.requested.borrow_mut().clear();
|
||||
load_window(&w, &ctl);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// A rating or flag key. Applies to the whole selection, which is what
|
||||
// makes judging a run of frames one keystroke rather than forty.
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user