move the debug stats panel out of the renderer
This commit is contained in:
@@ -331,7 +331,7 @@ Buildings and the belt subsystem stay outside any entity model regardless of wha
|
||||
|
||||
The game world is drawn into a single `GameWorldView` widget that inherits `QOpenGLWidget` and uses `QPainter` for all drawing. This gives the same imperative paint API as a plain `QWidget` with GPU acceleration, comfortably handling the expected scale (hundreds of ships, thousands of belt items) without blocking the main thread on CPU rasterization.
|
||||
|
||||
The drawing itself lives in `WorldRenderer`, not in the widget. `paintGL` is a call sequence: build the frame's `WorldCoordinates`, hand the renderer a `WorldRenderFrame`, then draw the screen-anchored chrome. The split is the world-space / screen-space line: the renderer draws everything positioned in tiles, while the pause and deconstruct vignettes and the replay overlay — which are anchored to the viewport and never took a `WorldCoordinates` — stay with the widget.
|
||||
The drawing itself lives in `WorldRenderer`, not in the widget. `paintGL` is a call sequence: build the frame's `WorldCoordinates`, hand the renderer a `WorldRenderFrame`, then draw the screen-anchored chrome. The split is the world-space / screen-space line, and it is exact: the renderer draws everything positioned in tiles, while everything positioned in pixels — the pause and deconstruct vignettes, the replay overlay, the debug stats panel — stays with the widget. A useful consequence is that the renderer draws no translatable text at all (its text is config-driven glyphs, ASCII port arrows, and numbers), so it needs no `tr()` and no tie to the meta-object system.
|
||||
|
||||
`WorldRenderFrame` is what makes the renderer independent of the widget. The renderer reads the simulation directly, but everything else it draws is interaction state the widget owns — the selection, the active build mode, live beams, the copy-settings feedback, the box-select rectangle. Those are gathered into the frame each `paintGL` and passed by reference, so the renderer keeps no copy that a later click could invalidate. The renderer knows nothing about input: the widget resolves clicks and hit-tests, and the renderer only draws the result.
|
||||
|
||||
|
||||
@@ -27,6 +27,7 @@
|
||||
#include <QPen>
|
||||
#include <QPixmap>
|
||||
#include <QRadialGradient>
|
||||
#include <QFontMetrics>
|
||||
#include <QStringList>
|
||||
#include <QTimer>
|
||||
|
||||
@@ -405,6 +406,7 @@ void GameWorldView::paintGL()
|
||||
|
||||
// Screen-anchored chrome over the finished world.
|
||||
drawScreenSpace(painter);
|
||||
if (m_debugDraw) { drawDebugOverlay(painter); }
|
||||
drawPauseBorder(painter);
|
||||
drawDeconstructBorder(painter);
|
||||
drawReplayOverlay(painter);
|
||||
@@ -821,6 +823,51 @@ void GameWorldView::applyBeltDragPath()
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
|
||||
void GameWorldView::drawDebugOverlay(QPainter& painter)
|
||||
{
|
||||
const QStringList lines = {
|
||||
tr("Accumulated Threat Level: %1")
|
||||
.arg(m_sim->getThreatLevel(), 0, 'f', 1),
|
||||
tr("Time until Wave: %1s")
|
||||
.arg(ticksToSeconds(m_sim->getNormalGapRemainingTicks()), 0, 'f', 1),
|
||||
tr("Threat Accumulation Rate: %1 threat/s")
|
||||
.arg(m_sim->getThreatAccumulationRate(), 0, 'f', 1),
|
||||
tr("Max Factory Production: %1 threat/s")
|
||||
.arg(m_sim->getMaxFactoryProductionThreatRate(), 0, 'f', 1),
|
||||
tr("Current Factory Production: %1 threat/s")
|
||||
.arg(m_sim->getCurrentFactoryProductionThreatRate(), 0, 'f', 1),
|
||||
};
|
||||
|
||||
QFont font = painter.font();
|
||||
font.setPointSize(m_visuals->toast.fontSize);
|
||||
painter.setFont(font);
|
||||
|
||||
const QFontMetrics fm = painter.fontMetrics();
|
||||
const int lineH = fm.height();
|
||||
const int padding = 8;
|
||||
const int spacing = 4;
|
||||
|
||||
int textW = 0;
|
||||
for (const QString& line : lines)
|
||||
{
|
||||
textW = std::max(textW, fm.horizontalAdvance(line));
|
||||
}
|
||||
const int bgW = textW + padding * 2;
|
||||
const int bgH = lineH * lines.size() + spacing * (lines.size() - 1) + padding * 2;
|
||||
|
||||
const QRect bgRect(padding, padding, bgW, bgH);
|
||||
painter.fillRect(bgRect, QColor(0, 0, 0, 160));
|
||||
|
||||
painter.setPen(Qt::white);
|
||||
int y = padding * 2;
|
||||
for (const QString& line : lines)
|
||||
{
|
||||
const QRect textRect(padding * 2, y, textW, lineH);
|
||||
painter.drawText(textRect, Qt::AlignLeft | Qt::AlignVCenter, line);
|
||||
y += lineH + spacing;
|
||||
}
|
||||
}
|
||||
|
||||
void GameWorldView::drawScreenSpace(QPainter& /*painter*/)
|
||||
{
|
||||
}
|
||||
|
||||
@@ -150,6 +150,10 @@ private:
|
||||
// Screen-anchored chrome, drawn after the world (see WorldRenderer): these
|
||||
// need no world transform, which is exactly why they stayed here.
|
||||
void drawScreenSpace(QPainter& painter);
|
||||
// Threat and production readout shown while debug draw is on (F3). Positioned
|
||||
// in pixels at the top-left corner, so it belongs with the chrome rather than
|
||||
// with the world.
|
||||
void drawDebugOverlay(QPainter& painter);
|
||||
// Vignette-style border shown while the game is paused (speed 0x, REQ-UI-PAUSE-BORDER)
|
||||
// to make the paused state hard to miss: a black frame whose alpha fades from 50% at
|
||||
// the viewport edges to 0% toward the center.
|
||||
|
||||
@@ -9,14 +9,12 @@
|
||||
#include <QDir>
|
||||
#include <QFile>
|
||||
#include <QFont>
|
||||
#include <QFontMetrics>
|
||||
#include <QLinearGradient>
|
||||
#include <QPainter>
|
||||
#include <QPen>
|
||||
#include <QPolygonF>
|
||||
#include <QRegion>
|
||||
#include <QRegularExpression>
|
||||
#include <QStringList>
|
||||
#include <QSvgRenderer>
|
||||
|
||||
#include "AttackBehavior.h"
|
||||
@@ -139,7 +137,6 @@ void WorldRenderer::render(QPainter& painter, const WorldCoordinates& coordinate
|
||||
{
|
||||
drawDebugSensorRanges(painter, coordinates, frame);
|
||||
drawDebugTargetLines(painter, coordinates, frame);
|
||||
drawDebugOverlay(painter);
|
||||
}
|
||||
drawShips(painter, coordinates, frame);
|
||||
drawBeams(painter, coordinates, frame);
|
||||
@@ -819,53 +816,6 @@ void WorldRenderer::drawDebugTargetLines(QPainter& painter, const WorldCoordinat
|
||||
});
|
||||
}
|
||||
|
||||
void WorldRenderer::drawDebugOverlay(QPainter& painter)
|
||||
{
|
||||
painter.resetTransform();
|
||||
|
||||
const QStringList lines = {
|
||||
tr("Accumulated Threat Level: %1")
|
||||
.arg(m_sim.getThreatLevel(), 0, 'f', 1),
|
||||
tr("Time until Wave: %1s")
|
||||
.arg(ticksToSeconds(m_sim.getNormalGapRemainingTicks()), 0, 'f', 1),
|
||||
tr("Threat Accumulation Rate: %1 threat/s")
|
||||
.arg(m_sim.getThreatAccumulationRate(), 0, 'f', 1),
|
||||
tr("Max Factory Production: %1 threat/s")
|
||||
.arg(m_sim.getMaxFactoryProductionThreatRate(), 0, 'f', 1),
|
||||
tr("Current Factory Production: %1 threat/s")
|
||||
.arg(m_sim.getCurrentFactoryProductionThreatRate(), 0, 'f', 1),
|
||||
};
|
||||
|
||||
QFont font = painter.font();
|
||||
font.setPointSize(m_visuals.toast.fontSize);
|
||||
painter.setFont(font);
|
||||
|
||||
const QFontMetrics fm = painter.fontMetrics();
|
||||
const int lineH = fm.height();
|
||||
const int padding = 8;
|
||||
const int spacing = 4;
|
||||
|
||||
int textW = 0;
|
||||
for (const QString& line : lines)
|
||||
{
|
||||
textW = std::max(textW, fm.horizontalAdvance(line));
|
||||
}
|
||||
const int bgW = textW + padding * 2;
|
||||
const int bgH = lineH * lines.size() + spacing * (lines.size() - 1) + padding * 2;
|
||||
|
||||
const QRect bgRect(padding, padding, bgW, bgH);
|
||||
painter.fillRect(bgRect, QColor(0, 0, 0, 160));
|
||||
|
||||
painter.setPen(Qt::white);
|
||||
int y = padding * 2;
|
||||
for (const QString& line : lines)
|
||||
{
|
||||
const QRect textRect(padding * 2, y, textW, lineH);
|
||||
painter.drawText(textRect, Qt::AlignLeft | Qt::AlignVCenter, line);
|
||||
y += lineH + spacing;
|
||||
}
|
||||
}
|
||||
|
||||
void WorldRenderer::drawBeams(QPainter& painter, const WorldCoordinates& coordinates,
|
||||
const WorldRenderFrame& frame)
|
||||
{
|
||||
|
||||
@@ -6,7 +6,6 @@
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include <QCoreApplication>
|
||||
#include <QColor>
|
||||
#include <QPoint>
|
||||
#include <QPointF>
|
||||
@@ -70,16 +69,13 @@ struct WorldRenderFrame
|
||||
//
|
||||
// Reads the simulation and never writes it, and knows nothing about input — the
|
||||
// view resolves clicks and owns the interaction state, and hands the parts the
|
||||
// renderer needs over in a WorldRenderFrame. Screen-anchored chrome that is not
|
||||
// part of the world (the pause and deconstruct vignettes, the replay overlay)
|
||||
// stays with the view, which is also why those never took a WorldCoordinates.
|
||||
// renderer needs over in a WorldRenderFrame.
|
||||
//
|
||||
// Everything here is positioned in tiles. Screen-anchored chrome — the pause and
|
||||
// deconstruct vignettes, the replay overlay, the debug stats panel — stays with
|
||||
// the view, which is why nothing in this class draws translatable text.
|
||||
class WorldRenderer
|
||||
{
|
||||
// Not a QObject, but drawDebugOverlay puts text on screen, so it still needs
|
||||
// tr() for translation (REQ-UI-...): this declares the tr() overloads without
|
||||
// dragging in the meta-object system.
|
||||
Q_DECLARE_TR_FUNCTIONS(WorldRenderer)
|
||||
|
||||
public:
|
||||
// `itemIcons` is the window-wide per-item icon cache (REQ-UI-ITEM-ICON); not
|
||||
// owned, must outlive this renderer. `configDir` is used once, to load the
|
||||
@@ -121,10 +117,6 @@ private:
|
||||
const WorldRenderFrame& frame);
|
||||
void drawOverlays(QPainter& painter, const WorldCoordinates& coordinates,
|
||||
const WorldRenderFrame& frame);
|
||||
// Screen-anchored, but drawn mid-world so ships and effects still paint over
|
||||
// it, as they always have.
|
||||
void drawDebugOverlay(QPainter& painter);
|
||||
|
||||
// Draws a single item centered at widget-space `center`, spanning `halfPx` in
|
||||
// each direction (a half-tile). Uses the item's icon when one exists
|
||||
// (REQ-UI-ITEM-ICON), otherwise falls back to the colored square from
|
||||
|
||||
Reference in New Issue
Block a user