#include "PanelsPanel.h" #include #include #include #include #include #include PanelsPanel::PanelsPanel(QWidget *parent) : QWidget(parent) { auto *layout = new QVBoxLayout(this); layout->setContentsMargins(4, 4, 4, 4); m_statusLabel = new QLabel("Connect to load panels...", this); m_statusLabel->setAlignment(Qt::AlignHCenter | Qt::AlignTop); m_statusLabel->setStyleSheet("color: #888; font-style: italic;"); layout->addWidget(m_statusLabel); layout->addStretch(1); setMinimumSize(200, 200); } void PanelsPanel::setRnpCount(int count) { m_count = count; m_colors.clear(); m_hits.clear(); m_statusLabel->setText(count > 0 ? QString("%1 panel%2").arg(count).arg(count == 1 ? "" : "s") : "No panels reported"); update(); } void PanelsPanel::setPanelColor(int index, const QColor &color) { m_colors[index] = color; update(); } void PanelsPanel::impactPanel(int index, int power) { m_hits[index] = power; update(); QTimer::singleShot(3000, this, [this, index]() { m_hits.remove(index); update(); }); } void PanelsPanel::reset() { m_count = 0; m_colors.clear(); m_hits.clear(); m_statusLabel->setText("Connect to load panels..."); update(); } static double bubbleScale(int power) { if (power < 150) return 0.20; if (power < 500) return 0.40; return 0.80; } // Draw a radial-gradient bubble at bCentre with radius circR static void drawBubble(QPainter &p, const QPointF &bCentre, double circR, bool isHighest, int power) { const QRectF circRect(bCentre.x() - circR, bCentre.y() - circR, circR * 2, circR * 2); // Drop shadow p.setBrush(QColor(0, 0, 0, 55)); p.setPen(Qt::NoPen); p.drawEllipse(circRect.adjusted(2, 3, 2, 3)); // Colour scheme: red = highest, orange/amber = others QColor bright, dark, rim; if (isHighest) { bright = QColor(255, 100, 100); dark = QColor(160, 10, 10); rim = QColor(255, 200, 200); } else { bright = QColor(255, 210, 60); dark = QColor(200, 100, 0); rim = QColor(255, 240, 180); } QRadialGradient grad(bCentre, circR, QPointF(bCentre.x() - circR * 0.3, bCentre.y() - circR * 0.3)); grad.setColorAt(0.0, bright); grad.setColorAt(1.0, dark); p.setBrush(grad); p.setPen(QPen(rim, 1.5)); p.drawEllipse(circRect); // Power text QFont pf = p.font(); pf.setBold(true); pf.setPointSize(qMax(5, qMin(11, static_cast(circR * 0.68)))); p.setFont(pf); p.setPen(Qt::white); p.drawText(circRect, Qt::AlignCenter, QString::number(power)); } void PanelsPanel::paintEvent(QPaintEvent *) { if (m_count <= 0) return; QPainter p(this); p.setRenderHint(QPainter::Antialiasing, true); const QRectF bounds = rect(); const QRectF drawArea = bounds.adjusted(0, 28, 0, 0); const double side = qMin(drawArea.width(), drawArea.height()) * 0.88; const double cx = drawArea.center().x(); const double cy = drawArea.center().y(); const double outerR = side / 2.0; const double innerR = outerR * 0.44; const double bandW = outerR - innerR; const QRectF outerRect(cx - outerR, cy - outerR, side, side); const QRectF innerRect(cx - innerR, cy - innerR, innerR * 2, innerR * 2); const double gapDeg = (m_count > 1) ? 3.0 : 0.0; const double segSpan = (360.0 - m_count * gapDeg) / m_count; const double maxCircR = qMin( bandW * 0.46, outerR * qSin(qDegreesToRadians(segSpan / 2.0)) * 0.82 ); // Find the highest-power panel index among active hits int topIndex = -1; int topPower = -1; for (auto it = m_hits.cbegin(); it != m_hits.cend(); ++it) { if (it.value() > topPower) { topPower = it.value(); topIndex = it.key(); } } static const QColor kDefault(100, 140, 180); static const QColor kHit(200, 50, 50); for (int i = 0; i < m_count; ++i) { const bool isHit = m_hits.contains(i); const double startAngle = 90.0 - i * (segSpan + gapDeg); const double sweep = -segSpan; const double midAngleRad = qDegreesToRadians(startAngle + sweep / 2.0); // --- Segment --- QPainterPath path; path.arcMoveTo(outerRect, startAngle); path.arcTo(outerRect, startAngle, sweep); path.arcTo(innerRect, startAngle + sweep, -sweep); path.closeSubpath(); p.setBrush(isHit ? kHit : m_colors.value(i, kDefault)); p.setPen(QPen(Qt::white, isHit ? 3 : 2)); p.drawPath(path); // --- Panel index label at midpoint --- const double midR = (outerR + innerR) / 2.0; const QPointF labelPt(cx + midR * qCos(midAngleRad), cy - midR * qSin(midAngleRad)); { QFont font = p.font(); font.setBold(true); font.setPointSize(qMax(7, qMin(12, static_cast(outerR / (m_count * 0.55 + 2))))); p.setFont(font); p.setPen(Qt::white); p.drawText(QRectF(labelPt.x() - 22, labelPt.y() - 11, 44, 22), Qt::AlignCenter, QString::number(i)); } if (isHit) { const int power = m_hits[i]; const bool isTop = (i == topIndex); const double circR = maxCircR * bubbleScale(power); const double bCentR = outerR - circR - 2.0; const QPointF bCentre(cx + bCentR * qCos(midAngleRad), cy - bCentR * qSin(midAngleRad)); // Glow halo on segment p.setBrush(Qt::NoBrush); p.setPen(QPen(isTop ? QColor(255, 80, 80, 120) : QColor(255, 200, 50, 100), 5)); p.drawPath(path); drawBubble(p, bCentre, circR, isTop, power); } } // --- Centre hole: total count --- QFont cf = p.font(); cf.setBold(true); cf.setPointSize(qMax(8, static_cast(innerR * 0.45))); p.setFont(cf); p.setPen(palette().text().color()); p.drawText(innerRect, Qt::AlignCenter, QString::number(m_count)); }