deselect build tool when it becomes unaffordable and fix stale enabled button

BuildButtonGrid now holds the Simulation and reads the building block stock
on demand, subscribing to BuildingBlocksChangedEvent itself instead of being
driven through MainWindow. When the currently selected build tool can no
longer be afforded, builder mode is exited so the button does not stay
selected or linger enabled after exit. m_activeIndex is now a std::optional.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Y7N59FsLA5e2kuVdqe4Uhc
This commit is contained in:
2026-07-19 20:18:20 +02:00
parent 62c26b91f9
commit 904dc9db6f
4 changed files with 54 additions and 28 deletions

View File

@@ -12,12 +12,13 @@
#include "DisplayName.h" #include "DisplayName.h"
#include "EventManager.h" #include "EventManager.h"
#include "ExitBuilderModeRequestedEvent.h" #include "ExitBuilderModeRequestedEvent.h"
#include "Simulation.h"
BuildButtonGrid::BuildButtonGrid(const GameConfig* config, QWidget* parent) BuildButtonGrid::BuildButtonGrid(Simulation* sim, const GameConfig* config, QWidget* parent)
: QWidget(parent) : QWidget(parent)
, m_sim(sim)
, m_config(config) , m_config(config)
, m_activeIndex(-1)
{ {
QGridLayout* layout = new QGridLayout(this); QGridLayout* layout = new QGridLayout(this);
layout->setSpacing(4); layout->setSpacing(4);
@@ -79,24 +80,42 @@ BuildButtonGrid::~BuildButtonGrid()
unregisterForEvents(); unregisterForEvents();
} }
void BuildButtonGrid::updateAffordability(int buildingBlocks) void BuildButtonGrid::updateAffordability()
{ {
const int buildingBlocks = m_sim->getBuildingBlocksStock();
// If the currently selected tool can no longer be afforded, exit builder mode
// before recomputing button states so it does not stay selected. Clearing the
// active index first lets the loop below disable the now-unaffordable button.
if (m_activeIndex)
{
const BuildingType activeType = m_types[*m_activeIndex];
const std::map<BuildingType, int>::const_iterator it = m_costs.find(activeType);
const int cost = (it != m_costs.end()) ? it->second : 0;
if (buildingBlocks < cost)
{
clearActiveButton();
EventManager::getInstance()->sendEventImmediately(
std::make_shared<ExitBuilderModeRequestedEvent>());
}
}
for (std::size_t i = 0; i < m_buttons.size(); ++i) for (std::size_t i = 0; i < m_buttons.size(); ++i)
{ {
const BuildingType type = m_types[i]; const BuildingType type = m_types[i];
const std::map<BuildingType, int>::const_iterator it = m_costs.find(type); const std::map<BuildingType, int>::const_iterator it = m_costs.find(type);
const int cost = (it != m_costs.end()) ? it->second : 0; const int cost = (it != m_costs.end()) ? it->second : 0;
m_buttons[i]->setEnabled(buildingBlocks >= cost || m_activeIndex == static_cast<int>(i)); m_buttons[i]->setEnabled(buildingBlocks >= cost || m_activeIndex == i);
} }
} }
void BuildButtonGrid::clearActiveButton() void BuildButtonGrid::clearActiveButton()
{ {
if (m_activeIndex >= 0 && m_activeIndex < static_cast<int>(m_buttons.size())) if (m_activeIndex)
{ {
m_buttons[static_cast<std::size_t>(m_activeIndex)]->setChecked(false); m_buttons[*m_activeIndex]->setChecked(false);
} }
m_activeIndex = -1; m_activeIndex.reset();
} }
void BuildButtonGrid::onBuildButton(int index) void BuildButtonGrid::onBuildButton(int index)
@@ -105,8 +124,9 @@ void BuildButtonGrid::onBuildButton(int index)
{ {
return; return;
} }
const std::size_t idx = static_cast<std::size_t>(index);
if (m_activeIndex == index) if (m_activeIndex == idx)
{ {
clearActiveButton(); clearActiveButton();
EventManager::getInstance()->sendEventImmediately( EventManager::getInstance()->sendEventImmediately(
@@ -114,15 +134,15 @@ void BuildButtonGrid::onBuildButton(int index)
return; return;
} }
if (m_activeIndex >= 0 && m_activeIndex < static_cast<int>(m_buttons.size())) if (m_activeIndex)
{ {
m_buttons[static_cast<std::size_t>(m_activeIndex)]->setChecked(false); m_buttons[*m_activeIndex]->setChecked(false);
} }
m_activeIndex = index; m_activeIndex = idx;
m_buttons[static_cast<std::size_t>(index)]->setChecked(true); m_buttons[idx]->setChecked(true);
EventManager::getInstance()->sendEventImmediately( EventManager::getInstance()->sendEventImmediately(
std::make_shared<BuildingTypeSelectedEvent>(m_types[static_cast<std::size_t>(index)])); std::make_shared<BuildingTypeSelectedEvent>(m_types[idx]));
} }
void BuildButtonGrid::handleEvent(std::shared_ptr<const BuilderModeExitedEvent> /*event*/) void BuildButtonGrid::handleEvent(std::shared_ptr<const BuilderModeExitedEvent> /*event*/)
@@ -130,6 +150,11 @@ void BuildButtonGrid::handleEvent(std::shared_ptr<const BuilderModeExitedEvent>
clearActiveButton(); clearActiveButton();
} }
void BuildButtonGrid::handleEvent(std::shared_ptr<const BuildingBlocksChangedEvent> /*event*/)
{
updateAffordability();
}
void BuildButtonGrid::handleEvent(std::shared_ptr<const DemolishModeChangedEvent> event) void BuildButtonGrid::handleEvent(std::shared_ptr<const DemolishModeChangedEvent> event)
{ {
m_demolishButton->setChecked(event->active); m_demolishButton->setChecked(event->active);

View File

@@ -1,46 +1,56 @@
#pragma once #pragma once
#include <map> #include <map>
#include <optional>
#include <vector> #include <vector>
#include <QWidget> #include <QWidget>
#include "BuilderModeExitedEvent.h" #include "BuilderModeExitedEvent.h"
#include "BuildHotkeyPressedEvent.h" #include "BuildHotkeyPressedEvent.h"
#include "BuildingBlocksChangedEvent.h"
#include "BuildingType.h" #include "BuildingType.h"
#include "DemolishModeChangedEvent.h" #include "DemolishModeChangedEvent.h"
#include "EventHandler.h" #include "EventHandler.h"
#include "GameConfig.h" #include "GameConfig.h"
class QPushButton; class QPushButton;
class Simulation;
class BuildButtonGrid : public QWidget, class BuildButtonGrid : public QWidget,
public CombinedEventHandler<BuilderModeExitedEvent, public CombinedEventHandler<BuilderModeExitedEvent,
DemolishModeChangedEvent, DemolishModeChangedEvent,
BuildHotkeyPressedEvent> BuildHotkeyPressedEvent,
BuildingBlocksChangedEvent>
{ {
Q_OBJECT Q_OBJECT
public: public:
BuildButtonGrid(const GameConfig* config, QWidget* parent = nullptr); BuildButtonGrid(Simulation* sim, const GameConfig* config, QWidget* parent = nullptr);
~BuildButtonGrid() override; ~BuildButtonGrid() override;
void updateAffordability(int buildingBlocks);
void clearActiveButton(); void clearActiveButton();
private: private:
// Re-evaluates which build buttons are enabled from the current building block
// stock (read from the simulation). If the currently selected tool can no longer
// be afforded, it exits builder mode so the button does not stay selected.
void updateAffordability();
void handleEvent(std::shared_ptr<const BuilderModeExitedEvent> event) override; void handleEvent(std::shared_ptr<const BuilderModeExitedEvent> event) override;
void handleEvent(std::shared_ptr<const DemolishModeChangedEvent> event) override; void handleEvent(std::shared_ptr<const DemolishModeChangedEvent> event) override;
void handleEvent(std::shared_ptr<const BuildHotkeyPressedEvent> event) override; void handleEvent(std::shared_ptr<const BuildHotkeyPressedEvent> event) override;
void handleEvent(std::shared_ptr<const BuildingBlocksChangedEvent> event) override;
private slots: private slots:
void onBuildButton(int index); void onBuildButton(int index);
private: private:
Simulation* m_sim;
const GameConfig* m_config; const GameConfig* m_config;
std::vector<BuildingType> m_types; std::vector<BuildingType> m_types;
std::vector<QPushButton*> m_buttons; std::vector<QPushButton*> m_buttons;
std::map<BuildingType, int> m_costs; std::map<BuildingType, int> m_costs;
int m_activeIndex; std::optional<std::size_t> m_activeIndex;
QPushButton* m_demolishButton; QPushButton* m_demolishButton;
}; };

View File

@@ -14,7 +14,6 @@
#include "BlueprintPanel.h" #include "BlueprintPanel.h"
#include "BuildButtonGrid.h" #include "BuildButtonGrid.h"
#include "BuildingBlocksChangedEvent.h"
#include "BuildingSystem.h" #include "BuildingSystem.h"
#include "Command.h" #include "Command.h"
#include "CommandRequestedEvent.h" #include "CommandRequestedEvent.h"
@@ -53,7 +52,7 @@ MainWindow::MainWindow(Simulation* sim, const std::string& configDir,
sideLayout->setSpacing(1); sideLayout->setSpacing(1);
m_selectedBuildingPanel = new SelectedBuildingPanel(sim, &sim->getConfig(), m_sidePanel); m_selectedBuildingPanel = new SelectedBuildingPanel(sim, &sim->getConfig(), m_sidePanel);
m_buildButtonGrid = new BuildButtonGrid(&sim->getConfig(), m_sidePanel); m_buildButtonGrid = new BuildButtonGrid(sim, &sim->getConfig(), m_sidePanel);
m_blueprintPanel = new BlueprintPanel(sim, &sim->getConfig(), m_sidePanel); m_blueprintPanel = new BlueprintPanel(sim, &sim->getConfig(), m_sidePanel);
sideLayout->addWidget(m_selectedBuildingPanel, 1); sideLayout->addWidget(m_selectedBuildingPanel, 1);
@@ -151,11 +150,6 @@ void MainWindow::layoutPanels()
m_dimOverlay->setGeometry(0, 0, totalW, totalH); m_dimOverlay->setGeometry(0, 0, totalW, totalH);
} }
void MainWindow::handleEvent(std::shared_ptr<const BuildingBlocksChangedEvent> event)
{
m_buildButtonGrid->updateAffordability(event->blocks);
}
void MainWindow::handleEvent(std::shared_ptr<const SchematicChoicesAvailableEvent> event) void MainWindow::handleEvent(std::shared_ptr<const SchematicChoicesAvailableEvent> event)
{ {
const double prevSpeed = m_gameWorldView->getGameSpeed(); const double prevSpeed = m_gameWorldView->getGameSpeed();

View File

@@ -6,7 +6,6 @@
#include <QWidget> #include <QWidget>
#include "BuildingBlocksChangedEvent.h"
#include "BuildingId.h" #include "BuildingId.h"
#include "EscapeMenuRequestedEvent.h" #include "EscapeMenuRequestedEvent.h"
#include "EventHandler.h" #include "EventHandler.h"
@@ -32,8 +31,7 @@ class QCloseEvent;
class QResizeEvent; class QResizeEvent;
class MainWindow : public QWidget, class MainWindow : public QWidget,
public CombinedEventHandler<BuildingBlocksChangedEvent, public CombinedEventHandler<SchematicChoicesAvailableEvent,
SchematicChoicesAvailableEvent,
GameOverEvent, GameOverEvent,
WinEvent, WinEvent,
EscapeMenuRequestedEvent, EscapeMenuRequestedEvent,
@@ -52,7 +50,6 @@ protected:
void closeEvent(QCloseEvent* event) override; void closeEvent(QCloseEvent* event) override;
private: private:
void handleEvent(std::shared_ptr<const BuildingBlocksChangedEvent> event) override;
void handleEvent(std::shared_ptr<const SchematicChoicesAvailableEvent> event) override; void handleEvent(std::shared_ptr<const SchematicChoicesAvailableEvent> event) override;
void handleEvent(std::shared_ptr<const GameOverEvent> event) override; void handleEvent(std::shared_ptr<const GameOverEvent> event) override;
void handleEvent(std::shared_ptr<const WinEvent> event) override; void handleEvent(std::shared_ptr<const WinEvent> event) override;