Four separate caches rasterized the same item SVGs, one of them rebuilt on every recipe-dialog open. MainWindow now owns one cache and hands a non-owning pointer to HeaderBar, BuildButtonGrid, GameWorldView and RecipeSelectionDialog. Co-Authored-By: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01GH8ZMRY3vhxxXcaUxBqxkk
224 lines
7.0 KiB
C++
224 lines
7.0 KiB
C++
#include "HeaderBar.h"
|
|
|
|
#include <cmath>
|
|
#include <string>
|
|
|
|
#include <QFontMetrics>
|
|
#include <QHBoxLayout>
|
|
#include <QIcon>
|
|
#include <QLabel>
|
|
#include <QPalette>
|
|
#include <QPushButton>
|
|
#include <QSignalMapper>
|
|
#include <QSize>
|
|
|
|
#include "Command.h"
|
|
#include "CommandRequestedEvent.h"
|
|
#include "EventManager.h"
|
|
#include "IconCaption.h"
|
|
#include "ItemIconCache.h"
|
|
#include "Simulation.h"
|
|
#include "SpeedChangeRequestedEvent.h"
|
|
#include "Tick.h"
|
|
|
|
namespace
|
|
{
|
|
// Item id of the building blocks resource, whose icon stands in for the "Blocks"
|
|
// word in the header stock and expand button (REQ-UI-BLOCKS-ICON).
|
|
const char* const kBlockItemId = "building_block";
|
|
}
|
|
|
|
const double HeaderBar::kSpeeds[] = { 0.0, 0.5, 1.0, 2.0, 10.0 };
|
|
const int HeaderBar::kSpeedCount = 5;
|
|
|
|
HeaderBar::HeaderBar(const Simulation* sim, const GameConfig* config,
|
|
ItemIconCache* itemIcons, QWidget* parent)
|
|
: QWidget(parent)
|
|
, m_itemIcons(itemIcons)
|
|
, m_sim(sim)
|
|
{
|
|
QHBoxLayout* layout = new QHBoxLayout(this);
|
|
layout->setContentsMargins(8, 4, 8, 4);
|
|
layout->setSpacing(8);
|
|
|
|
m_timeLabel = new QLabel("00:00", this);
|
|
m_blocksLabel = new QLabel(this);
|
|
if (config->world.buildingBlocksTooltip)
|
|
{
|
|
m_blocksLabel->setToolTip(
|
|
QString::fromStdString(*config->world.buildingBlocksTooltip));
|
|
}
|
|
updateBlocksLabel();
|
|
m_artifactsLabel = new QLabel(tr("Artifacts: 0/?"), this);
|
|
if (config->world.artifactTooltip)
|
|
{
|
|
m_artifactsLabel->setToolTip(
|
|
QString::fromStdString(*config->world.artifactTooltip));
|
|
}
|
|
m_bossWaveLabel = new QLabel(tr("Boss Wave #1"), this);
|
|
m_nextBossLabel = new QLabel(tr("Next boss: 5:00"), this);
|
|
|
|
// Asteroid expansion button, to the left of the speed buttons (REQ-UI-HEADER,
|
|
// REQ-UI-EXPAND-BUTTON). Caption/enabled state are set on the first
|
|
// ExpansionCostChangedEvent; clicking requests an ExpandAsteroidCommand.
|
|
m_expandButton = new QPushButton(tr("Expand"), this);
|
|
connect(m_expandButton, &QPushButton::clicked, this, []() {
|
|
EventManager::getInstance()->sendEventImmediately(
|
|
std::make_shared<CommandRequestedEvent>(
|
|
std::make_shared<ExpandAsteroidCommand>()));
|
|
});
|
|
|
|
const int itemSpacing = 20;
|
|
|
|
layout->addWidget(m_timeLabel);
|
|
layout->addSpacing(itemSpacing);
|
|
layout->addWidget(m_blocksLabel);
|
|
layout->addSpacing(itemSpacing);
|
|
layout->addWidget(m_artifactsLabel);
|
|
layout->addStretch();
|
|
layout->addWidget(m_bossWaveLabel);
|
|
layout->addSpacing(itemSpacing);
|
|
layout->addWidget(m_nextBossLabel);
|
|
layout->addSpacing(itemSpacing);
|
|
layout->addWidget(m_expandButton);
|
|
layout->addSpacing(itemSpacing);
|
|
|
|
const char* labels[] = { "0x", "0.5x", "1x", "2x", "10x" };
|
|
QSignalMapper* mapper = new QSignalMapper(this);
|
|
for (int i = 0; i < kSpeedCount; ++i)
|
|
{
|
|
QPushButton* btn = new QPushButton(labels[i], this);
|
|
btn->setCheckable(true);
|
|
btn->setChecked(i == 2);
|
|
layout->addWidget(btn);
|
|
m_speedButtons.push_back(btn);
|
|
mapper->setMapping(btn, i);
|
|
connect(btn, &QPushButton::clicked, mapper, qOverload<>(&QSignalMapper::map));
|
|
}
|
|
connect(mapper, qOverload<int>(&QSignalMapper::mapped), this, &HeaderBar::onSpeedButton);
|
|
|
|
setFixedHeight(sizeHint().height());
|
|
|
|
registerForEvents();
|
|
}
|
|
|
|
HeaderBar::~HeaderBar()
|
|
{
|
|
unregisterForEvents();
|
|
}
|
|
|
|
void HeaderBar::handleEvent(std::shared_ptr<const TickAdvancedEvent> event)
|
|
{
|
|
const int totalSeconds = static_cast<int>(ticksToSeconds(event->tick));
|
|
m_timeLabel->setText(
|
|
QString("%1:%2")
|
|
.arg(totalSeconds / 60, 2, 10, QChar('0'))
|
|
.arg(totalSeconds % 60, 2, 10, QChar('0')));
|
|
}
|
|
|
|
void HeaderBar::handleEvent(std::shared_ptr<const BuildingBlocksChangedEvent> /*event*/)
|
|
{
|
|
updateBlocksLabel();
|
|
updateExpandButton();
|
|
}
|
|
|
|
void HeaderBar::handleEvent(std::shared_ptr<const ExpansionCostChangedEvent> /*event*/)
|
|
{
|
|
updateExpandButton();
|
|
}
|
|
|
|
QPixmap HeaderBar::blockIcon() const
|
|
{
|
|
if (!m_itemIcons->hasIcon(kBlockItemId)) { return QPixmap(); }
|
|
// Sized to the header text height so it sits inline with the caption.
|
|
const int sizePx = QFontMetrics(font()).height();
|
|
return m_itemIcons->getPixmap(kBlockItemId, sizePx);
|
|
}
|
|
|
|
void HeaderBar::updateBlocksLabel()
|
|
{
|
|
const int blocks = m_sim->getBuildingBlocksStock();
|
|
|
|
const QPixmap icon = blockIcon();
|
|
if (icon.isNull())
|
|
{
|
|
// Fallback text form when no building_block icon exists (REQ-UI-BLOCKS-ICON).
|
|
m_blocksLabel->setText(tr("Stock: %1 Blocks").arg(blocks));
|
|
return;
|
|
}
|
|
m_blocksLabel->setPixmap(renderCaptionWithIcon(
|
|
tr("Stock: %1").arg(blocks), icon, font(),
|
|
m_blocksLabel->palette().color(QPalette::WindowText)));
|
|
}
|
|
|
|
void HeaderBar::updateExpandButton()
|
|
{
|
|
const int blocks = m_sim->getBuildingBlocksStock();
|
|
const int expansionCost = m_sim->getCurrentExpansionCost();
|
|
|
|
m_expandButton->setEnabled(blocks >= expansionCost);
|
|
|
|
const QPixmap icon = blockIcon();
|
|
if (icon.isNull())
|
|
{
|
|
// Fallback text form when no building_block icon exists (REQ-UI-EXPAND-BUTTON).
|
|
m_expandButton->setIcon(QIcon());
|
|
m_expandButton->setText(tr("Expand: %1 Blocks").arg(expansionCost));
|
|
return;
|
|
}
|
|
|
|
const QString text = tr("Expand: %1").arg(expansionCost);
|
|
const QPalette& pal = m_expandButton->palette();
|
|
const QPixmap normal = renderCaptionWithIcon(
|
|
text, icon, m_expandButton->font(), pal.color(QPalette::ButtonText));
|
|
const QPixmap greyed = renderCaptionWithIcon(
|
|
text, icon, m_expandButton->font(),
|
|
pal.color(QPalette::Disabled, QPalette::ButtonText));
|
|
|
|
QIcon buttonIcon;
|
|
buttonIcon.addPixmap(normal, QIcon::Normal);
|
|
buttonIcon.addPixmap(greyed, QIcon::Disabled);
|
|
m_expandButton->setText(QString());
|
|
m_expandButton->setIcon(buttonIcon);
|
|
const qreal dpr = normal.devicePixelRatio();
|
|
m_expandButton->setIconSize(QSize(
|
|
static_cast<int>(normal.width() / dpr),
|
|
static_cast<int>(normal.height() / dpr)));
|
|
}
|
|
|
|
void HeaderBar::handleEvent(std::shared_ptr<const GameSpeedChangedEvent> event)
|
|
{
|
|
for (int i = 0; i < kSpeedCount; ++i)
|
|
{
|
|
m_speedButtons[static_cast<std::size_t>(i)]->setChecked(
|
|
std::abs(kSpeeds[i] - event->speed) < 0.001);
|
|
}
|
|
}
|
|
|
|
void HeaderBar::handleEvent(std::shared_ptr<const BossWaveUpdatedEvent> event)
|
|
{
|
|
const int bossSeconds = static_cast<int>(
|
|
ticksToSeconds(event->countdownTicks > 0 ? event->countdownTicks : 0));
|
|
m_bossWaveLabel->setText(
|
|
tr("Boss Wave #%1")
|
|
.arg(event->counter));
|
|
m_nextBossLabel->setText(
|
|
tr("Next boss: %1:%2")
|
|
.arg(bossSeconds / 60)
|
|
.arg(bossSeconds % 60, 2, 10, QChar('0')));
|
|
}
|
|
|
|
void HeaderBar::handleEvent(std::shared_ptr<const ArtifactCountChangedEvent> event)
|
|
{
|
|
m_artifactsLabel->setText(tr("Artifacts: %1/%2").arg(event->count).arg(event->winCount));
|
|
}
|
|
|
|
void HeaderBar::onSpeedButton(int index)
|
|
{
|
|
if (index >= 0 && index < kSpeedCount)
|
|
{
|
|
EventManager::getInstance()->sendEventImmediately(
|
|
std::make_shared<SpeedChangeRequestedEvent>(kSpeeds[index]));
|
|
}
|
|
}
|