hold the header's width against a changing status caption
The panel is sized to its card, so anything that changes the card's width drags the panel with it. The status caption changes as a building works -- "producing" is shorter than "missing input", which is shorter than "output full" -- and the panel twitched every time it did. On a Smelter it also changed height, because a narrower card wraps its item chips differently. The pill now reserves room for the widest caption it can ever show, so the header keeps one width whatever the state is. The captions were spelled out in the switch that chose them, which left no way to ask for the set; they come from one function now, and the set is what the reservation is built from. Ship cards do the same with the behaviour names (REQ-UI-SHIP-BEHAVIOR), which change just as often while a ship fights. Miner card, sampled every tick for 900 ticks across two different-length captions: one distinct width, one distinct height. The reservation is what does it -- the same run without it sits at 138px, with it at 155px, the width of the longest caption. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01JcReq7hVk4KUPhTDKWAG7K
This commit is contained in:
@@ -32,6 +32,38 @@ const int kSymbolSizePx = 20;
|
|||||||
const int kCardSpacingPx = 6;
|
const int kCardSpacingPx = 6;
|
||||||
const int kHeaderSpacingPx = 6;
|
const int kHeaderSpacingPx = 6;
|
||||||
|
|
||||||
|
// The Salvage Bay has no recipe and no cycle: its two states say whether it is holding
|
||||||
|
// scrap, not whether it is producing (REQ-BLD-SALVAGE-BAY, REQ-UI-SELECTION-STATUS).
|
||||||
|
QString getStatusCaption(ProductionStatus status, bool isSalvageBay)
|
||||||
|
{
|
||||||
|
switch (status)
|
||||||
|
{
|
||||||
|
case ProductionStatus::Unconfigured: return QObject::tr("no recipe");
|
||||||
|
case ProductionStatus::Producing:
|
||||||
|
return isSalvageBay ? QObject::tr("holding scrap")
|
||||||
|
: QObject::tr("producing");
|
||||||
|
case ProductionStatus::Starved:
|
||||||
|
return isSalvageBay ? QObject::tr("empty") : QObject::tr("missing input");
|
||||||
|
case ProductionStatus::Blocked: return QObject::tr("output full");
|
||||||
|
}
|
||||||
|
return QString();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Every caption the status slot can end up showing for this building, so the header can
|
||||||
|
// keep its width as the state changes rather than the panel jumping with it.
|
||||||
|
QStringList getAllStatusCaptions(bool isSalvageBay)
|
||||||
|
{
|
||||||
|
QStringList captions;
|
||||||
|
for (ProductionStatus status : { ProductionStatus::Unconfigured,
|
||||||
|
ProductionStatus::Producing,
|
||||||
|
ProductionStatus::Starved,
|
||||||
|
ProductionStatus::Blocked })
|
||||||
|
{
|
||||||
|
captions << getStatusCaption(status, isSalvageBay);
|
||||||
|
}
|
||||||
|
return captions;
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace
|
} // namespace
|
||||||
|
|
||||||
|
|
||||||
@@ -168,25 +200,26 @@ void SelectionContent::setProductionStatusSlot(const Building& building)
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const StatusLightVisuals& colors = m_context.visuals->statusLight;
|
|
||||||
// The Salvage Bay has no recipe and no cycle: its two states say whether it is
|
|
||||||
// holding scrap, not whether it is producing (REQ-BLD-SALVAGE-BAY).
|
|
||||||
const bool isSalvageBay = (building.type == BuildingType::SalvageBay);
|
const bool isSalvageBay = (building.type == BuildingType::SalvageBay);
|
||||||
|
// Room for every caption this building can show, so the panel keeps its width as the
|
||||||
|
// building's state changes rather than jumping with it.
|
||||||
|
reserveSlotFor(getAllStatusCaptions(isSalvageBay));
|
||||||
|
|
||||||
|
const StatusLightVisuals& colors = m_context.visuals->statusLight;
|
||||||
|
QColor dotColor;
|
||||||
switch (*status)
|
switch (*status)
|
||||||
{
|
{
|
||||||
case ProductionStatus::Unconfigured:
|
case ProductionStatus::Unconfigured: dotColor = colors.grey; break;
|
||||||
setSlot(colors.grey, tr("no recipe"));
|
case ProductionStatus::Producing: dotColor = colors.green; break;
|
||||||
break;
|
case ProductionStatus::Starved: dotColor = colors.red; break;
|
||||||
case ProductionStatus::Producing:
|
case ProductionStatus::Blocked: dotColor = colors.yellow; break;
|
||||||
setSlot(colors.green, isSalvageBay ? tr("holding scrap") : tr("producing"));
|
|
||||||
break;
|
|
||||||
case ProductionStatus::Starved:
|
|
||||||
setSlot(colors.red, isSalvageBay ? tr("empty") : tr("missing input"));
|
|
||||||
break;
|
|
||||||
case ProductionStatus::Blocked:
|
|
||||||
setSlot(colors.yellow, tr("output full"));
|
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
|
setSlot(dotColor, getStatusCaption(*status, isSalvageBay));
|
||||||
|
}
|
||||||
|
|
||||||
|
void SelectionContent::reserveSlotFor(const QStringList& captions)
|
||||||
|
{
|
||||||
|
m_statusPill->reserveFor(captions);
|
||||||
}
|
}
|
||||||
|
|
||||||
void SelectionContent::refreshConstruction()
|
void SelectionContent::refreshConstruction()
|
||||||
|
|||||||
@@ -5,6 +5,7 @@
|
|||||||
#include <QColor>
|
#include <QColor>
|
||||||
#include <QPixmap>
|
#include <QPixmap>
|
||||||
#include <QString>
|
#include <QString>
|
||||||
|
#include <QStringList>
|
||||||
#include <QWidget>
|
#include <QWidget>
|
||||||
|
|
||||||
#include "BuildingId.h"
|
#include "BuildingId.h"
|
||||||
@@ -79,6 +80,11 @@ protected:
|
|||||||
void setCountSlot(int count);
|
void setCountSlot(int count);
|
||||||
void clearSlot();
|
void clearSlot();
|
||||||
|
|
||||||
|
// Reserves header room for the widest caption the slot will ever show. The panel is
|
||||||
|
// sized to its card (REQ-UI-SELECTION-PANEL), so without this it changes width every
|
||||||
|
// time the caption does -- and a card whose chips wrap changes height with it.
|
||||||
|
void reserveSlotFor(const QStringList& captions);
|
||||||
|
|
||||||
// Fills the right slot from the building's production status, mapped to the same
|
// Fills the right slot from the building's production status, mapped to the same
|
||||||
// colors and states the world's status light uses (REQ-UI-SELECTION-STATUS). Leaves
|
// colors and states the world's status light uses (REQ-UI-SELECTION-STATUS). Leaves
|
||||||
// the slot empty for a type that has no status light.
|
// the slot empty for a type that has no status light.
|
||||||
|
|||||||
@@ -33,3 +33,16 @@ QString getBehaviorLabel(BehaviorKind kind)
|
|||||||
}
|
}
|
||||||
return QString();
|
return QString();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
QStringList getAllBehaviorLabels()
|
||||||
|
{
|
||||||
|
QStringList labels;
|
||||||
|
for (BehaviorKind kind : { BehaviorKind::Retreat, BehaviorKind::Attack,
|
||||||
|
BehaviorKind::SalvageScrap, BehaviorKind::Repair,
|
||||||
|
BehaviorKind::Rally, BehaviorKind::Standby,
|
||||||
|
BehaviorKind::Advance })
|
||||||
|
{
|
||||||
|
labels << getBehaviorLabel(kind);
|
||||||
|
}
|
||||||
|
return labels;
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <QString>
|
#include <QString>
|
||||||
|
#include <QStringList>
|
||||||
|
|
||||||
#include "BehaviorKind.h"
|
#include "BehaviorKind.h"
|
||||||
#include "BuildingType.h"
|
#include "BuildingType.h"
|
||||||
@@ -13,3 +14,8 @@ QString getBuildingTypeName(BuildingType type);
|
|||||||
// Name of the behavior currently governing a ship, for the ship card's header slot
|
// Name of the behavior currently governing a ship, for the ship card's header slot
|
||||||
// (REQ-UI-SHIP-BEHAVIOR). Empty when no behavior has won yet, which shows no slot.
|
// (REQ-UI-SHIP-BEHAVIOR). Empty when no behavior has won yet, which shows no slot.
|
||||||
QString getBehaviorLabel(BehaviorKind kind);
|
QString getBehaviorLabel(BehaviorKind kind);
|
||||||
|
|
||||||
|
// Every name getBehaviorLabel can return. A ship's behavior changes as it fights, so the
|
||||||
|
// header reserves room for the widest of these rather than letting the panel change
|
||||||
|
// width each time (REQ-UI-SELECTION-PANEL).
|
||||||
|
QStringList getAllBehaviorLabels();
|
||||||
|
|||||||
@@ -21,6 +21,10 @@ ShipContent::ShipContent(const SelectionContext& context,
|
|||||||
m_statsPanel = new ShipStatsPanel(context.config, this);
|
m_statsPanel = new ShipStatsPanel(context.config, this);
|
||||||
getRuntimeLayout()->addWidget(m_statsPanel);
|
getRuntimeLayout()->addWidget(m_statsPanel);
|
||||||
|
|
||||||
|
// A ship's behavior changes as it fights, so the header holds room for the longest
|
||||||
|
// name rather than the panel resizing under the player each time it does.
|
||||||
|
reserveSlotFor(getAllBehaviorLabels());
|
||||||
|
|
||||||
EntityAdmin& admin = context.sim->getAdmin();
|
EntityAdmin& admin = context.sim->getAdmin();
|
||||||
if (admin.isValid(m_entity) && admin.hasAll<ShipIdentityComponent>(m_entity))
|
if (admin.isValid(m_entity) && admin.hasAll<ShipIdentityComponent>(m_entity))
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
#include "StatusPill.h"
|
#include "StatusPill.h"
|
||||||
|
|
||||||
|
#include <QFontMetrics>
|
||||||
#include <QGuiApplication>
|
#include <QGuiApplication>
|
||||||
#include <QHBoxLayout>
|
#include <QHBoxLayout>
|
||||||
#include <QLabel>
|
#include <QLabel>
|
||||||
@@ -49,6 +50,23 @@ StatusPill::StatusPill(QWidget* parent)
|
|||||||
hide();
|
hide();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void StatusPill::reserveFor(const QStringList& captions)
|
||||||
|
{
|
||||||
|
if (captions == m_reservedFor)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
m_reservedFor = captions;
|
||||||
|
|
||||||
|
const QFontMetrics metrics(m_captionLabel->font());
|
||||||
|
int widestPx = 0;
|
||||||
|
for (const QString& caption : captions)
|
||||||
|
{
|
||||||
|
widestPx = qMax(widestPx, metrics.horizontalAdvance(caption));
|
||||||
|
}
|
||||||
|
m_captionLabel->setMinimumWidth(widestPx);
|
||||||
|
}
|
||||||
|
|
||||||
void StatusPill::setStatus(const QColor& dotColor, const QColor& outlineColor,
|
void StatusPill::setStatus(const QColor& dotColor, const QColor& outlineColor,
|
||||||
const QString& caption)
|
const QString& caption)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
#include <QColor>
|
#include <QColor>
|
||||||
#include <QString>
|
#include <QString>
|
||||||
|
#include <QStringList>
|
||||||
#include <QWidget>
|
#include <QWidget>
|
||||||
|
|
||||||
class QLabel;
|
class QLabel;
|
||||||
@@ -23,7 +24,16 @@ public:
|
|||||||
void setStatus(const QColor& dotColor, const QColor& outlineColor,
|
void setStatus(const QColor& dotColor, const QColor& outlineColor,
|
||||||
const QString& caption);
|
const QString& caption);
|
||||||
|
|
||||||
|
// Reserves room for the widest of the captions this pill can show, so the header --
|
||||||
|
// and with it the panel, which is sized to its content (REQ-UI-SELECTION-PANEL) --
|
||||||
|
// keeps its width as the caption changes. Without it the whole panel jumps every
|
||||||
|
// time a building's status does, and a card whose chips wrap changes height with it.
|
||||||
|
void reserveFor(const QStringList& captions);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
QLabel* m_dotLabel;
|
QLabel* m_dotLabel;
|
||||||
QLabel* m_captionLabel;
|
QLabel* m_captionLabel;
|
||||||
|
// What the width is currently reserved for, so re-reserving the same set on every
|
||||||
|
// refresh costs nothing.
|
||||||
|
QStringList m_reservedFor;
|
||||||
};
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user