Files
dota_factory/src/ui/RecipeLineRow.h
Malte Langkabel 44ec3c51f1 explain an item wherever the UI names one
Every display naming an item now shows that item's production tooltip, not
only the selection panel's chips: the header block stock, the total cost of
a building multi-selection, the remaining scrap of a debris selection, the
icons of every recipe summary, and a blueprint card's cost.

ItemTooltip moves out of the selection panel and takes an
ItemTooltipContext of its own -- an item is named all over the UI, and the
panel's context carries visuals and a debug flag no tooltip reads. A
recipe line wraps each icon with its amount so the pair can be pointed at
as one statement, and attaches tooltips only where asked: a module button
and the item tooltip itself draw the same line and stay silent.

Hover only wherever the display sits on something the player clicks, whose
click is not free to explain. Note that a recipe line on an option button
can no longer be transparent to the mouse -- Qt never looks inside a
transparent widget for the cursor -- so it relies on press propagation to
keep picking the option.

The header block stock loses world.building_blocks_tooltip, showing what
every other block icon shows instead. The Expand button keeps no tooltip:
its cost is painted into its face with nowhere to hang one, and the button
is due to be removed.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Ne3mejABZoLWKLh8fgpM3x
2026-08-18 21:41:34 +02:00

146 lines
6.6 KiB
C++

#pragma once
#include <optional>
#include <string>
#include <vector>
#include <QString>
#include <QWidget>
#include "BuildingType.h"
#include "ItemTooltipContext.h"
#include "RecipesConfig.h"
#include "TooltipTrigger.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<BuildingType> 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<Amount> 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<std::vector<Amount>> outputGroups;
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 && 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<std::vector<Amount>> 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);
// Lets every amount on the line explain the item it names, the number and the icon
// together as one target (REQ-UI-ITEM-VALUE-TOOLTIP). Off by default, because the
// line is silent wherever what holds it carries a tooltip of its own -- a module
// button (REQ-MOD-UI-MODULE-TOOLTIP) -- or is itself a tooltip (REQ-UI-ITEM-TOOLTIP).
// HoverOnly where the line sits on something the player clicks, whose click is not
// free to explain (REQ-UI-TOOLTIP-TRIGGER).
//
// Call before the first setLine(): the tooltips are attached as the line is built,
// and an unchanged spec is not rebuilt.
void setItemTooltips(const ItemTooltipContext& context,
TooltipTrigger::Trigger trigger);
private:
void rebuild(const Spec& spec);
void addAmounts(const std::vector<Amount>& amounts);
// The icon and the number of one item, side by side in a widget of their own so the
// pair can be pointed at as one thing (REQ-UI-ITEM-VALUE-TOOLTIP).
void addAmount(const Amount& entry);
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;
// Unset on a line whose items say nothing, which is most of them.
std::optional<ItemTooltipContext> m_tooltipContext;
TooltipTrigger::Trigger m_tooltipTrigger =
TooltipTrigger::Trigger::HoverOnly;
};