add ship stats panel to ship layout dialog

This commit is contained in:
2026-06-06 21:21:48 +02:00
parent 8dad554800
commit 37a70ea321
9 changed files with 562 additions and 24 deletions

View File

@@ -10,6 +10,7 @@ SET(HDRS
${CMAKE_CURRENT_SOURCE_DIR}/BlueprintPanel.h
${CMAKE_CURRENT_SOURCE_DIR}/ShipLayoutDialog.h
${CMAKE_CURRENT_SOURCE_DIR}/ShipLayoutPreview.h
${CMAKE_CURRENT_SOURCE_DIR}/ShipStatsPanel.h
PARENT_SCOPE
)
@@ -24,5 +25,6 @@ SET(SRCS
${CMAKE_CURRENT_SOURCE_DIR}/BlueprintPanel.cpp
${CMAKE_CURRENT_SOURCE_DIR}/ShipLayoutDialog.cpp
${CMAKE_CURRENT_SOURCE_DIR}/ShipLayoutPreview.cpp
${CMAKE_CURRENT_SOURCE_DIR}/ShipStatsPanel.cpp
PARENT_SCOPE
)

View File

@@ -1,4 +1,5 @@
#include "ShipLayoutDialog.h"
#include "ShipStatsPanel.h"
#include <cctype>
#include <functional>
@@ -397,6 +398,7 @@ ShipLayoutDialog::ShipLayoutDialog(const GameConfig* config,
, m_currentRotation(Rotation::East)
, m_removeButton(nullptr)
, m_gridWidget(nullptr)
, m_statsPanel(nullptr)
{
setWindowTitle(tr("Configure Ship Layout"));
setModal(true);
@@ -437,17 +439,24 @@ ShipLayoutDialog::ShipLayoutDialog(const GameConfig* config,
rebuildOccupancy();
// --- UI layout ---
QHBoxLayout* mainLayout = new QHBoxLayout(this);
QVBoxLayout* outerLayout = new QVBoxLayout(this);
// Left: grid widget.
// Top: grid widget.
LayoutGridWidget* gridW = new LayoutGridWidget(this, this);
gridW->setGridData(&m_grid, m_rows, m_cols, &m_placedModules, m_config);
gridW->setGhostData(m_activeModuleIndex, m_currentRotation);
m_gridWidget = gridW;
mainLayout->addWidget(m_gridWidget);
outerLayout->addWidget(m_gridWidget, 0, Qt::AlignHCenter | Qt::AlignTop);
// Right: module buttons + confirm/cancel.
QVBoxLayout* rightLayout = new QVBoxLayout();
// Middle: three-column area (stats | module buttons | blueprints).
QHBoxLayout* columnsLayout = new QHBoxLayout();
// Left column: ship stats panel.
m_statsPanel = new ShipStatsPanel(config, this);
columnsLayout->addWidget(m_statsPanel);
// Center column: module selection buttons.
QVBoxLayout* centerLayout = new QVBoxLayout();
QGridLayout* buttonGrid = new QGridLayout();
buttonGrid->setSpacing(4);
@@ -508,30 +517,35 @@ ShipLayoutDialog::ShipLayoutDialog(const GameConfig* config,
updateGridWidget();
});
rightLayout->addLayout(buttonGrid);
rightLayout->addStretch();
centerLayout->addLayout(buttonGrid);
centerLayout->addStretch();
// Confirm / Cancel buttons.
QHBoxLayout* bottomBar = new QHBoxLayout();
QPushButton* confirmBtn = new QPushButton(tr("Confirm"), this);
QPushButton* cancelBtn = new QPushButton(tr("Cancel"), this);
bottomBar->addWidget(confirmBtn);
bottomBar->addWidget(cancelBtn);
rightLayout->addLayout(bottomBar);
columnsLayout->addLayout(centerLayout);
connect(confirmBtn, &QPushButton::clicked, this, &ShipLayoutDialog::onConfirm);
connect(cancelBtn, &QPushButton::clicked, this, &ShipLayoutDialog::onCancel);
mainLayout->addLayout(rightLayout);
// Right: blueprint panel (third column).
// Right column: blueprint panel.
ShipLayoutBlueprintPanel* bpPanel = new ShipLayoutBlueprintPanel(
allBlueprints,
m_shipId,
[this]() { return m_placedModules; },
[this](const std::vector<PlacedModule>& mods) { loadLayoutBlueprint(mods); },
this);
mainLayout->addWidget(bpPanel);
columnsLayout->addWidget(bpPanel);
outerLayout->addLayout(columnsLayout, 1);
// Bottom: confirm / cancel buttons.
QHBoxLayout* bottomBar = new QHBoxLayout();
QPushButton* confirmBtn = new QPushButton(tr("Confirm"), this);
QPushButton* cancelBtn = new QPushButton(tr("Cancel"), this);
bottomBar->addWidget(confirmBtn);
bottomBar->addWidget(cancelBtn);
outerLayout->addLayout(bottomBar);
connect(confirmBtn, &QPushButton::clicked, this, &ShipLayoutDialog::onConfirm);
connect(cancelBtn, &QPushButton::clicked, this, &ShipLayoutDialog::onCancel);
// Initial stats display.
updateStats();
// Grid click handler.
connect(this, &ShipLayoutDialog::gridCellClicked, this, [this](QPoint cell) {
@@ -551,6 +565,7 @@ ShipLayoutDialog::ShipLayoutDialog(const GameConfig* config,
m_placedModules.erase(m_placedModules.begin() + idx);
rebuildOccupancy();
updateGridWidget();
updateStats();
}
}
return;
@@ -567,6 +582,7 @@ ShipLayoutDialog::ShipLayoutDialog(const GameConfig* config,
m_placedModules.push_back(pm);
rebuildOccupancy();
updateGridWidget();
updateStats();
}
});
}
@@ -690,6 +706,20 @@ void ShipLayoutDialog::updateGridWidget()
gridW->update();
}
void ShipLayoutDialog::updateStats()
{
int level = 1;
for (const ShipDef& def : m_config->ships.ships)
{
if (def.id == m_shipId)
{
level = def.schematic.playerProductionLevel;
break;
}
}
m_statsPanel->refresh(m_shipId, level, m_placedModules);
}
bool ShipLayoutDialog::canPlaceModule(const ModuleDef& def, QPoint position,
Rotation rotation) const
{
@@ -795,4 +825,5 @@ void ShipLayoutDialog::loadLayoutBlueprint(const std::vector<PlacedModule>& modu
rebuildOccupancy();
updateGridWidget();
updateStats();
}

View File

@@ -13,6 +13,7 @@
#include "ShipLayoutBlueprint.h"
class QPushButton;
class ShipStatsPanel;
class ShipLayoutDialog : public QDialog
{
@@ -49,6 +50,7 @@ private:
void rebuildOccupancy();
void updateGridWidget();
void updateStats();
bool canPlaceModule(const ModuleDef& def, QPoint position, Rotation rotation) const;
std::vector<std::string> rotatedMask(const ModuleDef& def, Rotation rotation) const;
void loadLayoutBlueprint(const std::vector<PlacedModule>& modules);
@@ -68,6 +70,7 @@ private:
std::vector<QPushButton*> m_moduleButtons;
QPushButton* m_removeButton;
QWidget* m_gridWidget;
ShipStatsPanel* m_statsPanel;
std::optional<ShipLayoutConfig> m_result;
};

168
src/ui/ShipStatsPanel.cpp Normal file
View File

@@ -0,0 +1,168 @@
#include "ShipStatsPanel.h"
#include <QLabel>
#include <QString>
#include <QVBoxLayout>
#include "GameConfig.h"
#include "ShipStatsCalculator.h"
namespace
{
QString fmt(float value)
{
return QString::number(static_cast<double>(value), 'f', 1);
}
QLabel* makeSectionHeader(const QString& text, QWidget* parent)
{
QLabel* label = new QLabel(text, parent);
QFont f = label->font();
f.setBold(true);
label->setFont(f);
return label;
}
QLabel* makeStatLabel(QWidget* parent)
{
return new QLabel(parent);
}
} // namespace
ShipStatsPanel::ShipStatsPanel(const GameConfig* config, QWidget* parent)
: QWidget(parent)
, m_config(config)
{
QVBoxLayout* layout = new QVBoxLayout(this);
layout->setContentsMargins(4, 4, 4, 4);
layout->setSpacing(2);
layout->setAlignment(Qt::AlignTop);
// Hull stats — always visible.
m_hpLabel = makeStatLabel(this);
m_speedLabel = makeStatLabel(this);
m_sensorRangeLabel = makeStatLabel(this);
m_mainAccelLabel = makeStatLabel(this);
m_maneuveringAccelLabel = makeStatLabel(this);
m_angularAccelLabel = makeStatLabel(this);
m_maxRotSpeedLabel = makeStatLabel(this);
layout->addWidget(m_hpLabel);
layout->addWidget(m_speedLabel);
layout->addWidget(m_sensorRangeLabel);
layout->addWidget(m_mainAccelLabel);
layout->addWidget(m_maneuveringAccelLabel);
layout->addWidget(m_angularAccelLabel);
layout->addWidget(m_maxRotSpeedLabel);
// Weapon capability section.
m_weaponSection = new QWidget(this);
{
QVBoxLayout* sl = new QVBoxLayout(m_weaponSection);
sl->setContentsMargins(0, 4, 0, 0);
sl->setSpacing(2);
sl->addWidget(makeSectionHeader(tr("Weapons"), m_weaponSection));
m_weaponDpsLabel = makeStatLabel(m_weaponSection);
m_weaponRangeLabel = makeStatLabel(m_weaponSection);
sl->addWidget(m_weaponDpsLabel);
sl->addWidget(m_weaponRangeLabel);
}
m_weaponSection->setVisible(false);
layout->addWidget(m_weaponSection);
// Salvage capability section.
m_salvageSection = new QWidget(this);
{
QVBoxLayout* sl = new QVBoxLayout(m_salvageSection);
sl->setContentsMargins(0, 4, 0, 0);
sl->setSpacing(2);
sl->addWidget(makeSectionHeader(tr("Salvage"), m_salvageSection));
m_salvageRateLabel = makeStatLabel(m_salvageSection);
m_salvageRangeLabel = makeStatLabel(m_salvageSection);
sl->addWidget(m_salvageRateLabel);
sl->addWidget(m_salvageRangeLabel);
}
m_salvageSection->setVisible(false);
layout->addWidget(m_salvageSection);
// Repair capability section.
m_repairSection = new QWidget(this);
{
QVBoxLayout* sl = new QVBoxLayout(m_repairSection);
sl->setContentsMargins(0, 4, 0, 0);
sl->setSpacing(2);
sl->addWidget(makeSectionHeader(tr("Repair"), m_repairSection));
m_repairRateLabel = makeStatLabel(m_repairSection);
m_repairRangeLabel = makeStatLabel(m_repairSection);
sl->addWidget(m_repairRateLabel);
sl->addWidget(m_repairRangeLabel);
}
m_repairSection->setVisible(false);
layout->addWidget(m_repairSection);
layout->addStretch();
}
void ShipStatsPanel::refresh(const std::string& shipId,
int level,
const std::vector<PlacedModule>& modules)
{
const ShipStats stats = calculateShipStats(*m_config, shipId, level, modules);
m_hpLabel->setText(
tr("HP: %1").arg(static_cast<int>(stats.hp + 0.5f)));
m_speedLabel->setText(
tr("Max Speed: %1 tiles/s").arg(fmt(stats.maxSpeed_tps)));
m_sensorRangeLabel->setText(
tr("Sensor Range: %1 tiles").arg(fmt(stats.sensorRange_tiles)));
m_mainAccelLabel->setText(
tr("Main Accel: %1 tiles/s\xc2\xb2").arg(fmt(stats.mainAcceleration_tpss)));
m_maneuveringAccelLabel->setText(
tr("Maneuvering Accel: %1 tiles/s\xc2\xb2").arg(fmt(stats.maneuveringAcceleration_tpss)));
m_angularAccelLabel->setText(
tr("Angular Accel: %1 rad/s\xc2\xb2").arg(fmt(stats.angularAcceleration_radpss)));
m_maxRotSpeedLabel->setText(
tr("Max Rotation: %1 rad/s").arg(fmt(stats.maxRotationSpeed_radps)));
if (stats.weapons.has_value())
{
m_weaponDpsLabel->setText(
tr("DPS: %1").arg(fmt(stats.weapons->combinedDps)));
m_weaponRangeLabel->setText(
tr("Range: %1 tiles").arg(fmt(stats.weapons->maxRange_tiles)));
m_weaponSection->setVisible(true);
}
else
{
m_weaponSection->setVisible(false);
}
if (stats.salvage.has_value())
{
m_salvageRateLabel->setText(
tr("Collection Rate: %1/s").arg(fmt(stats.salvage->combinedCollectionRate)));
m_salvageRangeLabel->setText(
tr("Range: %1 tiles").arg(fmt(stats.salvage->maxRange_tiles)));
m_salvageSection->setVisible(true);
}
else
{
m_salvageSection->setVisible(false);
}
if (stats.repair.has_value())
{
m_repairRateLabel->setText(
tr("Repair Rate: %1 HP/s").arg(fmt(stats.repair->combinedRepairRate_hps)));
m_repairRangeLabel->setText(
tr("Range: %1 tiles").arg(fmt(stats.repair->maxRange_tiles)));
m_repairSection->setVisible(true);
}
else
{
m_repairSection->setVisible(false);
}
}

46
src/ui/ShipStatsPanel.h Normal file
View File

@@ -0,0 +1,46 @@
#pragma once
#include <string>
#include <vector>
#include <QWidget>
#include "ShipLayout.h"
struct GameConfig;
class QLabel;
class ShipStatsPanel : public QWidget
{
Q_OBJECT
public:
explicit ShipStatsPanel(const GameConfig* config, QWidget* parent = nullptr);
void refresh(const std::string& shipId,
int level,
const std::vector<PlacedModule>& modules);
private:
const GameConfig* m_config;
QLabel* m_hpLabel;
QLabel* m_speedLabel;
QLabel* m_sensorRangeLabel;
QLabel* m_mainAccelLabel;
QLabel* m_maneuveringAccelLabel;
QLabel* m_angularAccelLabel;
QLabel* m_maxRotSpeedLabel;
QWidget* m_weaponSection;
QLabel* m_weaponDpsLabel;
QLabel* m_weaponRangeLabel;
QWidget* m_salvageSection;
QLabel* m_salvageRateLabel;
QLabel* m_salvageRangeLabel;
QWidget* m_repairSection;
QLabel* m_repairRateLabel;
QLabel* m_repairRangeLabel;
};