Files
dota_factory/src/ui/HeaderBar.cpp
Malte Langkabel abe8c8f448 stand the expansion button on the ground it buys
The button leaves the header bar for the world, centered on the columns
the next purchase unlocks and panning with them until it leaves the view.
Those columns now take a lighter tint than the locked ground behind them,
so the boundary between the two says how far one purchase reaches.

Nothing in the code reported a scroll: the camera knew it had moved and
told only the hover. A ViewScrolledEvent says it now, which is what a
widget keeping a place in the world rather than on the screen needs. The
panels want none of it -- they are placed against the screen and stay put
as the world moves under them -- so the button is placed after their pass
and never joins the rectangles they step around.

Its face is two lines, "Expand" over the cost. That makes the cost a
display of its own, so it carries the building-block tooltip the way every
other item value does, on hover alone since the click buys the expansion.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Ne3mejABZoLWKLh8fgpM3x
2026-08-18 22:55:06 +02:00

161 lines
5.2 KiB
C++

#include "HeaderBar.h"
#include <cmath>
#include <string>
#include <QHBoxLayout>
#include <QLabel>
#include <QPalette>
#include <QPushButton>
#include <QSignalMapper>
#include "EventManager.h"
#include "IconCaption.h"
#include "ItemIconCache.h"
#include "ItemTooltip.h"
#include "Simulation.h"
#include "SpeedChangeRequestedEvent.h"
#include "Tick.h"
#include "TooltipTrigger.h"
const double HeaderBar::kSpeeds[] = { 0.0, 0.5, 1.0, 2.0, 10.0 };
const int HeaderBar::kSpeedCount = 5;
HeaderBar::HeaderBar(const ItemTooltipContext& context, QWidget* parent)
: QWidget(parent)
, m_context(context)
{
QHBoxLayout* layout = new QHBoxLayout(this);
layout->setContentsMargins(8, 4, 8, 4);
layout->setSpacing(8);
m_timeLabel = new QLabel("00:00", this);
// Both displays state a value and do nothing when clicked, so a click brings the
// tooltip up at once rather than waiting the hover out (REQ-UI-TOOLTIP-TRIGGER).
// The stock states an amount of an item, so what it shows is that item's production
// tooltip, as every other display naming an item does
// (REQ-UI-BLOCKS-ICON, REQ-UI-ITEM-VALUE-TOOLTIP).
m_blocksLabel = new QLabel(this);
ItemTooltip::attachTo(*m_blocksLabel, m_context, kBlockItemId,
TooltipTrigger::Trigger::HoverAndClick);
updateBlocksLabel();
// An artifact count is no item amount, so it has no production path to show and
// states configured text instead (REQ-UI-ARTIFACTS-TOOLTIP).
m_artifactsLabel = new QLabel(tr("Artifacts: 0/?"), this);
if (m_context.config->world.artifactTooltip)
{
TooltipTrigger::attachText(
*m_artifactsLabel,
QString::fromStdString(*m_context.config->world.artifactTooltip),
TooltipTrigger::Trigger::HoverAndClick);
}
m_bossWaveLabel = new QLabel(tr("Boss Wave #1"), this);
m_nextBossLabel = new QLabel(tr("Next boss: 5:00"), this);
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);
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(m_context.sim->getCurrentTick()));
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();
}
void HeaderBar::updateBlocksLabel()
{
const int blocks = m_context.sim->getBuildingBlocksStock();
const QPixmap icon = m_context.itemIcons->getInlineIcon(kBlockItemId, font());
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::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 Tick countdownTicks = m_context.sim->getBossCountdownTicks();
const int bossSeconds = static_cast<int>(
ticksToSeconds(countdownTicks > 0 ? countdownTicks : 0));
m_bossWaveLabel->setText(
tr("Boss Wave #%1")
.arg(m_context.sim->getBossWaveCounter()));
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(m_context.sim->getArtifactCount())
.arg(m_context.sim->getConfig().world.artifacts.artifactWinCount));
}
void HeaderBar::onSpeedButton(int index)
{
if (index >= 0 && index < kSpeedCount)
{
EventManager::getInstance()->sendEventImmediately(
std::make_shared<SpeedChangeRequestedEvent>(kSpeeds[index]));
}
}