Files
dota_factory/src/ui/selection/BarRow.cpp
Malte Langkabel 7d0f3e6daf order the selection card by what the player reads first
Six things the player reported, five of them about where things sit and what is
listed:

- HP goes to the top of the runtime group, above everything else a card shows.
  Only the HQ had it elsewhere; the ship and station cards already led with it.
- A construction site's progress moves out of the runtime group's place and
  directly under the header, so how far along the site is reads before what it
  is configured to become.
- The production bar moves between the input and output buffers, so a producing
  building reads in the direction its materials flow: what goes in, what is
  being made of it, what has come out. BufferSection held both buffer sections
  and so could not be split around it; the two sections and the production
  section are now the card's own, in that order.
- Locked items are left out of the buffers. An auto-recipe building's buffers
  are sized over every recipe of its type, so a Smelter carried an input for
  quartz -- which the player cannot mine yet -- and an output for the silicon it
  would smelt into. Both are dropped now, as everywhere else that hides what is
  not unlocked.
- An idle auto-recipe building lists what it handles rather than nothing. It has
  no selected recipe to name, so a Reprocessing Plant between cycles showed no
  output buffer at all. Its sections now list the unlocked items of every recipe
  of its type -- the union its buffers were sized over -- without a per-cycle
  denominator, since no one recipe is in force.

The sixth was the bars vanishing whenever the window lost focus, to a modal
dialog or to another application. They were filled with the palette's current
highlight, and the palette follows the window's focus: the inactive group's
highlight sits close enough to the card's background to read as gone. They ask
for the active group by name now.

Measured on a freshly built Smelter, which is the case that showed three of
these at once:

  INPUT BUFFERS  0 Copper Ore | 0 Iron Ore | 0 Scrap
  PRODUCTION     idle
  OUTPUT BUFFER  0/2 Copper Ingot | 0/2 Iron Ingot

Quartz and silicon filtered out, production between the buffers, and an idle
building still saying what it handles.

Requirements updated for the ordering, the locked-item rule and the idle
auto-recipe listing (REQ-UI-SELECTION-CARD, REQ-UI-SINGLE-SELECTION,
REQ-UI-PRODUCTION-PROGRESS, REQ-UI-HQ-PANEL).

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JcReq7hVk4KUPhTDKWAG7K
2026-08-07 21:39:39 +02:00

124 lines
3.4 KiB
C++

#include "BarRow.h"
#include <algorithm>
#include <QHBoxLayout>
#include <QLabel>
#include <QPainter>
#include <QPaintEvent>
#include <QVBoxLayout>
namespace
{
// Height and corner radius of the fill bar, in device-independent pixels.
const int kBarHeightPx = 6;
const qreal kBarRadiusPx = 3.0;
// Opacity of the unfilled track, over the card's background.
const int kTrackAlpha = 60;
} // namespace
// The bar itself. Painted rather than assembled from a QProgressBar, because all it
// needs is a rounded track with a rounded fill and a style sheet cannot be relied on to
// leave a progress bar's groove and chunk alone across styles.
class BarRow::Bar : public QWidget
{
public:
explicit Bar(QWidget* parent)
: QWidget(parent)
, m_fraction(0.0)
{
setFixedHeight(kBarHeightPx);
setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed);
}
void setFraction(double fraction)
{
m_fraction = std::max(0.0, std::min(1.0, fraction));
update();
}
void setFillColor(const QColor& color)
{
m_fillColor = color;
update();
}
protected:
void paintEvent(QPaintEvent* /*event*/) override
{
// The active group is asked for by name rather than taken from the current one,
// which follows the window's focus: the inactive group's highlight is close
// enough to the card's background that the bar reads as gone whenever the game
// is tabbed away from or a modal dialog holds focus -- exactly when a player
// watching a build finish is most likely to be looking at it.
const QColor fill = m_fillColor.isValid()
? m_fillColor
: palette().color(QPalette::Active, QPalette::Highlight);
QColor track = fill;
track.setAlpha(kTrackAlpha);
QPainter painter(this);
painter.setRenderHint(QPainter::Antialiasing, true);
painter.setPen(Qt::NoPen);
painter.setBrush(track);
painter.drawRoundedRect(rect(), kBarRadiusPx, kBarRadiusPx);
if (m_fraction > 0.0)
{
QRect filled = rect();
filled.setWidth(static_cast<int>(filled.width() * m_fraction));
painter.setBrush(fill);
painter.drawRoundedRect(filled, kBarRadiusPx, kBarRadiusPx);
}
}
private:
double m_fraction;
QColor m_fillColor;
};
BarRow::BarRow(const QString& caption, QWidget* parent)
: QWidget(parent)
{
QVBoxLayout* layout = new QVBoxLayout(this);
layout->setContentsMargins(0, 0, 0, 0);
layout->setSpacing(2);
QWidget* captionRow = new QWidget(this);
QHBoxLayout* captionLayout = new QHBoxLayout(captionRow);
captionLayout->setContentsMargins(0, 0, 0, 0);
captionLayout->setSpacing(8);
m_captionLabel = new QLabel(caption, captionRow);
m_captionLabel->setVisible(!caption.isEmpty());
m_valueLabel = new QLabel(captionRow);
m_valueLabel->setAlignment(Qt::AlignRight | Qt::AlignVCenter);
captionLayout->addWidget(m_captionLabel);
captionLayout->addStretch(1);
captionLayout->addWidget(m_valueLabel);
m_bar = new Bar(this);
layout->addWidget(captionRow);
layout->addWidget(m_bar);
}
void BarRow::setValue(double fraction, const QString& valueText)
{
m_bar->setFraction(fraction);
m_valueLabel->setText(valueText);
}
void BarRow::setFillColor(const QColor& color)
{
m_bar->setFillColor(color);
}