463 lines
18 KiB
C++
Raw Normal View History

2026-07-30 12:06:02 +08:00
#include "GuideDisplayWidget.h"
#include <QtMath>
#include <QFont>
#include <QFontMetricsF>
#include <QLinearGradient>
#include <QPainter>
#include <QPainterPath>
#include <QPaintEvent>
#include <QPolygonF>
namespace {
const QColor kNormalColor(218, 239, 35);
const QColor kGreenColor(35, 225, 103);
const QColor kStopColor(242, 54, 48);
const QColor kScreenColor(42, 49, 49);
QString NumberText(double value)
{
return QString::number(value, 'f', 1) + QStringLiteral("m");
}
QString SignedText(double value, int precision)
{
const QString sign = value > 0.0 ? QStringLiteral("+") : QString();
return sign + QString::number(value, 'f', precision);
}
QPolygonF Octagon(const QRectF& rect)
{
const qreal cut = qMin(rect.width(), rect.height()) * 0.18;
return QPolygonF()
<< QPointF(rect.left() + cut, rect.top())
<< QPointF(rect.right() - cut, rect.top())
<< QPointF(rect.right(), rect.top() + cut)
<< QPointF(rect.right(), rect.bottom() - cut)
<< QPointF(rect.right() - cut, rect.bottom())
<< QPointF(rect.left() + cut, rect.bottom())
<< QPointF(rect.left(), rect.bottom() - cut)
<< QPointF(rect.left(), rect.top() + cut);
}
} // namespace
GuideDisplayWidget::GuideDisplayWidget(QWidget* parent)
: QWidget(parent)
{
setAttribute(Qt::WA_OpaquePaintEvent);
}
void GuideDisplayWidget::SetStatus(const QJsonObject& status)
{
m_status = status;
const int stateCode = status.value("guideStateCode").toInt(12);
m_stateCode = stateCode >= 1 && stateCode <= 24 ? stateCode : 12;
update();
}
void GuideDisplayWidget::paintEvent(QPaintEvent* event)
{
Q_UNUSED(event);
QPainter painter(this);
painter.setRenderHint(QPainter::Antialiasing, true);
painter.fillRect(rect(), QColor(5, 8, 11));
const qreal margin = qMax<qreal>(16.0, qMin(width(), height()) * 0.035);
QRectF frame = QRectF(rect()).adjusted(margin, margin, -margin, -margin);
const qreal side = qMin(frame.width(), frame.height());
frame = QRectF(frame.center().x() - side / 2.0,
frame.center().y() - side / 2.0,
side,
side);
const qreal bevel = side * 0.065;
QLinearGradient frameGradient(frame.topLeft(), frame.bottomRight());
frameGradient.setColorAt(0.0, QColor(112, 119, 119));
frameGradient.setColorAt(0.45, QColor(44, 49, 50));
frameGradient.setColorAt(1.0, QColor(118, 123, 123));
painter.setPen(QPen(QColor(18, 22, 23), qMax<qreal>(2.0, side * 0.008)));
painter.setBrush(frameGradient);
painter.drawRect(frame);
QRectF screen = frame.adjusted(bevel, bevel, -bevel, -bevel);
painter.setPen(QPen(QColor(24, 29, 29), qMax<qreal>(1.0, side * 0.004)));
painter.setBrush(kScreenColor);
painter.drawRect(screen);
if (m_stateCode == 23 || m_stateCode == 24) {
painter.fillRect(screen, Qt::black);
return;
}
// 只绘制低对比度 LED 网格,所有文字和图形均为矢量绘制。
painter.save();
painter.setClipRect(screen);
painter.setPen(QPen(QColor(78, 87, 85, 80), 1.0));
const qreal grid = qMax<qreal>(7.0, screen.width() / 36.0);
for (qreal x = screen.left(); x <= screen.right(); x += grid) {
painter.drawLine(QPointF(x, screen.top()),
QPointF(x, screen.bottom()));
}
for (qreal y = screen.top(); y <= screen.bottom(); y += grid) {
painter.drawLine(QPointF(screen.left(), y),
QPointF(screen.right(), y));
}
painter.restore();
const QRectF content = screen.adjusted(screen.width() * 0.06,
screen.height() * 0.05,
-screen.width() * 0.06,
-screen.height() * 0.05);
switch (m_stateCode) {
case 1:
case 12:
DrawLedText(painter, content,
QStringList() << QStringLiteral("WAIT"),
kNormalColor, 0.25);
break;
case 2:
DrawCaptureGuidance(painter, content);
break;
case 3:
case 4:
case 5:
case 7:
DrawLedText(painter,
QRectF(content.left(), content.top(),
content.width(), content.height() * 0.32),
QStringList()
<< AircraftText()
<< NumberText(m_status.value("distanceToStop")
.toDouble(0.0) / 1000.0),
kNormalColor, 0.13);
DrawGuidance(painter,
QRectF(content.left(), content.top() + content.height() * 0.28,
content.width(), content.height() * 0.70),
m_stateCode != 5);
break;
case 6:
case 13:
case 14:
DrawLedText(painter, content,
QStringList() << AircraftText() << QStringLiteral("SLOW"),
kNormalColor, 0.19);
break;
case 8:
DrawStopSign(painter,
content.adjusted(0, content.height() * 0.08,
0, -content.height() * 0.38));
break;
case 9:
DrawLedText(painter, content,
QStringList() << QStringLiteral("OK"),
kGreenColor, 0.30);
break;
case 10:
DrawStopSign(painter,
QRectF(content.left(), content.top(),
content.width(), content.height() * 0.42));
DrawLedText(painter,
QRectF(content.left(), content.top() + content.height() * 0.45,
content.width(), content.height() * 0.52),
QStringList() << QStringLiteral("TOO")
<< QStringLiteral("FAR"),
kNormalColor, 0.17);
break;
case 11:
DrawStopSign(painter,
QRectF(content.left(), content.top(),
content.width(), content.height() * 0.48));
DrawLedText(painter,
QRectF(content.left(), content.top() + content.height() * 0.50,
content.width(), content.height() * 0.45),
QStringList() << QStringLiteral("OK"),
kGreenColor, 0.22);
break;
case 15:
DrawStopSign(painter,
QRectF(content.left(), content.top(),
content.width(), content.height() * 0.42));
DrawLedText(painter,
QRectF(content.left(), content.top() + content.height() * 0.44,
content.width(), content.height() * 0.54),
QStringList() << QStringLiteral("ID")
<< QStringLiteral("FAIL"),
kNormalColor, 0.17);
break;
case 16:
DrawLedText(painter, content,
QStringList() << QStringLiteral("WAIT")
<< QStringLiteral("GATE")
<< QStringLiteral("BLOCK"),
kNormalColor, 0.14);
break;
case 17:
DrawLedText(painter, content,
QStringList() << QStringLiteral("WAIT")
<< QStringLiteral("VIEW")
<< QStringLiteral("BLOCK"),
kNormalColor, 0.14);
break;
case 18:
DrawStopSign(painter,
QRectF(content.left(), content.top(),
content.width(), content.height() * 0.48));
DrawLedText(painter,
QRectF(content.left(), content.top() + content.height() * 0.51,
content.width(), content.height() * 0.42),
QStringList() << QStringLiteral("SBU"),
kNormalColor, 0.20);
break;
case 19:
DrawStopSign(painter,
QRectF(content.left(), content.top(),
content.width(), content.height() * 0.44));
DrawLedText(painter,
QRectF(content.left(), content.top() + content.height() * 0.46,
content.width(), content.height() * 0.50),
QStringList() << QStringLiteral("TOO")
<< QStringLiteral("FAST"),
kNormalColor, 0.17);
break;
case 20:
DrawStopSign(painter,
content.adjusted(0, content.height() * 0.08,
0, -content.height() * 0.38));
break;
case 21:
DrawLedText(painter, content,
QStringList() << QStringLiteral("CHOCK")
<< QStringLiteral("ON"),
kGreenColor, 0.20);
break;
case 22: {
const int errorCode = m_status.value("errorCode").toInt(0);
QStringList lines;
lines << QStringLiteral("ERROR");
if (errorCode != 0) {
lines << QString::number(errorCode);
}
DrawLedText(painter, content, lines, kStopColor, 0.18);
break;
}
default:
DrawLedText(painter, content,
QStringList() << QStringLiteral("WAIT"),
kNormalColor, 0.25);
break;
}
}
void GuideDisplayWidget::DrawLedText(QPainter& painter,
const QRectF& area,
const QStringList& lines,
const QColor& color,
qreal relativeSize) const
{
if (lines.isEmpty()) {
return;
}
QFont font(QStringLiteral("DejaVu Sans Mono"));
font.setStyleHint(QFont::Monospace);
font.setBold(true);
font.setPixelSize(qMax(14, qRound(area.height() * relativeSize)));
painter.save();
painter.setFont(font);
painter.setPen(color);
painter.setBrush(Qt::NoBrush);
painter.drawText(area,
Qt::AlignCenter,
lines.join(QChar('\n')));
painter.restore();
}
void GuideDisplayWidget::DrawStopSign(QPainter& painter,
const QRectF& area,
const QString& text) const
{
const QRectF sign = area.adjusted(area.width() * 0.05,
area.height() * 0.08,
-area.width() * 0.05,
-area.height() * 0.08);
painter.save();
painter.setBrush(Qt::NoBrush);
painter.setPen(QPen(kStopColor,
qMax<qreal>(3.0, sign.width() * 0.016),
Qt::SolidLine,
Qt::RoundCap,
Qt::RoundJoin));
painter.drawPolygon(Octagon(sign));
painter.restore();
DrawLedText(painter, sign, QStringList() << text, kStopColor, 0.34);
}
void GuideDisplayWidget::DrawCaptureGuidance(QPainter& painter,
const QRectF& screen) const
{
const double distanceMm =
m_status.value("distanceToStop").toDouble(0.0);
const double offsetMm =
m_status.value("lateralOffset").toDouble(0.0);
const double yawDegrees =
m_status.value("bodyYawAngle").toDouble(0.0);
const QRectF modelArea(screen.left(), screen.top(),
screen.width(), screen.height() * 0.24);
DrawLedText(painter, modelArea,
QStringList() << AircraftText(), kGreenColor, 0.50);
const QRectF distanceArea(
screen.left(),
screen.top() + screen.height() * 0.35,
screen.width(),
screen.height() * 0.31);
DrawLedText(painter, distanceArea,
QStringList() << NumberText(distanceMm / 1000.0),
kNormalColor, 0.62);
painter.save();
painter.setClipRect(screen);
const qreal lineWidth = qMax<qreal>(4.0, screen.width() * 0.018);
const qreal centerX = screen.center().x();
const qreal stopLineY = screen.top() + screen.height() / 3.0;
const qreal aircraftY = screen.top() + screen.height() * 0.74;
painter.setPen(QPen(kNormalColor, lineWidth,
Qt::SolidLine, Qt::SquareCap));
painter.drawLine(
QPointF(screen.left() + screen.width() * 0.22, stopLineY),
QPointF(screen.right() - screen.width() * 0.22, stopLineY));
painter.drawLine(QPointF(centerX, stopLineY),
QPointF(centerX, distanceArea.top() - lineWidth));
painter.drawLine(QPointF(centerX, distanceArea.bottom() + lineWidth),
QPointF(centerX, screen.top() + screen.height() * 0.83));
const qreal offsetRatio = qBound<qreal>(
-1.0, static_cast<qreal>(offsetMm / 1000.0), 1.0);
const qreal aircraftX = centerX + offsetRatio * screen.width() * 0.20;
const qreal clampedYaw = qBound<qreal>(
-30.0, static_cast<qreal>(yawDegrees), 30.0);
const qreal aircraftSize = screen.width() * 0.075;
if (!qFuzzyIsNull(aircraftX - centerX)) {
painter.setPen(QPen(kStopColor, lineWidth * 0.48,
Qt::SolidLine, Qt::RoundCap));
painter.drawLine(QPointF(centerX, aircraftY),
QPointF(aircraftX, aircraftY));
const qreal arrowDirection = aircraftX > centerX ? -1.0 : 1.0;
const qreal arrowSize = aircraftSize * 0.36;
painter.drawLine(
QPointF(centerX, aircraftY),
QPointF(centerX - arrowDirection * arrowSize,
aircraftY - arrowSize));
painter.drawLine(
QPointF(centerX, aircraftY),
QPointF(centerX - arrowDirection * arrowSize,
aircraftY + arrowSize));
}
painter.translate(aircraftX, aircraftY);
painter.rotate(clampedYaw);
painter.setPen(QPen(kGreenColor, lineWidth * 0.62,
Qt::SolidLine, Qt::RoundCap, Qt::RoundJoin));
painter.setBrush(Qt::NoBrush);
painter.drawLine(QPointF(0.0, aircraftSize * 0.75),
QPointF(0.0, -aircraftSize));
painter.drawLine(QPointF(-aircraftSize, 0.0),
QPointF(aircraftSize, 0.0));
painter.drawLine(QPointF(-aircraftSize * 0.45,
aircraftSize * 0.58),
QPointF(aircraftSize * 0.45,
aircraftSize * 0.58));
painter.drawLine(QPointF(0.0, -aircraftSize),
QPointF(-aircraftSize * 0.28,
-aircraftSize * 0.60));
painter.drawLine(QPointF(0.0, -aircraftSize),
QPointF(aircraftSize * 0.28,
-aircraftSize * 0.60));
painter.restore();
const QRectF metricArea(
screen.left(),
screen.top() + screen.height() * 0.85,
screen.width(),
screen.height() * 0.13);
const QRectF offsetArea(metricArea.left(), metricArea.top(),
metricArea.width() * 0.50,
metricArea.height());
const QRectF yawArea(metricArea.center().x(), metricArea.top(),
metricArea.width() * 0.50,
metricArea.height());
DrawLedText(painter, offsetArea,
QStringList() << QStringLiteral("偏移 %1mm")
.arg(SignedText(offsetMm, 0)),
kNormalColor, 0.34);
DrawLedText(painter, yawArea,
QStringList() << QStringLiteral("偏角 %1°")
.arg(SignedText(yawDegrees, 1)),
kNormalColor, 0.34);
}
void GuideDisplayWidget::DrawGuidance(QPainter& painter,
const QRectF& screen,
bool showCorrection) const
{
painter.save();
const qreal lineWidth = qMax<qreal>(4.0, screen.width() * 0.025);
const qreal centerX = screen.center().x();
const qreal topY = screen.top() + screen.height() * 0.22;
const qreal bottomY = screen.bottom() - screen.height() * 0.10;
painter.setPen(QPen(kNormalColor, lineWidth,
Qt::SolidLine, Qt::SquareCap));
painter.drawLine(QPointF(screen.left() + screen.width() * 0.28, topY),
QPointF(screen.right() - screen.width() * 0.28, topY));
painter.drawLine(QPointF(centerX, topY),
QPointF(centerX, bottomY));
const qreal arrow = screen.width() * 0.07;
painter.setPen(QPen(kNormalColor, lineWidth * 0.55,
Qt::SolidLine, Qt::RoundCap));
painter.drawLine(QPointF(centerX, bottomY),
QPointF(centerX - arrow, bottomY + arrow));
painter.drawLine(QPointF(centerX, bottomY),
QPointF(centerX + arrow, bottomY + arrow));
if (showCorrection) {
const double offset = m_status.value("lateralOffset").toDouble(0.0);
const bool correctionFromLeft = offset >= 0.0;
const qreal outerX = correctionFromLeft
? screen.left() + screen.width() * 0.08
: screen.right() - screen.width() * 0.08;
const qreal innerX = correctionFromLeft
? outerX + screen.width() * 0.10
: outerX - screen.width() * 0.10;
const qreal y = screen.top() + screen.height() * 0.31;
painter.setPen(QPen(kStopColor, lineWidth * 0.60,
Qt::SolidLine, Qt::RoundCap));
painter.drawLine(QPointF(outerX, y), QPointF(innerX, y));
const qreal direction = correctionFromLeft ? -1.0 : 1.0;
painter.drawLine(QPointF(outerX, y),
QPointF(outerX + direction * arrow,
y - arrow));
painter.drawLine(QPointF(outerX, y),
QPointF(outerX + direction * arrow,
y + arrow));
}
painter.restore();
}
QString GuideDisplayWidget::AircraftText() const
{
QString model = m_status.value("expectedModelType").toString().trimmed();
if (model.isEmpty()) {
model = m_status.value("modelType").toString().trimmed();
}
if (model.isEmpty() || model == QStringLiteral("未知")) {
model = QStringLiteral("----");
}
return model;
}