show recipes visually instead of describing them in text

This commit is contained in:
2026-08-11 21:40:36 +02:00
parent 3274df91d4
commit f255224ccd
38 changed files with 1023 additions and 452 deletions

140
src/ui/RecipeLineRow.cpp Normal file
View File

@@ -0,0 +1,140 @@
#include "RecipeLineRow.h"
#include <QHBoxLayout>
#include <QLabel>
#include <QLayoutItem>
#include <QPixmap>
#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();
}
} // namespace
RecipeLineRow::RecipeLineRow(ItemIconCache* itemIcons, BuildingIconCache* buildingIcons,
QWidget* parent)
: QWidget(parent)
, m_itemIcons(itemIcons)
, m_buildingIcons(buildingIcons)
{
m_layout = new QHBoxLayout(this);
m_layout->setContentsMargins(0, 0, 0, 0);
m_layout->setSpacing(4);
hide();
}
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)
{
while (QLayoutItem* item = m_layout->takeAt(0))
{
if (item->widget())
{
item->widget()->deleteLater();
}
delete item;
}
// The building the recipe runs in, 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(this);
chipLabel->setPixmap(chip);
addAndShow(m_layout, chipLabel);
}
}
if (!spec.name.isEmpty())
{
addAndShow(m_layout, new QLabel(spec.name, this));
}
addAmounts(spec.inputs);
if (!spec.inputs.empty() && !spec.outputs.empty())
{
const QChar rightArrow(0x2192); // U+2192 RIGHTWARDS ARROW
addAndShow(m_layout, new QLabel(QString(rightArrow), this));
}
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_layout, new QLabel(
QStringLiteral("%1 %2").arg(middleDot).arg(time), this));
}
m_layout->addStretch(1);
}
void RecipeLineRow::addAmounts(const std::vector<Amount>& amounts)
{
for (const Amount& entry : amounts)
{
// 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(this);
iconLabel->setPixmap(icon);
addAndShow(m_layout, iconLabel);
}
else
{
addAndShow(m_layout,
new QLabel(QString::fromStdString(entry.itemId), this));
}
addAndShow(m_layout, new QLabel(QString::number(entry.amount), this));
}
}