openpilot/frogpilot/ui/qt/onroad/frogpilot_onroad.cc
2025-11-01 12:00:00 -07:00

169 lines
5.8 KiB
C++

#include "frogpilot/ui/qt/onroad/frogpilot_onroad.h"
FrogPilotOnroadWindow::FrogPilotOnroadWindow(QWidget *parent) : QWidget(parent) {
signalTimer = new QTimer(this);
QObject::connect(signalTimer, &QTimer::timeout, [this] {
flickerActive = !flickerActive;
});
}
void FrogPilotOnroadWindow::updateState(const UIState &s, const FrogPilotUIState &fs) {
QJsonObject &frogpilot_toggles = fs.frogpilot_toggles;
SubMaster &fpsm = *(fs.sm);
const cereal::CarState::Reader &carState = fpsm["carState"].getCarState();
const cereal::CarControl::Reader &carControl = fpsm["carControl"].getCarControl();
blindSpotLeft = carState.getLeftBlindspot();
blindSpotRight = carState.getRightBlindspot();
steer = -carControl.getActuators().getSteer();
turnSignalLeft = carState.getLeftBlinker();
turnSignalRight = carState.getRightBlinker();
showBlindspot = (blindSpotLeft || blindSpotRight) && frogpilot_toggles.value("blind_spot_metrics").toBool();
showFPS = frogpilot_toggles.value("show_fps").toBool();
showSignal = (turnSignalLeft || turnSignalRight) && frogpilot_toggles.value("signal_metrics").toBool();
showSteering = frogpilot_toggles.value("steering_metrics").toBool();
if (showBlindspot || showFPS || showSignal || showSteering) {
update();
}
}
void FrogPilotOnroadWindow::paintEvent(QPaintEvent *event) {
QPainter p(this);
p.setRenderHints(QPainter::Antialiasing | QPainter::TextAntialiasing);
QRect rect = this->rect();
QRegion marginRegion;
marginRegion += QRegion(0, 0, rect.width(), UI_BORDER_SIZE);
marginRegion += QRegion(0, rect.height() - UI_BORDER_SIZE, rect.width(), UI_BORDER_SIZE);
marginRegion += QRegion(0, UI_BORDER_SIZE, UI_BORDER_SIZE, rect.height() - 2 * UI_BORDER_SIZE);
marginRegion += QRegion(rect.width() - UI_BORDER_SIZE, UI_BORDER_SIZE, UI_BORDER_SIZE, rect.height() - 2 * UI_BORDER_SIZE);
p.setClipRegion(marginRegion);
if (showSteering) {
paintSteeringTorqueBorder(p, rect);
}
if (showBlindspot || showSignal) {
int interval = showBlindspot ? 250 : 500;
if (!signalTimer->isActive()) {
signalTimer->start(interval);
} else if (signalTimer->interval() != interval) {
signalTimer->stop();
signalTimer->start(interval);
}
paintTurnSignalBorder(p, rect);
} else if (signalTimer->isActive()) {
signalTimer->stop();
}
if (showFPS) {
paintFPS(p, rect);
}
}
void FrogPilotOnroadWindow::paintFPS(QPainter &p, const QRect &rect) {
p.save();
qint64 now = QDateTime::currentMSecsSinceEpoch();
static double maxFPS = 0.0;
static double minFPS = 99.9;
static double totalFPS = 0.0;
static QList<QPair<qint64, double>> fpsHistory;
fpsHistory.append({now, fps});
totalFPS += fps;
while (!fpsHistory.isEmpty() && now - fpsHistory.first().first > 60000) {
totalFPS -= fpsHistory.first().second;
fpsHistory.removeFirst();
}
double avgFPS = fpsHistory.isEmpty() ? 0.0 : totalFPS / fpsHistory.size();
minFPS = std::min(minFPS, fps);
maxFPS = std::max(maxFPS, fps);
QString fpsDisplayString = QString(tr("FPS: %1 | Min: %2 | Max: %3 | Avg: %4"))
.arg(qRound(fps))
.arg(qRound(minFPS))
.arg(qRound(maxFPS))
.arg(qRound(avgFPS));
p.setFont(InterFont(28, QFont::DemiBold));
p.setPen(Qt::white);
int xPos = (rect.width() - p.fontMetrics().horizontalAdvance(fpsDisplayString)) / 2;
int yPos = rect.bottom() - 5;
p.drawText(xPos, yPos, fpsDisplayString);
p.restore();
}
void FrogPilotOnroadWindow::paintSteeringTorqueBorder(QPainter &p, const QRect &rect) {
p.save();
static float smoothedSteer = 0.0;
smoothedSteer = 0.25 * std::abs(steer) + 0.75 * smoothedSteer;
if (std::abs(smoothedSteer - steer) < 0.01) {
smoothedSteer = steer;
}
QLinearGradient gradient(rect.topLeft(), rect.bottomLeft());
gradient.setColorAt(0.0, bg_colors[STATUS_TRAFFIC_MODE_ENABLED]);
gradient.setColorAt(0.25, bg_colors[STATUS_EXPERIMENTAL_MODE_ENABLED]);
gradient.setColorAt(0.5, bg_colors[STATUS_CONDITIONAL_OVERRIDDEN]);
gradient.setColorAt(0.75, bg_colors[STATUS_ENGAGED]);
gradient.setColorAt(1.0, bg_colors[STATUS_ENGAGED]);
int visibleHeight = rect.height() * smoothedSteer;
QRect rectToFill, rectToHide;
if (steer < 0) {
rectToFill = QRect(rect.x(), rect.y() + rect.height() - visibleHeight, UI_BORDER_SIZE, visibleHeight);
rectToHide = QRect(rect.x(), rect.y(), UI_BORDER_SIZE, rect.height() - visibleHeight);
} else {
rectToFill = QRect(rect.x() + rect.width() - UI_BORDER_SIZE, rect.y() + rect.height() - visibleHeight, UI_BORDER_SIZE, visibleHeight);
rectToHide = QRect(rect.x() + rect.width() - UI_BORDER_SIZE, rect.y(), UI_BORDER_SIZE, rect.height() - visibleHeight);
}
p.fillRect(rectToFill, QBrush(gradient));
p.fillRect(rectToHide, Qt::transparent);
p.restore();
}
void FrogPilotOnroadWindow::paintTurnSignalBorder(QPainter &p, const QRect &rect) {
p.save();
std::function<QColor(bool, bool)> getBorderColor = [&](bool blindSpot, bool turnSignal) {
if (turnSignal && showSignal) {
if (blindSpot) {
return flickerActive ? bg_colors[STATUS_TRAFFIC_MODE_ENABLED] : bg_colors[STATUS_CONDITIONAL_OVERRIDDEN];
} else {
return flickerActive ? bg_colors[STATUS_CONDITIONAL_OVERRIDDEN] : bg;
}
} else if (blindSpot && showBlindspot) {
return bg_colors[STATUS_TRAFFIC_MODE_ENABLED];
} else {
return bg;
}
};
QColor borderColorLeft = getBorderColor(blindSpotLeft, turnSignalLeft);
QColor borderColorRight = getBorderColor(blindSpotRight, turnSignalRight);
p.fillRect(rect.x(), rect.y(), rect.width() / 2, rect.height(), borderColorLeft);
p.fillRect(rect.x() + rect.width() / 2, rect.y(), rect.width() / 2, rect.height(), borderColorRight);
p.restore();
}