Files
esa-remote-lite/LightsPanel.cpp

296 lines
10 KiB
C++

#include "LightsPanel.h"
#include <QColorDialog>
#include <QHBoxLayout>
#include <QLabel>
#include <QMouseEvent>
#include <QPainter>
#include <QPainterPath>
#include <QPushButton>
#include <QVBoxLayout>
#include <QtMath>
// ---------------------------------------------------------------------------
// Helpers
// ---------------------------------------------------------------------------
static QString toDecRGB(const QColor &c)
{
return QString("%1 %2 %3").arg(c.red()).arg(c.green()).arg(c.blue());
}
static void applyColorToBtn(QPushButton *btn, const QColor &c)
{
const bool dark = (c.red() * 299 + c.green() * 587 + c.blue() * 114) < 128000;
btn->setStyleSheet(QString(
"QPushButton { background:%1; color:%2; border:1px solid #555;"
" border-radius:4px; padding:3px 10px; }"
"QPushButton:hover { border:2px solid #fff; }")
.arg(c.name())
.arg(dark ? "#ffffff" : "#000000"));
btn->setText(QString("%1, %2, %3").arg(c.red()).arg(c.green()).arg(c.blue()));
}
// ---------------------------------------------------------------------------
// Constructor
// ---------------------------------------------------------------------------
LightsPanel::LightsPanel(QWidget *parent) : QWidget(parent)
{
auto *root = new QVBoxLayout(this);
root->setContentsMargins(4, 4, 4, 4);
root->setSpacing(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;");
root->addWidget(m_statusLabel);
root->addStretch(1);
// --- Control bar ---
auto *bar = new QHBoxLayout();
bar->setSpacing(8);
m_selectedLabel = new QLabel("No panel selected", this);
m_selectedLabel->setStyleSheet("font-weight: bold; min-width: 140px;");
auto *colorLabel = new QLabel("Colour:", this);
m_colorBtn = new QPushButton(this);
m_colorBtn->setFixedWidth(130);
applyColorToBtn(m_colorBtn, m_ledColor);
m_sendBtn = new QPushButton("Send to Panel", this);
m_sendBtn->setEnabled(false);
m_sendBtn->setStyleSheet(
"QPushButton { background:#01696f; color:white; border-radius:4px; padding:4px 10px; }"
"QPushButton:hover { background:#0c4e54; }"
"QPushButton:disabled { background:#888; color:#ccc; }");
m_sendAllBtn = new QPushButton("Send to All", this);
m_sendAllBtn->setEnabled(false);
m_sendAllBtn->setStyleSheet(
"QPushButton { background:#437a22; color:white; border-radius:4px; padding:4px 10px; }"
"QPushButton:hover { background:#2e5c10; }"
"QPushButton:disabled { background:#888; color:#ccc; }");
m_clearAllBtn = new QPushButton("Clear All", this);
m_clearAllBtn->setEnabled(false);
m_clearAllBtn->setStyleSheet(
"QPushButton { background:#555; color:white; border-radius:4px; padding:4px 10px; }"
"QPushButton:hover { background:#333; }"
"QPushButton:disabled { background:#888; color:#ccc; }");
bar->addWidget(m_selectedLabel);
bar->addStretch(1);
bar->addWidget(colorLabel);
bar->addWidget(m_colorBtn);
bar->addSpacing(8);
bar->addWidget(m_sendBtn);
bar->addWidget(m_sendAllBtn);
bar->addWidget(m_clearAllBtn);
root->addLayout(bar);
connect(m_colorBtn, &QPushButton::clicked, this, &LightsPanel::pickColor);
connect(m_sendBtn, &QPushButton::clicked, this, [this](){
if (m_selected >= 0) sendLedCommand(m_selected, m_ledColor);
});
connect(m_sendAllBtn, &QPushButton::clicked, this, [this](){
for (int i = 0; i < m_count; ++i) sendLedCommand(i, m_ledColor);
});
connect(m_clearAllBtn, &QPushButton::clicked, this, [this](){
const QColor black(0, 0, 0);
for (int i = 0; i < m_count; ++i) sendLedCommand(i, black);
});
setMinimumSize(220, 260);
setCursor(Qt::PointingHandCursor);
}
// ---------------------------------------------------------------------------
// Public API
// ---------------------------------------------------------------------------
void LightsPanel::setRnpCount(int count)
{
m_count = count;
m_selected = -1;
m_panelColors.clear();
m_statusLabel->setText(count > 0
? QString("%1 panel%2 — click a segment to select").arg(count).arg(count == 1 ? "" : "s")
: "No panels reported");
const bool ok = count > 0;
m_sendAllBtn->setEnabled(ok);
m_clearAllBtn->setEnabled(ok);
updateSendBtn();
update();
}
void LightsPanel::reset()
{
m_count = 0;
m_selected = -1;
m_panelColors.clear();
m_statusLabel->setText("Connect to load panels...");
m_sendBtn->setEnabled(false);
m_sendAllBtn->setEnabled(false);
m_clearAllBtn->setEnabled(false);
update();
}
// ---------------------------------------------------------------------------
// Internal
// ---------------------------------------------------------------------------
void LightsPanel::updateSendBtn()
{
m_sendBtn->setEnabled(m_selected >= 0);
m_sendBtn->setText(m_selected >= 0
? QString("Send to Panel %1").arg(m_selected)
: "Send to Panel");
}
void LightsPanel::pickColor()
{
auto *dlg = new QColorDialog(m_ledColor, this);
dlg->setOption(QColorDialog::DontUseNativeDialog);
dlg->setAttribute(Qt::WA_DeleteOnClose);
connect(dlg, &QColorDialog::colorSelected, this, [this](const QColor &c) {
m_ledColor = c;
applyColorToBtn(m_colorBtn, c);
update();
});
dlg->open();
}
void LightsPanel::sendLedCommand(int panel, const QColor &color)
{
emit commandRequested(QString("LED %1 %2 A").arg(panel).arg(toDecRGB(color)));
m_panelColors[panel] = color;
update();
}
// ---------------------------------------------------------------------------
// Geometry
// ---------------------------------------------------------------------------
static void calcGeom(const QRectF &widgetRect,
double &cx, double &cy, double &outerR, double &innerR)
{
const QRectF draw = widgetRect.adjusted(0, 28, 0, 44);
const double side = qMin(draw.width(), draw.height()) * 0.88;
cx = draw.center().x();
cy = draw.center().y();
outerR = side / 2.0;
innerR = outerR * 0.44;
}
int LightsPanel::segmentAt(const QPointF &pos) const
{
if (m_count <= 0) return -1;
double cx, cy, outerR, innerR;
calcGeom(rect(), cx, cy, outerR, innerR);
const double dx = pos.x() - cx;
const double dy = -(pos.y() - cy);
const double dist = qSqrt(dx*dx + dy*dy);
if (dist < innerR || dist > outerR) return -1;
double cwFromTop = 90.0 - qRadiansToDegrees(qAtan2(dy, dx));
if (cwFromTop < 0) cwFromTop += 360.0;
if (cwFromTop >= 360) cwFromTop -= 360.0;
const double gapDeg = (m_count > 1) ? 3.0 : 0.0;
const double segSpan = (360.0 - m_count * gapDeg) / m_count;
for (int i = 0; i < m_count; ++i) {
const double start = i * (segSpan + gapDeg);
if (cwFromTop >= start && cwFromTop < start + segSpan) return i;
}
return -1;
}
// ---------------------------------------------------------------------------
// Mouse
// ---------------------------------------------------------------------------
void LightsPanel::mousePressEvent(QMouseEvent *e)
{
const int idx = segmentAt(e->pos());
if (idx < 0) { QWidget::mousePressEvent(e); return; }
m_selected = idx;
m_selectedLabel->setText(QString("Panel %1 selected").arg(idx));
updateSendBtn();
update();
}
// ---------------------------------------------------------------------------
// Paint
// ---------------------------------------------------------------------------
void LightsPanel::paintEvent(QPaintEvent *)
{
if (m_count <= 0) return;
QPainter p(this);
p.setRenderHint(QPainter::Antialiasing, true);
double cx, cy, outerR, innerR;
calcGeom(rect(), cx, cy, outerR, innerR);
const double side = outerR * 2.0;
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;
static const QColor kDefault(70, 70, 80);
static const QColor kSelected(40, 120, 140);
for (int i = 0; i < m_count; ++i) {
const double startAngle = 90.0 - i * (segSpan + gapDeg);
const double sweep = -segSpan;
const double midAngleRad = qDegreesToRadians(startAngle + sweep / 2.0);
QPainterPath path;
path.arcMoveTo(outerRect, startAngle);
path.arcTo(outerRect, startAngle, sweep);
path.arcTo(innerRect, startAngle + sweep, -sweep);
path.closeSubpath();
QColor fill = m_panelColors.value(i,
i == m_selected ? kSelected : kDefault);
p.setBrush(fill);
p.setPen(QPen(i == m_selected ? Qt::white : QColor(255,255,255,80),
i == m_selected ? 3.0 : 1.5));
p.drawPath(path);
// Colour preview dot on selected segment (outer rim)
if (i == m_selected) {
const double previewR = qMin((outerR - innerR) * 0.22,
outerR * qSin(qDegreesToRadians(segSpan / 2.0)) * 0.55);
const double bCentR = outerR - previewR - 2.0;
const QPointF bc(cx + bCentR * qCos(midAngleRad),
cy - bCentR * qSin(midAngleRad));
const QRectF pr(bc.x()-previewR, bc.y()-previewR, previewR*2, previewR*2);
p.setBrush(m_ledColor);
p.setPen(QPen(Qt::white, 1.5));
p.drawEllipse(pr);
}
// Panel index label
const double midR = (outerR + innerR) / 2.0;
const QPointF lp(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);
const QColor bg = m_panelColors.value(i, i == m_selected ? kSelected : kDefault);
const bool darkBg = (bg.red()*299 + bg.green()*587 + bg.blue()*114) < 128000;
p.setPen(darkBg ? Qt::white : Qt::black);
p.drawText(QRectF(lp.x()-22, lp.y()-11, 44, 22), Qt::AlignCenter, QString::number(i));
}
// Centre 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));
}