#include "RecipeLineRow.h" #include #include #include #include #include #include #include #include "BuildingIconCache.h" #include "ItemIconCache.h" namespace { // Size the items' colored squares and the building's chip are drawn at on a line, in // device-independent pixels. Larger than the artwork they carry, which the square // insets (REQ-UI-ITEM-ICON). const int kIconSizePx = 18; // Adds a freshly built label to the line 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 surrounding widget measures itself against as soon // as this returns. void addAndShow(QHBoxLayout* layout, QLabel* label) { layout->addWidget(label); label->show(); } // Empties one of the two rows, leaving the row widget and its layout in place. void clearRow(QHBoxLayout* layout) { while (QLayoutItem* item = layout->takeAt(0)) { if (item->widget()) { item->widget()->deleteLater(); } delete item; } } } // namespace RecipeLineRow::RecipeLineRow(ItemIconCache* itemIcons, BuildingIconCache* buildingIcons, QWidget* parent) : QWidget(parent) , m_itemIcons(itemIcons) , m_buildingIcons(buildingIcons) { m_outerLayout = new QVBoxLayout(this); m_outerLayout->setContentsMargins(0, 0, 0, 0); m_outerLayout->setSpacing(1); m_headerRow = new QWidget(this); m_headerLayout = new QHBoxLayout(m_headerRow); m_headerLayout->setContentsMargins(0, 0, 0, 0); m_headerLayout->setSpacing(4); m_headerRow->hide(); m_outerLayout->addWidget(m_headerRow); m_amountsRow = new QWidget(this); m_amountsLayout = new QHBoxLayout(m_amountsRow); m_amountsLayout->setContentsMargins(0, 0, 0, 0); m_amountsLayout->setSpacing(4); m_outerLayout->addWidget(m_amountsRow); hide(); } void RecipeLineRow::setCardChrome(bool enabled) { // Palette colors like the item chip's chrome (REQ-UI-SELECTION-CARD), not // visuals.toml, which is for world rendering. The selector names this class alone, so // nothing inside the card inherits the border. setAttribute(Qt::WA_StyledBackground, enabled); setStyleSheet(enabled ? QStringLiteral("RecipeLineRow { border: 1px solid palette(mid); " "border-radius: 4px; }") : QString()); m_outerLayout->setContentsMargins(enabled ? QMargins(6, 4, 6, 4) : QMargins(0, 0, 0, 0)); } void RecipeLineRow::setLine(const Spec& spec) { if (spec.isEmpty()) { // Forgotten as well as hidden, so re-selecting the same recipe later is seen as // a change and shows the row again. m_spec = Spec(); hide(); return; } if (spec == m_spec) { return; } m_spec = spec; rebuild(spec); show(); } void RecipeLineRow::rebuild(const Spec& spec) { clearRow(m_headerLayout); clearRow(m_amountsLayout); // First line: which building runs the recipe and which recipe it is, where the line // has to distinguish one producer from another (REQ-UI-ITEM-TOOLTIP). A building with // no chip file leaves the icon off, as everywhere else (REQ-UI-BUILD-ICON). if (spec.building.has_value() && m_buildingIcons != nullptr) { const QPixmap chip = m_buildingIcons->getChip(buildingTypeId(*spec.building), kIconSizePx); if (!chip.isNull()) { QLabel* chipLabel = new QLabel(m_headerRow); chipLabel->setPixmap(chip); addAndShow(m_headerLayout, chipLabel); } } if (!spec.name.isEmpty()) { QLabel* nameLabel = new QLabel(spec.name, m_headerRow); QFont nameFont = nameLabel->font(); nameFont.setBold(true); nameLabel->setFont(nameFont); addAndShow(m_headerLayout, nameLabel); } const bool hasHeader = m_headerLayout->count() > 0; if (hasHeader) { m_headerLayout->addStretch(1); } m_headerRow->setVisible(hasHeader); // Second line: what the cycle costs, makes and takes. addAmounts(spec.inputs); if (!spec.inputs.empty() && !spec.outputs.empty()) { const QChar rightArrow(0x2192); // U+2192 RIGHTWARDS ARROW addAndShow(m_amountsLayout, new QLabel(QString(rightArrow), m_amountsRow)); } addAmounts(spec.outputs); if (spec.durationSeconds.has_value() && *spec.durationSeconds > 0.0) { const QChar middleDot(0x00B7); // U+00B7 MIDDLE DOT const QString time = spec.durationIsAddition ? tr("+%1 s").arg(*spec.durationSeconds, 0, 'f', 1) : tr("%1 s").arg(*spec.durationSeconds, 0, 'f', 1); addAndShow(m_amountsLayout, new QLabel( QStringLiteral("%1 %2").arg(middleDot).arg(time), m_amountsRow)); } m_amountsLayout->addStretch(1); } void RecipeLineRow::addAmounts(const std::vector& amounts) { for (const Amount& entry : amounts) { // Between one item and the next, so a run of icons and numbers reads as a sum // rather than as a list. if (&entry != &amounts.front()) { addAndShow(m_amountsLayout, new QLabel(QStringLiteral("+"), m_amountsRow)); } // The item's icon on its colored square (REQ-UI-ITEM-ICON). A missing icon file // is not an error: the square stands alone then, and only an item with no square // either falls back to its id in text. const QPixmap icon = (m_itemIcons != nullptr) ? m_itemIcons->getSquarePixmap(entry.itemId, kIconSizePx) : QPixmap(); if (!icon.isNull()) { QLabel* iconLabel = new QLabel(m_amountsRow); iconLabel->setPixmap(icon); addAndShow(m_amountsLayout, iconLabel); } else { addAndShow(m_amountsLayout, new QLabel(QString::fromStdString(entry.itemId), m_amountsRow)); } addAndShow(m_amountsLayout, new QLabel(QString::number(entry.amount), m_amountsRow)); } }