From b97347329f532d942361502211361694fe087680 Mon Sep 17 00:00:00 2001 From: Malte Langkabel Date: Sun, 9 Aug 2026 14:16:23 +0200 Subject: [PATCH] measure the card against every one of its layouts Changing a label's text posts a LayoutRequest to the widget holding it, and that event is only delivered when the event loop next runs. The panel measured its card by invalidating the body's own layout alone, so any layout nested inside the card -- which is all of them -- still answered with the width of the text before the change. The panel therefore placed itself against the previous values and corrected itself on the following refresh, a frame late. Measured on a BarRow: with the value going idle, 0%, 42%, 100% the panel's measurement reported 16, 16, 17, 23 where the settled widths were 16, 17, 23, 29 -- one change behind throughout. Re-activating every layout in the card gives the settled answer without waiting for the event loop. This is not the size change the player reported, which I could not reproduce here; it is a second, quieter one found while looking for it. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01JcReq7hVk4KUPhTDKWAG7K --- src/ui/SelectionPanel.cpp | 25 ++++++++++++++++++++++--- 1 file changed, 22 insertions(+), 3 deletions(-) diff --git a/src/ui/SelectionPanel.cpp b/src/ui/SelectionPanel.cpp index e584e6a..b4948e2 100644 --- a/src/ui/SelectionPanel.cpp +++ b/src/ui/SelectionPanel.cpp @@ -1,5 +1,7 @@ #include "SelectionPanel.h" +#include +#include #include #include #include @@ -263,9 +265,16 @@ void SelectionPanel::placeIn(const QRect& viewRect, const std::vector& oc // laid out, does the width it reports. Measuring at whatever width the panel // happens to have carries the previous card's shape into this one. // - // Each measurement re-runs the body layout. Cards are built and discarded whole, so - // its cached hint describes the card before this one until it is invalidated, and - // re-running it is also what accounts for the parts a card hides and shows as it + // Each measurement re-runs the card's layouts -- every one of them, not just the + // body's own. Changing a label's text posts a LayoutRequest to the widget holding it + // and that event is only delivered when the event loop next runs, so a layout nested + // inside the card still reports the width of the text before the change: measuring + // here and re-measuring on the following refresh then gives two different answers, + // and the panel visibly resizes a frame after its content changed. Re-activating + // them all is what a delivered LayoutRequest would have done. + // + // Cards are built and discarded whole, so this is also what discards the previous + // card's cached hints, and what accounts for the parts a card hides and shows as it // refreshes. The polish belongs to the same step: a freshly created chip reports an // unstyled hint until the stylesheet has reached it, and the chips carry border and // padding that change their size. @@ -273,6 +282,16 @@ void SelectionPanel::placeIn(const QRect& viewRect, const std::vector& oc { m_body->resize(widthPx, m_body->height()); m_body->ensurePolished(); + + // Deepest first, so no layout is re-activated from children that are themselves + // still stale. findChildren walks parents before children, hence the reverse. + const QList nested = m_body->findChildren(); + for (QList::const_reverse_iterator it = nested.rbegin(); + it != nested.rend(); ++it) + { + (*it)->invalidate(); + (*it)->activate(); + } m_body->layout()->invalidate(); m_body->layout()->activate(); return m_body->sizeHint();