show recipes visually instead of describing them in text

This commit is contained in:
2026-08-11 21:40:36 +02:00
parent 3274df91d4
commit f255224ccd
38 changed files with 1023 additions and 452 deletions

98
src/ui/RecipeLineRow.h Normal file
View File

@@ -0,0 +1,98 @@
#pragma once
#include <optional>
#include <string>
#include <vector>
#include <QString>
#include <QWidget>
#include "BuildingType.h"
class BuildingIconCache;
class ItemIconCache;
class QHBoxLayout;
// One recipe drawn on a line: what it consumes, an arrow, what it produces, and how
// long a cycle takes (REQ-UI-RECIPE-SUMMARY). Every place the UI states what something
// makes draws this same line, so a recipe reads alike wherever it is shown:
//
// * beneath the selection button, for the recipe a building is running
// (REQ-UI-RECIPE-SUMMARY);
// * on a selection dialog's option buttons, under the option's name
// (REQ-UI-SELECT-OPTIONS);
// * in an item's production tooltip and in the unlock-choice dialog, there led by the
// icon of the building that runs the recipe and its name
// (REQ-UI-ITEM-TOOLTIP, REQ-DEF-SCHEMATIC-DROP);
// * on a module button and in the layout dialog's build cost, where there is a price
// and a time but nothing produced (REQ-MOD-UI-DIALOG, REQ-MOD-UI-STATS-PANEL).
class RecipeLineRow : public QWidget
{
Q_OBJECT
public:
// One item and how many of it the line states.
struct Amount
{
std::string itemId;
int amount = 0;
bool operator==(const Amount& other) const
{
return itemId == other.itemId && amount == other.amount;
}
};
// What the line says. Everything but the inputs is optional, which is what lets the
// one widget serve the summary, the option buttons, the tooltip lines and the cost
// lines.
struct Spec
{
// Drawn as a leading chip when the line has to say which building runs the
// recipe -- an item may have several producers (REQ-UI-ITEM-TOOLTIP). Unset
// wherever the surrounding widget already establishes the building.
std::optional<BuildingType> building;
// Leading recipe name, for the same reason. Empty where the name is the
// caption above the line instead.
QString name;
std::vector<Amount> inputs;
// Empty for a line that produces no item of its own: a ship schematic, or a
// module's price. No arrow is drawn then.
std::vector<Amount> outputs;
std::optional<double> durationSeconds;
// True where the time is added to something else rather than being a cycle of
// its own, and so reads "+3.0 s" (REQ-MOD-UI-DIALOG).
bool durationIsAddition = false;
bool operator==(const Spec& other) const
{
return building == other.building && name == other.name
&& inputs == other.inputs && outputs == other.outputs
&& durationSeconds == other.durationSeconds
&& durationIsAddition == other.durationIsAddition;
}
bool isEmpty() const { return inputs.empty() && outputs.empty(); }
};
// Both caches may be null, which leaves the icons off: an item with no square and
// no icon falls back to its id, and a building with no chip to its name alone.
// Neither is owned.
RecipeLineRow(ItemIconCache* itemIcons, BuildingIconCache* buildingIcons,
QWidget* parent = nullptr);
// Hides the row when the spec has nothing to state, which is how a building with no
// recipe shows no summary at all (REQ-UI-RECIPE-SUMMARY).
void setLine(const Spec& spec);
private:
void rebuild(const Spec& spec);
void addAmounts(const std::vector<Amount>& amounts);
ItemIconCache* m_itemIcons;
BuildingIconCache* m_buildingIcons;
QHBoxLayout* m_layout;
// What the row currently shows, so a refresh at tick rate rebuilds it only when the
// recipe actually changed.
Spec m_spec;
};