allow to (multi) select scrap
This commit is contained in:
@@ -43,6 +43,7 @@
|
||||
#include "PositionComponent.h"
|
||||
#include "RepairBehavior.h"
|
||||
#include "SalvageScrapBehavior.h"
|
||||
#include "ScrapSelectionChangedEvent.h"
|
||||
#include "ScrapSystem.h"
|
||||
#include "SelectionChangedEvent.h"
|
||||
#include "SensorRangeComponent.h"
|
||||
@@ -271,6 +272,10 @@ void GameWorldView::onFrame()
|
||||
m_activeBeams = std::move(live);
|
||||
}
|
||||
|
||||
// Drop selected scrap piles that were collected or despawned this frame, so the
|
||||
// panel stops counting them and the selection empties out (REQ-UI-SCRAP-CLICK-SELECT).
|
||||
pruneDespawnedScrap();
|
||||
|
||||
// Expire copy/paste flashes. Lifetime is wall-clock (the frame delta), so the
|
||||
// flash plays for a fixed real duration regardless of game speed, including
|
||||
// while the game is paused (REQ-BLD-COPY-CONFIG-FEEDBACK).
|
||||
@@ -666,6 +671,36 @@ std::optional<QVector2D> GameWorldView::entityPosition(entt::entity entity) cons
|
||||
return m_sim->admin().get<PositionComponent>(entity).value;
|
||||
}
|
||||
|
||||
void GameWorldView::clearScrapSelection()
|
||||
{
|
||||
if (m_selectedScrap.empty()) { return; }
|
||||
m_selectedScrap.clear();
|
||||
EventManager::getInstance()->sendEventImmediately(
|
||||
std::make_shared<ScrapSelectionChangedEvent>(m_selectedScrap));
|
||||
}
|
||||
|
||||
void GameWorldView::pruneDespawnedScrap()
|
||||
{
|
||||
if (m_selectedScrap.empty()) { return; }
|
||||
|
||||
std::vector<entt::entity> live;
|
||||
for (const ScrapInfo& info : m_sim->scraps().allScrapInfo())
|
||||
{
|
||||
if (std::find(m_selectedScrap.begin(), m_selectedScrap.end(), info.entity)
|
||||
!= m_selectedScrap.end())
|
||||
{
|
||||
live.push_back(info.entity);
|
||||
}
|
||||
}
|
||||
|
||||
if (live.size() != m_selectedScrap.size())
|
||||
{
|
||||
m_selectedScrap = std::move(live);
|
||||
EventManager::getInstance()->sendEventImmediately(
|
||||
std::make_shared<ScrapSelectionChangedEvent>(m_selectedScrap));
|
||||
}
|
||||
}
|
||||
|
||||
void GameWorldView::stepSpeed(int delta)
|
||||
{
|
||||
const double kSpeeds[] = { 0.0, 0.5, 1.0, 2.0, 10.0 };
|
||||
@@ -1048,6 +1083,19 @@ void GameWorldView::drawSelectionHighlights(QPainter& painter)
|
||||
// Outline sits 1px outside the footprint (into adjacent tiles).
|
||||
painter.drawRect(rect->adjusted(-1, -1, 1, 1));
|
||||
}
|
||||
|
||||
// A ring around each selected scrap pile, sitting just outside the pile's
|
||||
// rendered circle (radius tilePx()*0.2, matching drawScrap) (REQ-UI-SCRAP-CLICK-SELECT).
|
||||
if (!m_selectedScrap.empty())
|
||||
{
|
||||
const qreal outlineRadius = static_cast<qreal>(tilePx() * 0.2f) + 3.0;
|
||||
for (const ScrapInfo& scrap : m_sim->scraps().allScrapInfo())
|
||||
{
|
||||
if (std::find(m_selectedScrap.begin(), m_selectedScrap.end(), scrap.entity)
|
||||
== m_selectedScrap.end()) { continue; }
|
||||
painter.drawEllipse(worldToWidget(scrap.position), outlineRadius, outlineRadius);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void GameWorldView::drawCopyConfigFeedback(QPainter& painter)
|
||||
@@ -1752,6 +1800,8 @@ void GameWorldView::mousePressEvent(QMouseEvent* event)
|
||||
|
||||
if (hitEntity != entt::null)
|
||||
{
|
||||
// Actors (ship/station) win over scrap and buildings (REQ-UI-SCRAP-CLICK-SELECT).
|
||||
clearScrapSelection();
|
||||
m_selectedBuildingIds.clear();
|
||||
EventManager::getInstance()->sendEventImmediately(
|
||||
std::make_shared<SelectionChangedEvent>(m_selectedBuildingIds));
|
||||
@@ -1775,6 +1825,8 @@ void GameWorldView::mousePressEvent(QMouseEvent* event)
|
||||
}
|
||||
if (id != kInvalidBuildingId)
|
||||
{
|
||||
// A building/construction site outranks scrap (REQ-UI-SCRAP-CLICK-SELECT).
|
||||
clearScrapSelection();
|
||||
if (event->modifiers() & Qt::ControlModifier)
|
||||
{
|
||||
bool found = false;
|
||||
@@ -1794,6 +1846,36 @@ void GameWorldView::mousePressEvent(QMouseEvent* event)
|
||||
EventManager::getInstance()->sendEventImmediately(
|
||||
std::make_shared<SelectionChangedEvent>(m_selectedBuildingIds));
|
||||
}
|
||||
else if (const entt::entity scrapHit =
|
||||
scrapAtWorldPos(m_sim->admin(), worldPos); scrapHit != entt::null)
|
||||
{
|
||||
// Scrap forms its own selection category; picking it clears any
|
||||
// building selection (REQ-UI-SCRAP-CLICK-SELECT, REQ-UI-SCRAP-MULTI-SELECT).
|
||||
if (!m_selectedBuildingIds.empty())
|
||||
{
|
||||
m_selectedBuildingIds.clear();
|
||||
EventManager::getInstance()->sendEventImmediately(
|
||||
std::make_shared<SelectionChangedEvent>(m_selectedBuildingIds));
|
||||
}
|
||||
if (event->modifiers() & Qt::ControlModifier)
|
||||
{
|
||||
bool found = false;
|
||||
std::vector<entt::entity> newSel;
|
||||
for (entt::entity sel : m_selectedScrap)
|
||||
{
|
||||
if (sel == scrapHit) { found = true; }
|
||||
else { newSel.push_back(sel); }
|
||||
}
|
||||
if (!found) { newSel.push_back(scrapHit); }
|
||||
m_selectedScrap = newSel;
|
||||
}
|
||||
else
|
||||
{
|
||||
m_selectedScrap = { scrapHit };
|
||||
}
|
||||
EventManager::getInstance()->sendEventImmediately(
|
||||
std::make_shared<ScrapSelectionChangedEvent>(m_selectedScrap));
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!(event->modifiers() & Qt::ControlModifier))
|
||||
@@ -1801,6 +1883,7 @@ void GameWorldView::mousePressEvent(QMouseEvent* event)
|
||||
m_selectedBuildingIds.clear();
|
||||
EventManager::getInstance()->sendEventImmediately(
|
||||
std::make_shared<SelectionChangedEvent>(m_selectedBuildingIds));
|
||||
clearScrapSelection();
|
||||
}
|
||||
m_boxSelecting = true;
|
||||
m_boxStartTile = tile;
|
||||
@@ -1873,24 +1956,75 @@ void GameWorldView::mouseReleaseEvent(QMouseEvent* event)
|
||||
return;
|
||||
}
|
||||
|
||||
if (!(event->modifiers() & Qt::ControlModifier))
|
||||
const bool ctrl = (event->modifiers() & Qt::ControlModifier) != 0;
|
||||
|
||||
if (!boxIds.empty())
|
||||
{
|
||||
m_selectedBuildingIds = boxIds;
|
||||
}
|
||||
else
|
||||
{
|
||||
for (BuildingId id : boxIds)
|
||||
// A box covering any building selects buildings; scrap in the box is
|
||||
// ignored (REQ-UI-MULTI-SELECT, REQ-UI-SCRAP-MULTI-SELECT).
|
||||
clearScrapSelection();
|
||||
if (!ctrl)
|
||||
{
|
||||
bool found = false;
|
||||
for (BuildingId sel : m_selectedBuildingIds)
|
||||
{
|
||||
if (sel == id) { found = true; break; }
|
||||
}
|
||||
if (!found) { m_selectedBuildingIds.push_back(id); }
|
||||
m_selectedBuildingIds = boxIds;
|
||||
}
|
||||
else
|
||||
{
|
||||
for (BuildingId id : boxIds)
|
||||
{
|
||||
bool found = false;
|
||||
for (BuildingId sel : m_selectedBuildingIds)
|
||||
{
|
||||
if (sel == id) { found = true; break; }
|
||||
}
|
||||
if (!found) { m_selectedBuildingIds.push_back(id); }
|
||||
}
|
||||
}
|
||||
EventManager::getInstance()->sendEventImmediately(
|
||||
std::make_shared<SelectionChangedEvent>(m_selectedBuildingIds));
|
||||
return;
|
||||
}
|
||||
|
||||
// No buildings in the box: a scrap-only box selects the scrap it covers
|
||||
// (REQ-UI-SCRAP-MULTI-SELECT).
|
||||
const std::vector<entt::entity> boxScrap =
|
||||
scrapInBox(m_sim->admin(), m_boxStartTile, m_boxCurrentTile);
|
||||
if (!boxScrap.empty())
|
||||
{
|
||||
if (!m_selectedBuildingIds.empty())
|
||||
{
|
||||
m_selectedBuildingIds.clear();
|
||||
EventManager::getInstance()->sendEventImmediately(
|
||||
std::make_shared<SelectionChangedEvent>(m_selectedBuildingIds));
|
||||
}
|
||||
if (!ctrl)
|
||||
{
|
||||
m_selectedScrap = boxScrap;
|
||||
}
|
||||
else
|
||||
{
|
||||
for (entt::entity e : boxScrap)
|
||||
{
|
||||
bool found = false;
|
||||
for (entt::entity sel : m_selectedScrap)
|
||||
{
|
||||
if (sel == e) { found = true; break; }
|
||||
}
|
||||
if (!found) { m_selectedScrap.push_back(e); }
|
||||
}
|
||||
}
|
||||
EventManager::getInstance()->sendEventImmediately(
|
||||
std::make_shared<ScrapSelectionChangedEvent>(m_selectedScrap));
|
||||
return;
|
||||
}
|
||||
|
||||
// Empty box: a plain (non-additive) drag clears the current selection.
|
||||
if (!ctrl)
|
||||
{
|
||||
m_selectedBuildingIds.clear();
|
||||
EventManager::getInstance()->sendEventImmediately(
|
||||
std::make_shared<SelectionChangedEvent>(m_selectedBuildingIds));
|
||||
clearScrapSelection();
|
||||
}
|
||||
EventManager::getInstance()->sendEventImmediately(
|
||||
std::make_shared<SelectionChangedEvent>(m_selectedBuildingIds));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -168,6 +168,13 @@ private:
|
||||
void placeBlueprintAtTile(QPoint center);
|
||||
|
||||
std::optional<QVector2D> entityPosition(entt::entity entity) const;
|
||||
// Clears the scrap selection, emitting an empty ScrapSelectionChangedEvent when
|
||||
// it was non-empty (REQ-UI-SCRAP-CLICK-SELECT). Used when another selection
|
||||
// category takes over.
|
||||
void clearScrapSelection();
|
||||
// Drops despawned or fully-collected piles from the scrap selection and re-emits
|
||||
// when it changed (REQ-UI-SCRAP-CLICK-SELECT). Called each frame from onFrame().
|
||||
void pruneDespawnedScrap();
|
||||
void stepSpeed(int delta);
|
||||
void placeAtTile(QPoint tile);
|
||||
|
||||
@@ -249,6 +256,7 @@ private:
|
||||
|
||||
std::vector<BuildingId> m_selectedBuildingIds;
|
||||
std::optional<entt::entity> m_selectedEntity;
|
||||
std::vector<entt::entity> m_selectedScrap;
|
||||
bool m_boxSelecting;
|
||||
QPoint m_boxStartTile;
|
||||
QPoint m_boxCurrentTile;
|
||||
|
||||
@@ -37,6 +37,7 @@
|
||||
#include "RecipeSelectionDialog.h"
|
||||
#include "RecipeSelectionRequestedEvent.h"
|
||||
#include "Rotation.h"
|
||||
#include "ScrapSystem.h"
|
||||
#include "ShipLayoutPreview.h"
|
||||
#include "Simulation.h"
|
||||
#include "WeaponComponent.h"
|
||||
@@ -190,6 +191,10 @@ SelectedBuildingPanel::SelectedBuildingPanel(Simulation* sim,
|
||||
m_layout->addWidget(m_stationStatsLabel);
|
||||
m_stationStatsLabel->hide();
|
||||
|
||||
m_scrapLabel = new QLabel(this);
|
||||
m_layout->addWidget(m_scrapLabel);
|
||||
m_scrapLabel->hide();
|
||||
|
||||
buildEmpty();
|
||||
|
||||
registerForEvents();
|
||||
@@ -206,6 +211,9 @@ void SelectedBuildingPanel::onSelectionChanged(const std::vector<BuildingId>& id
|
||||
if (!ids.empty())
|
||||
{
|
||||
clearEntityDisplay();
|
||||
// A building selection supersedes any scrap selection (REQ-UI-SCRAP-CLICK-SELECT).
|
||||
m_selectedScrap.clear();
|
||||
m_scrapLabel->hide();
|
||||
}
|
||||
rebuild();
|
||||
}
|
||||
@@ -238,6 +246,7 @@ void SelectedBuildingPanel::hideAllWidgets()
|
||||
m_filterBLabel->hide();
|
||||
m_filterBList->hide();
|
||||
m_buffersLabel->hide();
|
||||
m_scrapLabel->hide();
|
||||
}
|
||||
|
||||
void SelectedBuildingPanel::clearContent()
|
||||
@@ -638,6 +647,13 @@ void SelectedBuildingPanel::handleEvent(
|
||||
|
||||
void SelectedBuildingPanel::refreshSelectionDisplay(RefreshReason reason)
|
||||
{
|
||||
if (!m_selectedScrap.empty())
|
||||
{
|
||||
// The total shrinks live as piles are collected or despawn (REQ-UI-SCRAP-PANEL).
|
||||
refreshScrapTotal();
|
||||
return;
|
||||
}
|
||||
|
||||
if (m_selectedEntity.has_value())
|
||||
{
|
||||
refreshEntityStats();
|
||||
@@ -885,6 +901,9 @@ void SelectedBuildingPanel::handleEvent(std::shared_ptr<const EntitySelectedEven
|
||||
{
|
||||
m_selectedEntity = event->entity;
|
||||
m_selectedBuildingIds.clear();
|
||||
// An entity selection supersedes any scrap selection (REQ-UI-SCRAP-CLICK-SELECT).
|
||||
m_selectedScrap.clear();
|
||||
m_scrapLabel->hide();
|
||||
clearContent();
|
||||
|
||||
EntityAdmin& admin = m_sim->admin();
|
||||
@@ -1022,6 +1041,54 @@ void SelectedBuildingPanel::handleEvent(std::shared_ptr<const SelectionChangedEv
|
||||
onSelectionChanged(event->ids);
|
||||
}
|
||||
|
||||
void SelectedBuildingPanel::handleEvent(
|
||||
std::shared_ptr<const ScrapSelectionChangedEvent> event)
|
||||
{
|
||||
m_selectedScrap = event->scrap;
|
||||
if (!m_selectedScrap.empty())
|
||||
{
|
||||
// Scrap is its own selection category, mutually exclusive with buildings and
|
||||
// entities (REQ-UI-SCRAP-CLICK-SELECT).
|
||||
m_selectedBuildingIds.clear();
|
||||
clearContent();
|
||||
clearEntityDisplay();
|
||||
buildScrap();
|
||||
}
|
||||
else
|
||||
{
|
||||
m_scrapLabel->hide();
|
||||
if (m_selectedBuildingIds.empty() && !m_selectedEntity.has_value())
|
||||
{
|
||||
buildEmpty();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void SelectedBuildingPanel::buildScrap()
|
||||
{
|
||||
clearContent();
|
||||
m_entityTitleLabel->hide();
|
||||
m_entityStatsPanel->hide();
|
||||
m_stationStatsLabel->hide();
|
||||
refreshScrapTotal();
|
||||
m_scrapLabel->show();
|
||||
}
|
||||
|
||||
void SelectedBuildingPanel::refreshScrapTotal()
|
||||
{
|
||||
// Sum the remaining amounts of the still-living selected piles (REQ-UI-SCRAP-PANEL).
|
||||
int total = 0;
|
||||
for (const ScrapInfo& info : m_sim->scraps().allScrapInfo())
|
||||
{
|
||||
if (std::find(m_selectedScrap.begin(), m_selectedScrap.end(), info.entity)
|
||||
!= m_selectedScrap.end())
|
||||
{
|
||||
total += info.amount;
|
||||
}
|
||||
}
|
||||
m_scrapLabel->setText(tr("Scrap: %1").arg(total));
|
||||
}
|
||||
|
||||
void SelectedBuildingPanel::handleEvent(std::shared_ptr<const DebugDrawToggledEvent> event)
|
||||
{
|
||||
m_debugDraw = event->active;
|
||||
|
||||
@@ -18,6 +18,7 @@
|
||||
#include "GameConfig.h"
|
||||
#include "PlayerCommandsAppliedEvent.h"
|
||||
#include "RecipesConfig.h"
|
||||
#include "ScrapSelectionChangedEvent.h"
|
||||
#include "SelectionChangedEvent.h"
|
||||
#include "ShipLayout.h"
|
||||
#include "ShipsConfig.h"
|
||||
@@ -37,6 +38,7 @@ class SelectedBuildingPanel : public QWidget,
|
||||
PlayerCommandsAppliedEvent,
|
||||
EntitySelectedEvent,
|
||||
SelectionChangedEvent,
|
||||
ScrapSelectionChangedEvent,
|
||||
DebugDrawToggledEvent>
|
||||
{
|
||||
Q_OBJECT
|
||||
@@ -51,6 +53,7 @@ private:
|
||||
void handleEvent(std::shared_ptr<const PlayerCommandsAppliedEvent> event) override;
|
||||
void handleEvent(std::shared_ptr<const EntitySelectedEvent> event) override;
|
||||
void handleEvent(std::shared_ptr<const SelectionChangedEvent> event) override;
|
||||
void handleEvent(std::shared_ptr<const ScrapSelectionChangedEvent> event) override;
|
||||
void handleEvent(std::shared_ptr<const DebugDrawToggledEvent> event) override;
|
||||
|
||||
private slots:
|
||||
@@ -77,6 +80,8 @@ private:
|
||||
void buildEmpty();
|
||||
void buildSingle(BuildingId id);
|
||||
void buildMulti(const std::vector<BuildingId>& ids);
|
||||
void buildScrap();
|
||||
void refreshScrapTotal();
|
||||
void refreshBuffers(const Building* b);
|
||||
void refreshSiteProgress(const ConstructionSite* s);
|
||||
void updateShipyardLayoutWidgets(BuildingType type,
|
||||
@@ -115,6 +120,9 @@ private:
|
||||
QLabel* m_entityTitleLabel;
|
||||
QLabel* m_stationStatsLabel;
|
||||
|
||||
std::vector<entt::entity> m_selectedScrap;
|
||||
QLabel* m_scrapLabel;
|
||||
|
||||
void buildEntityShip(entt::entity entity);
|
||||
void buildEntityStation(entt::entity entity);
|
||||
void refreshEntityStats();
|
||||
|
||||
Reference in New Issue
Block a user