fix issue where the selection panel sometimes collapses to very small size
This commit is contained in:
@@ -62,7 +62,13 @@ SelectionPanel::SelectionPanel(Simulation* sim, const GameConfig* config,
|
|||||||
m_scrollArea->setFrameShape(QFrame::NoFrame);
|
m_scrollArea->setFrameShape(QFrame::NoFrame);
|
||||||
m_scrollArea->setWidgetResizable(true);
|
m_scrollArea->setWidgetResizable(true);
|
||||||
m_scrollArea->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
|
m_scrollArea->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
|
||||||
m_scrollArea->setVerticalScrollBarPolicy(Qt::ScrollBarAsNeeded);
|
// The panel works out for itself whether the card fits the band, and sizes itself to
|
||||||
|
// leave room for the bar when it does not (REQ-UI-SELECTION-PANEL), so refit() sets
|
||||||
|
// this policy rather than leaving the scroll area to decide. Asked to decide, it
|
||||||
|
// shows a bar the moment the card is momentarily larger than the viewport -- which
|
||||||
|
// happens while the card is being measured -- and does not take it back when the
|
||||||
|
// range turns out to be empty.
|
||||||
|
m_scrollArea->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
|
||||||
m_scrollArea->viewport()->setAutoFillBackground(false);
|
m_scrollArea->viewport()->setAutoFillBackground(false);
|
||||||
m_body->setAutoFillBackground(false);
|
m_body->setAutoFillBackground(false);
|
||||||
m_scrollArea->setWidget(m_body);
|
m_scrollArea->setWidget(m_body);
|
||||||
@@ -193,6 +199,13 @@ void SelectionPanel::rebuildContent()
|
|||||||
if (m_content)
|
if (m_content)
|
||||||
{
|
{
|
||||||
m_bodyLayout->addWidget(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();
|
m_content->refresh();
|
||||||
}
|
}
|
||||||
updateVisibility();
|
updateVisibility();
|
||||||
@@ -214,11 +227,6 @@ void SelectionPanel::updateVisibility()
|
|||||||
|
|
||||||
void SelectionPanel::refit()
|
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 =
|
const QRect band =
|
||||||
m_bandRect.adjusted(kMarginPx, kMarginPx, -kMarginPx, -kMarginPx);
|
m_bandRect.adjusted(kMarginPx, kMarginPx, -kMarginPx, -kMarginPx);
|
||||||
|
|
||||||
@@ -234,26 +242,61 @@ void SelectionPanel::refit()
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
int contentWidthPx = qMin(m_body->sizeHint().width(), maxWidthPx);
|
// What the card asks for at a given width. The width has to be applied before
|
||||||
// Word-wrapped labels only know their height once the width is fixed; the layout
|
// asking, because a card's height depends on the room it is given -- and so, once
|
||||||
// reports -1 when nothing in it wraps, in which case the plain hint is exact.
|
// laid out, does the width it reports. Measuring at whatever width the panel
|
||||||
int contentHeightPx = m_body->heightForWidth(contentWidthPx);
|
// happens to have carries the previous card's shape into this one.
|
||||||
if (contentHeightPx < 0)
|
//
|
||||||
|
// 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
|
||||||
|
// 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.
|
||||||
|
auto measureAt = [this](int widthPx) -> QSize
|
||||||
{
|
{
|
||||||
contentHeightPx = m_body->sizeHint().height();
|
m_body->resize(widthPx, m_body->height());
|
||||||
}
|
m_body->ensurePolished();
|
||||||
|
m_body->layout()->invalidate();
|
||||||
|
m_body->layout()->activate();
|
||||||
|
return m_body->sizeHint();
|
||||||
|
};
|
||||||
|
|
||||||
if (contentHeightPx > maxHeightPx)
|
// Run twice. Parts of a card report an unstyled size until the style has actually
|
||||||
|
// reached them, which for a freshly built card happens during the first round of
|
||||||
|
// measuring; the second round then measures a card that is fully laid out and
|
||||||
|
// settles on the answer. Without it a card can end up a few pixels short of what it
|
||||||
|
// turns out to need, and the difference shows as a scroll bar over a card that
|
||||||
|
// looks like it fits.
|
||||||
|
for (int pass = 0; pass < 2; ++pass)
|
||||||
{
|
{
|
||||||
|
// First at the cap, the most room the card can ever get, to learn how wide it
|
||||||
|
// wants to be; then at that width for the height that follows from it.
|
||||||
|
int contentWidthPx = qMin(measureAt(maxWidthPx).width(), maxWidthPx);
|
||||||
|
int contentHeightPx = measureAt(contentWidthPx).height();
|
||||||
|
|
||||||
// A card taller than the band is capped there and scrolls
|
// A card taller than the band is capped there and scrolls
|
||||||
// (REQ-UI-SELECTION-PANEL). The scroll bar is laid out beside the card, so the
|
// (REQ-UI-SELECTION-PANEL). The bar is laid out beside the card, so the panel
|
||||||
// panel widens by its width to keep the card as wide as the height was computed
|
// widens by its width to leave the card the width its height was measured for --
|
||||||
// for.
|
// and where the cap does not allow that, the card is measured again at what is
|
||||||
|
// left over.
|
||||||
|
const bool scrolls = (contentHeightPx > maxHeightPx);
|
||||||
|
int viewportWidthPx = contentWidthPx;
|
||||||
|
if (scrolls)
|
||||||
|
{
|
||||||
|
const int scrollBarWidthPx =
|
||||||
|
m_scrollArea->verticalScrollBar()->sizeHint().width();
|
||||||
|
contentWidthPx = qMin(contentWidthPx + scrollBarWidthPx, maxWidthPx);
|
||||||
contentHeightPx = maxHeightPx;
|
contentHeightPx = maxHeightPx;
|
||||||
contentWidthPx = qMin(
|
viewportWidthPx = contentWidthPx - scrollBarWidthPx;
|
||||||
contentWidthPx + m_scrollArea->verticalScrollBar()->sizeHint().width(),
|
measureAt(viewportWidthPx);
|
||||||
maxWidthPx);
|
|
||||||
}
|
}
|
||||||
|
m_scrollArea->setVerticalScrollBarPolicy(
|
||||||
|
scrolls ? Qt::ScrollBarAlwaysOn : Qt::ScrollBarAlwaysOff);
|
||||||
|
|
||||||
|
// Left at the size the scroll area is about to give it, so the card is not
|
||||||
|
// briefly wider than its viewport.
|
||||||
|
m_body->resize(viewportWidthPx, m_body->sizeHint().height());
|
||||||
|
|
||||||
const int panelWidthPx = contentWidthPx + 2 * borderPx;
|
const int panelWidthPx = contentWidthPx + 2 * borderPx;
|
||||||
const int panelHeightPx = contentHeightPx + 2 * borderPx;
|
const int panelHeightPx = contentHeightPx + 2 * borderPx;
|
||||||
@@ -264,4 +307,5 @@ void SelectionPanel::refit()
|
|||||||
setGeometry(band.right() - panelWidthPx + 1,
|
setGeometry(band.right() - panelWidthPx + 1,
|
||||||
band.top() + (band.height() - panelHeightPx) / 2,
|
band.top() + (band.height() - panelHeightPx) / 2,
|
||||||
panelWidthPx, panelHeightPx);
|
panelWidthPx, panelHeightPx);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -73,6 +73,11 @@ void ItemChipRow::rebuildChips(const std::vector<Entry>& entries)
|
|||||||
ItemChip* chip = new ItemChip(icon, this);
|
ItemChip* chip = new ItemChip(icon, this);
|
||||||
m_layout->addWidget(chip, static_cast<int>(index) / kChipsPerRow,
|
m_layout->addWidget(chip, static_cast<int>(index) / kChipsPerRow,
|
||||||
static_cast<int>(index) % kChipsPerRow);
|
static_cast<int>(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);
|
m_chips.push_back(chip);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,6 +13,18 @@ namespace
|
|||||||
// Size the item icons are drawn at on the summary line, in device-independent pixels.
|
// Size the item icons are drawn at on the summary line, in device-independent pixels.
|
||||||
const int kSummaryIconSizePx = 14;
|
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
|
} // namespace
|
||||||
|
|
||||||
|
|
||||||
@@ -71,14 +83,14 @@ void RecipeSummaryRow::rebuild(const std::vector<Amount>& inputs,
|
|||||||
if (!inputs.empty())
|
if (!inputs.empty())
|
||||||
{
|
{
|
||||||
const QChar rightArrow(0x2192); // U+2192 RIGHTWARDS ARROW
|
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);
|
addAmounts(outputs);
|
||||||
|
|
||||||
if (durationSeconds > 0.0)
|
if (durationSeconds > 0.0)
|
||||||
{
|
{
|
||||||
const QChar middleDot(0x00B7); // U+00B7 MIDDLE DOT
|
const QChar middleDot(0x00B7); // U+00B7 MIDDLE DOT
|
||||||
m_layout->addWidget(new QLabel(
|
addAndShow(m_layout, new QLabel(
|
||||||
QStringLiteral("%1 %2").arg(middleDot).arg(
|
QStringLiteral("%1 %2").arg(middleDot).arg(
|
||||||
tr("%1 s").arg(durationSeconds, 0, 'f', 1)), this));
|
tr("%1 s").arg(durationSeconds, 0, 'f', 1)), this));
|
||||||
}
|
}
|
||||||
@@ -96,13 +108,13 @@ void RecipeSummaryRow::addAmounts(const std::vector<Amount>& amounts)
|
|||||||
QLabel* iconLabel = new QLabel(this);
|
QLabel* iconLabel = new QLabel(this);
|
||||||
iconLabel->setPixmap(
|
iconLabel->setPixmap(
|
||||||
m_itemIcons->getPixmap(entry.itemId, kSummaryIconSizePx));
|
m_itemIcons->getPixmap(entry.itemId, kSummaryIconSizePx));
|
||||||
m_layout->addWidget(iconLabel);
|
addAndShow(m_layout, iconLabel);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
m_layout->addWidget(
|
addAndShow(m_layout,
|
||||||
new QLabel(QString::fromStdString(entry.itemId), this));
|
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));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user