fix selection panel contents
This commit is contained in:
@@ -3,6 +3,7 @@
|
||||
#include "Building.h"
|
||||
#include "BuildingTarget.h"
|
||||
#include "GameConfig.h"
|
||||
#include "ProductionRules.h"
|
||||
|
||||
AutoProductionContent::AutoProductionContent(const SelectionContext& context,
|
||||
const SelectionRequest& request,
|
||||
@@ -19,6 +20,27 @@ BufferedBuildingContent::CycleInfo AutoProductionContent::getCycleInfo(
|
||||
// REQ-BLD-REPROCESSING), so its production section is always shown -- but only a
|
||||
// running cycle names a recipe, so while it is idle there is no cycle to describe.
|
||||
info.runsProduction = true;
|
||||
|
||||
if (target.building)
|
||||
{
|
||||
// What the building handles at all, so its buffers are not blank whenever it
|
||||
// happens to be between cycles (REQ-UI-SINGLE-SELECTION). This is the same union
|
||||
// of every recipe of its type that the simulation sized the buffers over, and
|
||||
// the locked ones among them are dropped when the card lists them.
|
||||
for (const RecipeDef* recipe :
|
||||
gatherCandidateRecipes(*getContext().config, *target.building))
|
||||
{
|
||||
for (const RecipeIngredient& ingredient : recipe->inputs)
|
||||
{
|
||||
info.handledInputs.push_back(ingredient.item);
|
||||
}
|
||||
for (const RecipeOutput& output : recipe->outputs)
|
||||
{
|
||||
info.handledOutputs.push_back(output.item);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!target.building || !target.building->production.has_value())
|
||||
{
|
||||
return info;
|
||||
|
||||
@@ -50,9 +50,14 @@ public:
|
||||
protected:
|
||||
void paintEvent(QPaintEvent* /*event*/) override
|
||||
{
|
||||
// The active group is asked for by name rather than taken from the current one,
|
||||
// which follows the window's focus: the inactive group's highlight is close
|
||||
// enough to the card's background that the bar reads as gone whenever the game
|
||||
// is tabbed away from or a modal dialog holds focus -- exactly when a player
|
||||
// watching a build finish is most likely to be looking at it.
|
||||
const QColor fill = m_fillColor.isValid()
|
||||
? m_fillColor
|
||||
: palette().color(QPalette::Highlight);
|
||||
: palette().color(QPalette::Active, QPalette::Highlight);
|
||||
|
||||
QColor track = fill;
|
||||
track.setAlpha(kTrackAlpha);
|
||||
|
||||
@@ -1,103 +0,0 @@
|
||||
#include "BufferSection.h"
|
||||
|
||||
#include <vector>
|
||||
|
||||
#include <QVBoxLayout>
|
||||
|
||||
#include "Building.h"
|
||||
#include "DisplayName.h"
|
||||
#include "ItemChipRow.h"
|
||||
#include "SectionBox.h"
|
||||
|
||||
namespace
|
||||
{
|
||||
|
||||
int findPerCycle(const std::map<std::string, int>& perCycle, const std::string& itemId)
|
||||
{
|
||||
const std::map<std::string, int>::const_iterator it = perCycle.find(itemId);
|
||||
return (it != perCycle.end()) ? it->second : 0;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
|
||||
BufferSection::BufferSection(const SelectionContext& context, QWidget* parent)
|
||||
: QWidget(parent)
|
||||
{
|
||||
QVBoxLayout* layout = new QVBoxLayout(this);
|
||||
layout->setContentsMargins(0, 0, 0, 0);
|
||||
layout->setSpacing(6);
|
||||
|
||||
m_inputSection = new SectionBox(tr("Input buffers"), this);
|
||||
m_inputChips = new ItemChipRow(context.itemIcons, m_inputSection);
|
||||
m_inputSection->getContentLayout()->addWidget(m_inputChips);
|
||||
|
||||
m_outputSection = new SectionBox(tr("Output buffer"), this);
|
||||
m_outputChips = new ItemChipRow(context.itemIcons, m_outputSection);
|
||||
m_outputSection->getContentLayout()->addWidget(m_outputChips);
|
||||
|
||||
layout->addWidget(m_inputSection);
|
||||
layout->addWidget(m_outputSection);
|
||||
}
|
||||
|
||||
void BufferSection::setBuffers(const Building& building,
|
||||
const std::map<std::string, int>& perCycleInputs,
|
||||
const std::map<std::string, int>& perCycleOutputs)
|
||||
{
|
||||
std::vector<ItemChipRow::Entry> inputs;
|
||||
for (const std::pair<const ItemType, int>& entry : building.inputBuffer.counts)
|
||||
{
|
||||
ItemChipRow::Entry chip;
|
||||
chip.itemId = entry.first.id;
|
||||
chip.countText = QString::number(entry.second);
|
||||
|
||||
const int perCycle = findPerCycle(perCycleInputs, entry.first.id);
|
||||
if (perCycle > 0)
|
||||
{
|
||||
chip.subLine = tr("/ %1 per cycle").arg(perCycle);
|
||||
}
|
||||
inputs.push_back(chip);
|
||||
}
|
||||
|
||||
// Output-side items are the buffered ones plus those still emerging onto the output
|
||||
// belts: an emerging item still belongs to the output buffer (REQ-MAT-OUTPUT-EMERGE),
|
||||
// so leaving it out would make it vanish from the panel while it animates.
|
||||
std::map<std::string, int> outputCounts;
|
||||
for (const Item& item : building.outputBuffer.items)
|
||||
{
|
||||
outputCounts[item.type.id]++;
|
||||
}
|
||||
for (const std::vector<BeltItemSlot>& lane : building.emergingItems)
|
||||
{
|
||||
for (const BeltItemSlot& slot : lane)
|
||||
{
|
||||
outputCounts[slot.item.type.id]++;
|
||||
}
|
||||
}
|
||||
// A configured building lists everything its cycle produces, so an output the player
|
||||
// is waiting for reads as 0 rather than being absent.
|
||||
for (const std::pair<const std::string, int>& entry : perCycleOutputs)
|
||||
{
|
||||
outputCounts.emplace(entry.first, 0);
|
||||
}
|
||||
|
||||
std::vector<ItemChipRow::Entry> outputs;
|
||||
for (const std::pair<const std::string, int>& entry : outputCounts)
|
||||
{
|
||||
ItemChipRow::Entry chip;
|
||||
chip.itemId = entry.first;
|
||||
// Counted against the buffer's capacity, which is what production stops at
|
||||
// (REQ-MAT-OUTPUT-BUFFER).
|
||||
chip.countText = building.outputBuffer.capacity > 0
|
||||
? tr("%1 / %2").arg(entry.second).arg(building.outputBuffer.capacity)
|
||||
: QString::number(entry.second);
|
||||
chip.subLine = QString::fromStdString(toDisplayName(entry.first));
|
||||
outputs.push_back(chip);
|
||||
}
|
||||
|
||||
m_inputChips->setEntries(inputs);
|
||||
m_outputChips->setEntries(outputs);
|
||||
m_inputSection->setVisible(!inputs.empty());
|
||||
m_outputSection->setVisible(!outputs.empty());
|
||||
setVisible(!inputs.empty() || !outputs.empty());
|
||||
}
|
||||
@@ -1,39 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include <map>
|
||||
#include <string>
|
||||
|
||||
#include <QWidget>
|
||||
|
||||
#include "SelectionContext.h"
|
||||
|
||||
struct Building;
|
||||
class ItemChipRow;
|
||||
class SectionBox;
|
||||
|
||||
// The input and output buffer contents of one building, each a captioned section of item
|
||||
// chips (REQ-UI-SINGLE-SELECTION).
|
||||
//
|
||||
// Counting what is in the buffers is the same for every building type, so it happens
|
||||
// here; what a cycle consumes and produces is not, so the owning content supplies those
|
||||
// per-cycle amounts. An item with no entry in the maps is shown without a denominator,
|
||||
// and a section holding nothing is not shown at all.
|
||||
class BufferSection : public QWidget
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit BufferSection(const SelectionContext& context, QWidget* parent = nullptr);
|
||||
|
||||
// perCycleInputs and perCycleOutputs map an item id to the amount one production
|
||||
// cycle consumes or produces. Both may be empty, for a building that runs no cycle.
|
||||
void setBuffers(const Building& building,
|
||||
const std::map<std::string, int>& perCycleInputs,
|
||||
const std::map<std::string, int>& perCycleOutputs);
|
||||
|
||||
private:
|
||||
SectionBox* m_inputSection;
|
||||
ItemChipRow* m_inputChips;
|
||||
SectionBox* m_outputSection;
|
||||
ItemChipRow* m_outputChips;
|
||||
};
|
||||
@@ -1,15 +1,17 @@
|
||||
#include "BufferedBuildingContent.h"
|
||||
|
||||
#include <vector>
|
||||
#include <map>
|
||||
#include <set>
|
||||
|
||||
#include <QVBoxLayout>
|
||||
|
||||
#include "Building.h"
|
||||
#include "BufferSection.h"
|
||||
#include "BuildingTarget.h"
|
||||
#include "DisplayName.h"
|
||||
#include "FactoryQueries.h"
|
||||
#include "ProductionSection.h"
|
||||
#include "RecipeSummaryRow.h"
|
||||
#include "SectionBox.h"
|
||||
#include "SelectionNames.h"
|
||||
#include "Simulation.h"
|
||||
|
||||
@@ -27,6 +29,33 @@ std::vector<RecipeSummaryRow::Amount> toAmounts(const std::map<std::string, int>
|
||||
return amounts;
|
||||
}
|
||||
|
||||
// Every item one side of the card should list: what the buffer holds, what a cycle
|
||||
// moves, and what the building handles at all. A building's buffers can carry items it
|
||||
// is not currently making anything of, and an auto-recipe building between cycles names
|
||||
// nothing at all, so the three sources are unioned rather than one being picked.
|
||||
std::set<std::string> collectItemIds(const std::map<std::string, int>& buffered,
|
||||
const std::map<std::string, int>& perCycle,
|
||||
const std::vector<std::string>& handled)
|
||||
{
|
||||
std::set<std::string> itemIds;
|
||||
for (const std::pair<const std::string, int>& entry : buffered)
|
||||
{
|
||||
itemIds.insert(entry.first);
|
||||
}
|
||||
for (const std::pair<const std::string, int>& entry : perCycle)
|
||||
{
|
||||
itemIds.insert(entry.first);
|
||||
}
|
||||
itemIds.insert(handled.begin(), handled.end());
|
||||
return itemIds;
|
||||
}
|
||||
|
||||
int lookUp(const std::map<std::string, int>& map, const std::string& key)
|
||||
{
|
||||
const std::map<std::string, int>::const_iterator it = map.find(key);
|
||||
return (it != map.end()) ? it->second : 0;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
|
||||
@@ -40,10 +69,21 @@ BufferedBuildingContent::BufferedBuildingContent(const SelectionContext& context
|
||||
m_recipeSummary = new RecipeSummaryRow(context.itemIcons, this);
|
||||
getConfigurationLayout()->addWidget(m_recipeSummary);
|
||||
|
||||
m_buffers = new BufferSection(context, this);
|
||||
m_inputSection = new SectionBox(tr("Input buffers"), this);
|
||||
m_inputChips = new ItemChipRow(context.itemIcons, m_inputSection);
|
||||
m_inputSection->getContentLayout()->addWidget(m_inputChips);
|
||||
|
||||
m_production = new ProductionSection(this);
|
||||
getRuntimeLayout()->addWidget(m_buffers);
|
||||
|
||||
m_outputSection = new SectionBox(tr("Output buffer"), this);
|
||||
m_outputChips = new ItemChipRow(context.itemIcons, m_outputSection);
|
||||
m_outputSection->getContentLayout()->addWidget(m_outputChips);
|
||||
|
||||
// In the direction the materials flow: what goes in, what is being made of it, what
|
||||
// has come out (REQ-UI-SINGLE-SELECTION, REQ-UI-PRODUCTION-PROGRESS).
|
||||
getRuntimeLayout()->addWidget(m_inputSection);
|
||||
getRuntimeLayout()->addWidget(m_production);
|
||||
getRuntimeLayout()->addWidget(m_outputSection);
|
||||
}
|
||||
|
||||
void BufferedBuildingContent::refreshConfiguration()
|
||||
@@ -77,8 +117,93 @@ void BufferedBuildingContent::refreshRuntime()
|
||||
setProductionStatusSlot(*target.building);
|
||||
|
||||
const CycleInfo cycle = getCycleInfo(target);
|
||||
m_buffers->setBuffers(*target.building, cycle.perCycleInputs, cycle.perCycleOutputs);
|
||||
|
||||
const std::vector<ItemChipRow::Entry> inputs =
|
||||
buildInputEntries(*target.building, cycle);
|
||||
const std::vector<ItemChipRow::Entry> outputs =
|
||||
buildOutputEntries(*target.building, cycle);
|
||||
|
||||
m_inputChips->setEntries(inputs);
|
||||
m_outputChips->setEntries(outputs);
|
||||
m_inputSection->setVisible(!inputs.empty());
|
||||
m_outputSection->setVisible(!outputs.empty());
|
||||
|
||||
m_production->setProduction(cycle.runsProduction, *target.building,
|
||||
cycle.durationSeconds,
|
||||
getContext().sim->getCurrentTick());
|
||||
}
|
||||
|
||||
std::vector<ItemChipRow::Entry> BufferedBuildingContent::buildInputEntries(
|
||||
const Building& building, const CycleInfo& cycle) const
|
||||
{
|
||||
std::map<std::string, int> buffered;
|
||||
for (const std::pair<const ItemType, int>& entry : building.inputBuffer.counts)
|
||||
{
|
||||
buffered[entry.first.id] = entry.second;
|
||||
}
|
||||
|
||||
std::vector<ItemChipRow::Entry> entries;
|
||||
for (const std::string& itemId :
|
||||
collectItemIds(buffered, cycle.perCycleInputs, cycle.handledInputs))
|
||||
{
|
||||
// An auto-recipe building's buffers are sized over every recipe of its type,
|
||||
// including recipes still locked, so those entries are left out here
|
||||
// (REQ-UI-SINGLE-SELECTION, REQ-LOCK-UI-RECIPE).
|
||||
if (!getContext().sim->isItemUnlocked(itemId)) { continue; }
|
||||
|
||||
ItemChipRow::Entry chip;
|
||||
chip.itemId = itemId;
|
||||
chip.countText = QString::number(lookUp(buffered, itemId));
|
||||
|
||||
const int perCycle = lookUp(cycle.perCycleInputs, itemId);
|
||||
if (perCycle > 0)
|
||||
{
|
||||
chip.subLine = tr("/ %1 per cycle").arg(perCycle);
|
||||
}
|
||||
else
|
||||
{
|
||||
chip.subLine = QString::fromStdString(toDisplayName(itemId));
|
||||
}
|
||||
entries.push_back(chip);
|
||||
}
|
||||
return entries;
|
||||
}
|
||||
|
||||
std::vector<ItemChipRow::Entry> BufferedBuildingContent::buildOutputEntries(
|
||||
const Building& building, const CycleInfo& cycle) const
|
||||
{
|
||||
// The buffered items plus those still emerging onto the output belts: an emerging
|
||||
// item still belongs to the output buffer (REQ-MAT-OUTPUT-EMERGE), so leaving it out
|
||||
// would make it vanish from the panel while it animates.
|
||||
std::map<std::string, int> buffered;
|
||||
for (const Item& item : building.outputBuffer.items)
|
||||
{
|
||||
buffered[item.type.id]++;
|
||||
}
|
||||
for (const std::vector<BeltItemSlot>& lane : building.emergingItems)
|
||||
{
|
||||
for (const BeltItemSlot& slot : lane)
|
||||
{
|
||||
buffered[slot.item.type.id]++;
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<ItemChipRow::Entry> entries;
|
||||
for (const std::string& itemId :
|
||||
collectItemIds(buffered, cycle.perCycleOutputs, cycle.handledOutputs))
|
||||
{
|
||||
if (!getContext().sim->isItemUnlocked(itemId)) { continue; }
|
||||
|
||||
ItemChipRow::Entry chip;
|
||||
chip.itemId = itemId;
|
||||
// Counted against the buffer's capacity, which is what production stops at
|
||||
// (REQ-MAT-OUTPUT-BUFFER).
|
||||
chip.countText = building.outputBuffer.capacity > 0
|
||||
? tr("%1 / %2").arg(lookUp(buffered, itemId))
|
||||
.arg(building.outputBuffer.capacity)
|
||||
: QString::number(lookUp(buffered, itemId));
|
||||
chip.subLine = QString::fromStdString(toDisplayName(itemId));
|
||||
entries.push_back(chip);
|
||||
}
|
||||
return entries;
|
||||
}
|
||||
|
||||
@@ -2,20 +2,23 @@
|
||||
|
||||
#include <map>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "BuildingId.h"
|
||||
#include "ItemChipRow.h"
|
||||
#include "SelectionContent.h"
|
||||
|
||||
struct Building;
|
||||
struct BuildingTarget;
|
||||
class BufferSection;
|
||||
class ProductionSection;
|
||||
class RecipeSummaryRow;
|
||||
class SectionBox;
|
||||
|
||||
// Shared body of the four cards that show one building with buffers -- the Miner and
|
||||
// Assembler, the Smelter and Reprocessing Plant, the Shipyard, and the Salvage Bay
|
||||
// (REQ-UI-SELECTION-CONTENT). All four show the same header identity and status, recipe
|
||||
// summary, buffer contents and production progress; they differ only in what one
|
||||
// production cycle costs and how long it takes, which is what the subclass supplies.
|
||||
// (REQ-UI-SELECTION-CONTENT). All four show the same header status, recipe summary,
|
||||
// buffer contents and production progress; they differ only in what one production cycle
|
||||
// costs and how long it takes, which is what the subclass supplies.
|
||||
//
|
||||
// This is implementation sharing, not a catalog entry: every concrete subclass is one
|
||||
// row of the content catalog.
|
||||
@@ -29,6 +32,14 @@ protected:
|
||||
{
|
||||
std::map<std::string, int> perCycleInputs;
|
||||
std::map<std::string, int> perCycleOutputs;
|
||||
|
||||
// Items the card lists whether or not they are currently in the buffers, for a
|
||||
// building whose recipe is implicit and so has nothing to name while it sits
|
||||
// between cycles (REQ-BLD-SMELTER, REQ-BLD-REPROCESSING). They carry no
|
||||
// per-cycle denominator, since no one recipe is in force.
|
||||
std::vector<std::string> handledInputs;
|
||||
std::vector<std::string> handledOutputs;
|
||||
|
||||
// False when the building produces nothing at all (the Salvage Bay,
|
||||
// REQ-BLD-SALVAGE-BAY) or has no recipe or schematic selected yet: the
|
||||
// production section is then not shown (REQ-UI-PRODUCTION-PROGRESS).
|
||||
@@ -55,8 +66,20 @@ private:
|
||||
void refreshConfiguration() override;
|
||||
void refreshRuntime() override;
|
||||
|
||||
BuildingId m_id;
|
||||
RecipeSummaryRow* m_recipeSummary;
|
||||
BufferSection* m_buffers;
|
||||
std::vector<ItemChipRow::Entry> buildInputEntries(const Building& building,
|
||||
const CycleInfo& cycle) const;
|
||||
std::vector<ItemChipRow::Entry> buildOutputEntries(const Building& building,
|
||||
const CycleInfo& cycle) const;
|
||||
|
||||
BuildingId m_id;
|
||||
|
||||
RecipeSummaryRow* m_recipeSummary;
|
||||
|
||||
// Input buffers, production progress, output buffer -- in that order, so the card
|
||||
// reads the way the materials flow (REQ-UI-SINGLE-SELECTION).
|
||||
SectionBox* m_inputSection;
|
||||
ItemChipRow* m_inputChips;
|
||||
ProductionSection* m_production;
|
||||
SectionBox* m_outputSection;
|
||||
ItemChipRow* m_outputChips;
|
||||
};
|
||||
|
||||
@@ -15,7 +15,6 @@ SET(HDRS
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/ItemChip.h
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/ItemChipRow.h
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/RecipeSummaryRow.h
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/BufferSection.h
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/ProductionSection.h
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/RecipeSelectionControl.h
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/ClearBeltControl.h
|
||||
@@ -51,7 +50,6 @@ SET(SRCS
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/ItemChip.cpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/ItemChipRow.cpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/RecipeSummaryRow.cpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/BufferSection.cpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/ProductionSection.cpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/RecipeSelectionControl.cpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/ClearBeltControl.cpp
|
||||
|
||||
@@ -20,14 +20,15 @@ HqContent::HqContent(const SelectionContext& context, const SelectionRequest& re
|
||||
// (REQ-BLD-DECONSTRUCT), so it is never a construction site.
|
||||
: SelectionContent(context, std::nullopt, parent)
|
||||
{
|
||||
m_hpBar = new BarRow(tr("HP"), this);
|
||||
|
||||
m_stockSection = new SectionBox(tr("Building blocks"), this);
|
||||
m_stockChips = new ItemChipRow(context.itemIcons, m_stockSection);
|
||||
m_stockSection->getContentLayout()->addWidget(m_stockChips);
|
||||
|
||||
m_hpBar = new BarRow(tr("HP"), this);
|
||||
|
||||
getRuntimeLayout()->addWidget(m_stockSection);
|
||||
// HP first, as on every card that has it (REQ-UI-SELECTION-CARD, REQ-UI-HQ-PANEL).
|
||||
getRuntimeLayout()->addWidget(m_hpBar);
|
||||
getRuntimeLayout()->addWidget(m_stockSection);
|
||||
|
||||
setBuildingIdentity(BuildingType::Hq, getBuildingTypeName(BuildingType::Hq));
|
||||
}
|
||||
|
||||
@@ -96,7 +96,10 @@ SelectionContent::SelectionContent(const SelectionContext& context,
|
||||
m_constructionSection->getContentLayout()->addWidget(m_constructionBar);
|
||||
m_constructionSection->getContentLayout()->addWidget(
|
||||
new EmptyNote(tr("No buffers until built"), m_constructionSection));
|
||||
cardLayout->addWidget(m_constructionSection);
|
||||
// Directly below the header rather than where the runtime group sits, so how far
|
||||
// along the site is reads before what it is configured to become
|
||||
// (REQ-UI-SELECTION-CARD).
|
||||
cardLayout->insertWidget(1, m_constructionSection);
|
||||
|
||||
setSlot(QColor(), tr("constructing"));
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user