Files
esa-remote-lite/PanelsPanel.cpp

203 lines
6.2 KiB
C++

#include "PanelsPanel.h"
#include <QLabel>
#include <QPainter>
#include <QPainterPath>
#include <QTimer>
#include <QVBoxLayout>
#include <QtMath>
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<int>(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<int>(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<int>(innerR * 0.45)));
p.setFont(cf);
p.setPen(palette().text().color());
p.drawText(innerRect, Qt::AlignCenter, QString::number(m_count));
}