Replace recipe/schematic dropdowns with button and selection dialog
This commit is contained in:
@@ -12,6 +12,7 @@ SET(HDRS
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/ShipLayoutPreview.h
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/ShipStatsPanel.h
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/SchematicChoiceDialog.h
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/RecipeSelectionDialog.h
|
||||
PARENT_SCOPE
|
||||
)
|
||||
|
||||
@@ -28,5 +29,6 @@ SET(SRCS
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/ShipLayoutPreview.cpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/ShipStatsPanel.cpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/SchematicChoiceDialog.cpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/RecipeSelectionDialog.cpp
|
||||
PARENT_SCOPE
|
||||
)
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
#include "BuildingSystem.h"
|
||||
#include "ConfigLoader.h"
|
||||
#include "GameWorldView.h"
|
||||
#include "RecipeSelectionDialog.h"
|
||||
#include "SchematicChoiceDialog.h"
|
||||
#include "HeaderBar.h"
|
||||
#include "SelectedBuildingPanel.h"
|
||||
@@ -232,6 +233,35 @@ void MainWindow::handleEvent(std::shared_ptr<const LayoutDialogRequestedEvent> e
|
||||
m_gameWorldView->resetFrameTimer();
|
||||
}
|
||||
|
||||
void MainWindow::handleEvent(std::shared_ptr<const RecipeSelectionRequestedEvent> event)
|
||||
{
|
||||
const double prevSpeed = m_gameWorldView->gameSpeed();
|
||||
m_gameWorldView->setGameSpeed(0.0);
|
||||
|
||||
const Building* b = m_sim->buildings().findBuilding(event->buildingId);
|
||||
if (!b)
|
||||
{
|
||||
m_gameWorldView->setGameSpeed(prevSpeed);
|
||||
m_gameWorldView->resetFrameTimer();
|
||||
return;
|
||||
}
|
||||
|
||||
const std::vector<RecipeSelectionOption> options =
|
||||
buildRecipeSelectionOptions(*b, *m_sim, m_sim->config());
|
||||
const QString title = (b->type == BuildingType::Shipyard)
|
||||
? tr("Select Schematic")
|
||||
: tr("Select Recipe");
|
||||
|
||||
RecipeSelectionDialog dialog(options, title, this);
|
||||
if (dialog.exec() == QDialog::Accepted && dialog.getChosenId().has_value())
|
||||
{
|
||||
m_sim->buildings().setRecipe(event->buildingId, *dialog.getChosenId());
|
||||
}
|
||||
|
||||
m_gameWorldView->setGameSpeed(prevSpeed);
|
||||
m_gameWorldView->resetFrameTimer();
|
||||
}
|
||||
|
||||
void MainWindow::handleEvent(std::shared_ptr<const GameOverEvent> /*event*/)
|
||||
{
|
||||
const Tick tick = m_sim->currentTick();
|
||||
|
||||
@@ -11,6 +11,7 @@
|
||||
#include "EventHandler.h"
|
||||
#include "GameOverEvent.h"
|
||||
#include "LayoutDialogRequestedEvent.h"
|
||||
#include "RecipeSelectionRequestedEvent.h"
|
||||
#include "SchematicChoicesAvailableEvent.h"
|
||||
#include "ShipLayoutBlueprint.h"
|
||||
#include "Tick.h"
|
||||
@@ -30,7 +31,8 @@ class MainWindow : public QWidget,
|
||||
SchematicChoicesAvailableEvent,
|
||||
GameOverEvent,
|
||||
EscapeMenuRequestedEvent,
|
||||
LayoutDialogRequestedEvent>
|
||||
LayoutDialogRequestedEvent,
|
||||
RecipeSelectionRequestedEvent>
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
@@ -48,6 +50,7 @@ private:
|
||||
void handleEvent(std::shared_ptr<const GameOverEvent> event) override;
|
||||
void handleEvent(std::shared_ptr<const EscapeMenuRequestedEvent> event) override;
|
||||
void handleEvent(std::shared_ptr<const LayoutDialogRequestedEvent> event) override;
|
||||
void handleEvent(std::shared_ptr<const RecipeSelectionRequestedEvent> event) override;
|
||||
void layoutPanels();
|
||||
|
||||
private:
|
||||
|
||||
172
src/ui/RecipeSelectionDialog.cpp
Normal file
172
src/ui/RecipeSelectionDialog.cpp
Normal file
@@ -0,0 +1,172 @@
|
||||
#include "RecipeSelectionDialog.h"
|
||||
|
||||
#include <cstddef>
|
||||
|
||||
#include <QGridLayout>
|
||||
#include <QPushButton>
|
||||
#include <QStringList>
|
||||
#include <QVBoxLayout>
|
||||
|
||||
#include "Building.h"
|
||||
#include "BuildingType.h"
|
||||
#include "DisplayName.h"
|
||||
#include "GameConfig.h"
|
||||
#include "RecipesConfig.h"
|
||||
#include "ShipsConfig.h"
|
||||
#include "Simulation.h"
|
||||
|
||||
namespace
|
||||
{
|
||||
|
||||
QString itemLine(const std::string& itemId, int amount)
|
||||
{
|
||||
return QStringLiteral(" ")
|
||||
+ QString::fromStdString(toDisplayName(itemId))
|
||||
+ QStringLiteral(" ×") + QString::number(amount);
|
||||
}
|
||||
|
||||
QString recipeTooltip(const RecipeDef& recipe)
|
||||
{
|
||||
QStringList lines;
|
||||
lines << QObject::tr("Recipe: %1")
|
||||
.arg(QString::fromStdString(toDisplayName(recipe.id)));
|
||||
|
||||
if (recipe.inputs.empty())
|
||||
{
|
||||
lines << QObject::tr("Inputs: none");
|
||||
}
|
||||
else
|
||||
{
|
||||
lines << QObject::tr("Inputs:");
|
||||
for (const RecipeIngredient& ingredient : recipe.inputs)
|
||||
{
|
||||
lines << itemLine(ingredient.item, ingredient.amount);
|
||||
}
|
||||
}
|
||||
|
||||
lines << QObject::tr("Completion time: %1 s").arg(recipe.durationSeconds);
|
||||
|
||||
if (!recipe.outputs.empty())
|
||||
{
|
||||
lines << QObject::tr("Produces:");
|
||||
for (const RecipeOutput& output : recipe.outputs)
|
||||
{
|
||||
lines << itemLine(output.item, output.amount);
|
||||
}
|
||||
}
|
||||
|
||||
return lines.join('\n');
|
||||
}
|
||||
|
||||
QString shipTooltip(const ShipDef& def)
|
||||
{
|
||||
const QString name = QString::fromStdString(toDisplayName(def.id));
|
||||
|
||||
QStringList lines;
|
||||
lines << QObject::tr("Ship: %1").arg(name);
|
||||
|
||||
if (def.schematic.materials.empty())
|
||||
{
|
||||
lines << QObject::tr("Materials: none");
|
||||
}
|
||||
else
|
||||
{
|
||||
lines << QObject::tr("Materials:");
|
||||
for (const RecipeIngredient& material : def.schematic.materials)
|
||||
{
|
||||
lines << itemLine(material.item, material.amount);
|
||||
}
|
||||
}
|
||||
|
||||
lines << QObject::tr("Completion time: %1 s")
|
||||
.arg(def.schematic.productionTimeSeconds);
|
||||
lines << QObject::tr("Produces: 1 %1").arg(name);
|
||||
|
||||
return lines.join('\n');
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
std::vector<RecipeSelectionOption> buildRecipeSelectionOptions(
|
||||
const Building& building, Simulation& sim, const GameConfig& config)
|
||||
{
|
||||
std::vector<RecipeSelectionOption> options;
|
||||
options.push_back({std::string(), QObject::tr("(None)"), QString()});
|
||||
|
||||
if (building.type == BuildingType::Shipyard)
|
||||
{
|
||||
for (const ShipDef& def : config.ships.ships)
|
||||
{
|
||||
if (!sim.isSchematicUnlocked(def.id)) { continue; }
|
||||
options.push_back({def.id,
|
||||
QString::fromStdString(toDisplayName(def.id)),
|
||||
shipTooltip(def)});
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
for (const RecipeDef& recipe : config.recipes.recipes)
|
||||
{
|
||||
if (recipe.building != building.type) { continue; }
|
||||
if ((building.type == BuildingType::Miner
|
||||
|| building.type == BuildingType::Assembler)
|
||||
&& !sim.isRecipeUnlocked(recipe.id))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
options.push_back({recipe.id,
|
||||
QString::fromStdString(toDisplayName(recipe.id)),
|
||||
recipeTooltip(recipe)});
|
||||
}
|
||||
}
|
||||
|
||||
return options;
|
||||
}
|
||||
|
||||
RecipeSelectionDialog::RecipeSelectionDialog(
|
||||
const std::vector<RecipeSelectionOption>& options,
|
||||
const QString& title, QWidget* parent)
|
||||
: QDialog(parent)
|
||||
{
|
||||
setWindowTitle(title);
|
||||
setModal(true);
|
||||
|
||||
QVBoxLayout* mainLayout = new QVBoxLayout(this);
|
||||
QGridLayout* grid = new QGridLayout();
|
||||
mainLayout->addLayout(grid);
|
||||
|
||||
const int columnCount = 2;
|
||||
for (int i = 0; i < static_cast<int>(options.size()); ++i)
|
||||
{
|
||||
const RecipeSelectionOption& option = options[static_cast<std::size_t>(i)];
|
||||
|
||||
QPushButton* button = new QPushButton(option.caption, this);
|
||||
if (!option.tooltip.isEmpty())
|
||||
{
|
||||
button->setToolTip(option.tooltip);
|
||||
}
|
||||
button->setMinimumHeight(40);
|
||||
grid->addWidget(button, i / columnCount, i % columnCount);
|
||||
|
||||
m_optionIds.push_back(option.id);
|
||||
const int index = i;
|
||||
connect(button, &QPushButton::clicked, this, [this, index]()
|
||||
{
|
||||
onOptionClicked(index);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
std::optional<std::string> RecipeSelectionDialog::getChosenId() const
|
||||
{
|
||||
return m_chosenId;
|
||||
}
|
||||
|
||||
void RecipeSelectionDialog::onOptionClicked(int index)
|
||||
{
|
||||
if (index >= 0 && index < static_cast<int>(m_optionIds.size()))
|
||||
{
|
||||
m_chosenId = m_optionIds[static_cast<std::size_t>(index)];
|
||||
}
|
||||
accept();
|
||||
}
|
||||
50
src/ui/RecipeSelectionDialog.h
Normal file
50
src/ui/RecipeSelectionDialog.h
Normal file
@@ -0,0 +1,50 @@
|
||||
#pragma once
|
||||
|
||||
#include <optional>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include <QDialog>
|
||||
#include <QString>
|
||||
|
||||
struct Building;
|
||||
struct GameConfig;
|
||||
class Simulation;
|
||||
class QPushButton;
|
||||
|
||||
// One selectable entry in the recipe/schematic selection dialog
|
||||
// (REQ-UI-SELECT-BUTTON). The "(None)" entry uses an empty id.
|
||||
struct RecipeSelectionOption
|
||||
{
|
||||
std::string id;
|
||||
QString caption;
|
||||
QString tooltip;
|
||||
};
|
||||
|
||||
// Builds the lock-aware list of selectable options for a production building,
|
||||
// with display captions and info tooltips (REQ-UI-SELECT-BUTTON,
|
||||
// REQ-UI-SELECT-TOOLTIP). The first entry is always a "(None)" option with an
|
||||
// empty id. Shared by the selection dialog and the panel selection button so
|
||||
// both render identical captions and tooltips.
|
||||
std::vector<RecipeSelectionOption> buildRecipeSelectionOptions(
|
||||
const Building& building, Simulation& sim, const GameConfig& config);
|
||||
|
||||
// Modal dialog showing a grid of option buttons (REQ-UI-SELECT-BUTTON). The
|
||||
// game is paused by the caller while it is open. Clicking an option selects it
|
||||
// and closes the dialog; dismissing it (close/Esc) leaves no choice.
|
||||
class RecipeSelectionDialog : public QDialog
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
RecipeSelectionDialog(const std::vector<RecipeSelectionOption>& options,
|
||||
const QString& title, QWidget* parent = nullptr);
|
||||
|
||||
std::optional<std::string> getChosenId() const;
|
||||
|
||||
private:
|
||||
void onOptionClicked(int index);
|
||||
|
||||
std::vector<std::string> m_optionIds;
|
||||
std::optional<std::string> m_chosenId;
|
||||
};
|
||||
@@ -6,7 +6,6 @@
|
||||
#include <set>
|
||||
#include <string>
|
||||
|
||||
#include <QComboBox>
|
||||
#include <QLabel>
|
||||
#include <QListWidget>
|
||||
#include <QPushButton>
|
||||
@@ -32,6 +31,8 @@
|
||||
#include "ItemType.h"
|
||||
#include "LayoutDialogRequestedEvent.h"
|
||||
#include "ModulesConfig.h"
|
||||
#include "RecipeSelectionDialog.h"
|
||||
#include "RecipeSelectionRequestedEvent.h"
|
||||
#include "Rotation.h"
|
||||
#include "ShipLayoutPreview.h"
|
||||
#include "Simulation.h"
|
||||
@@ -110,7 +111,7 @@ SelectedBuildingPanel::SelectedBuildingPanel(Simulation* sim,
|
||||
m_layout->setAlignment(Qt::AlignTop);
|
||||
|
||||
m_titleLabel = new QLabel(this);
|
||||
m_recipeCombo = new QComboBox(this);
|
||||
m_recipeSelectButton = new QPushButton(this);
|
||||
m_clearBeltBtn = new QPushButton(tr("Clear Items"), this);
|
||||
m_filterALabel = new QLabel(this);
|
||||
m_filterAList = new QListWidget(this);
|
||||
@@ -125,7 +126,7 @@ SelectedBuildingPanel::SelectedBuildingPanel(Simulation* sim,
|
||||
m_filterBList->setMaximumHeight(100);
|
||||
|
||||
m_layout->addWidget(m_titleLabel);
|
||||
m_layout->addWidget(m_recipeCombo);
|
||||
m_layout->addWidget(m_recipeSelectButton);
|
||||
m_layout->addWidget(m_layoutPreview);
|
||||
m_layout->addWidget(m_configureLayoutBtn);
|
||||
m_layout->addWidget(m_clearBeltBtn);
|
||||
@@ -135,8 +136,8 @@ SelectedBuildingPanel::SelectedBuildingPanel(Simulation* sim,
|
||||
m_layout->addWidget(m_filterBList);
|
||||
m_layout->addWidget(m_buffersLabel);
|
||||
|
||||
connect(m_recipeCombo, qOverload<int>(&QComboBox::currentIndexChanged),
|
||||
this, &SelectedBuildingPanel::onRecipeChanged);
|
||||
connect(m_recipeSelectButton, &QPushButton::clicked,
|
||||
this, &SelectedBuildingPanel::onSelectRecipeClicked);
|
||||
connect(m_clearBeltBtn, &QPushButton::clicked,
|
||||
this, &SelectedBuildingPanel::onClearBelt);
|
||||
connect(m_configureLayoutBtn, &QPushButton::clicked, this, [this]() {
|
||||
@@ -206,7 +207,7 @@ void SelectedBuildingPanel::rebuild()
|
||||
void SelectedBuildingPanel::hideAllWidgets()
|
||||
{
|
||||
m_titleLabel->hide();
|
||||
m_recipeCombo->hide();
|
||||
m_recipeSelectButton->hide();
|
||||
m_layoutPreview->hide();
|
||||
m_configureLayoutBtn->hide();
|
||||
m_clearBeltBtn->hide();
|
||||
@@ -285,43 +286,33 @@ void SelectedBuildingPanel::buildSingle(BuildingId id)
|
||||
|
||||
if (isProductionBuilding(b->type))
|
||||
{
|
||||
m_recipeCombo->blockSignals(true);
|
||||
m_recipeCombo->clear();
|
||||
const std::vector<RecipeSelectionOption> options =
|
||||
buildRecipeSelectionOptions(*b, *m_sim, *m_config);
|
||||
|
||||
m_recipeCombo->addItem(tr("(none)"), QString());
|
||||
|
||||
if (b->type == BuildingType::Shipyard)
|
||||
const RecipeSelectionOption* current = nullptr;
|
||||
for (const RecipeSelectionOption& option : options)
|
||||
{
|
||||
for (const ShipDef& def : m_config->ships.ships)
|
||||
if (option.id == b->recipeId)
|
||||
{
|
||||
if (m_sim->isSchematicUnlocked(def.id))
|
||||
{
|
||||
m_recipeCombo->addItem(
|
||||
QString::fromStdString(def.id),
|
||||
QString::fromStdString(def.id));
|
||||
}
|
||||
current = &option;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (current && !current->id.empty())
|
||||
{
|
||||
m_recipeSelectButton->setText(current->caption);
|
||||
m_recipeSelectButton->setToolTip(current->tooltip);
|
||||
}
|
||||
else
|
||||
{
|
||||
for (const RecipeDef& recipe : m_config->recipes.recipes)
|
||||
{
|
||||
if (recipe.building != b->type) { continue; }
|
||||
if ((b->type == BuildingType::Miner || b->type == BuildingType::Assembler)
|
||||
&& !m_sim->isRecipeUnlocked(recipe.id))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
m_recipeCombo->addItem(
|
||||
QString::fromStdString(recipe.id),
|
||||
QString::fromStdString(recipe.id));
|
||||
}
|
||||
const QString placeholder = (b->type == BuildingType::Shipyard)
|
||||
? tr("Select schematic")
|
||||
: tr("Select recipe");
|
||||
m_recipeSelectButton->setText(placeholder);
|
||||
m_recipeSelectButton->setToolTip(QString());
|
||||
}
|
||||
|
||||
const int currentIdx = m_recipeCombo->findData(QString::fromStdString(b->recipeId));
|
||||
m_recipeCombo->setCurrentIndex(currentIdx >= 0 ? currentIdx : 0);
|
||||
m_recipeCombo->blockSignals(false);
|
||||
m_recipeCombo->show();
|
||||
m_recipeSelectButton->show();
|
||||
|
||||
if (b->type == BuildingType::Shipyard && !b->recipeId.empty())
|
||||
{
|
||||
@@ -352,7 +343,7 @@ void SelectedBuildingPanel::buildSingle(BuildingId id)
|
||||
}
|
||||
else
|
||||
{
|
||||
m_recipeCombo->hide();
|
||||
m_recipeSelectButton->hide();
|
||||
m_layoutPreview->hide();
|
||||
m_configureLayoutBtn->hide();
|
||||
}
|
||||
@@ -582,7 +573,7 @@ void SelectedBuildingPanel::handleEvent(std::shared_ptr<const TickAdvancedEvent>
|
||||
void SelectedBuildingPanel::buildMulti(const std::vector<BuildingId>& ids)
|
||||
{
|
||||
m_singleBuildingId = kInvalidBuildingId;
|
||||
m_recipeCombo->hide();
|
||||
m_recipeSelectButton->hide();
|
||||
m_clearBeltBtn->hide();
|
||||
m_filterALabel->hide();
|
||||
m_filterAList->hide();
|
||||
@@ -626,14 +617,18 @@ void SelectedBuildingPanel::buildMulti(const std::vector<BuildingId>& ids)
|
||||
}
|
||||
}
|
||||
|
||||
void SelectedBuildingPanel::onRecipeChanged(int comboIndex)
|
||||
void SelectedBuildingPanel::onSelectRecipeClicked()
|
||||
{
|
||||
if (m_singleBuildingId == kInvalidBuildingId)
|
||||
{
|
||||
return;
|
||||
}
|
||||
const QString recipeId = m_recipeCombo->itemData(comboIndex).toString();
|
||||
m_sim->buildings().setRecipe(m_singleBuildingId, recipeId.toStdString());
|
||||
// The emit is synchronous: MainWindow pauses the game, runs the modal
|
||||
// selection dialog, applies the chosen recipe/schematic, and restores the
|
||||
// speed before this returns. rebuild() then refreshes the button caption,
|
||||
// tooltip, preview, and buffers for the new selection.
|
||||
EventManager::getInstance()->sendEventImmediately(
|
||||
std::make_shared<RecipeSelectionRequestedEvent>(m_singleBuildingId));
|
||||
rebuild();
|
||||
}
|
||||
|
||||
|
||||
@@ -26,7 +26,6 @@ class Simulation;
|
||||
class ShipLayoutPreview;
|
||||
class ShipStatsPanel;
|
||||
class QLabel;
|
||||
class QComboBox;
|
||||
class QListWidget;
|
||||
class QPushButton;
|
||||
class QVBoxLayout;
|
||||
@@ -51,7 +50,7 @@ private:
|
||||
void handleEvent(std::shared_ptr<const DebugDrawToggledEvent> event) override;
|
||||
|
||||
private slots:
|
||||
void onRecipeChanged(int comboIndex);
|
||||
void onSelectRecipeClicked();
|
||||
void onClearBelt();
|
||||
void onSplitterFilterChanged();
|
||||
|
||||
@@ -75,7 +74,7 @@ private:
|
||||
|
||||
QVBoxLayout* m_layout;
|
||||
QLabel* m_titleLabel;
|
||||
QComboBox* m_recipeCombo;
|
||||
QPushButton* m_recipeSelectButton;
|
||||
QPushButton* m_clearBeltBtn;
|
||||
QLabel* m_filterALabel;
|
||||
QListWidget* m_filterAList;
|
||||
|
||||
Reference in New Issue
Block a user