#pragma once #include #include #include #include #include #include "BuildingType.h" #include "RecipesConfig.h" class BuildingIconCache; class ItemIconCache; class QHBoxLayout; class QVBoxLayout; // One recipe drawn: what it consumes, an arrow, what it produces, and how long a cycle // takes (REQ-UI-RECIPE-SUMMARY), under a header naming the building that runs it and the // recipe itself where the recipe has to identify itself. 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 { // The chip of the building running the recipe, on the header line, where the // recipe has to say which building that is -- an item may have several producers // (REQ-UI-ITEM-TOOLTIP). Unset wherever the surrounding widget already // establishes the building, and the header line is then left out entirely. std::optional building; // The recipe's name, beside the building chip on the header line. Empty where the // name is the caption of the widget around this one instead. QString name; std::vector inputs; // What the recipe produces, one entry per output group (REQ-MAT-OUTPUT-GROUP). // The items of a group are drawn joined by `+` because they come together, and the // groups joined by `/` because only one of them happens. 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> outputGroups; std::optional 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 && outputGroups == other.outputGroups && durationSeconds == other.durationSeconds && durationIsAddition == other.durationIsAddition; } bool isEmpty() const { return inputs.empty() && outputGroups.empty(); } }; // A recipe's output groups as this row states them (REQ-MAT-OUTPUT-GROUP). Shared, so // that every place drawing a recipe -- the summary, the option buttons, the tooltip // lines -- converts it the same way rather than each keeping its own copy. static std::vector> toOutputGroups(const RecipeDef& recipe); // 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); // Boxes the line in a card of its own, for the two places that stack several of them // (REQ-UI-ITEM-TOOLTIP, REQ-DEF-SCHEMATIC-DROP): a run of bare lines reads as one // field of icons and numbers, and the box is what separates one recipe from the next. // Off by default -- a line drawn inside a button or a panel section is already framed // by what holds it. void setCardChrome(bool enabled); private: void rebuild(const Spec& spec); void addAmounts(const std::vector& amounts); ItemIconCache* m_itemIcons; BuildingIconCache* m_buildingIcons; QVBoxLayout* m_outerLayout; // The two lines, each a row widget of its own so a rebuild only has to empty their // layouts -- no nested layout to take apart. The header is hidden where the spec // names neither a building nor a recipe. QWidget* m_headerRow; QHBoxLayout* m_headerLayout; QWidget* m_amountsRow; QHBoxLayout* m_amountsLayout; // What the row currently shows, so a refresh at tick rate rebuilds it only when the // recipe actually changed. Spec m_spec; };