#include "GuideDisplayWidget.h" #include #include #include #include #include #include #include #include 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(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(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(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(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(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(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( -1.0, static_cast(offsetMm / 1000.0), 1.0); const qreal aircraftX = centerX + offsetRatio * screen.width() * 0.20; const qreal clampedYaw = qBound( -30.0, static_cast(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(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; }