move blueprints out of the sidebar into Ctrl+C / Ctrl+V dialogs

This commit is contained in:
2026-08-06 19:03:59 +02:00
parent 2cbcf1554f
commit 18cfe238f6
22 changed files with 1021 additions and 400 deletions

View File

@@ -9,12 +9,15 @@
#include <QCloseEvent>
#include <QDir>
#include <QFile>
#include <QInputDialog>
#include <QLineEdit>
#include <QMessageBox>
#include <QPushButton>
#include <QResizeEvent>
#include <QVBoxLayout>
#include "BlueprintPanel.h"
#include "BlueprintLibrary.h"
#include "BlueprintSelectionDialog.h"
#include "BuildButtonBar.h"
#include "BuildingSystem.h"
#include "Command.h"
@@ -71,31 +74,29 @@ MainWindow::MainWindow(Simulation* sim, const std::string& configDir,
m_buildButtonBar = new BuildButtonBar(sim, &sim->getConfig(), iconDir,
m_itemIcons.get(), this);
// The blueprints have no widget of their own: they are saved with Ctrl+C and picked
// from a modal dialog (REQ-UI-BLUEPRINT-DIALOG), both driven from this window
// because only it can pause the game and raise the dim overlay. Built after the
// world view because loading blueprints.toml may put a message box on screen.
m_blueprintLibrary = std::make_unique<BlueprintLibrary>(sim, &sim->getConfig(), this);
m_sidePanel = new QWidget(this);
QVBoxLayout* sideLayout = new QVBoxLayout(m_sidePanel);
sideLayout->setContentsMargins(1, 1, 1, 1);
sideLayout->setSpacing(1);
m_selectedBuildingPanel = new SelectedBuildingPanel(sim, &sim->getConfig(), m_sidePanel);
m_blueprintPanel = new BlueprintPanel(sim, &sim->getConfig(), m_sidePanel);
// Equal stretch gives the two panels half the column height each
// The selected building panel is the column's only panel and fills its height
// (REQ-UI-PANEL-COLUMN).
m_selectedBuildingPanel = new SelectedBuildingPanel(sim, &sim->getConfig(), m_sidePanel);
sideLayout->addWidget(m_selectedBuildingPanel, 1);
sideLayout->addWidget(m_blueprintPanel, 1);
// Draw a thin border around each of the two side-panel sections. The class
// scoped selectors keep the border on the panels themselves rather than
// cascading onto their child widgets; WA_StyledBackground lets the plain
// QWidget subclasses honor the stylesheet box (border/background).
for (QWidget* panel : { static_cast<QWidget*>(m_selectedBuildingPanel),
static_cast<QWidget*>(m_blueprintPanel) })
{
panel->setAttribute(Qt::WA_StyledBackground, true);
}
// Draw a thin border around the side panel section. The class scoped selector keeps
// the border on the panel itself rather than cascading onto its child widgets;
// WA_StyledBackground lets the plain QWidget subclass honor the stylesheet box
// (border/background).
m_selectedBuildingPanel->setAttribute(Qt::WA_StyledBackground, true);
m_sidePanel->setStyleSheet(QStringLiteral(
"SelectedBuildingPanel, BlueprintPanel {"
" border: 1px solid palette(mid); }"));
"SelectedBuildingPanel { border: 1px solid palette(mid); }"));
// Created last so it stacks above the other children; covers the whole window and
// dims the game behind modal dialogs/menus (REQ-UI-MODAL-DIM).
@@ -369,6 +370,47 @@ void MainWindow::handleEvent(std::shared_ptr<const RecipeSelectionRequestedEvent
}
}
void MainWindow::handleEvent(std::shared_ptr<const BlueprintSaveRequestedEvent> /*event*/)
{
// Ctrl+C has effect only when something player-placeable is selected; otherwise no
// dialog opens at all (REQ-UI-BLUEPRINT-CREATE).
if (!m_blueprintLibrary->getCanCaptureSelection()) { return; }
// Both scopes are held across the naming dialog and the selection dialog it hands
// over to, so the dim never blinks off and the simulation is not resumed in between
// (REQ-UI-MODAL-DIM).
ModalPauseScope pause(*m_gameWorldView);
ModalDimScope dim(*m_dimOverlay);
bool ok = false;
const QString name = QInputDialog::getText(
this, tr("Create Blueprint"), tr("Blueprint name:"), QLineEdit::Normal,
QString(), &ok);
// Cancel, Escape, or an empty name: no blueprint, and no selection dialog.
if (!ok || name.trimmed().isEmpty()) { return; }
m_blueprintLibrary->saveSelectionAs(name.trimmed());
showBlueprintSelectionDialog();
}
void MainWindow::handleEvent(std::shared_ptr<const BlueprintSelectionRequestedEvent> /*event*/)
{
ModalPauseScope pause(*m_gameWorldView);
ModalDimScope dim(*m_dimOverlay);
showBlueprintSelectionDialog();
}
void MainWindow::showBlueprintSelectionDialog()
{
BlueprintSelectionDialog dialog(m_blueprintLibrary.get(), m_itemIcons.get(), this);
if (dialog.exec() == QDialog::Accepted && dialog.getChosenIndex().has_value())
{
// Entered after the dialog has closed, which is the order REQ-UI-BLUEPRINT-CARD
// describes: clicking a card closes the dialog and enters placement mode.
m_blueprintLibrary->beginPlacement(*dialog.getChosenIndex());
}
}
void MainWindow::handleEvent(std::shared_ptr<const GameOverEvent> /*event*/)
{
const Tick tick = m_sim->getCurrentTick();