Drive Summary

This commit is contained in:
James
2025-12-01 12:00:00 -07:00
parent 0ed15a14ce
commit 7ba746e286
4 changed files with 304 additions and 6 deletions
+225
View File
@@ -0,0 +1,225 @@
#include "selfdrive/ui/qt/widgets/scrollview.h"
#include "frogpilot/ui/qt/widgets/drive_summary.h"
FrogPilotDriveSummary::FrogPilotDriveSummary(QWidget *parent, bool randomEvents) : QFrame(parent), displayRandomEvents(randomEvents) {
QVBoxLayout *mainLayout = new QVBoxLayout(this);
mainLayout->setContentsMargins(20, 20, 20, 20);
mainLayout->setSpacing(15);
titleLabel = new QLabel(randomEvents ? tr("Random Events Summary") : tr("Drive Summary"), this);
titleLabel->setAlignment(Qt::AlignCenter);
titleLabel->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Fixed);
titleLabel->setStyleSheet(R"(
QLabel {
background-color: #444444;
border-radius: 12px;
color: #FFFFFF;
font-size: 50px;
font-weight: bold;
padding: 12px 28px;
}
)");
titleLabel->setMaximumHeight(titleLabel->sizeHint().height());
mainLayout->addWidget(titleLabel);
mainLayout->addSpacing(10);
QWidget *containerWidget = new QWidget(this);
QVBoxLayout *listLayout = new QVBoxLayout(containerWidget);
listLayout->setAlignment(Qt::AlignTop);
listLayout->setSpacing(20);
if (displayRandomEvents) {
randomEventsMap.insert("accel30", tr("UwUs"));
randomEventsMap.insert("accel35", tr("Loch Ness Encounters"));
randomEventsMap.insert("accel40", tr("Visits to 1955"));
randomEventsMap.insert("dejaVuCurve", tr("Deja Vu Moments"));
randomEventsMap.insert("firefoxSteerSaturated", tr("Internet Explorer Weeeeeeees"));
randomEventsMap.insert("hal9000", tr("HAL 9000 Denials"));
randomEventsMap.insert("openpilotCrashedRandomEvent", tr("openpilot Crashes"));
randomEventsMap.insert("thisIsFineSteerSaturated", tr("This Is Fine Moments"));
randomEventsMap.insert("toBeContinued", tr("To Be Continued Moments"));
randomEventsMap.insert("vCruise69", tr("Noices"));
randomEventsMap.insert("yourFrogTriedToKillMe", tr("Attempted Frog Murders"));
randomEventsMap.insert("youveGotMail", tr("Total Mail Received"));
} else {
listLayout->addWidget(createStatBox(tr("% of Drive With openpilot Engaged"), &engagementValue, this));
listLayout->addWidget(createStatBox(tr("Drive Distance"), &frogPilotMetersValue, this));
listLayout->addWidget(createStatBox(tr("Drive Time"), &trackedTimeValue, this));
listLayout->addWidget(createStatBox(tr("% of Drive In \"Experimental Mode\""), &experimentalModeTimeValue, this));
}
if (displayRandomEvents) {
eventsListLayout = listLayout;
mainLayout->addWidget(new ScrollView(containerWidget, this), 1);
} else {
mainLayout->addWidget(containerWidget, 1);
}
setLayout(mainLayout);
setStyleSheet(R"(
QFrame {
background-color: #333333;
}
)");
QObject::connect(device(), &Device::interactiveTimeout, [this]() {
emit panelClosed();
});
QObject::connect(uiState(), &UIState::offroadTransition, [this](bool offroad) {
if (!offroad) {
previousStats = QJsonDocument::fromJson(QString::fromStdString(params.get("FrogPilotStats")).toUtf8()).object();
}
});
}
void FrogPilotDriveSummary::mousePressEvent(QMouseEvent *e) {
emit panelClosed();
}
void FrogPilotDriveSummary::showEvent(QShowEvent *event) {
bool isMetric = params.getBool("IsMetric");
QJsonObject currentStats = QJsonDocument::fromJson(QString::fromStdString(params.get("FrogPilotStats")).toUtf8()).object();
if (displayRandomEvents) {
QJsonObject currentRandomEvents = currentStats.value("RandomEvents").toObject();
QJsonObject previousRandomEvents = previousStats.value("RandomEvents").toObject();
QList<QPair<QString, int>> eventsList;
for (QMap<QString, QString>::const_iterator it = randomEventsMap.constBegin(); it != randomEventsMap.constEnd(); ++it) {
int currentValue = currentRandomEvents.value(it.key()).toInt();
int previousValue = previousRandomEvents.value(it.key()).toInt();
int diffValue = currentValue - previousValue;
if (diffValue > 0) {
eventsList.append(qMakePair(it.value(), diffValue));
}
}
std::sort(eventsList.begin(), eventsList.end(), [](const QPair<QString, int> &a, const QPair<QString, int> &b) {
if (a.second != b.second) {
return a.second > b.second;
} else {
return a.first.localeAwareCompare(b.first) < 0;
}
});
QLayoutItem *child;
while ((child = eventsListLayout->takeAt(0)) != nullptr) {
if (QWidget *widget = child->widget()) {
widget->deleteLater();
}
delete child;
}
randomEventLabels.clear();
if (eventsList.isEmpty()) {
eventsListLayout->setAlignment(Qt::AlignCenter);
QLabel *noEventsLabel = new QLabel(tr("No Random Events Played!"), this);
noEventsLabel->setAlignment(Qt::AlignCenter);
noEventsLabel->setStyleSheet(R"(
QLabel {
font-size: 50px;
font-weight: bold;
color: #FFFFFF;
}
)");
eventsListLayout->addWidget(noEventsLabel);
} else {
eventsListLayout->setAlignment(Qt::AlignTop);
for (QList<QPair<QString, int>>::const_iterator it = eventsList.constBegin(); it != eventsList.constEnd(); ++it) {
QLabel *valueLabel = nullptr;
eventsListLayout->addWidget(createStatBox(it->first, &valueLabel, this));
randomEventLabels.insert(it->first, valueLabel);
valueLabel->setText(QLocale().toString(it->second));
}
}
} else {
std::function<double(const QString &)> diffDouble = [currentStats, this](const QString &key) -> double {
return currentStats.value(key).toDouble() - previousStats.value(key).toDouble();
};
std::function<QString(double)> formatDistance = [&](double meters) {
double value;
QString unit;
if (isMetric) {
value = meters / 1000.0;
unit = (qRound(value) == 1) ? tr(" kilometer") : tr(" kilometers");
} else {
value = meters * METER_TO_MILE;
unit = (qRound(value) == 1) ? tr(" mile") : tr(" miles");
}
return QLocale().toString(qRound(value)) + unit;
};
std::function<QString(int)> formatTime = [&](int seconds) {
static int secondsInDay = 60 * 60 * 24;
static int secondsInHour = 60 * 60;
int days = seconds / secondsInDay;
int hours = (seconds % secondsInDay) / secondsInHour;
int minutes = (seconds % secondsInHour) / 60;
QString result;
if (days > 0) result += QLocale().toString(days) + (days == 1 ? tr(" day ") : tr(" days "));
if (hours > 0 || days > 0) result += QLocale().toString(hours) + (hours == 1 ? tr(" hour ") : tr(" hours "));
result += QLocale().toString(minutes) + (minutes == 1 ? tr(" minute") : tr(" minutes"));
return result.trimmed();
};
int engagedTime = diffDouble("AOLTime") + diffDouble("LongitudinalTime");
int experimentalTime = diffDouble("ExperimentalModeTime");
int trackedTime = diffDouble("TrackedTime");
engagementValue->setText(QLocale().toString((trackedTime > 0) ? (engagedTime * 100 / trackedTime) : 0) + "%");
experimentalModeTimeValue->setText(QLocale().toString((trackedTime > 0) ? (experimentalTime * 100 / trackedTime) : 0) + "%");
frogPilotMetersValue->setText(formatDistance(diffDouble("FrogPilotMeters")));
trackedTimeValue->setText(formatTime(trackedTime));
}
}
void FrogPilotDriveSummary::hideEvent(QHideEvent *event) {
emit panelClosed();
}
QWidget *FrogPilotDriveSummary::createStatBox(const QString &title, QLabel **valueLabel, QWidget *parent) {
QWidget *box = new QWidget(parent);
QVBoxLayout *layout = new QVBoxLayout(box);
layout->setAlignment(Qt::AlignCenter);
layout->setContentsMargins(10, 10, 10, 10);
layout->setSpacing(8);
QLabel *statTitleLabel = new QLabel(title, box);
statTitleLabel->setAlignment(Qt::AlignCenter);
statTitleLabel->setStyleSheet(R"(
QLabel {
color: #AAAAAA;
font-size: 40px;
font-weight: bold;
}
)");
QLabel *value = new QLabel("-", box);
value->setAlignment(Qt::AlignCenter);
value->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Preferred);
value->setStyleSheet(R"(
QLabel {
color: #FFFFFF;
font-size: 75px;
font-weight: bold;
}
)");
*valueLabel = value;
layout->addWidget(statTitleLabel);
layout->addWidget(value);
return box;
}
+39
View File
@@ -0,0 +1,39 @@
#pragma once
#include "selfdrive/ui/ui.h"
class FrogPilotDriveSummary : public QFrame {
Q_OBJECT
public:
explicit FrogPilotDriveSummary(QWidget *parent = nullptr, bool random_events = false);
signals:
void panelClosed();
protected:
void showEvent(QShowEvent *event) override;
void hideEvent(QHideEvent *event) override;
void mousePressEvent(QMouseEvent *e);
private:
QWidget *createStatBox(const QString &title, QLabel **valueLabel, QWidget *parent);
bool displayRandomEvents;
Params params;
QJsonObject previousStats;
QLabel *experimentalModeTimeValue;
QLabel *frogPilotMetersValue;
QLabel *engagementValue;
QLabel *titleLabel;
QLabel *trackedTimeValue;
QMap<QString, QLabel*> randomEventLabels;
QMap<QString, QString> randomEventsMap;
QVBoxLayout *eventsListLayout;
};
+1
View File
@@ -49,6 +49,7 @@ frogpilot_src = ["../../frogpilot/ui/frogpilot_ui.cc", "../../frogpilot/ui/qt/of
"../../frogpilot/ui/qt/offroad/wheel_settings.cc", "../../frogpilot/ui/qt/onroad/frogpilot_annotated_camera.cc",
"../../frogpilot/ui/qt/onroad/frogpilot_buttons.cc", "../../frogpilot/ui/qt/onroad/frogpilot_onroad.cc",
"../../frogpilot/ui/qt/widgets/developer_sidebar.cc", "../../frogpilot/ui/qt/widgets/drive_stats.cc",
"../../frogpilot/ui/qt/widgets/drive_summary.cc",
"../../frogpilot/ui/qt/widgets/navigation_functions.cc"]
qt_src += frogpilot_src
+39 -6
View File
@@ -10,6 +10,7 @@
#include "selfdrive/ui/qt/widgets/prime.h"
#include "frogpilot/ui/qt/widgets/drive_stats.h"
#include "frogpilot/ui/qt/widgets/drive_summary.h"
// HomeWindow: the container for the offroad and onroad UIs
@@ -170,20 +171,35 @@ OffroadHome::OffroadHome(QWidget* parent) : QFrame(parent) {
home_layout->setContentsMargins(0, 0, 0, 0);
home_layout->setSpacing(30);
// left: stack of DriveStats
// left: stack of DriveStats / DriveSummary
QWidget *left_widget = new QWidget(this);
QStackedLayout *left_stack = new QStackedLayout(left_widget);
left_stack->setContentsMargins(0, 0, 0, 0);
left_stack->addWidget(new DriveStats());
FrogPilotDriveSummary *drive_summary = new FrogPilotDriveSummary(this);
left_stack->addWidget(drive_summary);
QObject::connect(drive_summary, &FrogPilotDriveSummary::panelClosed, [left_stack]() {
left_stack->setCurrentIndex(0);
});
QObject::connect(uiState(), &UIState::offroadTransition, [left_stack](bool offroad) {
static bool previouslyOnroad = false;
if (offroad && previouslyOnroad) {
left_stack->setCurrentIndex(1);
}
previouslyOnroad = !offroad;
});
home_layout->addWidget(left_widget, 1);
// right: ExperimentalModeButton, SetupWidget
QWidget* right_widget = new QWidget(this);
QVBoxLayout* right_column = new QVBoxLayout(right_widget);
right_column->setContentsMargins(0, 0, 0, 0);
// right: ExperimentalModeButton, SetupWidget, Random Events Summary
QStackedWidget *right_widget = new QStackedWidget(this);
right_widget->setFixedWidth(750);
QWidget *default_right = new QWidget(this);
QVBoxLayout *right_column = new QVBoxLayout(default_right);
right_column->setContentsMargins(0, 0, 0, 0);
right_column->setSpacing(30);
ExperimentalModeButton *experimental_mode = new ExperimentalModeButton(this);
@@ -194,7 +210,24 @@ OffroadHome::OffroadHome(QWidget* parent) : QFrame(parent) {
QObject::connect(setup_widget, &SetupWidget::openSettings, this, &OffroadHome::openSettings);
right_column->addWidget(setup_widget, 1);
home_layout->addWidget(right_widget, 1);
right_widget->addWidget(default_right);
FrogPilotDriveSummary *random_events_summary = new FrogPilotDriveSummary(this, true);
right_widget->addWidget(random_events_summary);
right_widget->setCurrentIndex(0);
QObject::connect(random_events_summary, &FrogPilotDriveSummary::panelClosed, [=]() {
right_widget->setCurrentIndex(0);
});
QObject::connect(uiState(), &UIState::offroadTransition, [right_widget](bool offroad) {
static bool previouslyOnroad = false;
if (offroad && previouslyOnroad && frogpilotUIState()->frogpilot_scene.frogpilot_toggles.value("random_events").toBool()) {
right_widget->setCurrentIndex(1);
}
previouslyOnroad = !offroad;
});
home_layout->addWidget(right_widget, 0);
}
center_layout->addWidget(home_widget);