fix(gallery): force layout on every realised cell after a relayout

Escalates the existing pulseRowsLayout retry (from 09c58ab): calling
rows.requestLayout() only asks VirtualFlow to reconsider ITS OWN
layout — whether that walks back down into any one already-realised
GalleryRowCell is VirtualFlow's own per-cell dirty tracking to decide,
not something a request at the ListView level can force. A cell whose
position/size VirtualFlow already considers settled can sit with
content that never actually went through a real layoutChildren() pass
at all — its thumbnail decoded, but never painted, until something
else (a mouse move) forces one. That single retry was enough for a
search filter clearing back to a longer list; a folder-tree selection
swapping in an unrelated, differently sized list has kept showing the
same symptom survive it on repeated filtered navigation.

pulseRowsLayout now also calls requestLayout() directly on every
currently-realised .list-cell, marking each one's own needsLayout
regardless of VirtualFlow's own conclusion — reaching rowContainer/
photosBox/each card's ImageView on the next pulse regardless.

Not verified visually — no GUI in this environment. Please retest the
filter-navigation case this was reported against.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016okXfGz39FtQawFWDYpQ5C
This commit is contained in:
2026-09-23 19:06:47 -04:00
co-authored by Claude Sonnet 5
parent fbb99c4574
commit 6abf6b2d0d
@@ -1311,15 +1311,33 @@ public class ThumbnailGalleryPane extends StackPane implements Disposable {
}
/**
* {@link #relayout()}'s own follow-up: forces a fresh {@code rows} layout pass on each of the next
* {@link #relayout()}'s own follow-up: forces a fresh layout pass on each of the next
* {@code attemptsLeft} pulses, the same "needs another pulse to settle" retry {@link #pulseCardOnceSettled}
* uses for a {@code scrollTo} target. One extra pulse was enough for 09c58ab's own case (a search filter
* clearing back to a much longer list); a folder-tree selection swapping in an unrelated, differently
* sized item list has since shown some of VirtualFlow's newly realised cells still miss that single
* retry, so this keeps requesting a layout for a few more pulses instead of stopping after one.
* retry — and, still surviving on filtered navigation (see the conversation this was tightened from),
* miss even several of the extra ones {@code rows.requestLayout()} alone had bought.
*
* <p>{@code rows.requestLayout()} only asks {@code VirtualFlow} itself to reconsider its own layout;
* whether that actually walks back down into any one already-realised {@link GalleryRowCell} is
* {@code VirtualFlow}'s own internal per-cell dirty tracking to decide, not something a request at the
* {@code ListView} level can force — a newly-realised cell whose position/size VirtualFlow already
* considers settled can sit with content genuinely never having gone through a real
* {@code layoutChildren()} pass at all, which is exactly what leaves its thumbnail decoded but never
* actually painted until something else (a mouse move — see {@link #relayout()}'s own javadoc) forces
* one. Calling {@code requestLayout()} directly on every currently-realised cell, not merely on
* {@code rows} itself, marks each one's own {@code needsLayout} regardless of what {@code VirtualFlow}'s
* dirty tracking otherwise concluded, which is what actually reaches {@link GalleryRowCell#rowContainer}/
* {@code photosBox}/each card's {@code ImageView} on the very next pulse.
*/
private void pulseRowsLayout(int attemptsLeft) {
rows.requestLayout();
for (Node node : rows.lookupAll(".list-cell")) {
if (node instanceof ListCell<?> cell) {
cell.requestLayout();
}
}
if (attemptsLeft > 0) {
Platform.runLater(() -> pulseRowsLayout(attemptsLeft - 1));
}