One tooltip mechanism for the whole UI: a Tooltip popup plus a TooltipTrigger event filter, replacing every setToolTip call. Qt's own tooltip cannot do what REQ-UI-TOOLTIP-TRIGGER and REQ-UI-TOOLTIP-DISMISS ask for -- it times out, hides on the first mouse move, cannot be hovered and cannot be brought up by a click. A tooltip now appears the moment an element with no click action of its own is clicked, and it is placed with its top-left corner on the pointer, so the pointer can move onto the tooltip and hold it open. The pointer is polled while a tooltip is up rather than tracked through enter and leave events, whose order depends on which widget the pointer crosses first. The item chip's children become transparent to the mouse. They were taking the chip's enter and leave events, so its tooltip only ever appeared when the pointer rested on the chip's padding. The modal header's close button loses its "Close" tooltip: a close button in a header says that by being one. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Ne3mejABZoLWKLh8fgpM3x
219 lines
7.1 KiB
C++
219 lines
7.1 KiB
C++
#include "HeaderBar.h"
|
|
|
|
#include <cmath>
|
|
#include <string>
|
|
|
|
#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"
|
|
#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 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);
|
|
// 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).
|
|
m_blocksLabel = new QLabel(this);
|
|
if (config->world.buildingBlocksTooltip)
|
|
{
|
|
TooltipTrigger::attachText(
|
|
*m_blocksLabel,
|
|
QString::fromStdString(*config->world.buildingBlocksTooltip),
|
|
TooltipTrigger::Trigger::HoverAndClick);
|
|
}
|
|
updateBlocksLabel();
|
|
m_artifactsLabel = new QLabel(tr("Artifacts: 0/?"), this);
|
|
if (config->world.artifactTooltip)
|
|
{
|
|
TooltipTrigger::attachText(
|
|
*m_artifactsLabel,
|
|
QString::fromStdString(*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);
|
|
|
|
// 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(m_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();
|
|
updateExpandButton();
|
|
}
|
|
|
|
void HeaderBar::handleEvent(std::shared_ptr<const ExpansionCostChangedEvent> /*event*/)
|
|
{
|
|
updateExpandButton();
|
|
}
|
|
|
|
void HeaderBar::updateBlocksLabel()
|
|
{
|
|
const int blocks = m_sim->getBuildingBlocksStock();
|
|
|
|
const QPixmap icon = m_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::updateExpandButton()
|
|
{
|
|
const int blocks = m_sim->getBuildingBlocksStock();
|
|
const int expansionCost = m_sim->getCurrentExpansionCost();
|
|
|
|
m_expandButton->setEnabled(blocks >= expansionCost);
|
|
|
|
const QPixmap icon = m_itemIcons->getInlineIcon(kBlockItemId, font());
|
|
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 Tick countdownTicks = m_sim->getBossCountdownTicks();
|
|
const int bossSeconds = static_cast<int>(
|
|
ticksToSeconds(countdownTicks > 0 ? countdownTicks : 0));
|
|
m_bossWaveLabel->setText(
|
|
tr("Boss Wave #%1")
|
|
.arg(m_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_sim->getArtifactCount())
|
|
.arg(m_sim->getConfig().world.artifacts.artifactWinCount));
|
|
}
|
|
|
|
void HeaderBar::onSpeedButton(int index)
|
|
{
|
|
if (index >= 0 && index < kSpeedCount)
|
|
{
|
|
EventManager::getInstance()->sendEventImmediately(
|
|
std::make_shared<SpeedChangeRequestedEvent>(kSpeeds[index]));
|
|
}
|
|
}
|