Show building_block icon in header stock, expand button and build costs
This commit is contained in:
@@ -3,12 +3,17 @@
|
||||
#include <string>
|
||||
|
||||
#include <QByteArray>
|
||||
#include <QColor>
|
||||
#include <QFile>
|
||||
#include <QFontMetrics>
|
||||
#include <QGridLayout>
|
||||
#include <QIcon>
|
||||
#include <QPainter>
|
||||
#include <QPaintEvent>
|
||||
#include <QPalette>
|
||||
#include <QPixmap>
|
||||
#include <QPushButton>
|
||||
#include <QRect>
|
||||
#include <QRegularExpression>
|
||||
#include <QSignalMapper>
|
||||
#include <QSize>
|
||||
@@ -21,6 +26,7 @@
|
||||
#include "DisplayName.h"
|
||||
#include "EventManager.h"
|
||||
#include "ExitBuilderModeRequestedEvent.h"
|
||||
#include "ItemIconCache.h"
|
||||
#include "Simulation.h"
|
||||
|
||||
namespace
|
||||
@@ -68,15 +74,123 @@ namespace
|
||||
icon.addPixmap(renderChip(greyed.toUtf8()), QIcon::Disabled);
|
||||
return icon;
|
||||
}
|
||||
|
||||
// Normal and grey-background chip pixmaps for a "<id>.svg" file, using the same
|
||||
// recolor rule as loadBuildingIcon. Empty pixmaps if the file cannot be read.
|
||||
struct ChipPixmaps { QPixmap normal; QPixmap grey; };
|
||||
ChipPixmaps loadChipPixmaps(const QString& path)
|
||||
{
|
||||
QFile file(path);
|
||||
if (!file.open(QIODevice::ReadOnly)) { return {}; }
|
||||
const QByteArray svg = file.readAll();
|
||||
|
||||
ChipPixmaps result;
|
||||
result.normal = renderChip(svg);
|
||||
QString greyed = QString::fromUtf8(svg);
|
||||
static const QRegularExpression fillPattern(QStringLiteral("fill=\"#[0-9a-fA-F]{6}\""));
|
||||
const QRegularExpressionMatch match = fillPattern.match(greyed);
|
||||
if (match.hasMatch())
|
||||
{
|
||||
greyed.replace(match.capturedStart(), match.capturedLength(),
|
||||
QStringLiteral("fill=\"#5f636e\""));
|
||||
}
|
||||
result.grey = renderChip(greyed.toUtf8());
|
||||
return result;
|
||||
}
|
||||
|
||||
// A build button that paints its own face — chip icon at the left, the building
|
||||
// name above the cost, and the building_block item icon after the cost number in
|
||||
// place of the "Blocks" word (REQ-UI-BUILD-COST, REQ-UI-BUILD-ICON). Custom paint
|
||||
// (rather than the native icon+text) is needed because a QPushButton holds only
|
||||
// one icon; this stays adaptive to the button width and greys itself when the
|
||||
// button is disabled/unaffordable (REQ-UI-BUILD-DISABLED).
|
||||
class BuildButton : public QPushButton
|
||||
{
|
||||
public:
|
||||
BuildButton(const ChipPixmaps& chip, const QString& name,
|
||||
const QString& costText, const QPixmap& blockIcon, QWidget* parent)
|
||||
: QPushButton(parent)
|
||||
, m_chip(chip)
|
||||
, m_name(name)
|
||||
, m_costText(costText)
|
||||
, m_blockIcon(blockIcon)
|
||||
{
|
||||
}
|
||||
|
||||
protected:
|
||||
void paintEvent(QPaintEvent* event) override
|
||||
{
|
||||
QPushButton::paintEvent(event); // frame, checked/hover state
|
||||
|
||||
QPainter painter(this);
|
||||
painter.setRenderHint(QPainter::Antialiasing, true);
|
||||
painter.setRenderHint(QPainter::SmoothPixmapTransform, true);
|
||||
|
||||
const bool on = isEnabled();
|
||||
const QRect area = rect().adjusted(6, 4, -6, -4);
|
||||
const int chipSize = kIconSize.width();
|
||||
const QPixmap& chip = on ? m_chip.normal : m_chip.grey;
|
||||
if (!chip.isNull())
|
||||
{
|
||||
painter.drawPixmap(
|
||||
QRect(area.x(), area.y() + (area.height() - chipSize) / 2,
|
||||
chipSize, chipSize), chip);
|
||||
}
|
||||
|
||||
const QRect textArea(area.x() + chipSize + 6, area.y(),
|
||||
area.width() - chipSize - 6, area.height());
|
||||
const QFontMetrics metrics(font());
|
||||
const int lineHeight = metrics.height();
|
||||
painter.setFont(font());
|
||||
painter.setPen(palette().color(on ? QPalette::Active : QPalette::Disabled,
|
||||
QPalette::ButtonText));
|
||||
|
||||
// Name fills everything above the bottom cost line (word-wrapped).
|
||||
painter.drawText(
|
||||
QRect(textArea.x(), textArea.y(),
|
||||
textArea.width(), textArea.height() - lineHeight),
|
||||
Qt::AlignLeft | Qt::AlignVCenter | Qt::TextWordWrap, m_name);
|
||||
|
||||
// Cost line: "<n>" then the block icon (or "<n> Blocks" when no icon).
|
||||
const int costY = textArea.bottom() - lineHeight + 1;
|
||||
if (m_blockIcon.isNull())
|
||||
{
|
||||
painter.drawText(
|
||||
QRect(textArea.x(), costY, textArea.width(), lineHeight),
|
||||
Qt::AlignLeft | Qt::AlignVCenter,
|
||||
QObject::tr("%1 Blocks").arg(m_costText));
|
||||
return;
|
||||
}
|
||||
const int costWidth = metrics.horizontalAdvance(m_costText);
|
||||
painter.drawText(QRect(textArea.x(), costY, costWidth, lineHeight),
|
||||
Qt::AlignLeft | Qt::AlignVCenter, m_costText);
|
||||
const qreal iconDpr = m_blockIcon.devicePixelRatio();
|
||||
const int iconW = static_cast<int>(m_blockIcon.width() / iconDpr);
|
||||
const int iconH = static_cast<int>(m_blockIcon.height() / iconDpr);
|
||||
if (!on) { painter.setOpacity(0.45); }
|
||||
painter.drawPixmap(
|
||||
QRect(textArea.x() + costWidth + 4, costY + (lineHeight - iconH) / 2,
|
||||
iconW, iconH), m_blockIcon);
|
||||
}
|
||||
|
||||
private:
|
||||
ChipPixmaps m_chip;
|
||||
QString m_name;
|
||||
QString m_costText;
|
||||
QPixmap m_blockIcon;
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
BuildButtonGrid::BuildButtonGrid(Simulation* sim, const GameConfig* config,
|
||||
const std::string& iconDir, QWidget* parent)
|
||||
const std::string& iconDir,
|
||||
const std::string& itemsIconDir, QWidget* parent)
|
||||
: QWidget(parent)
|
||||
, m_sim(sim)
|
||||
, m_config(config)
|
||||
, m_iconDir(iconDir)
|
||||
, m_itemIcons(std::make_unique<ItemIconCache>(
|
||||
QString::fromStdString(itemsIconDir)))
|
||||
{
|
||||
QGridLayout* layout = new QGridLayout(this);
|
||||
layout->setSpacing(4);
|
||||
@@ -87,6 +201,13 @@ BuildButtonGrid::BuildButtonGrid(Simulation* sim, const GameConfig* config,
|
||||
int row = 0;
|
||||
const int kCols = 3;
|
||||
|
||||
// Block icon shown in each button's cost line (REQ-UI-BUILD-COST); null when no
|
||||
// building_block icon exists, in which case buttons fall back to text costs.
|
||||
const bool hasBlockIcon = m_itemIcons->hasIcon("building_block");
|
||||
const QPixmap blockIcon = hasBlockIcon
|
||||
? m_itemIcons->getPixmap("building_block", QFontMetrics(font()).height())
|
||||
: QPixmap();
|
||||
|
||||
for (const BuildingDef& def : config->buildings.buildings)
|
||||
{
|
||||
if (!def.playerPlaceable)
|
||||
@@ -107,16 +228,28 @@ BuildButtonGrid::BuildButtonGrid(Simulation* sim, const GameConfig* config,
|
||||
const QString name = (def.type == BuildingType::TunnelEntry)
|
||||
? tr("Tunnel")
|
||||
: QString::fromStdString(toDisplayName(def.id));
|
||||
const QString label = name + "\n" + tr("%1 Building Blocks").arg(def.cost);
|
||||
QPushButton* btn = new QPushButton(label, this);
|
||||
btn->setCheckable(true);
|
||||
btn->setFixedHeight(48);
|
||||
// Icon file name matches the building id (REQ-UI-BUILD-GRID); Tunnel Entry's
|
||||
// "tunnel_entry.svg" serves the shared Tunnel button.
|
||||
const QString iconPath = QString::fromStdString(m_iconDir) + "/"
|
||||
+ QString::fromStdString(def.id) + ".svg";
|
||||
btn->setIcon(loadBuildingIcon(iconPath));
|
||||
btn->setIconSize(kIconSize);
|
||||
|
||||
QPushButton* btn = nullptr;
|
||||
if (hasBlockIcon)
|
||||
{
|
||||
// Custom-painted button showing the cost with the block icon in place of
|
||||
// the "Blocks" word (REQ-UI-BUILD-COST).
|
||||
btn = new BuildButton(loadChipPixmaps(iconPath), name,
|
||||
QString::number(def.cost), blockIcon, this);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Fallback: native chip icon + text cost when no block icon exists.
|
||||
btn = new QPushButton(name + "\n" + tr("%1 Blocks").arg(def.cost), this);
|
||||
btn->setIcon(loadBuildingIcon(iconPath));
|
||||
btn->setIconSize(kIconSize);
|
||||
}
|
||||
btn->setCheckable(true);
|
||||
btn->setFixedHeight(48);
|
||||
if (def.tooltip)
|
||||
{
|
||||
btn->setToolTip(QString::fromStdString(*def.tooltip));
|
||||
|
||||
Reference in New Issue
Block a user