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
184 lines
6.8 KiB
C++
184 lines
6.8 KiB
C++
#include "SchematicChoiceDialog.h"
|
|
|
|
#include <QHBoxLayout>
|
|
#include <QLabel>
|
|
#include <QPushButton>
|
|
#include <QVBoxLayout>
|
|
|
|
#include "DisplayName.h"
|
|
#include "RecipeLineRow.h"
|
|
#include "RecipesConfig.h"
|
|
|
|
namespace
|
|
{
|
|
|
|
std::vector<RecipeLineRow::Amount> toAmounts(
|
|
const std::vector<RecipeIngredient>& ingredients)
|
|
{
|
|
std::vector<RecipeLineRow::Amount> amounts;
|
|
amounts.reserve(ingredients.size());
|
|
for (const RecipeIngredient& ingredient : ingredients)
|
|
{
|
|
amounts.push_back(RecipeLineRow::Amount{ ingredient.item, ingredient.amount });
|
|
}
|
|
return amounts;
|
|
}
|
|
|
|
|
|
QString grantKindLabel(SchematicType type)
|
|
{
|
|
switch (type)
|
|
{
|
|
case SchematicType::Ship: return SchematicChoiceDialog::tr("Ship");
|
|
case SchematicType::Module: return SchematicChoiceDialog::tr("Module");
|
|
case SchematicType::Building: return SchematicChoiceDialog::tr("Building");
|
|
case SchematicType::Recipe: return SchematicChoiceDialog::tr("Recipe");
|
|
}
|
|
return QString();
|
|
}
|
|
|
|
} // namespace
|
|
|
|
SchematicChoiceDialog::SchematicChoiceDialog(
|
|
const std::vector<SchematicChoiceOption>& options,
|
|
const RecipesConfig& recipes, const ItemTooltipContext& context,
|
|
QWidget* parent)
|
|
: ModalDialog(parent)
|
|
, m_chosenIndex(0)
|
|
{
|
|
QVBoxLayout* mainLayout = new QVBoxLayout(this);
|
|
|
|
// Its own title line, and the only one: a modal draws its title rather than wearing
|
|
// one (REQ-UI-MODAL-CHROME), and this dialog carries no close button because there
|
|
// is no way out but choosing (REQ-DEF-SCHEMATIC-DROP).
|
|
QLabel* titleLabel = new QLabel(tr("Choose a schematic to unlock:"), this);
|
|
QFont titleFont = titleLabel->font();
|
|
titleFont.setPointSize(titleFont.pointSize() + 2);
|
|
titleFont.setBold(true);
|
|
titleLabel->setFont(titleFont);
|
|
titleLabel->setAlignment(Qt::AlignCenter);
|
|
mainLayout->addWidget(titleLabel);
|
|
|
|
QHBoxLayout* optionsLayout = new QHBoxLayout();
|
|
mainLayout->addLayout(optionsLayout);
|
|
|
|
for (int i = 0; i < static_cast<int>(options.size()); ++i)
|
|
{
|
|
const SchematicChoiceOption& option = options[static_cast<std::size_t>(i)];
|
|
|
|
QWidget* card = new QWidget(this);
|
|
QVBoxLayout* cardLayout = new QVBoxLayout(card);
|
|
// Scoped to the card by object name. An unscoped "QWidget" selector cascades to
|
|
// every descendant, which boxed each label and each icon of the recipe lines
|
|
// inside it as well.
|
|
card->setObjectName(QStringLiteral("schematicChoiceCard"));
|
|
card->setStyleSheet(QStringLiteral(
|
|
"QWidget#schematicChoiceCard { border: 1px solid gray; padding: 8px; }"));
|
|
|
|
QLabel* nameLabel = new QLabel(QString::fromStdString(option.displayName), card);
|
|
QFont nameFont = nameLabel->font();
|
|
nameFont.setPointSize(nameFont.pointSize() + 1);
|
|
nameFont.setBold(true);
|
|
nameLabel->setFont(nameFont);
|
|
nameLabel->setAlignment(Qt::AlignCenter);
|
|
cardLayout->addWidget(nameLabel);
|
|
|
|
if (option.isArtifact)
|
|
{
|
|
QLabel* artifactTypeLabel = new QLabel(tr("Artifact"), card);
|
|
artifactTypeLabel->setAlignment(Qt::AlignCenter);
|
|
cardLayout->addWidget(artifactTypeLabel);
|
|
}
|
|
else
|
|
{
|
|
QLabel* grantsHeaderLabel = new QLabel(tr("Unlocks:"), card);
|
|
QFont grantsHeaderFont = grantsHeaderLabel->font();
|
|
grantsHeaderFont.setBold(true);
|
|
grantsHeaderLabel->setFont(grantsHeaderFont);
|
|
grantsHeaderLabel->setAlignment(Qt::AlignCenter);
|
|
cardLayout->addWidget(grantsHeaderLabel);
|
|
|
|
for (const GrantedSchematic& grant : option.grantedItems)
|
|
{
|
|
QLabel* grantLabel = new QLabel(
|
|
grantKindLabel(grant.type) + ": "
|
|
+ QString::fromStdString(grant.displayName),
|
|
card);
|
|
grantLabel->setAlignment(Qt::AlignCenter);
|
|
cardLayout->addWidget(grantLabel);
|
|
}
|
|
|
|
QLabel* unlocksHeaderLabel = new QLabel(tr("Unlocks recipes:"), card);
|
|
QFont unlocksHeaderFont = unlocksHeaderLabel->font();
|
|
unlocksHeaderFont.setBold(true);
|
|
unlocksHeaderLabel->setFont(unlocksHeaderFont);
|
|
unlocksHeaderLabel->setAlignment(Qt::AlignCenter);
|
|
cardLayout->addWidget(unlocksHeaderLabel);
|
|
|
|
if (option.newlyUnlockedRecipeIds.empty())
|
|
{
|
|
QLabel* noneLabel = new QLabel(tr("None"), card);
|
|
noneLabel->setAlignment(Qt::AlignCenter);
|
|
cardLayout->addWidget(noneLabel);
|
|
}
|
|
else
|
|
{
|
|
for (const std::string& recipeId : option.newlyUnlockedRecipeIds)
|
|
{
|
|
const RecipeDef* def = recipes.findRecipeDef(recipeId);
|
|
if (def == nullptr) { continue; }
|
|
|
|
// The recipe drawn as it is everywhere else, led by the building
|
|
// that runs it and its name, so the line says everything there is to
|
|
// say about the recipe itself (REQ-DEF-SCHEMATIC-DROP). The items it
|
|
// names still explain themselves, and the card around them is not
|
|
// clicked -- the Select button below it is -- so a click on one says
|
|
// it at once (REQ-UI-ITEM-VALUE-TOOLTIP).
|
|
RecipeLineRow::Spec spec;
|
|
spec.building = def->building;
|
|
spec.name = QString::fromStdString(toDisplayName(def->id));
|
|
spec.inputs = toAmounts(def->inputs);
|
|
spec.outputGroups = RecipeLineRow::toOutputGroups(*def);
|
|
spec.durationSeconds = def->durationSeconds;
|
|
|
|
RecipeLineRow* line =
|
|
new RecipeLineRow(context.itemIcons, context.buildingIcons, card);
|
|
line->setCardChrome(true);
|
|
line->setItemTooltips(context,
|
|
TooltipTrigger::Trigger::HoverAndClick);
|
|
cardLayout->addWidget(line);
|
|
line->setLine(spec);
|
|
}
|
|
}
|
|
}
|
|
|
|
QPushButton* selectButton = new QPushButton(tr("Select"), card);
|
|
cardLayout->addWidget(selectButton);
|
|
|
|
const int index = i;
|
|
connect(selectButton, &QPushButton::clicked, this, [this, index]()
|
|
{
|
|
onOptionClicked(index);
|
|
});
|
|
|
|
optionsLayout->addWidget(card);
|
|
}
|
|
}
|
|
|
|
int SchematicChoiceDialog::getChosenIndex() const
|
|
{
|
|
return m_chosenIndex;
|
|
}
|
|
|
|
void SchematicChoiceDialog::reject()
|
|
{
|
|
// Deliberately empty: the dialog stays open until an option is clicked
|
|
// (REQ-DEF-SCHEMATIC-DROP). The game is paused meanwhile, so nothing waits on it.
|
|
}
|
|
|
|
void SchematicChoiceDialog::onOptionClicked(int index)
|
|
{
|
|
m_chosenIndex = index;
|
|
accept();
|
|
}
|