show the selection card before measuring the panel against it

Clicking something sometimes left the panel collapsed to its scroll bar, most
often for field entities.

Same cause as the controls panel's collapse in d7c6734: a widget created under
an already-visible parent starts hidden, and a layout counts a hidden item as
empty. rebuildContent() built the card, added it to the body layout and
measured immediately, so the body reported nothing but its own margins. A width
of nearly zero makes heightForWidth() on the wrapped labels enormous, that
overflows the band, and refit() then caps the height and widens by the scroll
bar -- leaving the panel exactly one scroll bar wide.

It could not recover on the next tick either, because a word-wrapped label's
size hint follows its current width: once narrow, it keeps reporting narrow and
tall. So refit() now measures the body at the width cap rather than at whatever
width the panel currently has, and invalidates the body layout before reading
it -- cards are built and discarded whole, so its cached hint otherwise
describes the card before this one.

Field entities hit it more often because a field selection publishes twice,
actors then debris, so one click rebuilds the card twice.

The same defect existed one level down, where parts are rebuilt while the card
is already visible: ItemChipRow::rebuildChips() and RecipeSummaryRow::rebuild()
now show what they create. Those were measuring the buffer section and the
recipe line as empty on every recipe change.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JcReq7hVk4KUPhTDKWAG7K
This commit is contained in:
2026-08-07 20:56:18 +02:00
parent f06d79e60d
commit 6fa3ba7f0c
3 changed files with 46 additions and 11 deletions

View File

@@ -193,6 +193,13 @@ void SelectionPanel::rebuildContent()
if (m_content)
{
m_bodyLayout->addWidget(m_content);
// The show is what makes the card count. A widget created under an
// already-visible parent starts hidden, and a layout treats a hidden item as
// empty -- it adds nothing to the size hint until something shows it, which
// otherwise does not happen until the event loop next runs, long after refit()
// has measured the panel. The panel then fits itself to an empty body and
// collapses to its scroll bar.
m_content->show();
m_content->refresh();
}
updateVisibility();
@@ -214,11 +221,6 @@ void SelectionPanel::updateVisibility()
void SelectionPanel::refit()
{
// The layout drops hidden widgets from its size hint, but only once it has been
// re-run: a card hides and shows its parts as it refreshes, before Qt would get
// around to it on its own.
m_body->layout()->activate();
const QRect band =
m_bandRect.adjusted(kMarginPx, kMarginPx, -kMarginPx, -kMarginPx);
@@ -234,6 +236,22 @@ void SelectionPanel::refit()
return;
}
// Measured at the cap rather than at whatever width the panel happens to have. A
// word-wrapped label's size hint follows its current width, so a panel that once
// came out too narrow would keep reporting a narrow, tall hint and stay that way.
m_body->resize(maxWidthPx, m_body->height());
// Cards are built and thrown away whole, so the body layout's cached hint describes
// the card before this one until it is discarded. Re-running the layout is what
// makes the current card's parts measurable: a card hides and shows its parts as it
// refreshes, and Qt would not get around to either on its own before the panel is
// sized here. 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 width.
m_body->ensurePolished();
m_body->layout()->invalidate();
m_body->layout()->activate();
int contentWidthPx = qMin(m_body->sizeHint().width(), maxWidthPx);
// Word-wrapped labels only know their height once the width is fixed; the layout
// reports -1 when nothing in it wraps, in which case the plain hint is exact.