The belt card's only content was a button reading "Clear stuck items", which assumed a state the items need not be in. It now reads "Clear items", and above it the card lists what the selected tiles hold -- one item chip per type, the same chip the buffer sections and the HQ's block stock draw -- so a line's contents can be read before they are removed, and can be read at all: items on a moving belt are too small and too transient to count by eye, and items inside a tunnel are drawn nowhere. BeltSystem gains countItems(tiles), a query of the same kind as forEachVisualItem: a method rather than exposed tile containers, so the per-tile representation stays swappable. It walks the same five containers as clearTiles, in the same order, so the list and the button cannot drift apart. The tunnel's two ends are now told apart. Items in transit are counted on the exit they are travelling toward, and a clear removes exactly what the panel listed for the tile it acts on: the exit discards them, the entry leaves them travelling. BeltSystemTest's tunnel case splits in two accordingly. Splitters now aggregate with belts and tunnel ends. Their output filters are per-object configuration, which an aggregate simply does not show -- a splitter selected alone still gets them. Both cards share one BeltItemList widget, and both derive their tiles from collectBeltTiles, so nothing is stated twice. The mixed count summary loses the clear action it carried: a button acting on part of a selection is worse than no button, and the tiles can be selected by themselves. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Ne3mejABZoLWKLh8fgpM3x
178 lines
6.4 KiB
C++
178 lines
6.4 KiB
C++
#include "SelectionContentFactory.h"
|
|
|
|
#include "BeltContent.h"
|
|
#include "DebrisContent.h"
|
|
#include "FactoryQueries.h"
|
|
#include "FieldMultiContent.h"
|
|
#include "HqContent.h"
|
|
#include "MultiBuildingContent.h"
|
|
#include "RecipeProductionContent.h"
|
|
#include "ShipContent.h"
|
|
#include "ShipIdentityComponent.h"
|
|
#include "ShipyardContent.h"
|
|
#include "SplitterContent.h"
|
|
#include "StationBodyComponent.h"
|
|
#include "StationContent.h"
|
|
#include "StorageContent.h"
|
|
#include "Simulation.h"
|
|
|
|
namespace
|
|
{
|
|
|
|
// The catalog row a single building type belongs to (REQ-UI-SELECTION-CONTENT).
|
|
SelectionContentKind getKindForType(BuildingType type)
|
|
{
|
|
switch (type)
|
|
{
|
|
case BuildingType::Miner:
|
|
case BuildingType::Assembler:
|
|
case BuildingType::Smelter:
|
|
case BuildingType::ReprocessingPlant:
|
|
return SelectionContentKind::RecipeProduction;
|
|
case BuildingType::Shipyard:
|
|
return SelectionContentKind::Shipyard;
|
|
case BuildingType::SalvageBay:
|
|
return SelectionContentKind::Storage;
|
|
case BuildingType::Hq:
|
|
return SelectionContentKind::Hq;
|
|
case BuildingType::Belt:
|
|
case BuildingType::TunnelEntry:
|
|
case BuildingType::TunnelExit:
|
|
return SelectionContentKind::Belt;
|
|
case BuildingType::Splitter:
|
|
return SelectionContentKind::Splitter;
|
|
case BuildingType::PlayerDefenceStation:
|
|
case BuildingType::EnemyDefenceStation:
|
|
// Defence stations are field objects backed by entities; these two enum
|
|
// values exist only for cost and visuals lookup and never reach the panel as
|
|
// a building selection. The count summary is the harmless fallback.
|
|
return SelectionContentKind::MultiBuilding;
|
|
}
|
|
return SelectionContentKind::MultiBuilding;
|
|
}
|
|
|
|
ContentKey chooseBuildingContent(const SelectionRequest& request, Simulation& sim)
|
|
{
|
|
const FactoryState& state = sim.getFactoryState();
|
|
|
|
if (request.buildings.size() == 1)
|
|
{
|
|
const BuildingId id = request.buildings.front();
|
|
const Building* building = findBuilding(state, id);
|
|
if (building)
|
|
{
|
|
return { getKindForType(building->type), false };
|
|
}
|
|
const ConstructionSite* site = findSite(state, id);
|
|
if (site)
|
|
{
|
|
return { getKindForType(site->type), true };
|
|
}
|
|
// The building went away under the panel; the selection is stale and there is
|
|
// nothing to show (REQ-UI-EMPTY-SELECTION).
|
|
return {};
|
|
}
|
|
|
|
// Several buildings aggregate into one card only when every part of that card's
|
|
// runtime content aggregates (REQ-UI-SELECTION-AGGREGATE). Belt-subsystem tiles do:
|
|
// the item list and the clear action already read and act on the whole selection
|
|
// (REQ-UI-BELT-ITEMS, REQ-UI-BELT-CLEAR). Splitters are among them -- their output
|
|
// filters are per-object configuration, which an aggregate simply does not show.
|
|
//
|
|
// Construction sites are excluded: findBuilding fails for a site, whose card is its
|
|
// own construction progress and whose tile the belt subsystem does not know yet.
|
|
bool allBeltTiles = true;
|
|
for (BuildingId id : request.buildings)
|
|
{
|
|
const Building* building = findBuilding(state, id);
|
|
if (!building || !isBeltSubsystemType(building->type))
|
|
{
|
|
allBeltTiles = false;
|
|
break;
|
|
}
|
|
}
|
|
if (allBeltTiles)
|
|
{
|
|
return { SelectionContentKind::Belt, false };
|
|
}
|
|
return { SelectionContentKind::MultiBuilding, false };
|
|
}
|
|
|
|
ContentKey chooseFieldContent(const SelectionRequest& request, Simulation& sim)
|
|
{
|
|
// A full single-object card is shown for a lone actor, and for debris whether one
|
|
// piece or several -- debris is the field category's other aggregating content
|
|
// (REQ-UI-FIELD-MULTI-SELECTION, REQ-UI-SELECTION-AGGREGATE).
|
|
if (request.actors.size() == 1 && request.debris.empty())
|
|
{
|
|
EntityAdmin& admin = sim.getAdmin();
|
|
const entt::entity actor = request.actors.front();
|
|
if (admin.isValid(actor) && admin.hasAll<ShipIdentityComponent>(actor))
|
|
{
|
|
return { SelectionContentKind::Ship, false };
|
|
}
|
|
if (admin.isValid(actor) && admin.hasAll<StationBodyComponent>(actor))
|
|
{
|
|
return { SelectionContentKind::Station, false };
|
|
}
|
|
return {};
|
|
}
|
|
|
|
if (request.actors.empty() && !request.debris.empty())
|
|
{
|
|
return { SelectionContentKind::Debris, false };
|
|
}
|
|
return { SelectionContentKind::FieldMulti, false };
|
|
}
|
|
|
|
} // namespace
|
|
|
|
|
|
ContentKey chooseContent(const SelectionRequest& request, Simulation& sim)
|
|
{
|
|
if (request.isEmpty())
|
|
{
|
|
return {};
|
|
}
|
|
// Buildings win, so a non-empty building selection is the whole selection
|
|
// (REQ-UI-SELECTION-CATEGORIES).
|
|
if (!request.buildings.empty())
|
|
{
|
|
return chooseBuildingContent(request, sim);
|
|
}
|
|
return chooseFieldContent(request, sim);
|
|
}
|
|
|
|
SelectionContent* createContent(const ContentKey& key, const SelectionRequest& request,
|
|
const SelectionContext& context, QWidget* parent)
|
|
{
|
|
switch (key.kind)
|
|
{
|
|
case SelectionContentKind::None:
|
|
return nullptr;
|
|
case SelectionContentKind::RecipeProduction:
|
|
return new RecipeProductionContent(context, request, parent);
|
|
case SelectionContentKind::Shipyard:
|
|
return new ShipyardContent(context, request, parent);
|
|
case SelectionContentKind::Storage:
|
|
return new StorageContent(context, request, parent);
|
|
case SelectionContentKind::Hq:
|
|
return new HqContent(context, request, parent);
|
|
case SelectionContentKind::Belt:
|
|
return new BeltContent(context, request, parent);
|
|
case SelectionContentKind::Splitter:
|
|
return new SplitterContent(context, request, parent);
|
|
case SelectionContentKind::MultiBuilding:
|
|
return new MultiBuildingContent(context, request, parent);
|
|
case SelectionContentKind::Ship:
|
|
return new ShipContent(context, request, parent);
|
|
case SelectionContentKind::Station:
|
|
return new StationContent(context, request, parent);
|
|
case SelectionContentKind::Debris:
|
|
return new DebrisContent(context, request, parent);
|
|
case SelectionContentKind::FieldMulti:
|
|
return new FieldMultiContent(context, request, parent);
|
|
}
|
|
return nullptr;
|
|
}
|