No reload flash when the rescan finds nothing new

The scan's final set_list replaced the whole model unconditionally:
generation bump + painted-state clear + set_vec, so Slint recreated
every row and every visible thumbnail flashed out and back — even
when the rescan changed nothing (the normal warm start). Now:
- unchanged list -> status line only, model untouched
- displayed list is a prefix of the final one (cold-start fill)
  -> append the tail rows in place (no row recreation)
- genuine change -> full replacement (the correct signal there)
This commit is contained in:
Greg Pomerantz 2026-10-28 13:31:47 -04:00
parent 16526a402a
commit 835659131e

View File

@ -1184,10 +1184,50 @@ fn scan_thread(weak: &slint::Weak<Main>) {
let n = entries.len(); let n = entries.len();
let ms = t0.elapsed().as_millis(); let ms = t0.elapsed().as_millis();
log::info!("gallery: scan {n} files ({ins}+ {upd}~ {del}-) in {ms} ms"); log::info!("gallery: scan {n} files ({ins}+ {upd}~ {del}-) in {ms} ms");
let status = format!("{n} files ({ins}+ {upd}~ {del}–) in {ms} ms");
// Final list: set_list bumps the generation + re-anchors the viewport; // Scan finished: touch the model ONLY if what's on screen actually
// the next tick fills it (warm start: disk-cache hits, nearly free). // changed. A full set_list bumps the generation (drops in-flight +
set_list(weak, entries, format!("{n} files ({ins}+ {upd}~ {del}–) in {ms} ms")); // painted state) and replaces the list model — Slint recreates every
// row and every visible thumbnail flashes out and back, even when the
// rescan found nothing new. The normal warm start must be a no-op.
let same = |a: &Entry, b: &Entry| a.path == b.path && a.mtime == b.mtime && a.size == b.size;
let cur: Vec<Entry> = state().entries.lock().unwrap().clone();
if cur.len() == entries.len() && cur.iter().zip(&entries).all(|(a, b)| same(a, b)) {
log::info!("gallery: rescan unchanged — keeping live list (no reload)");
set_status(weak, status);
return;
}
// Progressive-fill case (cold start): the displayed list is the final
// list's prefix — append the tail in place instead of replacing (no row
// recreation, no flash, no generation bump).
if entries.len() > cur.len()
&& entries[..cur.len()].iter().zip(&cur).all(|(a, b)| same(a, b))
{
let tail: Vec<Entry> = entries[cur.len()..].to_vec();
let full = entries;
log::info!(
"gallery: rescan added {} — extending list in place",
tail.len()
);
let weak = weak.clone();
let _ = slint::invoke_from_event_loop(move || {
let Some(w) = weak.upgrade() else { return };
let Some(app) = STATE.get() else { return };
*app.entries.lock().unwrap() = full;
if let Some(m) = w.get_items().as_any().downcast_ref::<VecModel<Item>>() {
let at = m.row_count();
for (i, it) in chunk(&tail).into_iter().enumerate() {
m.insert(at + i, it);
}
}
w.set_status(status.into());
});
return;
}
// Genuine change (files added mid-list, modified, removed): full
// replacement — the one case where the flash is the correct signal.
set_list(weak, entries, status);
} }
/// A clone of `AndroidApp` kept for the app's whole lifetime so the Java side /// A clone of `AndroidApp` kept for the app's whole lifetime so the Java side