From 6fa3ba7f0c08277994137e10e5432f58d043e023 Mon Sep 17 00:00:00 2001 From: Malte Langkabel Date: Fri, 7 Aug 2026 20:56:18 +0200 Subject: [PATCH] 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 Claude-Session: https://claude.ai/code/session_01JcReq7hVk4KUPhTDKWAG7K --- src/ui/SelectionPanel.cpp | 28 ++++++++++++++++++++++----- src/ui/selection/ItemChipRow.cpp | 5 +++++ src/ui/selection/RecipeSummaryRow.cpp | 24 +++++++++++++++++------ 3 files changed, 46 insertions(+), 11 deletions(-) diff --git a/src/ui/SelectionPanel.cpp b/src/ui/SelectionPanel.cpp index aa791d2..4962693 100644 --- a/src/ui/SelectionPanel.cpp +++ b/src/ui/SelectionPanel.cpp @@ -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. diff --git a/src/ui/selection/ItemChipRow.cpp b/src/ui/selection/ItemChipRow.cpp index bccf8a8..7501458 100644 --- a/src/ui/selection/ItemChipRow.cpp +++ b/src/ui/selection/ItemChipRow.cpp @@ -73,6 +73,11 @@ void ItemChipRow::rebuildChips(const std::vector& entries) ItemChip* chip = new ItemChip(icon, this); m_layout->addWidget(chip, static_cast(index) / kChipsPerRow, static_cast(index) % kChipsPerRow); + // Shown right away, because the panel measures itself as soon as this returns. A + // widget created under an already-visible parent starts hidden, and a layout + // treats a hidden item as empty, so an unshown chip would add nothing to the + // size hint and the panel would be fitted to a buffer section that looks empty. + chip->show(); m_chips.push_back(chip); } } diff --git a/src/ui/selection/RecipeSummaryRow.cpp b/src/ui/selection/RecipeSummaryRow.cpp index 37e3369..ecf85b5 100644 --- a/src/ui/selection/RecipeSummaryRow.cpp +++ b/src/ui/selection/RecipeSummaryRow.cpp @@ -13,6 +13,18 @@ namespace // Size the item icons are drawn at on the summary line, in device-independent pixels. const int kSummaryIconSizePx = 14; +// Adds a freshly built label to the summary and shows it. +// +// The show is what makes it count: a widget created under an already-visible parent +// starts hidden, and a layout treats a hidden item as empty, so an unshown label would +// add nothing to the size hint the panel measures itself against as soon as this +// returns. +void addAndShow(QHBoxLayout* layout, QLabel* label) +{ + layout->addWidget(label); + label->show(); +} + } // namespace @@ -71,14 +83,14 @@ void RecipeSummaryRow::rebuild(const std::vector& inputs, if (!inputs.empty()) { const QChar rightArrow(0x2192); // U+2192 RIGHTWARDS ARROW - m_layout->addWidget(new QLabel(QString(rightArrow), this)); + addAndShow(m_layout, new QLabel(QString(rightArrow), this)); } addAmounts(outputs); if (durationSeconds > 0.0) { const QChar middleDot(0x00B7); // U+00B7 MIDDLE DOT - m_layout->addWidget(new QLabel( + addAndShow(m_layout, new QLabel( QStringLiteral("%1 %2").arg(middleDot).arg( tr("%1 s").arg(durationSeconds, 0, 'f', 1)), this)); } @@ -96,13 +108,13 @@ void RecipeSummaryRow::addAmounts(const std::vector& amounts) QLabel* iconLabel = new QLabel(this); iconLabel->setPixmap( m_itemIcons->getPixmap(entry.itemId, kSummaryIconSizePx)); - m_layout->addWidget(iconLabel); + addAndShow(m_layout, iconLabel); } else { - m_layout->addWidget( - new QLabel(QString::fromStdString(entry.itemId), this)); + addAndShow(m_layout, + new QLabel(QString::fromStdString(entry.itemId), this)); } - m_layout->addWidget(new QLabel(QString::number(entry.amount), this)); + addAndShow(m_layout, new QLabel(QString::number(entry.amount), this)); } }