Files
dota_factory/src/ui/RecipeLineRow.cpp

197 lines
6.2 KiB
C++

#include "RecipeLineRow.h"
#include <QFont>
#include <QHBoxLayout>
#include <QLabel>
#include <QLayoutItem>
#include <QMargins>
#include <QPixmap>
#include <QVBoxLayout>
#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<Amount>& 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));
}
}