FrogPilot panel

This commit is contained in:
James
2025-12-01 12:00:00 -07:00
parent ef16012768
commit b9f00dd14c
77 changed files with 7232 additions and 25 deletions
Binary file not shown.

After

Width:  |  Height:  |  Size: 9.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 24 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 38 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 43 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 19 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 46 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 25 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

+1
View File
@@ -8,6 +8,7 @@ static void update_state(FrogPilotUIState *fs) {
if (fpsm.updated("deviceState")) {
const cereal::DeviceState::Reader &deviceState = fpsm["deviceState"].getDeviceState();
frogpilot_scene.online = deviceState.getNetworkType() != cereal::DeviceState::NetworkType::NONE;
}
if (fpsm.updated("selfdriveState")) {
const cereal::SelfdriveState::Reader &selfdriveState = fpsm["selfdriveState"].getSelfdriveState();
+2
View File
@@ -7,6 +7,8 @@
struct FrogPilotUIScene {
bool enabled;
bool frogpilot_panel_active;
bool online;
bool parked;
bool reverse;
bool standstill;
+12
View File
@@ -0,0 +1,12 @@
#include "frogpilot/ui/qt/offroad/data_settings.h"
FrogPilotDataPanel::FrogPilotDataPanel(FrogPilotSettingsWindow *parent, bool forceOpen) : FrogPilotListWidget(parent), parent(parent) {
forceOpenDescriptions = forceOpen;
QStackedLayout *dataLayout = new QStackedLayout();
addItem(dataLayout);
FrogPilotListWidget *dataMainList = new FrogPilotListWidget(this);
ScrollView *dataMainPanel = new ScrollView(dataMainList, this);
dataLayout->addWidget(dataMainPanel);
}
+21
View File
@@ -0,0 +1,21 @@
#pragma once
#include "frogpilot/ui/qt/offroad/frogpilot_settings.h"
class FrogPilotDataPanel : public FrogPilotListWidget {
Q_OBJECT
public:
explicit FrogPilotDataPanel(FrogPilotSettingsWindow *parent, bool forceOpen = false);
signals:
void openSubPanel();
private:
bool forceOpenDescriptions;
bool isMetric;
FrogPilotSettingsWindow *parent;
Params params;
};
+220
View File
@@ -0,0 +1,220 @@
#include "frogpilot/ui/qt/offroad/device_settings.h"
FrogPilotDevicePanel::FrogPilotDevicePanel(FrogPilotSettingsWindow *parent, bool forceOpen) : FrogPilotListWidget(parent), parent(parent) {
forceOpenDescriptions = forceOpen;
QStackedLayout *deviceLayout = new QStackedLayout();
addItem(deviceLayout);
FrogPilotListWidget *deviceList = new FrogPilotListWidget(this);
ScrollView *devicePanel = new ScrollView(deviceList, this);
deviceLayout->addWidget(devicePanel);
FrogPilotListWidget *deviceManagementList = new FrogPilotListWidget(this);
FrogPilotListWidget *screenList = new FrogPilotListWidget(this);
ScrollView *deviceManagementPanel = new ScrollView(deviceManagementList, this);
ScrollView *screenPanel = new ScrollView(screenList, this);
deviceLayout->addWidget(deviceManagementPanel);
deviceLayout->addWidget(screenPanel);
const std::vector<std::tuple<QString, QString, QString, QString>> deviceToggles {
{"DeviceManagement", tr("Device Settings"), tr("<b>Settings that control how the device runs, powers off, and manages driving data.</b>"), "../../frogpilot/assets/toggle_icons/icon_device.png"},
{"DeviceShutdown", tr("Device Shutdown Timer"), tr("<b>Keep the device on for the set amount of time after a drive</b> before it shuts down automatically."), ""},
{"NoLogging", tr("Disable Logging"), QString("<b>%1</b><br><br>%2").arg(tr("WARNING: This will prevent your drives from being recorded and all data will be unobtainable!")).arg(tr("<b>Prevent the device from saving driving data.</b>")), ""},
{"NoUploads", tr("Disable Uploads"), QString("<b>%1</b><br><br>%2").arg(tr("WARNING: This will prevent your drives from being uploaded to <b>comma connect</b> which will impact debugging and official support from comma!")).arg(tr("<b>Prevent the device from uploading driving data.</b>")), ""},
{"HigherBitrate", tr("High-Quality Recording"), tr("<b>Save drive footage in higher video quality.</b>"), ""},
{"LowVoltageShutdown", tr("Low-Voltage Cutoff"), tr("<b>While parked, if the battery voltage falls below the set level, the device shuts down</b> to prevent excessive battery drain."), ""},
{"IncreaseThermalLimits", tr("Raise Temperature Limits"), QString("<b>%1</b><br><br>%2").arg(tr("WARNING: Running at higher temperatures may damage your device!")).arg(tr("<b>Allow the device to run at higher temperatures</b> before throttling or shutting down. Use only if you understand the risks!")), ""},
{"ScreenManagement", tr("Screen Settings"), tr("<b>Settings that control screen brightness, screen recording, and timeout duration.</b>"), "../../frogpilot/assets/toggle_icons/icon_light.png"},
{"ScreenBrightness", tr("Screen Brightness (Offroad)"), tr("<b>The screen brightness while not driving.</b>"), ""},
{"ScreenBrightnessOnroad", tr("Screen Brightness (Onroad)"), tr("<b>The screen brightness while driving.</b>"), ""},
{"ScreenRecorder", tr("Screen Recorder"), tr("<b>Add a button to the driving screen to record the display.</b>"), ""},
{"ScreenTimeout", tr("Screen Timeout (Offroad)"), tr("<b>How long the screen stays on after being tapped while not driving.</b>"), ""},
{"ScreenTimeoutOnroad", tr("Screen Timeout (Onroad)"), tr("<b>How long the screen stays on after being tapped while driving.</b>"), ""},
{"StandbyMode", tr("Standby Mode"), tr("<b>Turn the screen off while driving and automatically wake it up for alerts or engagement state changes.</b>"), ""},
{"IgnoreMe", "Ignore Me", "This is simply used to fix the layout when the user opens the descriptions and the menu gets wonky. No idea why it happens, but I can't be asked to properly fix it so whatever. Sue me.", ""},
{"IgnoreMe2", "Ignore Me", "This is simply used to fix the layout when the user opens the descriptions and the menu gets wonky. No idea why it happens, but I can't be asked to properly fix it so whatever. Sue me.", ""},
{"IgnoreMe3", "Ignore Me", "This is simply used to fix the layout when the user opens the descriptions and the menu gets wonky. No idea why it happens, but I can't be asked to properly fix it so whatever. Sue me.", ""},
{"IgnoreMe4", "Ignore Me", "This is simply used to fix the layout when the user opens the descriptions and the menu gets wonky. No idea why it happens, but I can't be asked to properly fix it so whatever. Sue me.", ""},
{"IgnoreMe5", "Ignore Me", "This is simply used to fix the layout when the user opens the descriptions and the menu gets wonky. No idea why it happens, but I can't be asked to properly fix it so whatever. Sue me.", ""}
};
for (const auto &[param, title, desc, icon] : deviceToggles) {
AbstractControl *deviceToggle;
if (param == "DeviceManagement") {
FrogPilotManageControl *deviceManagementToggle = new FrogPilotManageControl(param, title, desc, icon);
QObject::connect(deviceManagementToggle, &FrogPilotManageControl::manageButtonClicked, [deviceLayout, deviceManagementPanel]() {
deviceLayout->setCurrentWidget(deviceManagementPanel);
});
deviceToggle = deviceManagementToggle;
} else if (param == "DeviceShutdown") {
std::map<float, QString> shutdownLabels;
for (int i = 0; i <= 33; ++i) {
shutdownLabels[i] = i == 0 ? tr("5 mins") : i <= 3 ? QString::number(i * 15) + tr(" mins") : QString::number(i - 3) + (i == 4 ? tr(" hour") : tr(" hours"));
}
deviceToggle = new FrogPilotParamValueControl(param, title, desc, icon, 0, 33, QString(), shutdownLabels);
} else if (param == "NoUploads") {
std::vector<QString> uploadsToggles{"DisableOnroadUploads"};
std::vector<QString> uploadsToggleNames{tr("Disable Onroad Only")};
deviceToggle = new FrogPilotButtonToggleControl(param, title, desc, icon, uploadsToggles, uploadsToggleNames);
} else if (param == "LowVoltageShutdown") {
deviceToggle = new FrogPilotParamValueControl(param, title, desc, icon, 11.8, 12.5, tr(" volts"), std::map<float, QString>(), 0.1);
} else if (param == "ScreenManagement") {
FrogPilotManageControl *screenToggle = new FrogPilotManageControl(param, title, desc, icon);
QObject::connect(screenToggle, &FrogPilotManageControl::manageButtonClicked, [deviceLayout, screenPanel]() {
deviceLayout->setCurrentWidget(screenPanel);
});
deviceToggle = screenToggle;
} else if (param == "ScreenBrightness" || param == "ScreenBrightnessOnroad") {
std::map<float, QString> brightnessLabels;
int minBrightness = (param == "ScreenBrightnessOnroad") ? 0 : 1;
for (int i = 0; i <= 101; ++i) {
brightnessLabels[i] = i == 0 ? tr("Screen Off") : i == 101 ? tr("Auto") : QString::number(i) + "%";
}
deviceToggle = new FrogPilotParamValueControl(param, title, desc, icon, minBrightness, 101, QString(), brightnessLabels, 1, true);
} else if (param == "ScreenTimeout" || param == "ScreenTimeoutOnroad") {
deviceToggle = new FrogPilotParamValueControl(param, title, desc, icon, 5, 60, tr(" seconds"), {}, 5);
} else {
deviceToggle = new ParamControl(param, title, desc, icon);
}
toggles[param] = deviceToggle;
if (deviceManagementKeys.contains(param)) {
deviceManagementList->addItem(deviceToggle);
} else if (screenKeys.contains(param)) {
screenList->addItem(deviceToggle);
} else {
deviceList->addItem(deviceToggle);
parentKeys.insert(param);
}
if (FrogPilotManageControl *frogPilotManageToggle = qobject_cast<FrogPilotManageControl*>(deviceToggle)) {
QObject::connect(frogPilotManageToggle, &FrogPilotManageControl::manageButtonClicked, [this]() {
emit openSubPanel();
openDescriptions(forceOpenDescriptions, toggles);
});
}
QObject::connect(deviceToggle, &AbstractControl::hideDescriptionEvent, [this]() {
update();
});
QObject::connect(deviceToggle, &AbstractControl::showDescriptionEvent, [this]() {
update();
});
}
static_cast<ParamControl*>(toggles["IncreaseThermalLimits"])->setConfirmation(true, false);
static_cast<ParamControl*>(toggles["NoLogging"])->setConfirmation(true, false);
static_cast<ParamControl*>(toggles["NoUploads"])->setConfirmation(true, false);
QSet<QString> brightnessKeys = {"ScreenBrightness", "ScreenBrightnessOnroad"};
for (const QString &key : brightnessKeys) {
FrogPilotParamValueControl *paramControl = static_cast<FrogPilotParamValueControl*>(toggles[key]);
QObject::connect(paramControl, &FrogPilotParamValueControl::valueChanged, [key, this](int value) {
if (!started && key == "ScreenBrightness") {
Hardware::set_brightness(value);
} else if (started && key == "ScreenBrightnessOnroad") {
Hardware::set_brightness(value);
}
});
}
QSet<QString> forceUpdateKeys = {"NoUploads"};
for (const QString &key : forceUpdateKeys) {
QObject::connect(static_cast<FrogPilotButtonToggleControl*>(toggles[key]), &FrogPilotButtonToggleControl::buttonClicked, this, &FrogPilotDevicePanel::updateToggles);
QObject::connect(static_cast<ToggleControl*>(toggles[key]), &ToggleControl::toggleFlipped, this, &FrogPilotDevicePanel::updateToggles);
}
QSet<QString> rebootKeys = {"HigherBitrate"};
for (const QString &key : rebootKeys) {
QObject::connect(static_cast<ToggleControl*>(toggles[key]), &ToggleControl::toggleFlipped, [key, this](bool state) {
QString filePath;
if (key == "HigherBitrate") {
filePath = "/cache/use_HD";
}
if (!filePath.isEmpty()) {
QFile toggleFile(filePath);
if (state) {
if (!toggleFile.exists()) {
toggleFile.open(QIODevice::WriteOnly);
toggleFile.close();
}
} else {
if (toggleFile.exists()) {
toggleFile.remove();
}
}
}
if (FrogPilotConfirmationDialog::toggleReboot(this)) {
Hardware::reboot();
}
});
}
openDescriptions(forceOpenDescriptions, toggles);
QObject::connect(parent, &FrogPilotSettingsWindow::closeSubPanel, [deviceLayout, devicePanel, this] {
openDescriptions(forceOpenDescriptions, toggles);
deviceLayout->setCurrentWidget(devicePanel);
});
QObject::connect(uiState(), &UIState::uiUpdate, this, &FrogPilotDevicePanel::updateState);
}
void FrogPilotDevicePanel::showEvent(QShowEvent *event) {
updateToggles();
}
void FrogPilotDevicePanel::updateState(const UIState &s) {
if (!isVisible()) {
return;
}
started = s.scene.started;
}
void FrogPilotDevicePanel::updateToggles() {
for (auto &[key, toggle] : toggles) {
if (parentKeys.contains(key)) {
toggle->setVisible(false);
}
}
for (auto &[key, toggle] : toggles) {
if (parentKeys.contains(key)) {
continue;
}
bool setVisible = parent->tuningLevel >= parent->frogpilotToggleLevels[key].toDouble();
if (key == "HigherBitrate") {
setVisible &= params.getBool("DeviceManagement") && params.getBool("NoUploads") && !params.getBool("DisableOnroadUploads");
}
toggle->setVisible(setVisible);
if (setVisible) {
if (deviceManagementKeys.contains(key)) {
toggles["DeviceManagement"]->setVisible(true);
} else if (screenKeys.contains(key)) {
toggles["ScreenManagement"]->setVisible(true);
}
}
}
openDescriptions(forceOpenDescriptions, toggles);
update();
}
+34
View File
@@ -0,0 +1,34 @@
#pragma once
#include "frogpilot/ui/qt/offroad/frogpilot_settings.h"
class FrogPilotDevicePanel : public FrogPilotListWidget {
Q_OBJECT
public:
explicit FrogPilotDevicePanel(FrogPilotSettingsWindow *parent, bool forceOpen = false);
signals:
void openSubPanel();
protected:
void showEvent(QShowEvent *event) override;
private:
void updateState(const UIState &s);
void updateToggles();
bool forceOpenDescriptions;
bool started;
std::map<QString, AbstractControl*> toggles;
QSet<QString> deviceManagementKeys = {"DeviceShutdown", "HigherBitrate", "IncreaseThermalLimits", "LowVoltageShutdown", "NoLogging", "NoUploads"};
QSet<QString> screenKeys = {"ScreenBrightness", "ScreenBrightnessOnroad", "ScreenRecorder", "ScreenTimeout", "ScreenTimeoutOnroad", "StandbyMode"};
QSet<QString> parentKeys;
FrogPilotSettingsWindow *parent;
Params params;
};
@@ -0,0 +1,425 @@
#include "frogpilot/ui/qt/offroad/data_settings.h"
#include "frogpilot/ui/qt/offroad/device_settings.h"
#include "frogpilot/ui/qt/offroad/frogpilot_settings.h"
#include "frogpilot/ui/qt/offroad/lateral_settings.h"
#include "frogpilot/ui/qt/offroad/longitudinal_settings.h"
#include "frogpilot/ui/qt/offroad/maps_settings.h"
#include "frogpilot/ui/qt/offroad/model_settings.h"
#include "frogpilot/ui/qt/offroad/navigation_settings.h"
#include "frogpilot/ui/qt/offroad/sounds_settings.h"
#include "frogpilot/ui/qt/offroad/theme_settings.h"
#include "frogpilot/ui/qt/offroad/utilities.h"
#include "frogpilot/ui/qt/offroad/vehicle_settings.h"
#include "frogpilot/ui/qt/offroad/visual_settings.h"
#include "frogpilot/ui/qt/offroad/wheel_settings.h"
void FrogPilotSettingsWindow::createPanelButtons(FrogPilotListWidget *list) {
FrogPilotDataPanel *frogpilotDataPanel = new FrogPilotDataPanel(this, !shownDescriptions.value("FrogPilotDataPanel").toBool(false));
FrogPilotDevicePanel *frogpilotDevicePanel = new FrogPilotDevicePanel(this, !shownDescriptions.value("FrogPilotDevicePanel").toBool(false));
FrogPilotLateralPanel *frogpilotLateralPanel = new FrogPilotLateralPanel(this, !shownDescriptions.value("FrogPilotLateralPanel").toBool(false));
FrogPilotLongitudinalPanel *frogpilotLongitudinalPanel = new FrogPilotLongitudinalPanel(this, !shownDescriptions.value("FrogPilotLongitudinalPanel").toBool(false));
FrogPilotMapsPanel *frogpilotMapsPanel = new FrogPilotMapsPanel(this, !shownDescriptions.value("FrogPilotMapsPanel").toBool(false));
FrogPilotModelPanel *frogpilotModelPanel = new FrogPilotModelPanel(this, !shownDescriptions.value("FrogPilotModelPanel").toBool(false));
FrogPilotNavigationPanel *frogpilotNavigationPanel = new FrogPilotNavigationPanel(this, !shownDescriptions.value("FrogPilotNavigationPanel").toBool(false));
FrogPilotSoundsPanel *frogpilotSoundsPanel = new FrogPilotSoundsPanel(this, !shownDescriptions.value("FrogPilotSoundsPanel").toBool(false));
FrogPilotThemesPanel *frogpilotThemesPanel = new FrogPilotThemesPanel(this, !shownDescriptions.value("FrogPilotThemesPanel").toBool(false));
FrogPilotUtilitiesPanel *frogpilotUtilitiesPanel = new FrogPilotUtilitiesPanel(this, !shownDescriptions.value("FrogPilotUtilitiesPanel").toBool(false));
FrogPilotVehiclesPanel *frogpilotVehiclesPanel = new FrogPilotVehiclesPanel(this, !shownDescriptions.value("FrogPilotVehiclesPanel").toBool(false));
FrogPilotVisualsPanel *frogpilotVisualsPanel = new FrogPilotVisualsPanel(this, !shownDescriptions.value("FrogPilotVisualsPanel").toBool(false));
FrogPilotWheelPanel *frogpilotWheelPanel = new FrogPilotWheelPanel(this, !shownDescriptions.value("FrogPilotWheelPanel").toBool(false));
std::vector<std::vector<std::tuple<QString, QWidget*>>> panelButtons = {
{{tr("MANAGE"), frogpilotSoundsPanel}},
{{tr("DRIVING MODEL"), frogpilotModelPanel}, {tr("GAS / BRAKE"), frogpilotLongitudinalPanel}, {tr("STEERING"), frogpilotLateralPanel}},
{{tr("MAP DATA"), frogpilotMapsPanel}, {tr("NAVIGATION"), frogpilotNavigationPanel}},
{{tr("DATA"), frogpilotDataPanel}, {tr("DEVICE CONTROLS"), frogpilotDevicePanel}, {tr("UTILITIES"), frogpilotUtilitiesPanel}},
{{tr("APPEARANCE"), frogpilotVisualsPanel}, {tr("THEME"), frogpilotThemesPanel}},
{{tr("VEHICLE SETTINGS"), frogpilotVehiclesPanel}, {tr("WHEEL CONTROLS"), frogpilotWheelPanel}}
};
std::vector<std::tuple<QString, QString, QString>> panelInfo = {
{tr("Alerts and Sounds"), tr("<b>Adjust alert volumes and enable custom notifications.</b>"), "../../frogpilot/assets/toggle_icons/icon_sound.png"},
{tr("Driving Controls"), tr("<b>Fine-tune custom FrogPilot acceleration, braking, and steering controls.</b>"), "../../frogpilot/assets/toggle_icons/icon_steering.png"},
{tr("Navigation"), tr("<b>Download map data for the \"Speed Limit Controller\".</b>"), "../../frogpilot/assets/toggle_icons/icon_navigate.png"},
{tr("System Settings"), tr("<b>Manage backups, device settings, screen options, storage, and tools to keep FrogPilot running smoothly.</b>"), "../../frogpilot/assets/toggle_icons/icon_system.png"},
{tr("Theme and Appearance"), tr("<b>Customize the look of the driving screen and interface, including themes!</b>"), "../../frogpilot/assets/toggle_icons/icon_display.png"},
{tr("Vehicle Settings"), tr("<b>Configure car-specific options and steering wheel button mappings.</b>"), "../../frogpilot/assets/toggle_icons/icon_vehicle.png"}
};
for (size_t i = 0; i < panelInfo.size(); ++i) {
const QString &title = std::get<0>(panelInfo[i]);
const QString &description = std::get<1>(panelInfo[i]);
const QString &icon = std::get<2>(panelInfo[i]);
const std::vector<std::tuple<QString, QWidget*>> &widgetLabels = panelButtons[i];
std::vector<QString> labels;
std::vector<QWidget*> widgets;
for (size_t j = 0; j < widgetLabels.size(); ++j) {
labels.push_back(std::get<0>(widgetLabels[j]));
QWidget *panel = std::get<1>(widgetLabels[j]);
panel->setContentsMargins(50, 25, 50, 25);
ScrollView *panelFrame = new ScrollView(panel, this);
mainLayout->addWidget(panelFrame);
widgets.push_back(panelFrame);
}
FrogPilotButtonsControl *panelButton = new FrogPilotButtonsControl(title, description, icon, labels);
if (title == tr("Alerts and Sounds")) soundPanelButtons = panelButton;
if (title == tr("Driving Controls")) drivingPanelButtons = panelButton;
if (title == tr("Navigation")) navigationPanelButtons = panelButton;
if (title == tr("System Settings")) systemPanelButtons = panelButton;
if (title == tr("Theme and Appearance")) themePanelButtons = panelButton;
if (title == tr("Vehicle Settings")) vehiclePanelButtons = panelButton;
if (forceOpenDescriptions) {
panelButton->showDescription();
}
QObject::connect(panelButton, &FrogPilotButtonsControl::buttonClicked, [widgets, this](int id) {
mainLayout->setCurrentWidget(widgets[id]);
panelOpen = true;
openPanel();
ScrollView *panelFrame = qobject_cast<ScrollView*>(widgets[id]);
if (panelFrame) {
QWidget *panel = panelFrame->widget();
QString className = panel->metaObject()->className();
if (!shownDescriptions.value(className).toBool(false)) {
shownDescriptions.insert(className, true);
params.putNonBlocking("ShownToggleDescriptions", QJsonDocument(shownDescriptions).toJson(QJsonDocument::Compact).toStdString());
}
}
});
list->addItem(panelButton);
}
QObject::connect(frogpilotDataPanel, &FrogPilotDataPanel::openSubPanel, this, &FrogPilotSettingsWindow::openSubPanel);
QObject::connect(frogpilotDevicePanel, &FrogPilotDevicePanel::openSubPanel, this, &FrogPilotSettingsWindow::openSubPanel);
QObject::connect(frogpilotLateralPanel, &FrogPilotLateralPanel::openSubPanel, this, &FrogPilotSettingsWindow::openSubPanel);
QObject::connect(frogpilotLongitudinalPanel, &FrogPilotLongitudinalPanel::openSubPanel, this, &FrogPilotSettingsWindow::openSubPanel);
QObject::connect(frogpilotLongitudinalPanel, &FrogPilotLongitudinalPanel::openSubSubPanel, this, &FrogPilotSettingsWindow::openSubSubPanel);
QObject::connect(frogpilotLongitudinalPanel, &FrogPilotLongitudinalPanel::openSubSubSubPanel, this, &FrogPilotSettingsWindow::openSubSubSubPanel);
QObject::connect(frogpilotMapsPanel, &FrogPilotMapsPanel::openSubPanel, this, &FrogPilotSettingsWindow::openSubPanel);
QObject::connect(frogpilotModelPanel, &FrogPilotModelPanel::openSubPanel, this, &FrogPilotSettingsWindow::openSubPanel);
QObject::connect(frogpilotNavigationPanel, &FrogPilotNavigationPanel::closeSubPanel, this, &FrogPilotSettingsWindow::closeSubPanel);
QObject::connect(frogpilotNavigationPanel, &FrogPilotNavigationPanel::openSubPanel, this, &FrogPilotSettingsWindow::openSubPanel);
QObject::connect(frogpilotSoundsPanel, &FrogPilotSoundsPanel::openSubPanel, this, &FrogPilotSettingsWindow::openSubPanel);
QObject::connect(frogpilotThemesPanel, &FrogPilotThemesPanel::openSubPanel, this, &FrogPilotSettingsWindow::openSubPanel);
QObject::connect(frogpilotVehiclesPanel, &FrogPilotVehiclesPanel::openSubPanel, this, &FrogPilotSettingsWindow::openSubPanel);
QObject::connect(frogpilotVisualsPanel, &FrogPilotVisualsPanel::openSubPanel, this, &FrogPilotSettingsWindow::openSubPanel);
QObject::connect(frogpilotVisualsPanel, &FrogPilotVisualsPanel::openSubSubPanel, this, &FrogPilotSettingsWindow::openSubSubPanel);
}
FrogPilotSettingsWindow::FrogPilotSettingsWindow(SettingsWindow *parent) : QFrame(parent) {
shownDescriptions = QJsonDocument::fromJson(QString::fromStdString(params.get("ShownToggleDescriptions")).toUtf8()).object();
QString className = this->metaObject()->className();
if (!shownDescriptions.value(className).toBool(false)) {
forceOpenDescriptions = true;
}
mainLayout = new QStackedLayout(this);
QWidget *frogpilotWidget = new QWidget(this);
QVBoxLayout *frogpilotLayout = new QVBoxLayout(frogpilotWidget);
frogpilotLayout->setContentsMargins(50, 25, 50, 25);
frogpilotWidget->setLayout(frogpilotLayout);
frogpilotPanel = new ScrollView(frogpilotWidget, this);
mainLayout->addWidget(frogpilotPanel);
frogpilotPanel->setWidget(frogpilotWidget);
FrogPilotListWidget *list = new FrogPilotListWidget(this);
frogpilotLayout->addWidget(list);
std::vector<QString> togglePresets{tr("Minimal"), tr("Standard"), tr("Advanced"), tr("Developer")};
togglePreset = new FrogPilotButtonsControl(tr("Tuning Level"),
tr("Choose your tuning level. Lower levels keep it simple; higher levels unlock more toggles for finer control.\n\n"
"Minimal - Ideal for those who prefer simplicity or ease of use\n"
"Standard - Recommended for most users for a balanced experience\n"
"Advanced - Fine-tuning for experienced users\n"
"Developer - Highly customizable settings for seasoned enthusiasts"),
"../../frogpilot/assets/toggle_icons/icon_tuning.png", togglePresets, true);
QObject::connect(togglePreset, &FrogPilotButtonsControl::buttonClicked, [this](int id) {
tuningLevel = id;
params.putIntNonBlocking("TuningLevel", tuningLevel);
updateVariables();
emit tuningLevelChanged(tuningLevel);
if (id == 3) {
ConfirmationDialog::alert(tr("WARNING: These settings are risky and can drastically change how openpilot drives. Only change if you fully understand what they do!"), this);
}
});
togglePreset->setCheckedButton(params.getInt("TuningLevel"));
if (forceOpenDescriptions) {
togglePreset->showDescription();
}
list->addItem(togglePreset, true);
createPanelButtons(list);
QObject::connect(parent, &SettingsWindow::closePanel, this, &FrogPilotSettingsWindow::closePanel);
QObject::connect(parent, &SettingsWindow::closeSubPanel, this, &FrogPilotSettingsWindow::closeSubPanel);
QObject::connect(parent, &SettingsWindow::closeSubSubPanel, this, &FrogPilotSettingsWindow::closeSubSubPanel);
QObject::connect(parent, &SettingsWindow::closeSubSubSubPanel, this, &FrogPilotSettingsWindow::closeSubSubSubPanel);
QObject::connect(parent, &SettingsWindow::updateMetric, this, &FrogPilotSettingsWindow::updateMetric);
QObject::connect(parent, &SettingsWindow::updateTuningLevel, this, &FrogPilotSettingsWindow::updateTuningLevel);
QObject::connect(uiState(), &UIState::offroadTransition, this, &FrogPilotSettingsWindow::updateVariables);
QObject::connect(uiState(), &UIState::uiUpdate, this, &FrogPilotSettingsWindow::updateState);
std::vector<std::string> keys = params.allKeys();
for (std::vector<std::string>::const_iterator it = keys.begin(); it != keys.end(); ++it) {
const std::string &key = *it;
frogpilotToggleLevels[QString::fromStdString(key)] = params.getTuningLevel(key);
}
tuningLevel = params.getInt("TuningLevel");
closeSubPanel();
updateMetric(params.getBool("IsMetric"), true);
updateVariables();
}
void FrogPilotSettingsWindow::updateTuningLevel() {
tuningLevel = params.getInt("TuningLevel");
togglePreset->setCheckedButton(params.getInt("TuningLevel"));
updateVariables();
emit tuningLevelChanged(tuningLevel);
}
void FrogPilotSettingsWindow::showEvent(QShowEvent *event) {
static bool alertShown = false;
QString className = this->metaObject()->className();
if (!shownDescriptions.value(className).toBool(false)) {
shownDescriptions.insert(className, true);
params.putNonBlocking("ShownToggleDescriptions", QJsonDocument(shownDescriptions).toJson(QJsonDocument::Compact).toStdString());
}
if (forceOpenDescriptions) {
togglePreset->showDescription();
drivingPanelButtons->showDescription();
navigationPanelButtons->showDescription();
soundPanelButtons->showDescription();
systemPanelButtons->showDescription();
themePanelButtons->showDescription();
vehiclePanelButtons->showDescription();
if (!alertShown) {
ConfirmationDialog::alert(tr("All toggle descriptions are currently expanded. You can tap a toggle's name to open or close its description at any time!"), this);
alertShown = true;
}
}
}
void FrogPilotSettingsWindow::hideEvent(QHideEvent *event) {
closePanel();
}
void FrogPilotSettingsWindow::closePanel() {
if (forceOpenDescriptions) {
togglePreset->showDescription();
drivingPanelButtons->showDescription();
navigationPanelButtons->showDescription();
soundPanelButtons->showDescription();
systemPanelButtons->showDescription();
themePanelButtons->showDescription();
vehiclePanelButtons->showDescription();
}
mainLayout->setCurrentWidget(frogpilotPanel);
panelOpen = false;
}
void FrogPilotSettingsWindow::updateState() {
FrogPilotUIState &fs = *frogpilotUIState();
FrogPilotUIScene &frogpilot_scene = fs.frogpilot_scene;
frogpilot_scene.frogpilot_panel_active = panelOpen && keepScreenOn;
}
void FrogPilotSettingsWindow::updateVariables() {
FrogPilotUIState &fs = *frogpilotUIState();
FrogPilotUIScene &frogpilot_scene = fs.frogpilot_scene;
QJsonObject &frogpilot_toggles = frogpilot_scene.frogpilot_toggles;
isFrogsGoMoo = ::isFrogsGoMoo();
std::string carParams = params.get("CarParamsPersistent");
if (!carParams.empty()) {
AlignedBuffer aligned_buf;
capnp::FlatArrayMessageReader cmsg(aligned_buf.align(carParams.data(), carParams.size()));
cereal::CarParams::Reader CP = cmsg.getRoot<cereal::CarParams>();
cereal::CarParams::SafetyModel safetyModel = CP.getSafetyConfigs()[0].getSafetyModel();
std::string carFingerprint = CP.getCarFingerprint();
carMake = CP.getBrand();
friction = CP.getLateralTuning().getTorque().getFriction();
hasAlphaLongitudinal = CP.getAlphaLongitudinalAvailable();
hasBSM = CP.getEnableBsm();
hasDashSpeedLimits = carMake == "ford" || carMake == "hyundai" || carMake == "toyota";
hasOpenpilotLongitudinal = hasLongitudinalControl(CP);
hasPCMCruise = CP.getPcmCruise();
hasPedal = CP.getEnableGasInterceptorDEPRECATED();
hasRadar = !CP.getRadarUnavailable();
hasSDSU = frogpilot_toggles.value("has_sdsu").toBool();
hasSNG = CP.getAutoResumeSng();
hasZSS = frogpilot_toggles.value("has_zss").toBool();
isAngleCar = CP.getSteerControlType() == cereal::CarParams::SteerControlType::ANGLE;
isBolt = carFingerprint == "CHEVROLET_BOLT_CC" || carFingerprint == "CHEVROLET_BOLT_EUV";
isGM = carMake == "gm";
isHKG = carMake == "hyundai";
isHKGCanFd = isHKG && safetyModel == cereal::CarParams::SafetyModel::HYUNDAI_CANFD;
isSubaru = carMake == "subaru";
isTorqueCar = CP.getLateralTuning().which() == cereal::CarParams::LateralTuning::TORQUE;
isToyota = carMake == "toyota";
isTSK = CP.getSecOcRequired();
isVolt = carFingerprint == "CHEVROLET_VOLT";
latAccelFactor = CP.getLateralTuning().getTorque().getLatAccelFactor();
lkasAllowedForAOL = frogpilot_toggles.value("lkas_allowed_for_aol").toBool();
longitudinalActuatorDelay = CP.getLongitudinalActuatorDelay();
startAccel = CP.getStartAccel();
steerActuatorDelay = CP.getSteerActuatorDelay();
steerKp = 1.0f;
steerRatio = CP.getSteerRatio();
stopAccel = CP.getStopAccel();
stoppingDecelRate = CP.getStoppingDecelRate();
vEgoStarting = CP.getVEgoStarting();
vEgoStopping = CP.getVEgoStopping();
float currentDelayStock = params.getFloat("SteerDelayStock");
float currentFrictionStock = params.getFloat("SteerFrictionStock");
float currentKPStock = params.getFloat("SteerKPStock");
float currentLatAccelStock = params.getFloat("SteerLatAccelStock");
float currentLongDelayStock = params.getFloat("LongitudinalActuatorDelayStock");
float currentStartAccelStock = params.getFloat("StartAccelStock");
float currentSteerRatioStock = params.getFloat("SteerRatioStock");
float currentStopAccelStock = params.getFloat("StopAccelStock");
float currentStoppingDecelRateStock = params.getFloat("StoppingDecelRateStock");
float currentVEgoStartingStock = params.getFloat("VEgoStartingStock");
float currentVEgoStoppingStock = params.getFloat("VEgoStoppingStock");
if (currentDelayStock != steerActuatorDelay && steerActuatorDelay != 0) {
if (params.getFloat("SteerDelay") == currentDelayStock || currentDelayStock == 0) {
params.putFloatNonBlocking("SteerDelay", steerActuatorDelay);
}
params.putFloatNonBlocking("SteerDelayStock", steerActuatorDelay);
}
if (currentFrictionStock != friction && friction != 0) {
if (params.getFloat("SteerFriction") == currentFrictionStock || currentFrictionStock == 0) {
params.putFloatNonBlocking("SteerFriction", friction);
}
params.putFloatNonBlocking("SteerFrictionStock", friction);
}
if (currentKPStock != steerKp && steerKp != 0) {
if (params.getFloat("SteerKP") == currentKPStock || currentKPStock == 0) {
params.putFloatNonBlocking("SteerKP", steerKp);
}
params.putFloatNonBlocking("SteerKPStock", steerKp);
}
if (currentLatAccelStock != latAccelFactor && latAccelFactor != 0) {
if (params.getFloat("SteerLatAccel") == currentLatAccelStock || currentLatAccelStock == 0) {
params.putFloatNonBlocking("SteerLatAccel", latAccelFactor);
}
params.putFloatNonBlocking("SteerLatAccelStock", latAccelFactor);
}
if (currentLongDelayStock != longitudinalActuatorDelay && longitudinalActuatorDelay != 0) {
if (params.getFloat("LongitudinalActuatorDelay") == currentLongDelayStock || currentLongDelayStock == 0) {
params.putFloatNonBlocking("LongitudinalActuatorDelay", longitudinalActuatorDelay);
}
params.putFloatNonBlocking("LongitudinalActuatorDelayStock", longitudinalActuatorDelay);
}
if (currentStartAccelStock != startAccel && startAccel != 0) {
if (params.getFloat("StartAccel") == currentStartAccelStock || currentStartAccelStock == 0) {
params.putFloatNonBlocking("StartAccel", startAccel);
}
params.putFloatNonBlocking("StartAccelStock", startAccel);
}
if (currentSteerRatioStock != steerRatio && steerRatio != 0) {
if (params.getFloat("SteerRatio") == currentSteerRatioStock || currentSteerRatioStock == 0) {
params.putFloatNonBlocking("SteerRatio", steerRatio);
}
params.putFloatNonBlocking("SteerRatioStock", steerRatio);
}
if (currentStopAccelStock != stopAccel && stopAccel != 0) {
if (params.getFloat("StopAccel") == currentStopAccelStock || currentStopAccelStock == 0) {
params.putFloatNonBlocking("StopAccel", stopAccel);
}
params.putFloatNonBlocking("StopAccelStock", stopAccel);
}
if (currentStoppingDecelRateStock != stoppingDecelRate && stoppingDecelRate != 0) {
if (params.getFloat("StoppingDecelRate") == currentStoppingDecelRateStock || currentStoppingDecelRateStock == 0) {
params.putFloatNonBlocking("StoppingDecelRate", stoppingDecelRate);
}
params.putFloatNonBlocking("StoppingDecelRateStock", stoppingDecelRate);
}
if (currentVEgoStartingStock != vEgoStarting && vEgoStarting != 0) {
if (params.getFloat("VEgoStarting") == currentVEgoStartingStock || currentVEgoStartingStock == 0) {
params.putFloatNonBlocking("VEgoStarting", vEgoStarting);
}
params.putFloatNonBlocking("VEgoStartingStock", vEgoStarting);
}
if (currentVEgoStoppingStock != vEgoStopping && vEgoStopping != 0) {
if (params.getFloat("VEgoStopping") == currentVEgoStoppingStock || currentVEgoStoppingStock == 0) {
params.putFloatNonBlocking("VEgoStopping", vEgoStopping);
}
params.putFloatNonBlocking("VEgoStoppingStock", vEgoStopping);
}
}
std::string frogpilotCarParams = params.get("FrogPilotCarParamsPersistent");
if (!frogpilotCarParams.empty()) {
AlignedBuffer aligned_buf;
capnp::FlatArrayMessageReader fpcmsg(aligned_buf.align(frogpilotCarParams.data(), frogpilotCarParams.size()));
cereal::FrogPilotCarParams::Reader FPCP = fpcmsg.getRoot<cereal::FrogPilotCarParams>();
canUsePedal = FPCP.getCanUsePedal();
canUseSDSU = FPCP.getCanUseSDSU();
openpilotLongitudinalControlDisabled = FPCP.getOpenpilotLongitudinalControlDisabled();
}
std::string liveTorqueParameters = params.get("LiveTorqueParameters");
if (!liveTorqueParameters.empty()) {
AlignedBuffer aligned_buf;
capnp::FlatArrayMessageReader reader(aligned_buf.align(liveTorqueParameters.data(), liveTorqueParameters.size()));
cereal::Event::Reader event = reader.getRoot<cereal::Event>();
cereal::LiveTorqueParametersData::Reader LTP = event.getLiveTorqueParameters();
hasAutoTune = LTP.getUseParams();
}
drivingPanelButtons->setVisibleButton(0, tuningLevel >= frogpilotToggleLevels.value("DrivingModel").toDouble());
drivingPanelButtons->setVisibleButton(1, hasOpenpilotLongitudinal);
systemPanelButtons->setVisibleButton(1, tuningLevel >= frogpilotToggleLevels.value("DeviceManagement").toDouble() || tuningLevel >= frogpilotToggleLevels.value("ScreenManagement").toDouble());
vehiclePanelButtons->setVisibleButton(1, tuningLevel >= frogpilotToggleLevels.value("WheelControls").toDouble());
update();
}
@@ -0,0 +1,103 @@
#pragma once
#include "selfdrive/ui/qt/offroad/settings.h"
#include "selfdrive/ui/qt/widgets/scrollview.h"
#include "frogpilot/ui/frogpilot_ui.h"
#include "frogpilot/ui/qt/widgets/frogpilot_controls.h"
class QNetworkAccessManager;
class FrogPilotSettingsWindow : public QFrame {
Q_OBJECT
public:
explicit FrogPilotSettingsWindow(SettingsWindow *parent);
void updateVariables();
bool canUsePedal = false;
bool canUseSDSU = false;
bool forceOpenDescriptions = false;
bool hasAlphaLongitudinal = false;
bool hasAutoTune = true;
bool hasBSM = true;
bool hasDashSpeedLimits = true;
bool hasNNFFLog = true;
bool hasOpenpilotLongitudinal = true;
bool hasPCMCruise = false;
bool hasPedal = false;
bool hasRadar = true;
bool hasSDSU = false;
bool hasSNG = false;
bool hasZSS = false;
bool isAngleCar = false;
bool isBolt = false;
bool isFrogsGoMoo = false;
bool isGM = true;
bool isHKG = true;
bool isHKGCanFd = true;
bool isSubaru = false;
bool isTorqueCar = false;
bool isToyota = true;
bool isTSK = false;
bool isVolt = true;
bool keepScreenOn = false;
bool lkasAllowedForAOL = false;
bool openpilotLongitudinalControlDisabled = false;
float friction;
float latAccelFactor;
float longitudinalActuatorDelay;
float startAccel;
float steerActuatorDelay;
float steerKp;
float steerRatio;
float stopAccel;
float stoppingDecelRate;
float vEgoStarting;
float vEgoStopping;
int tuningLevel;
QJsonObject frogpilotToggleLevels;
signals:
void closeSubPanel();
void closeSubSubPanel();
void closeSubSubSubPanel();
void openPanel();
void openSubPanel();
void openSubSubPanel();
void openSubSubSubPanel();
void tuningLevelChanged(int level);
void updateMetric(bool metric, bool bootRun=false);
private:
void closePanel();
void createPanelButtons(FrogPilotListWidget *list);
void hideEvent(QHideEvent *event) override;
void showEvent(QShowEvent *event) override;
void updateState();
void updateTuningLevel();
bool panelOpen;
std::string carMake;
FrogPilotButtonsControl *drivingPanelButtons;
FrogPilotButtonsControl *navigationPanelButtons;
FrogPilotButtonsControl *soundPanelButtons;
FrogPilotButtonsControl *systemPanelButtons;
FrogPilotButtonsControl *themePanelButtons;
FrogPilotButtonsControl *togglePreset;
FrogPilotButtonsControl *vehiclePanelButtons;
Params params;
QJsonObject shownDescriptions;
QStackedLayout *mainLayout;
ScrollView *frogpilotPanel;
};
+431
View File
@@ -0,0 +1,431 @@
#include "frogpilot/ui/qt/offroad/lateral_settings.h"
FrogPilotLateralPanel::FrogPilotLateralPanel(FrogPilotSettingsWindow *parent, bool forceOpen) : FrogPilotListWidget(parent), parent(parent) {
forceOpenDescriptions = forceOpen;
QStackedLayout *lateralLayout = new QStackedLayout();
addItem(lateralLayout);
FrogPilotListWidget *lateralList = new FrogPilotListWidget(this);
ScrollView *lateralPanel = new ScrollView(lateralList, this);
lateralLayout->addWidget(lateralPanel);
FrogPilotListWidget *advancedLateralTuneList = new FrogPilotListWidget(this);
FrogPilotListWidget *aolList = new FrogPilotListWidget(this);
FrogPilotListWidget *laneChangeList = new FrogPilotListWidget(this);
FrogPilotListWidget *lateralTuneList = new FrogPilotListWidget(this);
FrogPilotListWidget *qolList = new FrogPilotListWidget(this);
ScrollView *advancedLateralTunePanel = new ScrollView(advancedLateralTuneList, this);
ScrollView *aolPanel = new ScrollView(aolList, this);
ScrollView *laneChangePanel = new ScrollView(laneChangeList, this);
ScrollView *lateralTunePanel = new ScrollView(lateralTuneList, this);
ScrollView *qolPanel = new ScrollView(qolList, this);
lateralLayout->addWidget(advancedLateralTunePanel);
lateralLayout->addWidget(aolPanel);
lateralLayout->addWidget(laneChangePanel);
lateralLayout->addWidget(lateralTunePanel);
lateralLayout->addWidget(qolPanel);
const std::vector<std::tuple<QString, QString, QString, QString>> lateralToggles {
{"AdvancedLateralTune", tr("Advanced Lateral Tuning"), tr("<b>Advanced steering control changes to fine-tune how openpilot drives.</b>"), "../../frogpilot/assets/toggle_icons/icon_advanced_lateral_tune.png"},
{"SteerDelay", parent->steerActuatorDelay != 0 ? QString(tr("Actuator Delay (Default: %1)")).arg(QString::number(parent->steerActuatorDelay, 'f', 2)) : tr("Actuator Delay"), tr("<b>The time between openpilot's steering command and the vehicle's response.</b> Increase if the vehicle reacts late; decrease if it feels jumpy. Auto-learned by default."), ""},
{"SteerFriction", parent->friction != 0 ? QString(tr("Friction (Default: %1)")).arg(QString::number(parent->friction, 'f', 2)) : tr("Friction"), tr("<b>Compensates for steering friction.</b> Increase if the wheel sticks near center; decrease if it jitters. Auto-learned by default."), ""},
{"SteerKP", parent->steerKp != 0 ? QString(tr("Kp Factor (Default: %1)")).arg(QString::number(parent->steerKp, 'f', 2)) : tr("Kp Factor"), tr("<b>How strongly openpilot corrects lane position.</b> Higher is tighter but twitchier; lower is smoother but slower. Auto-learned by default."), ""},
{"SteerLatAccel", parent->latAccelFactor != 0 ? QString(tr("Lateral Acceleration (Default: %1)")).arg(QString::number(parent->latAccelFactor, 'f', 2)) : tr("Lateral Acceleration"), tr("<b>Maps steering torque to turning response.</b> Increase for sharper turns; decrease for gentler steering. Auto-learned by default."), ""},
{"SteerRatio", parent->steerRatio != 0 ? QString(tr("Steer Ratio (Default: %1)")).arg(QString::number(parent->steerRatio, 'f', 2)) : tr("Steer Ratio"), tr("<b>The relationship between steering wheel rotation and road wheel angle.</b> Increase if steering feels too quick or twitchy; decrease if it feels too slow or weak. Auto-learned by default."), ""},
{"ForceAutoTune", tr("Force Auto-Tune On"), tr("<b>Force-enable openpilot's live auto-tuning for \"Friction\" and \"Lateral Acceleration\".</b>"), ""},
{"ForceAutoTuneOff", tr("Force Auto-Tune Off"), tr("<b>Force-disable openpilot's live auto-tuning for \"Friction\" and \"Lateral Acceleration\" and use the set value instead.</b>"), ""},
{"ForceTorqueController", tr("Force Torque Controller"), tr("<b>Use torque-based steering control instead of angle-based control for smoother lane keeping, especially in curves.</b>"), ""},
{"AlwaysOnLateral", tr("Always On Lateral"), tr("<b>openpilot's steering remains active even when the accelerator or brake pedals are pressed.</b>"), "../../frogpilot/assets/toggle_icons/icon_always_on_lateral.png"},
{"AlwaysOnLateralLKAS", tr("Enable With LKAS"), tr("<b>Enable \"Always On Lateral\" whenever \"LKAS\" is on, even when openpilot is not engaged.</b>"), ""},
{"PauseAOLOnBrake", tr("Pause on Brake Press Below"), tr("<b>Pause \"Always On Lateral\" below the set speed while the brake pedal is pressed.</b>"), ""},
{"LaneChanges", tr("Lane Changes"), tr("<b>Allow openpilot to change lanes.</b>"), "../../frogpilot/assets/toggle_icons/icon_lane.png"},
{"NudgelessLaneChange", tr("Automatic Lane Changes"), tr("<b>When the turn signal is on, openpilot will automatically change lanes.</b> No steering-wheel nudge required!"), ""},
{"LaneChangeTime", tr("Lane Change Delay"), tr("<b>Delay between turn signal activation and the start of an automatic lane change.</b>"), ""},
{"MinimumLaneChangeSpeed", tr("Minimum Lane Change Speed"), tr("<b>Lowest speed at which openpilot will change lanes.</b>"), ""},
{"LaneDetectionWidth", tr("Minimum Lane Width"), tr("<b>Prevent automatic lane changes into lanes narrower than the set width.</b>"), ""},
{"OneLaneChange", tr("One Lane Change Per Signal"), tr("<b>Limit automatic lane changes to one per turn-signal activation.</b>"), ""},
{"LateralTune", tr("Lateral Tuning"), tr("<b>Miscellaneous steering control changes</b> to fine-tune how openpilot drives."), "../../frogpilot/assets/toggle_icons/icon_lateral_tune.png"},
{"TurnDesires", tr("Force Turn Desires Below Lane Change Speed"), tr("<b>While driving below the minimum lane change speed with an active turn signal, instruct openpilot to turn left/right.</b>"), ""},
{"NNFF", tr("Neural Network Feedforward (NNFF)"), tr("<b>Twilsonco's \"Neural Network FeedForward\" controller.</b> Uses a trained neural network model to predict steering torque based on vehicle speed, roll, and past/future planned path data for smoother, model-based steering."), ""},
{"NNFFLite", tr("Neural Network Feedforward (NNFF) Lite"), tr("<b>A lightweight version of Twilsonco's \"Neural Network FeedForward\" controller.</b> Uses the \"look-ahead\" planned lateral jerk logic from the full model to help smoothen steering adjustments in curves, but does not use the full neural network for torque calculation."), ""},
{"QOLLateral", tr("Quality of Life"), tr("<b>Steering control changes to fine-tune how openpilot drives.</b>"), "../../frogpilot/assets/toggle_icons/icon_quality_of_life.png"},
{"PauseLateralSpeed", tr("Pause Steering Below"), tr("<b>Pause steering below the set speed.</b>"), ""},
{"IgnoreMe", "Ignore Me", "This is simply used to fix the layout when the user opens the descriptions and the menu gets wonky. No idea why it happens, but I can't be asked to properly fix it so whatever. Sue me.", ""},
{"IgnoreMe2", "Ignore Me", "This is simply used to fix the layout when the user opens the descriptions and the menu gets wonky. No idea why it happens, but I can't be asked to properly fix it so whatever. Sue me.", ""}
};
for (const auto &[param, title, desc, icon] : lateralToggles) {
AbstractControl *lateralToggle;
if (param == "AdvancedLateralTune") {
FrogPilotManageControl *advancedLateralTuneToggle = new FrogPilotManageControl(param, title, desc, icon);
QObject::connect(advancedLateralTuneToggle, &FrogPilotManageControl::manageButtonClicked, [lateralLayout, advancedLateralTunePanel]() {
lateralLayout->setCurrentWidget(advancedLateralTunePanel);
});
lateralToggle = advancedLateralTuneToggle;
} else if (param == "SteerDelay") {
std::vector<QString> steerDelayButton{"Reset"};
lateralToggle = new FrogPilotParamValueButtonControl(param, title, desc, icon, 0.01, 1, QString(), std::map<float, QString>(), 0.01, false, {}, steerDelayButton, false, false);
} else if (param == "SteerFriction") {
std::vector<QString> steerFrictionButton{"Reset"};
lateralToggle = new FrogPilotParamValueButtonControl(param, title, desc, icon, 0, 1, QString(), std::map<float, QString>(), 0.01, false, {}, steerFrictionButton, false, false);
} else if (param == "SteerKP") {
std::vector<QString> steerKPButton{"Reset"};
lateralToggle = new FrogPilotParamValueButtonControl(param, title, desc, icon, parent->steerKp * 0.5, parent->steerKp * 1.5, QString(), std::map<float, QString>(), 0.01, false, {}, steerKPButton, false, false);
} else if (param == "SteerLatAccel") {
std::vector<QString> steerLatAccelButton{"Reset"};
lateralToggle = new FrogPilotParamValueButtonControl(param, title, desc, icon, parent->latAccelFactor * 0.5, parent->latAccelFactor * 1.5, QString(), std::map<float, QString>(), 0.01, false, {}, steerLatAccelButton, false, false);
} else if (param == "SteerRatio") {
std::vector<QString> steerRatioButton{"Reset"};
lateralToggle = new FrogPilotParamValueButtonControl(param, title, desc, icon, parent->steerRatio * 0.5, parent->steerRatio * 1.5, QString(), std::map<float, QString>(), 0.01, false, {}, steerRatioButton, false, false);
} else if (param == "AlwaysOnLateral") {
FrogPilotManageControl *aolToggle = new FrogPilotManageControl(param, title, desc, icon);
QObject::connect(aolToggle, &FrogPilotManageControl::manageButtonClicked, [lateralLayout, aolPanel]() {
lateralLayout->setCurrentWidget(aolPanel);
});
lateralToggle = aolToggle;
} else if (param == "PauseAOLOnBrake") {
lateralToggle = new FrogPilotParamValueControl(param, title, desc, icon, 0, 99, QString(), std::map<float, QString>(), 1, true);
} else if (param == "LaneChanges") {
FrogPilotManageControl *laneChangeToggle = new FrogPilotManageControl(param, title, desc, icon);
QObject::connect(laneChangeToggle, &FrogPilotManageControl::manageButtonClicked, [lateralLayout, laneChangePanel]() {
lateralLayout->setCurrentWidget(laneChangePanel);
});
lateralToggle = laneChangeToggle;
} else if (param == "LaneChangeTime") {
std::map<float, QString> laneChangeTimeLabels;
for (float i = 0; i <= 5; i += 0.1) {
laneChangeTimeLabels[i] = i == 0 ? tr("Instant") : std::lround(i / 0.1) == 1 / 0.1 ? QString::number(i, 'f', 1) + tr(" second") : QString::number(i, 'f', 1) + tr(" seconds");
}
lateralToggle = new FrogPilotParamValueControl(param, title, desc, icon, 0, 5, QString(), laneChangeTimeLabels, 0.1);
} else if (param == "LaneDetectionWidth") {
lateralToggle = new FrogPilotParamValueControl(param, title, desc, icon, 0, 15, QString(), std::map<float, QString>(), 0.1, true);
} else if (param == "MinimumLaneChangeSpeed") {
lateralToggle = new FrogPilotParamValueControl(param, title, desc, icon, 0, 99, QString(), std::map<float, QString>(), 1, true);
} else if (param == "LateralTune") {
FrogPilotManageControl *lateralTuneToggle = new FrogPilotManageControl(param, title, desc, icon);
QObject::connect(lateralTuneToggle, &FrogPilotManageControl::manageButtonClicked, [lateralLayout, lateralTunePanel]() {
lateralLayout->setCurrentWidget(lateralTunePanel);
});
lateralToggle = lateralTuneToggle;
} else if (param == "QOLLateral") {
FrogPilotManageControl *qolLateralToggle = new FrogPilotManageControl(param, title, desc, icon);
QObject::connect(qolLateralToggle, &FrogPilotManageControl::manageButtonClicked, [lateralLayout, qolPanel]() {
lateralLayout->setCurrentWidget(qolPanel);
});
lateralToggle = qolLateralToggle;
} else if (param == "PauseLateralSpeed") {
std::vector<QString> pauseLateralToggles{"PauseLateralOnSignal"};
std::vector<QString> pauseLateralToggleNames{tr("Turn Signal Only")};
lateralToggle = new FrogPilotParamValueButtonControl(param, title, desc, icon, 0, 99, QString(), std::map<float, QString>(), 1, true, pauseLateralToggles, pauseLateralToggleNames, true);
} else {
lateralToggle = new ParamControl(param, title, desc, icon);
}
toggles[param] = lateralToggle;
if (advancedLateralTuneKeys.contains(param)) {
advancedLateralTuneList->addItem(lateralToggle);
} else if (aolKeys.contains(param)) {
aolList->addItem(lateralToggle);
} else if (laneChangeKeys.contains(param)) {
laneChangeList->addItem(lateralToggle);
} else if (lateralTuneKeys.contains(param)) {
lateralTuneList->addItem(lateralToggle);
} else if (qolKeys.contains(param)) {
qolList ->addItem(lateralToggle);
} else {
lateralList->addItem(lateralToggle);
parentKeys.insert(param);
}
if (FrogPilotManageControl *frogPilotManageToggle = qobject_cast<FrogPilotManageControl*>(lateralToggle)) {
QObject::connect(frogPilotManageToggle, &FrogPilotManageControl::manageButtonClicked, [this]() {
emit openSubPanel();
openDescriptions(forceOpenDescriptions, toggles);
});
}
QObject::connect(lateralToggle, &AbstractControl::hideDescriptionEvent, [this]() {
update();
});
QObject::connect(lateralToggle, &AbstractControl::showDescriptionEvent, [this]() {
update();
});
}
QSet<QString> forceUpdateKeys = {"ForceAutoTune", "ForceAutoTuneOff", "LateralTune", "NNFF", "NudgelessLaneChange"};
for (const QString &key : forceUpdateKeys) {
QObject::connect(static_cast<ToggleControl*>(toggles[key]), &ToggleControl::toggleFlipped, this, &FrogPilotLateralPanel::updateToggles);
}
QSet<QString> rebootKeys = {"AlwaysOnLateral", "ForceTorqueController", "NNFF", "NNFFLite"};
for (const QString &key : rebootKeys) {
QObject::connect(static_cast<ToggleControl*>(toggles[key]), &ToggleControl::toggleFlipped, [key, this](bool state) {
if (started) {
if (key == "AlwaysOnLateral" && state) {
if (FrogPilotConfirmationDialog::toggleReboot(this)) {
Hardware::reboot();
}
} else if (key != "AlwaysOnLateral") {
if (FrogPilotConfirmationDialog::toggleReboot(this)) {
Hardware::reboot();
}
}
}
});
}
steerDelayToggle = static_cast<FrogPilotParamValueButtonControl*>(toggles["SteerDelay"]);
QObject::connect(steerDelayToggle, &FrogPilotParamValueButtonControl::buttonClicked, [parent, this]() {
if (FrogPilotConfirmationDialog::yesorno(tr("Reset <b>Actuator Delay</b> to its default value?"), this)) {
params.putFloat("SteerDelay", parent->steerActuatorDelay);
steerDelayToggle->refresh();
}
});
steerFrictionToggle = static_cast<FrogPilotParamValueButtonControl*>(toggles["SteerFriction"]);
QObject::connect(steerFrictionToggle, &FrogPilotParamValueButtonControl::buttonClicked, [parent, this]() {
if (FrogPilotConfirmationDialog::yesorno(tr("Reset <b>Friction</b> to its default value?"), this)) {
params.putFloat("SteerFriction", parent->friction);
steerFrictionToggle->refresh();
}
});
steerKPToggle = static_cast<FrogPilotParamValueButtonControl*>(toggles["SteerKP"]);
QObject::connect(steerKPToggle, &FrogPilotParamValueButtonControl::buttonClicked, [parent, this]() {
if (FrogPilotConfirmationDialog::yesorno(tr("Reset <b>Kp Factor</b> to its default value?"), this)) {
params.putFloat("SteerKP", parent->steerKp);
steerKPToggle->refresh();
}
});
steerLatAccelToggle = static_cast<FrogPilotParamValueButtonControl*>(toggles["SteerLatAccel"]);
QObject::connect(steerLatAccelToggle, &FrogPilotParamValueButtonControl::buttonClicked, [parent, this]() {
if (FrogPilotConfirmationDialog::yesorno(tr("Reset <b>Lateral Accel</b> to its default value?"), this)) {
params.putFloat("SteerLatAccel", parent->latAccelFactor);
steerLatAccelToggle->refresh();
}
});
steerRatioToggle = static_cast<FrogPilotParamValueButtonControl*>(toggles["SteerRatio"]);
QObject::connect(steerRatioToggle, &FrogPilotParamValueButtonControl::buttonClicked, [parent, this]() {
if (FrogPilotConfirmationDialog::yesorno(tr("Reset <b>Steer Ratio</b> to its default value?"), this)) {
params.putFloat("SteerRatio", parent->steerRatio);
steerRatioToggle->refresh();
}
});
openDescriptions(forceOpenDescriptions, toggles);
QObject::connect(parent, &FrogPilotSettingsWindow::closeSubPanel, [lateralLayout, lateralPanel, this] {
openDescriptions(forceOpenDescriptions, toggles);
lateralLayout->setCurrentWidget(lateralPanel);
});
QObject::connect(parent, &FrogPilotSettingsWindow::updateMetric, this, &FrogPilotLateralPanel::updateMetric);
QObject::connect(uiState(), &UIState::uiUpdate, this, &FrogPilotLateralPanel::updateState);
}
void FrogPilotLateralPanel::showEvent(QShowEvent *event) {
steerDelayToggle->setTitle(QString(tr("Actuator Delay (Default: %1)")).arg(QString::number(parent->steerActuatorDelay, 'f', 2)));
steerFrictionToggle->setTitle(QString(tr("Friction (Default: %1)")).arg(QString::number(parent->friction, 'f', 2)));
steerKPToggle->setTitle(QString(tr("Kp Factor (Default: %1)")).arg(QString::number(parent->steerKp, 'f', 2)));
steerKPToggle->updateControl(parent->steerKp * 0.5, parent->steerKp * 1.5);
steerLatAccelToggle->setTitle(QString(tr("Lateral Accel (Default: %1)")).arg(QString::number(parent->latAccelFactor, 'f', 2)));
steerLatAccelToggle->updateControl(parent->latAccelFactor * 0.5, parent->latAccelFactor * 1.5);
steerRatioToggle->setTitle(QString(tr("Steer Ratio (Default: %1)")).arg(QString::number(parent->steerRatio, 'f', 2)));
steerRatioToggle->updateControl(parent->steerRatio * 0.5, parent->steerRatio * 1.5);
updateToggles();
}
void FrogPilotLateralPanel::updateState(const UIState &s) {
if (!isVisible()) return;
started = s.scene.started;
}
void FrogPilotLateralPanel::updateMetric(bool metric, bool bootRun) {
static bool previousMetric;
if (metric != previousMetric && !bootRun) {
double distanceConversion = metric ? FOOT_TO_METER : METER_TO_FOOT;
double speedConversion = metric ? MILE_TO_KM : KM_TO_MILE;
params.putFloatNonBlocking("LaneDetectionWidth", params.getFloat("LaneDetectionWidth") * distanceConversion);
params.putIntNonBlocking("MinimumLaneChangeSpeed", params.getInt("MinimumLaneChangeSpeed") * speedConversion);
params.putIntNonBlocking("PauseAOLOnBrake", params.getInt("PauseAOLOnBrake") * speedConversion);
params.putIntNonBlocking("PauseLateralSpeed", params.getInt("PauseLateralSpeed") * speedConversion);
}
previousMetric = metric;
static std::map<float, QString> imperialDistanceLabels;
static std::map<float, QString> imperialSpeedLabels;
static std::map<float, QString> metricDistanceLabels;
static std::map<float, QString> metricSpeedLabels;
static bool labelsInitialized = false;
if (!labelsInitialized) {
for (int i = 0; i <= 150; ++i) {
float key = i / 10.0f;
imperialDistanceLabels[key] = key == 0 ? tr("Off") : i == 1 ? QString::number(i) + tr(" foot") : QString::number(key, 'f', 1) + tr(" feet");
}
for (int i = 0; i <= 99; ++i) {
imperialSpeedLabels[i] = i == 0 ? tr("Off") : QString::number(i) + tr(" mph");
}
for (int i = 0; i <= 50; ++i) {
float key = i / 10.0f;
metricDistanceLabels[key] = key == 0 ? tr("Off") : i == 1 ? QString::number(i) + tr(" meter") : QString::number(key, 'f', 1) + tr(" meters");
}
for (int i = 0; i <= 150; ++i) {
metricSpeedLabels[i] = i == 0 ? tr("Off") : QString::number(i) + tr(" km/h");
}
labelsInitialized = true;
}
FrogPilotParamValueControl *laneWidthToggle = static_cast<FrogPilotParamValueControl*>(toggles["LaneDetectionWidth"]);
FrogPilotParamValueControl *minimumLaneChangeSpeedToggle = static_cast<FrogPilotParamValueControl*>(toggles["MinimumLaneChangeSpeed"]);
FrogPilotParamValueControl *pauseAOLOnBrakeToggle = static_cast<FrogPilotParamValueControl*>(toggles["PauseAOLOnBrake"]);
FrogPilotParamValueControl *pauseLateralToggle = static_cast<FrogPilotParamValueControl*>(toggles["PauseLateralSpeed"]);
if (metric) {
laneWidthToggle->updateControl(0, 5, metricDistanceLabels);
minimumLaneChangeSpeedToggle->updateControl(0, 150, metricSpeedLabels);
pauseAOLOnBrakeToggle->updateControl(0, 150, metricSpeedLabels);
pauseLateralToggle->updateControl(0, 150, metricSpeedLabels);
} else {
laneWidthToggle->updateControl(0, 15, imperialDistanceLabels);
minimumLaneChangeSpeedToggle->updateControl(0, 99, imperialSpeedLabels);
pauseAOLOnBrakeToggle->updateControl(0, 99, imperialSpeedLabels);
pauseLateralToggle->updateControl(0, 99, imperialSpeedLabels);
}
}
void FrogPilotLateralPanel::updateToggles() {
for (auto &[key, toggle] : toggles) {
if (parentKeys.contains(key)) {
toggle->setVisible(false);
}
}
bool forcingAutoTune = !parent->hasAutoTune && params.getBool("ForceAutoTune");
bool forcingAutoTuneOff = parent->hasAutoTune && params.getBool("ForceAutoTuneOff");
bool forcingTorqueController = !parent->isAngleCar && params.getBool("ForceTorqueController");
bool usingNNFF = parent->hasNNFFLog && params.getBool("LateralTune") && params.getBool("NNFF");
for (auto &[key, toggle] : toggles) {
if (parentKeys.contains(key)) {
continue;
}
bool setVisible = parent->tuningLevel >= parent->frogpilotToggleLevels[key].toDouble();
if (key == "AlwaysOnLateralLKAS") {
setVisible &= parent->lkasAllowedForAOL;
}
else if (key == "ForceAutoTune") {
setVisible &= !parent->hasAutoTune;
setVisible &= !parent->isAngleCar;
setVisible &= parent->isTorqueCar || forcingTorqueController || usingNNFF;
}
else if (key == "ForceAutoTuneOff") {
setVisible &= parent->hasAutoTune;
}
else if (key == "ForceTorqueController") {
setVisible &= !parent->isAngleCar;
setVisible &= !parent->isTorqueCar;
}
else if (key == "LaneChangeTime") {
setVisible &= params.getBool("LaneChanges") && params.getBool("NudgelessLaneChange");
}
else if (key == "LaneDetectionWidth") {
setVisible &= params.getBool("LaneChanges") && params.getBool("NudgelessLaneChange");
}
else if (key == "NNFF") {
setVisible &= parent->hasNNFFLog;
setVisible &= !parent->isAngleCar;
}
else if (key == "NNFFLite") {
setVisible &= !usingNNFF;
setVisible &= !parent->isAngleCar;
}
else if (key == "SteerDelay") {
setVisible &= parent->steerActuatorDelay != 0;
}
else if (key == "SteerFriction") {
setVisible &= parent->friction != 0;
setVisible &= parent->hasAutoTune ? forcingAutoTuneOff : !forcingAutoTune;
setVisible &= parent->isTorqueCar || forcingTorqueController || usingNNFF;
setVisible &= !usingNNFF;
}
else if (key == "SteerKP") {
setVisible &= parent->steerKp != 0;
setVisible &= parent->isTorqueCar || forcingTorqueController || usingNNFF;
setVisible &= !parent->isAngleCar;
}
else if (key == "SteerLatAccel") {
setVisible &= parent->latAccelFactor != 0;
setVisible &= parent->hasAutoTune ? forcingAutoTuneOff : !forcingAutoTune;
setVisible &= parent->isTorqueCar || forcingTorqueController || usingNNFF;
setVisible &= !usingNNFF;
}
else if (key == "SteerRatio") {
setVisible &= parent->steerRatio != 0;
setVisible &= parent->hasAutoTune ? forcingAutoTuneOff : !forcingAutoTune;
}
toggle->setVisible(setVisible);
if (setVisible) {
if (advancedLateralTuneKeys.contains(key)) {
toggles["AdvancedLateralTune"]->setVisible(true);
} else if (aolKeys.contains(key)) {
toggles["AlwaysOnLateral"]->setVisible(true);
} else if (laneChangeKeys.contains(key)) {
toggles["LaneChanges"]->setVisible(true);
} else if (lateralTuneKeys.contains(key)) {
toggles["LateralTune"]->setVisible(true);
} else if (qolKeys.contains(key)) {
toggles["QOLLateral"]->setVisible(true);
}
}
}
openDescriptions(forceOpenDescriptions, toggles);
update();
}
@@ -0,0 +1,44 @@
#pragma once
#include "frogpilot/ui/qt/offroad/frogpilot_settings.h"
class FrogPilotLateralPanel : public FrogPilotListWidget {
Q_OBJECT
public:
explicit FrogPilotLateralPanel(FrogPilotSettingsWindow *parent, bool forceOpen = false);
signals:
void openSubPanel();
protected:
void showEvent(QShowEvent *event) override;
private:
void updateMetric(bool metric, bool bootRun);
void updateState(const UIState &s);
void updateToggles();
bool forceOpenDescriptions;
bool started;
std::map<QString, AbstractControl*> toggles;
QSet<QString> advancedLateralTuneKeys = {"ForceAutoTune", "ForceAutoTuneOff", "ForceTorqueController", "SteerDelay", "SteerFriction", "SteerLatAccel", "SteerKP", "SteerRatio"};
QSet<QString> aolKeys = {"AlwaysOnLateralLKAS", "PauseAOLOnBrake"};
QSet<QString> laneChangeKeys = {"LaneChangeTime", "LaneDetectionWidth", "MinimumLaneChangeSpeed", "NudgelessLaneChange", "OneLaneChange"};
QSet<QString> lateralTuneKeys = {"NNFF", "NNFFLite", "TurnDesires"};
QSet<QString> qolKeys = {"PauseLateralSpeed"};
QSet<QString> parentKeys;
FrogPilotParamValueButtonControl *steerDelayToggle;
FrogPilotParamValueButtonControl *steerFrictionToggle;
FrogPilotParamValueButtonControl *steerLatAccelToggle;
FrogPilotParamValueButtonControl *steerKPToggle;
FrogPilotParamValueButtonControl *steerRatioToggle;
FrogPilotSettingsWindow *parent;
Params params;
};
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,70 @@
#pragma once
#include "frogpilot/ui/qt/offroad/frogpilot_settings.h"
class FrogPilotLongitudinalPanel : public FrogPilotListWidget {
Q_OBJECT
public:
explicit FrogPilotLongitudinalPanel(FrogPilotSettingsWindow *parent, bool forceOpen = false);
signals:
void openSubPanel();
void openSubSubPanel();
void openSubSubSubPanel();
protected:
void showEvent(QShowEvent *event) override;
private:
void updateMetric(bool metric, bool bootRun);
void updateToggles();
bool customPersonalityOpen;
bool forceOpenDescriptions;
bool qolOpen;
bool slcOpen;
bool weatherOpen;
std::map<QString, AbstractControl*> toggles;
QSet<QString> advancedLongitudinalTuneKeys = {"LongitudinalActuatorDelay", "MaxDesiredAcceleration", "StartAccel", "StopAccel", "StoppingDecelRate", "VEgoStarting", "VEgoStopping"};
QSet<QString> aggressivePersonalityKeys = {"AggressiveFollow", "AggressiveJerkAcceleration", "AggressiveJerkDeceleration", "AggressiveJerkDanger", "AggressiveJerkSpeed", "AggressiveJerkSpeedDecrease", "ResetAggressivePersonality"};
QSet<QString> conditionalExperimentalKeys = {"CESpeed", "CESpeedLead", "CECurves", "CELead", "CEModelStopTime", "CESignalSpeed", "CEStopLights", "ShowCEMStatus"};
QSet<QString> curveSpeedKeys = {"CalibratedLateralAcceleration", "CalibrationProgress", "ResetCurveData", "ShowCSCStatus"};
QSet<QString> customDrivingPersonalityKeys = {"AggressivePersonalityProfile", "RelaxedPersonalityProfile", "StandardPersonalityProfile", "TrafficPersonalityProfile"};
QSet<QString> longitudinalTuneKeys = {"AccelerationProfile", "DecelerationProfile", "HumanAcceleration", "HumanFollowing", "HumanLaneChanges", "LeadDetectionThreshold", "TacoTune"};
QSet<QString> qolKeys = {"CustomCruise", "CustomCruiseLong", "ForceStops", "IncreasedStoppedDistance", "MapGears", "ReverseCruise", "SetSpeedOffset", "WeatherPresets"};
QSet<QString> relaxedPersonalityKeys = {"RelaxedFollow", "RelaxedJerkAcceleration", "RelaxedJerkDeceleration", "RelaxedJerkDanger", "RelaxedJerkSpeed", "RelaxedJerkSpeedDecrease", "ResetRelaxedPersonality"};
QSet<QString> speedLimitControllerKeys = {"SLCOffsets", "SLCFallback", "SLCOverride", "SLCPriority", "SLCQOL", "SLCVisuals"};
QSet<QString> speedLimitControllerOffsetsKeys = {"Offset1", "Offset2", "Offset3", "Offset4", "Offset5", "Offset6", "Offset7"};
QSet<QString> speedLimitControllerQOLKeys = {"SetSpeedLimit", "SLCConfirmation", "SLCLookaheadHigher", "SLCLookaheadLower", "SLCMapboxFiller"};
QSet<QString> speedLimitControllerVisualKeys = {"ShowSLCOffset", "SpeedLimitSources"};
QSet<QString> standardPersonalityKeys = {"StandardFollow", "StandardJerkAcceleration", "StandardJerkDeceleration", "StandardJerkDanger", "StandardJerkSpeed", "StandardJerkSpeedDecrease", "ResetStandardPersonality"};
QSet<QString> trafficPersonalityKeys = {"TrafficFollow", "TrafficJerkAcceleration", "TrafficJerkDeceleration", "TrafficJerkDanger", "TrafficJerkSpeed", "TrafficJerkSpeedDecrease", "ResetTrafficPersonality"};
QSet<QString> weatherKeys = {"LowVisibilityOffsets", "RainOffsets", "RainStormOffsets", "SetWeatherKey", "SnowOffsets"};
QSet<QString> weatherLowVisibilityKeys = {"IncreaseFollowingLowVisibility", "IncreasedStoppedDistanceLowVisibility", "ReduceAccelerationLowVisibility", "ReduceLateralAccelerationLowVisibility"};
QSet<QString> weatherRainKeys = {"IncreaseFollowingRain", "IncreasedStoppedDistanceRain", "ReduceAccelerationRain", "ReduceLateralAccelerationRain"};
QSet<QString> weatherRainStormKeys = {"IncreaseFollowingRainStorm", "IncreasedStoppedDistanceRainStorm", "ReduceAccelerationRainStorm", "ReduceLateralAccelerationRainStorm"};
QSet<QString> weatherSnowKeys = {"IncreaseFollowingSnow", "IncreasedStoppedDistanceSnow", "ReduceAccelerationSnow", "ReduceLateralAccelerationSnow"};
QSet<QString> parentKeys;
FrogPilotButtonsControl *weatherKeyControl;
FrogPilotParamValueControl *longitudinalActuatorDelayToggle;
FrogPilotParamValueControl *startAccelToggle;
FrogPilotParamValueControl *stopAccelToggle;
FrogPilotParamValueControl *stoppingDecelRateToggle;
FrogPilotParamValueControl *vEgoStartingToggle;
FrogPilotParamValueControl *vEgoStoppingToggle;
FrogPilotSettingsWindow *parent;
LabelControl *calibratedLateralAccelerationLabel;
LabelControl *calibrationProgressLabel;
Params params;
QNetworkAccessManager *networkManager;
};
+312
View File
@@ -0,0 +1,312 @@
#include <regex>
#include <QtConcurrent>
#include "frogpilot/ui/qt/offroad/maps_settings.h"
FrogPilotMapsPanel::FrogPilotMapsPanel(FrogPilotSettingsWindow *parent, bool forceOpen) : FrogPilotListWidget(parent), parent(parent) {
forceOpenDescriptions = forceOpen;
QStackedLayout *mapsLayout = new QStackedLayout();
addItem(mapsLayout);
FrogPilotListWidget *settingsList = new FrogPilotListWidget(this);
std::vector<QString> scheduleOptions{tr("Manually"), tr("Weekly"), tr("Monthly")};
preferredSchedule = new ButtonParamControl("PreferredSchedule", tr("Automatically Update Maps"),
tr("<b>How often maps update</b> from \"OpenStreetMap (OSM)\" with the latest speed limit information. "
"Weekly updates run every Sunday; monthly updates run on the 1st."),
"",
scheduleOptions);
settingsList->addItem(preferredSchedule);
downloadMapsButton = new ButtonControl(tr("Download Maps"), tr("DOWNLOAD"), tr("<b>Manually update your selected map sources</b> so \"Speed Limit Controller\" has the latest speed limit information."));
QObject::connect(downloadMapsButton, &ButtonControl::clicked, [this] {
if (downloadMapsButton->text() == tr("CANCEL")) {
if (FrogPilotConfirmationDialog::yesorno(tr("Cancel the download?"), this)) {
cancelDownload();
}
} else {
startDownload();
}
});
settingsList->addItem(downloadMapsButton);
settingsList->addItem(lastMapsDownload = new LabelControl(tr("Last Updated"), params.get("LastMapsUpdate").empty() ? "Never" : QString::fromStdString(params.get("LastMapsUpdate"))));
selectMaps = new FrogPilotButtonsControl(tr("Map Sources"),
tr("<b>Select the countries or U.S. states to use with \"Speed Limit Controller\".</b>") ,
"", {tr("COUNTRIES"), tr("STATES")});
QObject::connect(selectMaps, &FrogPilotButtonsControl::buttonClicked, [mapsLayout, this](int id) {
mapsLayout->setCurrentIndex(id + 1);
openSubPanel();
});
settingsList->addItem(selectMaps);
settingsList->addItem(downloadStatus = new LabelControl(tr("Progress")));
settingsList->addItem(downloadTimeElapsed = new LabelControl(tr("Time Elapsed")));
settingsList->addItem(downloadETA = new LabelControl(tr("Time Remaining")));
downloadETA->setVisible(false);
downloadStatus->setVisible(false);
downloadTimeElapsed->setVisible(false);
removeMapsButton = new ButtonControl(tr("Remove Maps"), tr("REMOVE"), tr("<b>Delete downloaded map data</b> to free up storage space."));
QObject::connect(removeMapsButton, &ButtonControl::clicked, [this] {
if (FrogPilotConfirmationDialog::yesorno(tr("Delete all downloaded maps?"), this)) {
std::thread([this] {
mapsSize->setText(tr("0 MB"));
mapsFolderPath.removeRecursively();
}).detach();
}
});
settingsList->addItem(removeMapsButton);
resetMapdButton = new ButtonControl(tr("Reset Downloader"), tr("RESET"),
tr("<b>Reset the map downloader.</b> Use this if downloads are stuck or failing."));
QObject::connect(resetMapdButton, &ButtonControl::clicked, [parent, this]() {
if (ConfirmationDialog::confirm(tr("Reset the map downloader? Your device will reboot afterward."), tr("Reset"), this)) {
std::thread([parent, this]() {
parent->keepScreenOn = true;
resetMapdButton->setEnabled(false);
resetMapdButton->setValue(tr("Resetting..."));
std::system("pkill mapd");
QDir("/data/media/0/osm").removeRecursively();
resetMapdButton->setValue(tr("Reset!"));
util::sleep_for(2500);
resetMapdButton->setValue(tr("Rebooting..."));
util::sleep_for(2500);
Hardware::reboot();
}).detach();
}
});
settingsList->addItem(resetMapdButton);
settingsList->addItem(mapsSize = new LabelControl(tr("Storage Used"), calculateDirectorySize(mapsFolderPath)));
ScrollView *settingsPanel = new ScrollView(settingsList, this);
mapsLayout->addWidget(settingsPanel);
FrogPilotListWidget *countriesList = new FrogPilotListWidget(this);
std::vector<std::pair<QString, QMap<QString, QString>>> countries = {
{tr("Africa"), africaMap},
{tr("Antarctica"), antarcticaMap},
{tr("Asia"), asiaMap},
{tr("Europe"), europeMap},
{tr("North America"), northAmericaMap},
{tr("Oceania"), oceaniaMap},
{tr("South America"), southAmericaMap}
};
for (std::pair<QString, QMap<QString, QString>> country : countries) {
countriesList->addItem(new LabelControl(country.first, ""));
countriesList->addItem(new MapSelectionControl(country.second, true));
}
ScrollView *countryMapsPanel = new ScrollView(countriesList, this);
mapsLayout->addWidget(countryMapsPanel);
FrogPilotListWidget *statesList = new FrogPilotListWidget(this);
std::vector<std::pair<QString, QMap<QString, QString>>> states = {
{tr("United States - Midwest"), midwestMap},
{tr("United States - Northeast"), northeastMap},
{tr("United States - South"), southMap},
{tr("United States - West"), westMap},
{tr("United States - Territories"), territoriesMap}
};
for (std::pair<QString, QMap<QString, QString>> state : states) {
statesList->addItem(new LabelControl(state.first, ""));
statesList->addItem(new MapSelectionControl(state.second));
}
ScrollView *stateMapsPanel = new ScrollView(statesList, this);
mapsLayout->addWidget(stateMapsPanel);
QObject::connect(parent, &FrogPilotSettingsWindow::closeSubPanel, [mapsLayout, settingsPanel, this] {
if (forceOpenDescriptions) {
downloadMapsButton->showDescription();
preferredSchedule->showDescription();
removeMapsButton->showDescription();
resetMapdButton->showDescription();
selectMaps->showDescription();
}
std::string mapsSelected = params.get("MapsSelected");
hasMapsSelected = !QJsonDocument::fromJson(QByteArray::fromStdString(mapsSelected)).object().value("nations").toArray().isEmpty();
hasMapsSelected |= !QJsonDocument::fromJson(QByteArray::fromStdString(mapsSelected)).object().value("states").toArray().isEmpty();
mapsLayout->setCurrentWidget(settingsPanel);
});
QObject::connect(uiState(), &UIState::uiUpdate, this, &FrogPilotMapsPanel::updateState);
}
void FrogPilotMapsPanel::showEvent(QShowEvent *event) {
if (forceOpenDescriptions) {
downloadMapsButton->showDescription();
preferredSchedule->showDescription();
removeMapsButton->showDescription();
resetMapdButton->showDescription();
selectMaps->showDescription();
}
UIState &s = *uiState();
UIScene &scene = s.scene;
FrogPilotUIState &fs = *frogpilotUIState();
FrogPilotUIScene &frogpilot_scene = fs.frogpilot_scene;
std::string mapsSelected = params.get("MapsSelected");
hasMapsSelected = !QJsonDocument::fromJson(QByteArray::fromStdString(mapsSelected)).object().value("nations").toArray().isEmpty();
hasMapsSelected |= !QJsonDocument::fromJson(QByteArray::fromStdString(mapsSelected)).object().value("states").toArray().isEmpty();
bool parked = !scene.started || frogpilot_scene.parked || parent->isFrogsGoMoo;
removeMapsButton->setVisible(mapsFolderPath.exists());
std::string osmDownloadProgress = params.get("OSMDownloadProgress");
if (!osmDownloadProgress.empty()) {
downloadMapsButton->setText(tr("CANCEL"));
downloadStatus->setText(tr("Calculating..."));
downloadStatus->setVisible(true);
lastMapsDownload->setVisible(false);
removeMapsButton->setVisible(false);
resetMapdButton->setVisible(false);
updateDownloadLabels(osmDownloadProgress);
} else {
downloadMapsButton->setEnabled(!cancellingDownload && hasMapsSelected && frogpilot_scene.online && parked);
downloadMapsButton->setValue(frogpilot_scene.online ? (parked ? "" : tr("Not parked")) : tr("Offline..."));
}
}
void FrogPilotMapsPanel::updateState(const UIState &s, const FrogPilotUIState &fs) {
if (!isVisible() || s.sm->frame % (UI_FREQ / 2) != 0) {
return;
}
const FrogPilotUIScene &frogpilot_scene = fs.frogpilot_scene;
const UIScene &scene = s.scene;
bool parked = !scene.started || frogpilot_scene.parked || parent->isFrogsGoMoo;
std::string osmDownloadProgress = params.get("OSMDownloadProgress");
if (!osmDownloadProgress.empty() && !cancellingDownload) {
updateDownloadLabels(osmDownloadProgress);
} else {
downloadMapsButton->setEnabled(!cancellingDownload && hasMapsSelected && frogpilot_scene.online && parked);
downloadMapsButton->setValue(frogpilot_scene.online ? (parked ? "" : tr("Not parked")) : tr("Offline..."));
}
parent->keepScreenOn = !osmDownloadProgress.empty();
}
void FrogPilotMapsPanel::cancelDownload() {
cancellingDownload = true;
downloadMapsButton->setEnabled(false);
downloadETA->setText(tr("Calculating..."));
downloadMapsButton->setText(tr("CANCEL"));
downloadStatus->setText(tr("Calculating..."));
downloadTimeElapsed->setText(tr("Calculating..."));
params.remove("OSMDownloadProgress");
params_memory.remove("OSMDownloadLocations");
std::system("pkill mapd");
QTimer::singleShot(2500, [this]() {
cancellingDownload = false;
downloadMapsButton->setEnabled(true);
downloadMapsButton->setText(tr("DOWNLOAD"));
downloadETA->setVisible(false);
downloadStatus->setVisible(false);
downloadTimeElapsed->setVisible(false);
lastMapsDownload->setVisible(true);
removeMapsButton->setVisible(mapsFolderPath.exists());
resetMapdButton->setVisible(true);
update();
});
}
void FrogPilotMapsPanel::startDownload() {
downloadETA->setText(tr("Calculating..."));
downloadMapsButton->setText(tr("CANCEL"));
downloadStatus->setText(tr("Calculating..."));
downloadTimeElapsed->setText(tr("Calculating..."));
downloadETA->setVisible(true);
downloadStatus->setVisible(true);
downloadTimeElapsed->setVisible(true);
lastMapsDownload->setVisible(false);
removeMapsButton->setVisible(false);
resetMapdButton->setVisible(false);
elapsedTime.start();
startTime = QDateTime::currentDateTime();
params_memory.put("OSMDownloadLocations", params.get("MapsSelected"));
}
void FrogPilotMapsPanel::updateDownloadLabels(std::string &osmDownloadProgress) {
static std::regex fileStatusRegex(R"("total_files":(\d+),.*"downloaded_files":(\d+))");
std::smatch match;
if (std::regex_search(osmDownloadProgress, match, fileStatusRegex)) {
int totalFiles = std::stoi(match[1].str());
int downloadedFiles = std::stoi(match[2].str());
if (downloadedFiles == totalFiles) {
downloadMapsButton->setText(tr("DOWNLOAD"));
lastMapsDownload->setText(formatCurrentDate());
downloadETA->setVisible(false);
downloadStatus->setVisible(false);
downloadTimeElapsed->setVisible(false);
lastMapsDownload->setVisible(true);
removeMapsButton->setVisible(true);
resetMapdButton->setVisible(true);
params.put("LastMapsUpdate", formatCurrentDate().toStdString());
params.remove("OSMDownloadProgress");
update();
return;
}
static int previousDownloadedFiles = 0;
if (downloadedFiles != previousDownloadedFiles) {
std::thread([this]() {
mapsSize->setText(calculateDirectorySize(mapsFolderPath));
}).detach();
}
downloadETA->setText(QString("%1").arg(formatETA(elapsedTime.elapsed(), downloadedFiles, previousDownloadedFiles, totalFiles, startTime)));
downloadStatus->setText(QString("%1 / %2 (%3%)").arg(downloadedFiles).arg(totalFiles).arg((downloadedFiles * 100) / totalFiles));
downloadTimeElapsed->setText(formatElapsedTime(elapsedTime.elapsed()));
previousDownloadedFiles = downloadedFiles;
}
}
+51
View File
@@ -0,0 +1,51 @@
#pragma once
#include "frogpilot/ui/qt/offroad/frogpilot_settings.h"
#include "frogpilot/ui/qt/widgets/navigation_functions.h"
class FrogPilotMapsPanel : public FrogPilotListWidget {
Q_OBJECT
public:
explicit FrogPilotMapsPanel(FrogPilotSettingsWindow *parent, bool forceOpen = false);
signals:
void openSubPanel();
protected:
void showEvent(QShowEvent *event) override;
private:
void cancelDownload();
void startDownload();
void updateDownloadLabels(std::string &osmDownloadProgress);
void updateState(const UIState &s, const FrogPilotUIState &fs);
bool cancellingDownload;
bool forceOpenDescriptions;
bool hasMapsSelected;
ButtonControl *downloadMapsButton;
ButtonControl *removeMapsButton;
ButtonControl *resetMapdButton;
ButtonParamControl *preferredSchedule;
FrogPilotButtonsControl *selectMaps;
FrogPilotSettingsWindow *parent;
LabelControl *downloadETA;
LabelControl *downloadStatus;
LabelControl *downloadTimeElapsed;
LabelControl *lastMapsDownload;
LabelControl *mapsSize;
Params params;
QDateTime startTime;
QDir mapsFolderPath{"/data/media/0/osm/offline"};
QElapsedTimer elapsedTime;
};
+611
View File
@@ -0,0 +1,611 @@
#include "frogpilot/ui/qt/offroad/model_settings.h"
bool hasAllTinygradFiles(const QDir &modelDir, const QString &modelKey) {
QStringList tinygradSuffixes = {
"_driving_policy_metadata.pkl",
"_driving_policy_tinygrad.pkl",
"_driving_vision_metadata.pkl",
"_driving_vision_tinygrad.pkl"
};
for (const QString &suffix : tinygradSuffixes) {
if (!modelDir.exists(modelKey + suffix)) {
return false;
}
}
return true;
}
FrogPilotModelPanel::FrogPilotModelPanel(FrogPilotSettingsWindow *parent, bool forceOpen) : FrogPilotListWidget(parent), parent(parent) {
forceOpenDescriptions = forceOpen;
defaultModel = QString::fromStdString(params.getKeyDefaultValue("DrivingModel").value());
QStackedLayout *modelLayout = new QStackedLayout();
addItem(modelLayout);
FrogPilotListWidget *modelList = new FrogPilotListWidget(this);
ScrollView *modelPanel = new ScrollView(modelList, this);
modelLayout->addWidget(modelPanel);
FrogPilotListWidget *modelLabelsList = new FrogPilotListWidget(this);
ScrollView *modelLabelsPanel = new ScrollView(modelLabelsList, this);
modelLayout->addWidget(modelLabelsPanel);
const std::vector<std::tuple<QString, QString, QString, QString>> modelToggles {
{"AutomaticallyDownloadModels", tr("Automatically Download New Models"), tr("<b>Automatically download new driving models</b> as they become available."), ""},
{"DeleteModel", tr("Delete Driving Models"), tr("<b>Delete downloaded driving models</b> to free up storage space."), ""},
{"DownloadModel", tr("Download Driving Models"), tr("<b>Manually download driving models</b> to the device."), ""},
{"ModelRandomizer", tr("Model Randomizer"), tr("<b>Select a random driving model each drive</b> and use feedback prompts at the end of the drive to help find the model that best suits you!"), ""},
{"ManageBlacklistedModels", tr("Manage Model Blacklist"), tr("<b>Add or remove driving models from the \"Model Randomizer\" blacklist.</b>"), ""},
{"ManageScores", tr("Manage Model Ratings"), tr("<b>View or reset saved model ratings</b> used by the \"Model Randomizer\"."), ""},
{"SelectModel", tr("Select Driving Model"), tr("<b>Choose which driving model openpilot uses.</b>"), ""},
{"UpdateTinygrad", tr("Update Model Manager"), tr("<b>Update the \"Model Manager\"</b> to support the latest models."), ""}
};
for (const auto &[param, title, desc, icon] : modelToggles) {
AbstractControl *modelToggle;
if (param == "DeleteModel") {
deleteModelButton = new FrogPilotButtonsControl(title, desc, icon, {tr("DELETE"), tr("DELETE ALL")});
QObject::connect(deleteModelButton, &FrogPilotButtonsControl::buttonClicked, [this](int id) {
QStringList deletableModels;
for (const QString &file : modelDir.entryList(QDir::Files)) {
QString base = QFileInfo(file).baseName();
for (const QString &modelKey : modelFileToNameMapProcessed.keys()) {
if (base.startsWith(modelKey)) {
QString modelName = modelFileToNameMapProcessed.value(modelKey);
if (!deletableModels.contains(modelName)) {
deletableModels.append(modelName);
}
}
}
}
deletableModels.removeAll(cleanModelName(currentModel));
deletableModels.removeAll(modelFileToNameMapProcessed.value(cleanModelName(defaultModel)));
noModelsDownloaded = deletableModels.isEmpty();
if (id == 0) {
QString modelToDelete = MultiOptionDialog::getSelection(tr("Select a driving model to delete"), deletableModels, "", this);
if (!modelToDelete.isEmpty() && ConfirmationDialog::confirm(tr("Are you sure you want to delete the \"%1\" model?").arg(modelToDelete), tr("Delete"), this)) {
QString modelFile = modelFileToNameMapProcessed.key(modelToDelete);
for (const QString &file : modelDir.entryList(QDir::Files)) {
QString base = QFileInfo(file).baseName();
if (base.startsWith(modelFile)) {
QFile::remove(modelDir.filePath(file));
}
}
noModelsDownloaded = deletableModels.isEmpty();
deletableModels.removeAll(modelToDelete);
deleteModelButton->setEnabled(!noModelsDownloaded);
allModelsDownloaded = false;
}
} else if (id == 1) {
if (ConfirmationDialog::confirm(tr("Are you sure you want to delete all of your downloaded driving models?"), tr("Delete"), this)) {
for (const QString &file : modelDir.entryList(QDir::Files)) {
QString base = QFileInfo(file).baseName();
for (const QString &modelKey : modelFileToNameMapProcessed.keys()) {
QString modelName = modelFileToNameMapProcessed.value(modelKey);
if (deletableModels.contains(modelName) && base.startsWith(modelKey)) {
QFile::remove(modelDir.filePath(file));
break;
}
}
}
deleteModelButton->setEnabled(false);
allModelsDownloaded = false;
noModelsDownloaded = true;
}
}
});
modelToggle = deleteModelButton;
} else if (param == "DownloadModel") {
downloadModelButton = new FrogPilotButtonsControl(title, desc, icon, {tr("DOWNLOAD"), tr("DOWNLOAD ALL")});
QObject::connect(downloadModelButton, &FrogPilotButtonsControl::buttonClicked, [this](int id) {
if (tinygradUpdate) {
if (FrogPilotConfirmationDialog::yesorno(tr("Tinygrad is out of date and must be updated before you can download new models. Update now?"), this)) {
if (FrogPilotConfirmationDialog::yesorno(tr("Updating Tinygrad will delete all existing Tinygrad-based models which will need to be re-downloaded. Proceed?"), this)) {
params_memory.putBool("UpdateTinygrad", true);
params_memory.put("ModelDownloadProgress", "Downloading...");
updateTinygradButton->setText(0, tr("CANCEL"));
updateTinygradButton->setValue(tr("Updating..."));
updatingTinygrad = true;
}
}
} else if (id == 0) {
if (modelDownloading) {
params_memory.putBool("CancelModelDownload", true);
cancellingDownload = true;
} else {
QStringList downloadableModels = availableModelNames;
for (const QString &modelKey : modelFileToNameMap.keys()) {
QString modelName = modelFileToNameMap.value(modelKey);
if (hasAllTinygradFiles(modelDir, modelKey)) {
downloadableModels.removeAll(modelName);
}
}
allModelsDownloaded = downloadableModels.isEmpty();
QString modelToDownload = MultiOptionDialog::getSelection(tr("Select a driving model to download"), downloadableModels, "", this);
if (!modelToDownload.isEmpty()) {
params_memory.put("ModelToDownload", modelFileToNameMap.key(modelToDownload).toStdString());
params_memory.put("ModelDownloadProgress", "Downloading...");
downloadModelButton->setText(0, tr("CANCEL"));
downloadModelButton->setValue(tr("Downloading..."));
downloadModelButton->setVisibleButton(1, false);
modelDownloading = true;
}
}
} else if (id == 1) {
if (allModelsDownloading) {
params_memory.putBool("CancelModelDownload", true);
cancellingDownload = true;
} else {
params_memory.putBool("DownloadAllModels", true);
params_memory.put("ModelDownloadProgress", "Downloading...");
downloadModelButton->setText(1, tr("CANCEL"));
downloadModelButton->setValue(tr("Downloading..."));
downloadModelButton->setVisibleButton(0, false);
allModelsDownloading = true;
}
}
});
modelToggle = downloadModelButton;
} else if (param == "ManageBlacklistedModels") {
FrogPilotButtonsControl *blacklistButton = new FrogPilotButtonsControl(title, desc, icon, {tr("ADD"), tr("REMOVE"), tr("REMOVE ALL")});
QObject::connect(blacklistButton, &FrogPilotButtonsControl::buttonClicked, [this](int id) {
QStringList blacklistedModels = QString::fromStdString(params.get("BlacklistedModels")).split(",");
blacklistedModels.removeAll("");
if (id == 0) {
QStringList blacklistableModels;
for (const QString &model : modelFileToNameMapProcessed.keys()) {
if (!blacklistedModels.contains(model)) {
blacklistableModels.append(modelFileToNameMapProcessed.value(model));
}
}
if (blacklistableModels.size() <= 1) {
ConfirmationDialog::alert(tr("There are no more driving models to blacklist. The only available model is \"%1\"!").arg(blacklistableModels.first()), this);
} else {
QString modelToBlacklist = MultiOptionDialog::getSelection(tr("Select a driving model to add to the blacklist"), blacklistableModels, "", this);
if (!modelToBlacklist.isEmpty()) {
if (ConfirmationDialog::confirm(tr("Are you sure you want to add the \"%1\" model to the blacklist?").arg(modelToBlacklist), tr("Add"), this)) {
blacklistedModels.append(modelFileToNameMapProcessed.key(modelToBlacklist));
params.put("BlacklistedModels", blacklistedModels.join(",").toStdString());
}
}
}
} else if (id == 1) {
QStringList whitelistableModels;
for (const QString &model : blacklistedModels) {
QString modelName = modelFileToNameMapProcessed.value(model);
whitelistableModels.append(modelName);
}
whitelistableModels.sort();
QString modelToWhitelist = MultiOptionDialog::getSelection(tr("Select a driving model to remove from the blacklist"), whitelistableModels, "", this);
if (!modelToWhitelist.isEmpty()) {
if (ConfirmationDialog::confirm(tr("Are you sure you want to remove the \"%1\" model from the blacklist?").arg(modelToWhitelist), tr("Remove"), this)) {
blacklistedModels.removeAll(modelFileToNameMapProcessed.key(modelToWhitelist));
params.put("BlacklistedModels", blacklistedModels.join(",").toStdString());
}
}
} else if (id == 2) {
if (FrogPilotConfirmationDialog::yesorno(tr("Are you sure you want to remove all of your blacklisted driving models?"), this)) {
params.remove("BlacklistedModels");
}
}
});
modelToggle = blacklistButton;
} else if (param == "ManageScores") {
FrogPilotButtonsControl *manageScoresButton = new FrogPilotButtonsControl(title, desc, icon, {tr("RESET"), tr("VIEW")});
QObject::connect(manageScoresButton, &FrogPilotButtonsControl::buttonClicked, [modelLayout, modelLabelsList, modelLabelsPanel, this](int id) {
if (id == 0) {
if (FrogPilotConfirmationDialog::yesorno(tr("Reset all model drives and ratings? This clears your drive history and collected feedback!"), this)) {
params.remove("ModelDrivesAndScores");
}
} else if (id == 1) {
openSubPanel();
updateModelLabels(modelLabelsList);
modelLayout->setCurrentWidget(modelLabelsPanel);
}
});
modelToggle = manageScoresButton;
} else if (param == "SelectModel") {
selectModelButton = new ButtonControl(title, tr("SELECT"), desc);
QObject::connect(selectModelButton, &ButtonControl::clicked, [this]() {
QStringList selectableModels;
for (const QString &modelKey : modelFileToNameMap.keys()) {
if (modelKey != cleanModelName(defaultModel) && hasAllTinygradFiles(modelDir, modelKey)) {
selectableModels.append(modelFileToNameMap.value(modelKey));
}
}
selectableModels.sort();
selectableModels.prepend(modelFileToNameMap.value(cleanModelName(defaultModel)) + " (Default)");
QString currentModelDisplay = currentModel;
if (currentModel == modelFileToNameMap.value(cleanModelName(defaultModel))) {
currentModelDisplay += " (Default)";
}
QString modelToSelect = MultiOptionDialog::getSelection(tr("Select a Model"), selectableModels, currentModelDisplay, this);
if (!modelToSelect.isEmpty()) {
modelToSelect.remove(" (Default)");
currentModel = modelToSelect;
params.put("DrivingModel", modelFileToNameMap.key(modelToSelect).toStdString());
updateFrogPilotToggles();
if (started) {
if (FrogPilotConfirmationDialog::toggleReboot(this)) {
Hardware::reboot();
}
}
selectModelButton->setValue(modelToSelect);
QStringList deletableModels;
for (const QString &file : modelDir.entryList(QDir::Files)) {
QString base = QFileInfo(file).baseName();
for (const QString &modelKey : modelFileToNameMapProcessed.keys()) {
if (base.startsWith(modelKey)) {
QString modelName = modelFileToNameMapProcessed.value(modelKey);
if (!deletableModels.contains(modelName)) {
deletableModels.append(modelName);
}
}
}
}
deletableModels.removeAll(cleanModelName(currentModel));
deletableModels.removeAll(modelFileToNameMapProcessed.value(cleanModelName(defaultModel)));
noModelsDownloaded = deletableModels.isEmpty();
}
});
modelToggle = selectModelButton;
} else if (param == "UpdateTinygrad") {
updateTinygradButton = new FrogPilotButtonsControl(title, desc, icon, {tr("UPDATE")});
QObject::connect(updateTinygradButton, &FrogPilotButtonsControl::buttonClicked, [this]() {
if (updatingTinygrad) {
params_memory.putBool("CancelModelDownload", true);
updateTinygradButton->setEnabled(false);
updateTinygradButton->setValue(tr("Cancelling..."));
cancellingDownload = true;
} else {
if (FrogPilotConfirmationDialog::yesorno(tr("Updating Tinygrad will delete existing Tinygrad-based driving models and need to be re-downloaded. Proceed?"), this)) {
params_memory.putBool("UpdateTinygrad", true);
params_memory.put("ModelDownloadProgress", "Downloading...");
updateTinygradButton->setText(0, tr("CANCEL"));
updateTinygradButton->setValue(tr("Updating..."));
updatingTinygrad = true;
}
}
});
modelToggle = updateTinygradButton;
} else {
modelToggle = new ParamControl(param, title, desc, icon);
}
toggles[param] = modelToggle;
modelList->addItem(modelToggle);
QObject::connect(modelToggle, &AbstractControl::hideDescriptionEvent, [this]() {
update();
});
QObject::connect(modelToggle, &AbstractControl::showDescriptionEvent, [this]() {
update();
});
}
openDescriptions(forceOpenDescriptions, toggles);
QObject::connect(static_cast<ToggleControl*>(toggles["ModelRandomizer"]), &ToggleControl::toggleFlipped, [this](bool state) {
updateToggles();
if (state && !allModelsDownloaded) {
if (FrogPilotConfirmationDialog::yesorno(tr("The \"Model Randomizer\" works only with downloaded models. Download all models now?"), this)) {
params_memory.putBool("DownloadAllModels", true);
params_memory.put("ModelDownloadProgress", "Downloading...");
downloadModelButton->setValue(tr("Downloading..."));
allModelsDownloading = true;
}
}
});
QObject::connect(parent, &FrogPilotSettingsWindow::closeSubPanel, [modelLayout, modelPanel, this] {
openDescriptions(forceOpenDescriptions, toggles);
modelLayout->setCurrentWidget(modelPanel);
});
QObject::connect(uiState(), &UIState::uiUpdate, this, &FrogPilotModelPanel::updateState);
}
void FrogPilotModelPanel::showEvent(QShowEvent *event) {
UIState &s = *uiState();
FrogPilotUIState &fs = *frogpilotUIState();
FrogPilotUIScene &frogpilot_scene = fs.frogpilot_scene;
allModelsDownloading = params_memory.getBool("DownloadAllModels");
modelDownloading = !params_memory.get("ModelDownloadProgress").empty();
tinygradUpdate = params.getBool("TinygradUpdateAvailable");
updatingTinygrad = params_memory.getBool("UpdateTinygrad");
modelDownloading &= !updatingTinygrad;
QStringList availableModels = QString::fromStdString(params.get("AvailableModels")).split(",");
availableModels.sort();
availableModelNames = QString::fromStdString(params.get("AvailableModelNames")).split(",");
availableModelNames.sort();
modelFileToNameMap.clear();
modelFileToNameMapProcessed.clear();
for (int i = 0; i < qMin(availableModels.size(), availableModelNames.size()); ++i) {
modelFileToNameMap.insert(availableModels[i], availableModelNames[i]);
modelFileToNameMapProcessed.insert(availableModels[i], cleanModelName(availableModelNames[i]));
}
QStringList downloadableModels = availableModelNames;
for (const QString &modelKey : modelFileToNameMap.keys()) {
QString modelName = modelFileToNameMap.value(modelKey);
if (hasAllTinygradFiles(modelDir, modelKey)) {
downloadableModels.removeAll(modelName);
}
}
allModelsDownloaded = downloadableModels.isEmpty();
QStringList deletableModels;
for (const QString &file : modelDir.entryList(QDir::Files)) {
QString base = QFileInfo(file).baseName();
for (const QString &modelKey : modelFileToNameMapProcessed.keys()) {
if (base.startsWith(modelKey)) {
QString modelName = modelFileToNameMapProcessed.value(modelKey);
if (!deletableModels.contains(modelName)) {
deletableModels.append(modelName);
}
}
}
}
deletableModels.removeAll(cleanModelName(currentModel));
deletableModels.removeAll(modelFileToNameMapProcessed.value(cleanModelName(defaultModel)));
noModelsDownloaded = deletableModels.isEmpty();
QString modelKey = cleanModelName(QString::fromStdString(params.get("DrivingModel")));
if (!hasAllTinygradFiles(modelDir, modelKey)) {
modelKey = defaultModel;
}
currentModel = modelFileToNameMap.value(modelKey);
QString currentModelName = currentModel;
if (modelKey == defaultModel) {
currentModelName.append(" (Default)");
}
selectModelButton->setValue(currentModelName);
bool parked = !s.scene.started || frogpilot_scene.parked || parent->isFrogsGoMoo;
deleteModelButton->setEnabled(!(allModelsDownloading || modelDownloading || noModelsDownloaded));
downloadModelButton->setEnabledButtons(0, !allModelsDownloaded && !allModelsDownloading && !cancellingDownload && !updatingTinygrad && frogpilot_scene.online && parked);
downloadModelButton->setEnabledButtons(1, !allModelsDownloaded && !modelDownloading && !cancellingDownload && !updatingTinygrad && frogpilot_scene.online && parked);
downloadModelButton->setValue(frogpilot_scene.online ? (parked ? "" : "Not parked") : tr("Offline..."));
updateTinygradButton->setEnabled(!modelDownloading && !cancellingDownload && frogpilot_scene.online && parked && tinygradUpdate);
updateTinygradButton->setValue(tinygradUpdate ? tr("Update available!") : tr("Up to date!"));
started = s.scene.started;
updateToggles();
}
void FrogPilotModelPanel::updateState(const UIState &s, const FrogPilotUIState &fs) {
if (!isVisible() || finalizingDownload) {
return;
}
const FrogPilotUIScene &frogpilot_scene = fs.frogpilot_scene;
bool parked = !started || frogpilot_scene.parked || parent->isFrogsGoMoo;
if (allModelsDownloading || modelDownloading) {
QString progress = QString::fromStdString(params_memory.get("ModelDownloadProgress"));
bool downloadFailed = progress.contains(QRegularExpression("cancelled|exists|failed|missing|offline", QRegularExpression::CaseInsensitiveOption));
{
QString translatedProgress;
if (progress == "Downloading...") {
translatedProgress = tr("Downloading...");
} else if (progress == "Downloaded!") {
translatedProgress = tr("Downloaded!");
} else if (progress == "All models downloaded!") {
translatedProgress = tr("All models downloaded!");
} else if (progress.contains("cancelled", Qt::CaseInsensitive)) {
translatedProgress = tr("Download cancelled...");
} else if (progress.contains("failed", Qt::CaseInsensitive)) {
translatedProgress = tr("Download failed...");
} else if (progress.contains("offline", Qt::CaseInsensitive)) {
translatedProgress = tr("GitHub and GitLab are offline...");
} else if (progress == "Repository unavailable") {
translatedProgress = tr("Repository unavailable");
} else {
translatedProgress = progress;
}
downloadModelButton->setValue(translatedProgress);
}
if (progress == "All models downloaded!" || progress == "Downloaded!" && !allModelsDownloading || downloadFailed) {
finalizingDownload = true;
QTimer::singleShot(2500, [progress, this]() {
allModelsDownloading = false;
cancellingDownload = false;
finalizingDownload = false;
modelDownloading = false;
noModelsDownloaded = false;
QStringList downloadableModels = availableModelNames;
for (const QString &modelKey : modelFileToNameMap.keys()) {
QString modelName = modelFileToNameMap.value(modelKey);
if (hasAllTinygradFiles(modelDir, modelKey)) {
downloadableModels.removeAll(modelName);
}
}
allModelsDownloaded = downloadableModels.isEmpty();
params_memory.remove("ModelDownloadProgress");
downloadModelButton->setEnabled(true);
downloadModelButton->setValue("");
});
}
} else {
downloadModelButton->setValue(frogpilot_scene.online ? (parked ? "" : "Not parked") : tr("Offline..."));
}
if (updatingTinygrad) {
QString progress = QString::fromStdString(params_memory.get("ModelDownloadProgress"));
bool downloadFailed = progress.contains(QRegularExpression("cancelled|exists|failed|missing|offline", QRegularExpression::CaseInsensitiveOption));
{
QString translatedProgress;
if (progress == "Downloading...") {
translatedProgress = tr("Downloading...");
} else if (progress == "Downloaded!") {
translatedProgress = tr("Downloaded!");
} else if (progress == "All models downloaded!") {
translatedProgress = tr("All models downloaded!");
} else if (progress.contains("cancelled", Qt::CaseInsensitive)) {
translatedProgress = tr("Download cancelled...");
} else if (progress.contains("failed", Qt::CaseInsensitive)) {
translatedProgress = tr("Download failed...");
} else if (progress.contains("offline", Qt::CaseInsensitive)) {
translatedProgress = tr("GitHub and GitLab are offline...");
} else if (progress == "Repository unavailable") {
translatedProgress = tr("Repository unavailable");
} else {
translatedProgress = progress;
}
updateTinygradButton->setValue(translatedProgress);
}
if (progress == "Updated!" && updatingTinygrad || downloadFailed) {
finalizingDownload = true;
QTimer::singleShot(2500, [progress, this]() {
modelDownloading = !params_memory.get("ModelDownloadProgress").empty();
if (modelDownloading) {
downloadModelButton->setText(1, tr("CANCEL"));
downloadModelButton->setValue(tr("Downloading..."));
downloadModelButton->setVisibleButton(0, false);
} else {
cancellingDownload = false;
}
tinygradUpdate = params.getBool("TinygradUpdateAvailable");
finalizingDownload = false;
updatingTinygrad = false;
updateTinygradButton->setEnabled(tinygradUpdate);
updateTinygradButton->setText(0, tr("UPDATE"));
updateTinygradButton->setValue(tinygradUpdate ? tr("Update available!") : tr("Up to date!"));
});
}
}
deleteModelButton->setEnabled(!(allModelsDownloading || modelDownloading || noModelsDownloaded));
downloadModelButton->setText(0, modelDownloading ? tr("CANCEL") : tr("DOWNLOAD"));
downloadModelButton->setText(1, allModelsDownloading ? tr("CANCEL") : tr("DOWNLOAD ALL"));
downloadModelButton->setEnabledButtons(0, !allModelsDownloaded && !allModelsDownloading && !cancellingDownload && !finalizingDownload && !updatingTinygrad && frogpilot_scene.online && parked);
downloadModelButton->setEnabledButtons(1, !allModelsDownloaded && !modelDownloading && !cancellingDownload && !finalizingDownload && !updatingTinygrad && frogpilot_scene.online && parked);
downloadModelButton->setVisibleButton(0, !allModelsDownloading);
downloadModelButton->setVisibleButton(1, !modelDownloading);
updateTinygradButton->setEnabled(!modelDownloading && !cancellingDownload && !cancellingDownload && !finalizingDownload && frogpilot_scene.online && parked && tinygradUpdate);
started = s.scene.started;
parent->keepScreenOn = allModelsDownloading || modelDownloading || updatingTinygrad;
}
void FrogPilotModelPanel::updateModelLabels(FrogPilotListWidget *labelsList) {
labelsList->clear();
QJsonObject modelDrivesAndScores = QJsonDocument::fromJson(QString::fromStdString(params.get("ModelDrivesAndScores")).toUtf8()).object();
for (const QString &modelName : availableModelNames) {
QJsonObject modelData = modelDrivesAndScores.value(cleanModelName(modelName)).toObject();
int drives = modelData.value("Drives").toInt(0);
int score = modelData.value("Score").toInt(0);
QString drivesDisplay = drives == 1 ? QString("%1 Drive").arg(drives) : drives > 0 ? QString("%1 Drives").arg(drives) : "N/A";
QString scoreDisplay = drives > 0 ? QString("Score: %1%").arg(score) : "N/A";
QString labelTitle = cleanModelName(modelName);
QString labelText = QString("%1 (%2)").arg(scoreDisplay, drivesDisplay);
LabelControl *labelControl = new LabelControl(labelTitle, labelText, "", this);
labelsList->addItem(labelControl);
}
}
void FrogPilotModelPanel::updateToggles() {
for (auto &[key, toggle] : toggles) {
bool setVisible = parent->tuningLevel >= parent->frogpilotToggleLevels[key].toDouble();
if (key == "ManageBlacklistedModels" || key == "ManageScores") {
setVisible &= params.getBool("ModelRandomizer");
}
else if (key == "SelectModel") {
setVisible &= !params.getBool("ModelRandomizer");
}
toggle->setVisible(setVisible);
}
openDescriptions(forceOpenDescriptions, toggles);
update();
}
+54
View File
@@ -0,0 +1,54 @@
#pragma once
#include "frogpilot/ui/qt/offroad/frogpilot_settings.h"
class FrogPilotModelPanel : public FrogPilotListWidget {
Q_OBJECT
public:
explicit FrogPilotModelPanel(FrogPilotSettingsWindow *parent, bool forceOpen = false);
signals:
void openSubPanel();
protected:
void showEvent(QShowEvent *event) override;
private:
void updateModelLabels(FrogPilotListWidget *labelsList);
void updateState(const UIState &s, const FrogPilotUIState &fs);
void updateToggles();
bool allModelsDownloaded;
bool allModelsDownloading;
bool cancellingDownload;
bool finalizingDownload;
bool forceOpenDescriptions;
bool modelDownloading;
bool noModelsDownloaded;
bool started;
bool tinygradUpdate;
bool updatingTinygrad;
std::map<QString, AbstractControl*> toggles;
ButtonControl *selectModelButton;
FrogPilotButtonsControl *deleteModelButton;
FrogPilotButtonsControl *downloadModelButton;
FrogPilotButtonsControl *updateTinygradButton;
FrogPilotSettingsWindow *parent;
Params params;
QDir modelDir{"/data/models/"};
QMap<QString, QString> modelFileToNameMap;
QMap<QString, QString> modelFileToNameMapProcessed;
QString currentModel;
QString defaultModel;
QStringList availableModelNames;
};
@@ -0,0 +1,330 @@
#include "frogpilot/ui/qt/offroad/navigation_settings.h"
FrogPilotNavigationPanel::FrogPilotNavigationPanel(FrogPilotSettingsWindow *parent, bool forceOpen) : FrogPilotListWidget(parent), parent(parent) {
forceOpenDescriptions = forceOpen;
networkManager = new QNetworkAccessManager(this);
primelessLayout = new QStackedLayout();
addItem(primelessLayout);
FrogPilotListWidget *settingsList = new FrogPilotListWidget(this);
ipLabel = new LabelControl(tr("Manage Your Settings At"), tr("Offline..."));
settingsList->addItem(ipLabel);
publicMapboxKeyControl = new FrogPilotButtonsControl(tr("Public Mapbox Key"), tr("<b>Manage your Public Mapbox Key.</b>"), "", {tr("ADD"), tr("TEST")});
QObject::connect(publicMapboxKeyControl, &FrogPilotButtonsControl::buttonClicked, [this](int id) {
if (id == 0) {
if (mapboxPublicKeySet) {
if (FrogPilotConfirmationDialog::yesorno(tr("Remove your Public Mapbox Key?"), this)) {
params.remove("MapboxPublicKey");
updateButtons();
}
} else {
int minKeyLength = 80;
QString key = InputDialog::getText(tr("Enter your Public Mapbox Key"), this, "", false, minKeyLength).trimmed();
if (!key.isEmpty()) {
if (!key.startsWith("pk.")) {
key = "pk." + key;
}
params.put("MapboxPublicKey", key.toStdString());
updateButtons();
}
}
} else {
publicMapboxKeyControl->setValue(tr("Testing..."));
QString key = QString::fromStdString(params.get("MapboxPublicKey"));
QString url = QString("https://api.mapbox.com/geocoding/v5/mapbox.places/mapbox.json?access_token=%1").arg(key);
QNetworkRequest request(url);
QNetworkReply *reply = networkManager->get(request);
connect(reply, &QNetworkReply::finished, [=]() {
publicMapboxKeyControl->setValue("");
QString message;
if (reply->error() == QNetworkReply::NoError) {
message = tr("Key is valid!");
} else if (reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt() == 401) {
message = tr("Key is invalid!");
} else {
message = tr("An error occurred: %1").arg(reply->errorString());
}
ConfirmationDialog::alert(message, this);
reply->deleteLater();
});
}
});
settingsList->addItem(publicMapboxKeyControl);
secretMapboxKeyControl = new FrogPilotButtonsControl(tr("Secret Mapbox Key"), tr("<b>Manage your Secret Mapbox Key.</b>"), "", {tr("ADD"), tr("TEST")});
QObject::connect(secretMapboxKeyControl, &FrogPilotButtonsControl::buttonClicked, [this](int id) {
if (id == 0) {
if (mapboxSecretKeySet) {
if (FrogPilotConfirmationDialog::yesorno(tr("Remove your Secret Mapbox Key?"), this)) {
params.remove("MapboxSecretKey");
updateButtons();
}
} else {
int minKeyLength = 80;
QString key = InputDialog::getText(tr("Enter your Secret Mapbox Key"), this, "", false, minKeyLength).trimmed();
if (!key.isEmpty()) {
if (!key.startsWith("sk.")) {
key = "sk." + key;
}
params.put("MapboxSecretKey", key.toStdString());
updateButtons();
}
}
} else {
secretMapboxKeyControl->setValue(tr("Testing..."));
QString key = QString::fromStdString(params.get("MapboxSecretKey"));
QString url = QString("https://api.mapbox.com/directions/v5/mapbox/driving/-73.989,40.733;-74,40.733?access_token=%1").arg(key);
QNetworkRequest request(url);
QNetworkReply *reply = networkManager->get(request);
connect(reply, &QNetworkReply::finished, [=]() {
secretMapboxKeyControl->setValue("");
QString message;
if (reply->error() == QNetworkReply::NoError) {
message = tr("Key is valid!");
} else if (reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt() == 401) {
message = tr("Key is invalid!");
} else {
message = tr("An error occurred: %1").arg(reply->errorString());
}
ConfirmationDialog::alert(message, this);
reply->deleteLater();
});
}
});
settingsList->addItem(secretMapboxKeyControl);
setupButton = new ButtonControl(tr("Mapbox Setup Instructions"), tr("VIEW"), tr("<b>Instructions on how to set up Mapbox</b> for \"Primeless Navigation\"."), this);
QObject::connect(setupButton, &ButtonControl::clicked, [this]() {
openSubPanel();
updateStep();
primelessLayout->setCurrentIndex(1);
});
settingsList->addItem(setupButton);
std::vector<QString> filterButtonNames{tr("CANCEL"), tr("Manually Update Speed Limits")};
updateSpeedLimitsToggle = new FrogPilotButtonControl("SpeedLimitFiller", tr("Speed Limit Filler"),
tr("<b>Automatically collect missing or incorrect speed limits while you drive</b> using speeds limits sourced from your dashboard (if supported), "
"Mapbox, and \"Navigate on openpilot\".<br><br>"
"When you're parked and connected to Wi-Fi, FrogPilot will automatically processes this data into a file "
"to be used with the tool located at \"SpeedLimitFiller.frogpilot.com\".<br><br>"
"You can download this file from \"The Pond\" in the \"Download Speed Limits\" menu.<br><br>"
"Need a step-by-step guide? Visit <b>#speed-limit-filler</b> in the FrogPilot Discord!"),
"", filterButtonNames);
QObject::connect(updateSpeedLimitsToggle, &FrogPilotButtonControl::buttonClicked, [this](int id) {
if (id == 0) {
if (FrogPilotConfirmationDialog::yesorno(tr("Cancel the speed-limit update?"), this)) {
updatingLimits = false;
updateSpeedLimitsToggle->setEnabledButton(0, false);
updateSpeedLimitsToggle->setValue(tr("Cancelled..."));
params_memory.remove("UpdateSpeedLimits");
QTimer::singleShot(2500, [this]() {
updateSpeedLimitsToggle->clearCheckedButtons();
updateSpeedLimitsToggle->setEnabledButton(0, true);
updateSpeedLimitsToggle->setValue("");
updateSpeedLimitsToggle->setVisibleButton(0, false);
updateSpeedLimitsToggle->setVisibleButton(1, true);
params_memory.remove("UpdateSpeedLimitsStatus");
});
}
} else if (id == 1) {
QJsonObject overpassRequests = QJsonDocument::fromJson(QString::fromStdString(params.get("OverpassRequests")).toUtf8()).object();
int totalRequests = overpassRequests.value("total_requests").toInt(0);
int maxRequests = overpassRequests.value("max_requests").toInt(10000);
int savedDay = overpassRequests.value("day").toInt(QDate::currentDate().day());
int currentDay = QDate::currentDate().day();
if (savedDay != currentDay) {
totalRequests = 0;
}
if (totalRequests >= maxRequests) {
QTime now = QTime::currentTime();
int secondsUntilMidnight = (24 * 3600) - (now.hour() * 3600 + now.minute() * 60 + now.second());
int hours = secondsUntilMidnight / 3600;
int minutes = (secondsUntilMidnight % 3600) / 60;
ConfirmationDialog::alert(QString(tr("You've hit today's request limit.\n\nIt will reset in %1 hours and %2 minutes.")).arg(hours).arg(minutes), this);
updateSpeedLimitsToggle->clearCheckedButtons();
return;
}
updateSpeedLimitsToggle->setVisibleButton(0, true);
updateSpeedLimitsToggle->setVisibleButton(1, false);
if (FrogPilotConfirmationDialog::yesorno(tr("This process takes a while. It's recommended to start when you're done driving and connected to stable Wi-Fi. Continue?"), this)) {
updatingLimits = true;
updateSpeedLimitsToggle->setValue("Calculating...");
params_memory.put("UpdateSpeedLimitsStatus", "Calculating...");
params_memory.putBool("UpdateSpeedLimits", true);
} else {
updateSpeedLimitsToggle->setVisibleButton(0, false);
updateSpeedLimitsToggle->setVisibleButton(1, true);
updateSpeedLimitsToggle->clearCheckedButtons();
}
}
});
updateSpeedLimitsToggle->setVisibleButton(0, false);
settingsList->addItem(updateSpeedLimitsToggle);
ScrollView *settingsPanel = new ScrollView(settingsList, this);
primelessLayout->addWidget(settingsPanel);
imageLabel = new QLabel(this);
ScrollView *instructionsPanel = new ScrollView(imageLabel, this);
primelessLayout->addWidget(instructionsPanel);
QObject::connect(parent, &FrogPilotSettingsWindow::closeSubPanel, [this]() {
primelessLayout->setCurrentIndex(0);
if (forceOpenDescriptions) {
publicMapboxKeyControl->showDescription();
secretMapboxKeyControl->showDescription();
setupButton->showDescription();
updateSpeedLimitsToggle->showDescription();
}
});
QObject::connect(uiState(), &UIState::uiUpdate, this, &FrogPilotNavigationPanel::updateState);
}
void FrogPilotNavigationPanel::showEvent(QShowEvent *event) {
if (forceOpenDescriptions) {
publicMapboxKeyControl->showDescription();
secretMapboxKeyControl->showDescription();
setupButton->showDescription();
updateSpeedLimitsToggle->showDescription();
}
UIState &s = *uiState();
FrogPilotUIState &fs = *frogpilotUIState();
FrogPilotUIScene &frogpilot_scene = fs.frogpilot_scene;
QString ipAddress = fs.wifi->getIp4Address();
ipLabel->setText(ipAddress.isEmpty() ? tr("Offline...") : QString("%1:8082").arg(ipAddress));
updateButtons();
updatingLimits = !params_memory.get("UpdateSpeedLimitsStatus").empty() && QString::fromStdString(params_memory.get("UpdateSpeedLimitsStatus")) != "Completed!";
bool parked = !s.scene.started || frogpilot_scene.parked || parent->isFrogsGoMoo;
updateSpeedLimitsToggle->setVisibleButton(0, updatingLimits);
updateSpeedLimitsToggle->setVisibleButton(1, !updatingLimits);
if (updatingLimits) {
updateSpeedLimitsToggle->setValue(QString::fromStdString(params_memory.get("UpdateSpeedLimitsStatus")));
} else {
updateSpeedLimitsToggle->setEnabledButton(1, frogpilot_scene.online && util::system_time_valid() && parked);
updateSpeedLimitsToggle->setValue(frogpilot_scene.online ? (parked ? "" : "Not parked") : tr("Offline..."));
updateSpeedLimitsToggle->setVisible(parent->tuningLevel >= parent->frogpilotToggleLevels["SpeedLimitFiller"].toDouble());
}
}
void FrogPilotNavigationPanel::hideEvent(QHideEvent *event) {
primelessLayout->setCurrentIndex(0);
}
void FrogPilotNavigationPanel::mousePressEvent(QMouseEvent *event) {
if (primelessLayout->currentIndex() == 1) {
closeSubPanel();
primelessLayout->setCurrentIndex(0);
if (forceOpenDescriptions) {
publicMapboxKeyControl->showDescription();
secretMapboxKeyControl->showDescription();
setupButton->showDescription();
updateSpeedLimitsToggle->showDescription();
}
}
}
void FrogPilotNavigationPanel::updateButtons() {
FrogPilotUIState &fs = *frogpilotUIState();
FrogPilotUIScene &frogpilot_scene = fs.frogpilot_scene;
mapboxPublicKeySet = QString::fromStdString(params.get("MapboxPublicKey")).startsWith("pk");
mapboxSecretKeySet = QString::fromStdString(params.get("MapboxSecretKey")).startsWith("sk");
publicMapboxKeyControl->setText(0, mapboxPublicKeySet ? tr("REMOVE") : tr("ADD"));
publicMapboxKeyControl->setVisibleButton(1, mapboxPublicKeySet && frogpilot_scene.online);
secretMapboxKeyControl->setText(0, mapboxSecretKeySet ? tr("REMOVE") : tr("ADD"));
secretMapboxKeyControl->setVisibleButton(1, mapboxSecretKeySet && frogpilot_scene.online);
}
void FrogPilotNavigationPanel::updateState(const UIState &s, const FrogPilotUIState &fs) {
if (!isVisible() || s.sm->frame % (UI_FREQ / 2) != 0) {
return;
}
const FrogPilotUIScene &frogpilot_scene = fs.frogpilot_scene;
updateButtons();
updateStep();
bool parked = !s.scene.started || frogpilot_scene.parked || parent->isFrogsGoMoo;
if (updatingLimits) {
if (QString::fromStdString(params_memory.get("UpdateSpeedLimitsStatus")) == "Completed!") {
updatingLimits = false;
updateSpeedLimitsToggle->setValue(tr("Completed!"));
QTimer::singleShot(2500, [this]() {
updateSpeedLimitsToggle->clearCheckedButtons();
updateSpeedLimitsToggle->setValue("");
updateSpeedLimitsToggle->setVisibleButton(0, false);
updateSpeedLimitsToggle->setVisibleButton(1, true);
params_memory.remove("UpdateSpeedLimitsStatus");
});
} else {
updateSpeedLimitsToggle->setValue(QString::fromStdString(params_memory.get("UpdateSpeedLimitsStatus")));
}
} else {
updateSpeedLimitsToggle->setEnabledButton(1, frogpilot_scene.online && util::system_time_valid() && parked);
updateSpeedLimitsToggle->setValue(frogpilot_scene.online ? (parked ? "" : "Not parked") : tr("Offline..."));
}
parent->keepScreenOn = primelessLayout->currentIndex() == 1 || updatingLimits;
}
void FrogPilotNavigationPanel::updateStep() {
QString currentStep;
if (mapboxPublicKeySet) {
currentStep = "../../frogpilot/navigation/navigation_training/public_key_set.png";
} else {
currentStep = "../../frogpilot/navigation/navigation_training/no_keys_set.png";
}
QPixmap pixmap;
pixmap.load(currentStep);
imageLabel->setPixmap(pixmap.scaledToWidth(1500, Qt::SmoothTransformation));
update();
}
@@ -0,0 +1,47 @@
#pragma once
#include "frogpilot/ui/qt/offroad/frogpilot_settings.h"
class FrogPilotNavigationPanel : public FrogPilotListWidget {
Q_OBJECT
public:
explicit FrogPilotNavigationPanel(FrogPilotSettingsWindow *parent, bool forceOpen = false);
signals:
void closeSubPanel();
void openSubPanel();
protected:
void hideEvent(QHideEvent *event);
void showEvent(QShowEvent *event) override;
private:
void mousePressEvent(QMouseEvent *event);
void updateButtons();
void updateState(const UIState &s, const FrogPilotUIState &fs);
void updateStep();
bool forceOpenDescriptions;
bool mapboxPublicKeySet;
bool mapboxSecretKeySet;
bool updatingLimits;
FrogPilotButtonsControl *publicMapboxKeyControl;
FrogPilotButtonsControl *secretMapboxKeyControl;
ButtonControl *setupButton;
FrogPilotButtonControl *updateSpeedLimitsToggle;
FrogPilotSettingsWindow *parent;
LabelControl *ipLabel;
Params params;
QLabel *imageLabel;
QNetworkAccessManager *networkManager;
QStackedLayout *primelessLayout;
};
+164
View File
@@ -0,0 +1,164 @@
#include "frogpilot/ui/qt/offroad/sounds_settings.h"
FrogPilotSoundsPanel::FrogPilotSoundsPanel(FrogPilotSettingsWindow *parent, bool forceOpen) : FrogPilotListWidget(parent), parent(parent) {
forceOpenDescriptions = forceOpen;
QStackedLayout *soundsLayout = new QStackedLayout();
addItem(soundsLayout);
FrogPilotListWidget *soundsList = new FrogPilotListWidget(this);
ScrollView *soundsPanel = new ScrollView(soundsList, this);
soundsLayout->addWidget(soundsPanel);
FrogPilotListWidget *alertVolumeControlList = new FrogPilotListWidget(this);
FrogPilotListWidget *customAlertsList = new FrogPilotListWidget(this);
ScrollView *alertVolumeControlPanel = new ScrollView(alertVolumeControlList, this);
ScrollView *customAlertsPanel = new ScrollView(customAlertsList, this);
soundsLayout->addWidget(alertVolumeControlPanel);
soundsLayout->addWidget(customAlertsPanel);
const std::vector<std::tuple<QString, QString, QString, QString>> soundsToggles {
{"AlertVolumeControl", tr("Alert Volume Controller"), tr("<b>Set how loud each type of openpilot alert is</b> to keep routine prompts from becoming distracting."), "../../frogpilot/assets/toggle_icons/icon_mute.png"},
{"DisengageVolume", tr("Disengage Volume"), tr("<b>Set the volume for alerts when openpilot disengages.</b><br><br>Examples include: \"Cruise Fault: Restart the Car\", \"Parking Brake Engaged\", \"Pedal Pressed\"."), ""},
{"EngageVolume", tr("Engage Volume"), tr("<b>Set the volume for the chime when openpilot engages</b>, such as after pressing the \"RESUME\" or \"SET\" steering wheel buttons."), ""},
{"PromptVolume", tr("Prompt Volume"), tr("<b>Set the volume for prompts that need attention.</b><br><br>Examples include: \"Car Detected in Blindspot\", \"Steering Temporarily Unavailable\", \"Turn Exceeds Steering Limit\"."), ""},
{"PromptDistractedVolume", tr("Prompt Distracted Volume"), tr("<b>Set the volume for prompts when openpilot detects driver distraction or unresponsiveness.</b><br><br>Examples include: \"Pay Attention\", \"Touch Steering Wheel\"."), ""},
{"RefuseVolume", tr("Refuse Volume"), tr("<b>Set the volume for alerts when openpilot refuses to engage.</b><br><br>Examples include: \"Brake Hold Active\", \"Door Open\", \"Seatbelt Unlatched\"."), ""},
{"WarningSoftVolume", tr("Warning Soft Volume"), tr("<b>Set the volume for softer warnings about potential risks.</b><br><br>Examples include: \"BRAKE! Risk of Collision\", \"Steering Temporarily Unavailable\"."), ""},
{"WarningImmediateVolume", tr("Warning Immediate Volume"), tr("<b>Set the volume for the loudest warnings that require urgent attention.</b><br><br>Examples include: \"DISENGAGE IMMEDIATELY — Driver Distracted\", \"DISENGAGE IMMEDIATELY — Driver Unresponsive\"."), ""},
{"CustomAlerts", tr("FrogPilot Alerts"), tr("<b>Optional FrogPilot alerts</b> that highlight driving events in a more noticeable way."), "../../frogpilot/assets/toggle_icons/icon_green_light.png"},
{"GoatScream", tr("Goat Scream"), tr("<b>Play the infamous \"Goat Scream\" when the steering controller reaches its limit.</b> Based on the \"Turn Exceeds Steering Limit\" event."), ""},
{"GreenLightAlert", tr("Green Light Alert"), tr("<b>Play an alert when the model predicts a red light has turned green.</b><br><br><i><b>Disclaimer</b>: openpilot does not explicitly detect traffic lights. This alert is based on end-to-end model predictions from camera input and may trigger even when the light has not changed.</i>"), ""},
{"LeadDepartingAlert", tr("Lead Departing Alert"), tr("<b>Play an alert when the lead vehicle departs from a stop.</b>"), ""},
{"LoudBlindspotAlert", tr("Loud \"Car Detected in Blindspot\" Alert"), tr("<b>Play a louder alert if a vehicle is in the blind spot when attempting to change lanes.</b> Based on the \"Car Detected in Blindspot\" event."), ""},
{"SpeedLimitChangedAlert", tr("Speed Limit Changed Alert"), tr("<b>Play an alert when the posted speed limit changes.</b>"), ""}
};
for (const auto &[param, title, desc, icon] : soundsToggles) {
AbstractControl *soundsToggle;
if (param == "AlertVolumeControl") {
FrogPilotManageControl *alertVolumeControlToggle = new FrogPilotManageControl(param, title, desc, icon);
QObject::connect(alertVolumeControlToggle, &FrogPilotManageControl::manageButtonClicked, [soundsLayout, alertVolumeControlPanel]() {
soundsLayout->setCurrentWidget(alertVolumeControlPanel);
});
soundsToggle = alertVolumeControlToggle;
} else if (alertVolumeControlKeys.contains(param)) {
std::map<float, QString> volumeLabels;
for (int i = 0; i <= 101; ++i) {
volumeLabels[i] = i == 0 ? tr("Muted") : i == 101 ? tr("Auto") : QString::number(i) + "%";
}
std::vector<QString> alertButton{tr("Test")};
if (param == "WarningImmediateVolume" || param == "WarningSoftVolume") {
soundsToggle = new FrogPilotParamValueButtonControl(param, title, desc, icon, 25, 101, QString(), volumeLabels, 1, true, {}, alertButton, false, false);
} else {
soundsToggle = new FrogPilotParamValueButtonControl(param, title, desc, icon, 0, 101, QString(), volumeLabels, 1, true, {}, alertButton, false, false);
}
} else if (param == "CustomAlerts") {
FrogPilotManageControl *customAlertsToggle = new FrogPilotManageControl(param, title, desc, icon);
QObject::connect(customAlertsToggle, &FrogPilotManageControl::manageButtonClicked, [soundsLayout, customAlertsPanel]() {
soundsLayout->setCurrentWidget(customAlertsPanel);
});
soundsToggle = customAlertsToggle;
} else {
soundsToggle = new ParamControl(param, title, desc, icon);
}
toggles[param] = soundsToggle;
if (alertVolumeControlKeys.contains(param)) {
alertVolumeControlList->addItem(soundsToggle);
} else if (customAlertsKeys.contains(param)) {
customAlertsList->addItem(soundsToggle);
} else {
soundsList->addItem(soundsToggle);
parentKeys.insert(param);
}
if (FrogPilotManageControl *frogPilotManageToggle = qobject_cast<FrogPilotManageControl*>(soundsToggle)) {
QObject::connect(frogPilotManageToggle, &FrogPilotManageControl::manageButtonClicked, [this]() {
emit openSubPanel();
openDescriptions(forceOpenDescriptions, toggles);
});
}
QObject::connect(soundsToggle, &AbstractControl::hideDescriptionEvent, [this]() {
update();
});
QObject::connect(soundsToggle, &AbstractControl::showDescriptionEvent, [this]() {
update();
});
}
QObject::connect(parent, &FrogPilotSettingsWindow::closeSubPanel, [soundsLayout, soundsPanel, this] {
openDescriptions(forceOpenDescriptions, toggles);
soundsLayout->setCurrentWidget(soundsPanel);
});
QObject::connect(uiState(), &UIState::uiUpdate, this, &FrogPilotSoundsPanel::updateState);
for (auto &[key, toggle] : toggles) {
if (alertVolumeControlKeys.contains(key)) {
toggle->setVisible(true);
}
}
updateToggles();
}
void FrogPilotSoundsPanel::showEvent(QShowEvent *event) {
updateToggles();
}
void FrogPilotSoundsPanel::updateState(const UIState &s) {
if (!isVisible()) {
return;
}
started = s.scene.started;
}
void FrogPilotSoundsPanel::updateToggles() {
for (auto &[key, toggle] : toggles) {
if (parentKeys.contains(key)) {
toggle->setVisible(false);
}
}
for (auto &[key, toggle] : toggles) {
if (parentKeys.contains(key)) {
continue;
}
bool setVisible = parent->tuningLevel >= parent->frogpilotToggleLevels[key].toDouble();
if (key == "LoudBlindspotAlert") {
setVisible &= parent->hasBSM;
}
else if (key == "SpeedLimitChangedAlert") {
setVisible &= params.getBool("ShowSpeedLimits") || (parent->hasOpenpilotLongitudinal && params.getBool("SpeedLimitController"));
}
toggle->setVisible(setVisible);
if (setVisible) {
if (alertVolumeControlKeys.contains(key)) {
toggles["AlertVolumeControl"]->setVisible(true);
} else if (customAlertsKeys.contains(key)) {
toggles["CustomAlerts"]->setVisible(true);
}
}
}
openDescriptions(forceOpenDescriptions, toggles);
update();
}
+34
View File
@@ -0,0 +1,34 @@
#pragma once
#include "frogpilot/ui/qt/offroad/frogpilot_settings.h"
class FrogPilotSoundsPanel : public FrogPilotListWidget {
Q_OBJECT
public:
explicit FrogPilotSoundsPanel(FrogPilotSettingsWindow *parent, bool forceOpen = false);
signals:
void openSubPanel();
protected:
void showEvent(QShowEvent *event) override;
private:
void updateState(const UIState &s);
void updateToggles();
bool forceOpenDescriptions;
bool started;
std::map<QString, AbstractControl*> toggles;
QSet<QString> alertVolumeControlKeys {"DisengageVolume", "EngageVolume", "PromptDistractedVolume", "PromptVolume", "RefuseVolume", "WarningImmediateVolume", "WarningSoftVolume"};
QSet<QString> customAlertsKeys {"GoatScream", "GreenLightAlert", "LeadDepartingAlert", "LoudBlindspotAlert", "SpeedLimitChangedAlert"};
QSet<QString> parentKeys;
FrogPilotSettingsWindow *parent;
Params params;
};
+869
View File
@@ -0,0 +1,869 @@
#include "frogpilot/ui/qt/offroad/theme_settings.h"
bool isUserCreatedTheme(const QString &themeName) {
return themeName.endsWith("-user_created");
}
void updateAssetParam(const QString &assetParam, Params &params, const QString &value, bool add) {
QStringList assets = QString::fromStdString(params.get(assetParam.toStdString())).split(",", QString::SkipEmptyParts);
if (add) {
if (!assets.contains(value)) {
assets.append(value);
}
} else {
assets.removeAll(value);
}
assets.sort();
params.put(assetParam.toStdString(), assets.join(",").toStdString());
}
void deleteThemeAsset(QDir &directory, const QString &subFolder, const QString &assetParam, const QString &themeToDelete, Params &params) {
bool useFiles = subFolder.isEmpty();
QString baseName = themeToDelete.toLower();
baseName.replace("(", "-").replace(")", "").replace(" ", "-");
baseName.remove(QRegularExpression("[^a-z0-9\\-]"));
while (baseName.endsWith("-")) {
baseName.chop(1);
}
QString baseUnderscore = baseName;
baseUnderscore.replace("-", "_");
QStringList candidateNames = {
baseName,
baseName + "-user-created",
baseUnderscore,
baseUnderscore + "-user_created"
};
if (useFiles) {
QStringList files = directory.entryList(QDir::Files);
for (QString &file : files) {
QString normalizedFile = QFileInfo(file).baseName().toLower();
normalizedFile.replace("_", "-");
normalizedFile.remove(QRegularExpression("[^a-z0-9\\-~]"));
if (candidateNames.contains(normalizedFile)) {
QFile::remove(directory.filePath(file));
break;
}
}
} else {
for (QString &candidate : candidateNames) {
QString fullSubPath = QDir(candidate).filePath(subFolder);
QDir targetDir(directory.filePath(fullSubPath));
if (targetDir.exists()) {
targetDir.removeRecursively();
break;
}
}
}
updateAssetParam(assetParam, params, themeToDelete, true);
}
void downloadThemeAsset(const QString &input, const std::string &paramKey, const QString &assetParam, Params &params, Params &params_memory) {
QString output = input;
int tilde = output.indexOf("~");
if (tilde >= 0) {
output = output.left(tilde).toLower() + "~" + output.mid(tilde + 1);
} else {
output = output.toLower();
}
output.remove("(").remove(")");
output.replace(" ", input.contains("(") ? "-" : "_");
params_memory.put(paramKey, output.toStdString());
}
QStringList getHolidayThemes() {
return QStringList()
<< "New Year's"
<< "Valentine's Day"
<< "St. Patrick's Day"
<< "World Frog Day"
<< "April Fools"
<< "Easter"
<< "May the Fourth"
<< "Cinco de Mayo"
<< "Stitch Day"
<< "Fourth of July"
<< "Halloween"
<< "Thanksgiving"
<< "Christmas";
}
QStringList getThemeList(const bool &randomThemes, const QDir &themePacksDirectory, const QString &subFolder, const QString &assetParam, Params &params) {
bool useFiles = subFolder.isEmpty();
QString currentAsset = randomThemes ? "" : QString::fromStdString(params.get(assetParam.toStdString()));
QStringList themeList;
for (const QFileInfo &entry : themePacksDirectory.entryInfoList(QDir::Dirs | QDir::Files | QDir::NoDotAndDotDot)) {
if (entry.baseName() == currentAsset) {
continue;
}
if (useFiles && entry.isDir()) {
continue;
}
if (!useFiles) {
QString targetPath = QDir(entry.filePath()).filePath(subFolder);
if (!QFileInfo(targetPath).exists()) {
continue;
}
}
QString baseName = entry.baseName();
bool userCreated = isUserCreatedTheme(baseName);
if (userCreated) {
baseName = baseName.replace("-user_created", "");
}
int tildeIdx = baseName.indexOf("~");
QString creator;
if (tildeIdx >= 0) {
creator = baseName.mid(tildeIdx + 1);
baseName = baseName.left(tildeIdx);
}
QStringList parts = baseName.split(baseName.contains("-") ? "-" : "_", QString::SkipEmptyParts);
for (QString &part : parts) {
part[0] = part[0].toUpper();
}
QString displayName;
if (userCreated) {
displayName = parts.join(" ");
} else {
displayName = (parts.size() <= 1 || useFiles) ? parts.join(" ") : QString("%1 (%2)").arg(parts[0], parts.mid(1).join(" "));
}
if (userCreated) {
displayName += " 🌟";
}
if (!creator.isEmpty()) {
displayName += " - by: " + creator;
}
themeList.append(displayName);
}
return themeList;
}
QString getThemeName(const std::string &paramKey, Params &params) {
QString value = QString::fromStdString(params.get(paramKey));
QString baseName = value;
int tildeIdx = baseName.indexOf("~");
QString creator;
if (tildeIdx >= 0) {
creator = baseName.mid(tildeIdx + 1);
baseName = baseName.left(tildeIdx);
}
QStringList parts = baseName.split(baseName.contains("-") ? "-" : "_", QString::SkipEmptyParts);
for (QString &part : parts) {
part[0] = part[0].toUpper();
}
QString displayName;
if (baseName.contains("-") && parts.size() > 1) {
displayName = QString("%1 (%2)").arg(parts[0], parts.mid(1).join(" "));
} else {
displayName = parts.join(" ");
}
if (isUserCreatedTheme(value)) {
displayName = displayName.split(" (")[0] + " 🌟";
}
if (!creator.isEmpty()) {
displayName += " - by: " + creator;
}
return displayName;
}
QString storeThemeName(const QString &input, const std::string &paramKey, Params &params) {
QString output = input.toLower().remove("(").remove(")").remove("'").remove(".");
output.replace(" ", input.contains("(") ? "-" : "_");
output.replace("_🌟", "-user_created");
output = output.trimmed();
params.put(paramKey, output.toStdString());
return getThemeName(paramKey, params);
}
FrogPilotThemesPanel::FrogPilotThemesPanel(FrogPilotSettingsWindow *parent, bool forceOpen) : FrogPilotListWidget(parent), parent(parent) {
forceOpenDescriptions = forceOpen;
QStackedLayout *themesLayout = new QStackedLayout();
addItem(themesLayout);
FrogPilotListWidget *themesList = new FrogPilotListWidget(this);
ScrollView *themesPanel = new ScrollView(themesList, this);
themesLayout->addWidget(themesPanel);
FrogPilotListWidget *customThemesList = new FrogPilotListWidget(this);
ScrollView *customThemesPanel = new ScrollView(customThemesList, this);
themesLayout->addWidget(customThemesPanel);
const std::vector<std::tuple<QString, QString, QString, QString>> themeToggles {
{"CustomThemes", tr("Custom Themes"), tr("<b>The overall look and feel of openpilot.</b> Use the \"Theme Maker\" in \"The Pond\" to create and share your own themes!"), "../../frogpilot/assets/toggle_icons/icon_frog.png"},
{"ColorScheme", tr("Color Scheme"), tr("<b>The color scheme used throughout openpilot.</b> Use the \"Theme Maker\" in \"The Pond\" to create and share your own themes!"), ""},
{"DistanceIconPack", tr("Distance Button"), tr("<b>The distance button icons shown on the driving screen.</b> Use the \"Theme Maker\" in \"The Pond\" to create and share your own themes!"), ""},
{"IconPack", tr("Icon Pack"), tr("<b>The icon style used across openpilot.</b> Use the \"Theme Maker\" in \"The Pond\" to create and share your own themes!"), ""},
{"SignalAnimation", tr("Turn Signal"), tr("<b>Themed turn-signal animations.</b> Use the \"Theme Maker\" in \"The Pond\" to create and share your own themes!"), ""},
{"SoundPack", tr("Sound Pack"), tr("<b>The sound pack used by openpilot.</b> Use the \"Theme Maker\" in \"The Pond\" to create and share your own themes!"), ""},
{"WheelIcon", tr("Steering Wheel"), tr("<b>The steering-wheel icon</b> shown at the top-right of the driving screen. Use the \"Theme Maker\" in \"The Pond\" to create and share your own themes!"), ""},
{"DownloadStatusLabel", tr("Download Status"), "", ""},
{"HolidayThemes", tr("Holiday Themes"), tr("<b>Themes based on U.S. holidays.</b> Minor holidays last one day; major holidays (Christmas, Easter, Halloween) run for a full week."), "../../frogpilot/assets/toggle_icons/icon_calendar.png"},
{"RainbowPath", tr("Rainbow Path"), tr("<b>Color the driving path like a Mario Kartstyle \"Rainbow Road\".</b>"), "../../frogpilot/assets/toggle_icons/icon_rainbow.png"},
{"RandomEvents", tr("Random Events"), tr("<b>Occasional on-screen effects triggered by driving conditions.</b> These are purely a visual and don't impact how openpilot drives!"), "../../frogpilot/assets/toggle_icons/icon_random.png"},
{"RandomThemes", tr("Random Themes"), tr("<b>Pick a random theme between each drive</b> from the themes you have downloaded. Great for variety without changing settings while driving."), "../../frogpilot/assets/toggle_icons/icon_random_themes.png"},
{"StartupAlert", tr("Startup Alert"), tr("<b>Customize the \"Startup Alert\" message</b> shown at the start of each drive."), "../../frogpilot/assets/toggle_icons/icon_message.png"}
};
for (const auto &[param, title, desc, icon] : themeToggles) {
AbstractControl *themeToggle;
if (param == "CustomThemes") {
FrogPilotManageControl *personalizeOpenpilotToggle = new FrogPilotManageControl(param, title, desc, icon);
QObject::connect(personalizeOpenpilotToggle, &FrogPilotManageControl::manageButtonClicked, [customThemesPanel, themesLayout]() {
themesLayout->setCurrentWidget(customThemesPanel);
});
themeToggle = personalizeOpenpilotToggle;
} else if (param == "ColorScheme") {
manageColorSchemeButton = new FrogPilotButtonsControl(title, desc, icon, {tr("DELETE"), tr("DOWNLOAD"), tr("SELECT")});
QObject::connect(manageColorSchemeButton, &FrogPilotButtonsControl::buttonClicked, [this](int id) {
QStringList colorSchemes = getThemeList(randomThemes, QDir(themePacksDirectory.path()), "colors", "ColorScheme", params);
if (id == 0) {
QString colorSchemeToDelete = MultiOptionDialog::getSelection(tr("Select a color scheme to delete"), colorSchemes, "", this);
if (!colorSchemeToDelete.isEmpty() && ConfirmationDialog::confirm(tr("Delete the \"%1\" color scheme?").arg(colorSchemeToDelete), tr("Delete"), this)) {
colorsDownloaded = false;
deleteThemeAsset(themePacksDirectory, "colors", "DownloadableColors", colorSchemeToDelete, params);
}
} else if (id == 1) {
if (colorDownloading) {
cancellingDownload = true;
params_memory.putBool("CancelThemeDownload", true);
QTimer::singleShot(2500, [this]() {
cancellingDownload = false;
colorDownloading = false;
themeDownloading = false;
params_memory.putBool("CancelThemeDownload", false);
});
} else {
QStringList downloadableColorSchemes = QString::fromStdString(params.get("DownloadableColors")).split(",");
colorSchemeToDownload = MultiOptionDialog::getSelection(tr("Select a color scheme to download"), downloadableColorSchemes, "", this);
if (!colorSchemeToDownload.isEmpty()) {
colorDownloading = true;
themeDownloading = true;
params_memory.put("ThemeDownloadProgress", "Downloading...");
downloadThemeAsset(colorSchemeToDownload, "ColorToDownload", "DownloadableColors", params, params_memory);
downloadStatusLabel->setText(tr("Downloading..."));
}
}
} else if (id == 2) {
colorSchemes.append("Stock");
colorSchemes.append(getHolidayThemes());
colorSchemes.sort();
QString colorSchemeToSelect = MultiOptionDialog::getSelection(tr("Select a color scheme"), colorSchemes, getThemeName("ColorScheme", params), this);
if (!colorSchemeToSelect.isEmpty()) {
manageColorSchemeButton->setValue(storeThemeName(colorSchemeToSelect, "ColorScheme", params));
}
}
});
manageColorSchemeButton->setValue(getThemeName(param.toStdString(), params));
themeToggle = manageColorSchemeButton;
} else if (param == "DistanceIconPack") {
manageDistanceIconPackButton = new FrogPilotButtonsControl(title, desc, icon, {tr("DELETE"), tr("DOWNLOAD"), tr("SELECT")});
QObject::connect(manageDistanceIconPackButton, &FrogPilotButtonsControl::buttonClicked, [this](int id) {
QStringList distanceIconPacks = getThemeList(randomThemes, QDir(themePacksDirectory.path()), "distance_icons", "DistanceIconPack", params);
if (id == 0) {
QString distanceIconPackToDelete = MultiOptionDialog::getSelection(tr("Select a distance icon pack to delete"), distanceIconPacks, "", this);
if (!distanceIconPackToDelete.isEmpty() && ConfirmationDialog::confirm(tr("Delete the \"%1\" distance icon pack?").arg(distanceIconPackToDelete), tr("Delete"), this)) {
distanceIconsDownloaded = false;
deleteThemeAsset(themePacksDirectory, "distance_icons", "DownloadableDistanceIcons", distanceIconPackToDelete, params);
}
} else if (id == 1) {
if (distanceIconDownloading) {
cancellingDownload = true;
params_memory.putBool("CancelThemeDownload", true);
QTimer::singleShot(2500, [this]() {
cancellingDownload = false;
distanceIconDownloading = false;
themeDownloading = false;
params_memory.putBool("CancelThemeDownload", false);
});
} else {
QStringList downloadableDistanceIconPacks = QString::fromStdString(params.get("DownloadableDistanceIcons")).split(",");
distanceIconPackToDownload = MultiOptionDialog::getSelection(tr("Select a distance icon pack to download"), downloadableDistanceIconPacks, "", this);
if (!distanceIconPackToDownload.isEmpty()) {
distanceIconDownloading = true;
themeDownloading = true;
params_memory.put("ThemeDownloadProgress", "Downloading...");
downloadThemeAsset(distanceIconPackToDownload, "DistanceIconToDownload", "DownloadableDistanceIcons", params, params_memory);
downloadStatusLabel->setText(tr("Downloading..."));
}
}
} else if (id == 2) {
distanceIconPacks.append("Stock");
distanceIconPacks.append(getHolidayThemes());
distanceIconPacks.sort();
QString distanceIconPackToSelect = MultiOptionDialog::getSelection(tr("Select a distance icon pack"), distanceIconPacks, getThemeName("DistanceIconPack", params), this);
if (!distanceIconPackToSelect.isEmpty()) {
manageDistanceIconPackButton->setValue(storeThemeName(distanceIconPackToSelect, "DistanceIconPack", params));
}
}
});
manageDistanceIconPackButton->setValue(getThemeName(param.toStdString(), params));
themeToggle = manageDistanceIconPackButton;
} else if (param == "IconPack") {
manageIconPackButton = new FrogPilotButtonsControl(title, desc, icon, {tr("DELETE"), tr("DOWNLOAD"), tr("SELECT")});
QObject::connect(manageIconPackButton, &FrogPilotButtonsControl::buttonClicked, [this](int id) {
QStringList iconPacks = getThemeList(randomThemes, QDir(themePacksDirectory.path()), "icons", "IconPack", params);
if (id == 0) {
QString iconPackToDelete = MultiOptionDialog::getSelection(tr("Select an icon pack to delete"), iconPacks, "", this);
if (!iconPackToDelete.isEmpty() && ConfirmationDialog::confirm(tr("Delete the \"%1\" icon pack?").arg(iconPackToDelete), tr("Delete"), this)) {
iconsDownloaded = false;
deleteThemeAsset(themePacksDirectory, "icons", "DownloadableIcons", iconPackToDelete, params);
}
} else if (id == 1) {
if (iconDownloading) {
cancellingDownload = true;
params_memory.putBool("CancelThemeDownload", true);
QTimer::singleShot(2500, [this]() {
cancellingDownload = false;
iconDownloading = false;
themeDownloading = false;
params_memory.putBool("CancelThemeDownload", false);
});
} else {
QStringList downloadableIconPacks = QString::fromStdString(params.get("DownloadableIcons")).split(",");
iconPackToDownload = MultiOptionDialog::getSelection(tr("Select an icon pack to download"), downloadableIconPacks, "", this);
if (!iconPackToDownload.isEmpty()) {
iconDownloading = true;
themeDownloading = true;
params_memory.put("ThemeDownloadProgress", "Downloading...");
downloadThemeAsset(iconPackToDownload, "IconToDownload", "DownloadableIcons", params, params_memory);
downloadStatusLabel->setText(tr("Downloading..."));
}
}
} else if (id == 2) {
iconPacks.append("Stock");
iconPacks.append(getHolidayThemes());
iconPacks.sort();
QString iconPackToSelect = MultiOptionDialog::getSelection(tr("Select an icon pack"), iconPacks, getThemeName("IconPack", params), this);
if (!iconPackToSelect.isEmpty()) {
manageIconPackButton->setValue(storeThemeName(iconPackToSelect, "IconPack", params));
}
}
});
manageIconPackButton->setValue(getThemeName(param.toStdString(), params));
themeToggle = manageIconPackButton;
} else if (param == "SignalAnimation") {
manageSignalAnimationButton = new FrogPilotButtonsControl(title, desc, icon, {tr("DELETE"), tr("DOWNLOAD"), tr("SELECT")});
QObject::connect(manageSignalAnimationButton, &FrogPilotButtonsControl::buttonClicked, [this](int id) {
QStringList signalAnimations = getThemeList(randomThemes, QDir(themePacksDirectory.path()), "signals", "SignalAnimation", params);
if (id == 0) {
QString signalAnimationToDelete = MultiOptionDialog::getSelection(tr("Select a signal animation to delete"), signalAnimations, "", this);
if (!signalAnimationToDelete.isEmpty() && ConfirmationDialog::confirm(tr("Delete the \"%1\" signal animation?").arg(signalAnimationToDelete), tr("Delete"), this)) {
signalsDownloaded = false;
deleteThemeAsset(themePacksDirectory, "signals", "DownloadableSignals", signalAnimationToDelete, params);
}
} else if (id == 1) {
if (signalDownloading) {
cancellingDownload = true;
params_memory.putBool("CancelThemeDownload", true);
QTimer::singleShot(2500, [this]() {
cancellingDownload = false;
signalDownloading = false;
themeDownloading = false;
params_memory.putBool("CancelThemeDownload", false);
});
} else {
QStringList downloadableSignalAnimations = QString::fromStdString(params.get("DownloadableSignals")).split(",");
signalAnimationToDownload = MultiOptionDialog::getSelection(tr("Select a signal animation to download"), downloadableSignalAnimations, "", this);
if (!signalAnimationToDownload.isEmpty()) {
signalDownloading = true;
themeDownloading = true;
params_memory.put("ThemeDownloadProgress", "Downloading...");
downloadThemeAsset(signalAnimationToDownload, "SignalToDownload", "DownloadableSignals", params, params_memory);
downloadStatusLabel->setText(tr("Downloading..."));
}
}
} else if (id == 2) {
signalAnimations.append("None");
signalAnimations.append(getHolidayThemes());
signalAnimations.sort();
QString signalAnimationToSelect = MultiOptionDialog::getSelection(tr("Select a signal animation"), signalAnimations, getThemeName("SignalAnimation", params), this);
if (!signalAnimationToSelect.isEmpty()) {
manageSignalAnimationButton->setValue(storeThemeName(signalAnimationToSelect, "SignalAnimation", params));
}
}
});
manageSignalAnimationButton->setValue(getThemeName(param.toStdString(), params));
themeToggle = manageSignalAnimationButton;
} else if (param == "SoundPack") {
manageSoundPackButton = new FrogPilotButtonsControl(title, desc, icon, {tr("DELETE"), tr("DOWNLOAD"), tr("SELECT")});
QObject::connect(manageSoundPackButton, &FrogPilotButtonsControl::buttonClicked, [this](int id) {
QStringList soundPacks = getThemeList(randomThemes, QDir(themePacksDirectory.path()), "sounds", "SoundPack", params);
if (id == 0) {
QString soundPackToDelete = MultiOptionDialog::getSelection(tr("Select a sound pack to delete"), soundPacks, "", this);
if (!soundPackToDelete.isEmpty() && ConfirmationDialog::confirm(tr("Delete the \"%1\" sound pack?").arg(soundPackToDelete), tr("Delete"), this)) {
soundsDownloaded = false;
deleteThemeAsset(themePacksDirectory, "sounds", "DownloadableSounds", soundPackToDelete, params);
}
} else if (id == 1) {
if (soundDownloading) {
cancellingDownload = true;
params_memory.putBool("CancelThemeDownload", true);
QTimer::singleShot(2500, [this]() {
cancellingDownload = false;
soundDownloading = false;
themeDownloading = false;
params_memory.putBool("CancelThemeDownload", false);
});
} else {
QStringList downloadableSoundPacks = QString::fromStdString(params.get("DownloadableSounds")).split(",");
soundPackToDownload = MultiOptionDialog::getSelection(tr("Select a sound pack to download"), downloadableSoundPacks, "", this);
if (!soundPackToDownload.isEmpty()) {
soundDownloading = true;
themeDownloading = true;
params_memory.put("ThemeDownloadProgress", "Downloading...");
downloadThemeAsset(soundPackToDownload, "SoundToDownload", "DownloadableSounds", params, params_memory);
downloadStatusLabel->setText(tr("Downloading..."));
}
}
} else if (id == 2) {
soundPacks.append("Stock");
soundPacks.append(getHolidayThemes());
soundPacks.sort();
QString soundPackToSelect = MultiOptionDialog::getSelection(tr("Select a sound pack"), soundPacks, getThemeName("SoundPack", params), this);
if (!soundPackToSelect.isEmpty()) {
manageSoundPackButton->setValue(storeThemeName(soundPackToSelect, "SoundPack", params));
}
}
});
manageSoundPackButton->setValue(getThemeName(param.toStdString(), params));
themeToggle = manageSoundPackButton;
} else if (param == "WheelIcon") {
manageWheelIconsButton = new FrogPilotButtonsControl(title, desc, icon, {tr("DELETE"), tr("DOWNLOAD"), tr("SELECT")});
QObject::connect(manageWheelIconsButton, &FrogPilotButtonsControl::buttonClicked, [this](int id) {
QStringList wheelIcons = getThemeList(randomThemes, QDir(wheelsDirectory.path()), "", "WheelIcon", params);
if (id == 0) {
QString wheelIconToDelete = MultiOptionDialog::getSelection(tr("Select a steering wheel to delete"), wheelIcons, "", this);
if (!wheelIconToDelete.isEmpty() && ConfirmationDialog::confirm(tr("Delete the \"%1\" steering wheel?").arg(wheelIconToDelete), tr("Delete"), this)) {
wheelsDownloaded = false;
deleteThemeAsset(wheelsDirectory, "", "DownloadableWheels", wheelIconToDelete, params);
}
} else if (id == 1) {
if (wheelDownloading) {
cancellingDownload = true;
params_memory.putBool("CancelThemeDownload", true);
QTimer::singleShot(2500, [this]() {
cancellingDownload = false;
wheelDownloading = false;
themeDownloading = false;
params_memory.putBool("CancelThemeDownload", false);
});
} else {
QStringList downloadableWheels = QString::fromStdString(params.get("DownloadableWheels")).split(",");
wheelToDownload = MultiOptionDialog::getSelection(tr("Select a steering wheel to download"), downloadableWheels, "", this);
if (!wheelToDownload.isEmpty()) {
wheelDownloading = true;
themeDownloading = true;
params_memory.put("ThemeDownloadProgress", "Downloading...");
downloadThemeAsset(wheelToDownload, "WheelToDownload", "DownloadableWheels", params, params_memory);
downloadStatusLabel->setText(tr("Downloading..."));
}
}
} else if (id == 2) {
wheelIcons.append("None");
wheelIcons.append("Stock");
wheelIcons.append(getHolidayThemes());
wheelIcons.sort();
QString steeringWheelToSelect = MultiOptionDialog::getSelection(tr("Select a steering wheel"), wheelIcons, getThemeName("WheelIcon", params), this);
if (!steeringWheelToSelect.isEmpty()) {
manageWheelIconsButton->setValue(storeThemeName(steeringWheelToSelect, "WheelIcon", params));
}
}
});
manageWheelIconsButton->setValue(getThemeName(param.toStdString(), params));
themeToggle = manageWheelIconsButton;
} else if (param == "DownloadStatusLabel") {
downloadStatusLabel = new LabelControl(title, tr("Idle"));
themeToggle = downloadStatusLabel;
} else if (param == "RandomThemes") {
std::vector<QString> randomThemesToggles{"RandomThemesHolidays"};
std::vector<QString> randomThemesToggleNames{tr("Include Holiday Themes")};
themeToggle = new FrogPilotButtonToggleControl(param, title, desc, icon, randomThemesToggles, randomThemesToggleNames);
} else if (param == "StartupAlert") {
FrogPilotButtonsControl *startupAlertButton = new FrogPilotButtonsControl(title, desc, icon, {tr("STOCK"), tr("FROGPILOT"), tr("CUSTOM"), tr("CLEAR")}, true);
QString currentTop = QString::fromStdString(params.get("StartupMessageTop"));
QString currentBottom = QString::fromStdString(params.get("StartupMessageBottom"));
QString stockTop = "Be ready to take over at any time";
QString stockBottom = "Always keep hands on wheel and eyes on road";
QString frogpilotTop = "Hop in and buckle up!";
QString frogpilotBottom = "Human-tested, frog-approved 🐸";
if (currentTop == stockTop && currentBottom == stockBottom) {
startupAlertButton->setCheckedButton(0);
} else if (currentTop == frogpilotTop && currentBottom == frogpilotBottom) {
startupAlertButton->setCheckedButton(1);
} else if (!currentTop.isEmpty() || !currentBottom.isEmpty()) {
startupAlertButton->setCheckedButton(2);
}
QObject::connect(startupAlertButton, &FrogPilotButtonsControl::buttonClicked, [=](int id) {
int maxLengthTop = 35;
int maxLengthBottom = 45;
if (id == 0) {
params.put("StartupMessageTop", stockTop.toStdString());
params.put("StartupMessageBottom", stockBottom.toStdString());
} else if (id == 1) {
params.put("StartupMessageTop", frogpilotTop.toStdString());
params.put("StartupMessageBottom", frogpilotBottom.toStdString());
} else if (id == 2) {
QString currentTop = QString::fromStdString(params.get("StartupMessageTop"));
QString newTop = InputDialog::getText(tr("Enter the text for the top half"), this, tr("Characters: 0/%1").arg(maxLengthTop), false, 1, currentTop, maxLengthTop).trimmed();
if (!newTop.isEmpty()) {
params.put("StartupMessageTop", newTop.toStdString());
QString currentBottom = QString::fromStdString(params.get("StartupMessageBottom"));
QString newBottom = InputDialog::getText(tr("Enter the text for the bottom half"), this, tr("Characters: 0/%1").arg(maxLengthBottom), false, 1, currentBottom, maxLengthBottom).trimmed();
if (!newBottom.isEmpty()) {
params.put("StartupMessageBottom", newBottom.toStdString());
}
}
} else if (id == 3) {
if (FrogPilotConfirmationDialog::yesorno(tr("Are you sure you want to completely reset your startup message?"), this)) {
params.remove("StartupMessageTop");
params.remove("StartupMessageBottom");
startupAlertButton->clearCheckedButtons();
}
}
});
themeToggle = startupAlertButton;
} else {
themeToggle = new ParamControl(param, title, desc, icon);
}
toggles[param] = themeToggle;
if (customThemeKeys.contains(param)) {
customThemesList->addItem(themeToggle);
} else {
themesList->addItem(themeToggle);
if (param == "CustomThemes") {
parentKeys.insert(param);
}
}
if (FrogPilotManageControl *frogPilotManageToggle = qobject_cast<FrogPilotManageControl*>(themeToggle)) {
QObject::connect(frogPilotManageToggle, &FrogPilotManageControl::manageButtonClicked, [this]() {
emit openSubPanel();
openDescriptions(forceOpenDescriptions, toggles);
});
}
QObject::connect(themeToggle, &AbstractControl::hideDescriptionEvent, [this]() {
update();
});
QObject::connect(themeToggle, &AbstractControl::showDescriptionEvent, [this]() {
update();
});
}
openDescriptions(forceOpenDescriptions, toggles);
QObject::connect(static_cast<ToggleControl *>(toggles["CustomThemes"]), &ToggleControl::toggleFlipped, this, &FrogPilotThemesPanel::updateToggles);
QObject::connect(static_cast<ToggleControl*>(toggles["RandomThemes"]), &ToggleControl::toggleFlipped, [this](bool state) {
if (state) {
ConfirmationDialog::alert(tr("\"Random Themes\" only works with downloaded themes, so make sure you download the themes you want it to use!"), this);
manageColorSchemeButton->setValue("");
manageColorSchemeButton->setVisibleButton(2, false);
manageDistanceIconPackButton->setValue("");
manageDistanceIconPackButton->setVisibleButton(2, false);
manageIconPackButton->setValue("");
manageIconPackButton->setVisibleButton(2, false);
manageSignalAnimationButton->setValue("");
manageSignalAnimationButton->setVisibleButton(2, false);
manageSoundPackButton->setValue("");
manageSoundPackButton->setVisibleButton(2, false);
manageWheelIconsButton->setValue("");
manageWheelIconsButton->setVisibleButton(2, false);
} else {
manageColorSchemeButton->setValue(getThemeName("ColorScheme", params));
manageColorSchemeButton->setVisibleButton(2, true);
manageDistanceIconPackButton->setValue(getThemeName("DistanceIconPack", params));
manageDistanceIconPackButton->setVisibleButton(2, true);
manageIconPackButton->setValue(getThemeName("IconPack", params));
manageIconPackButton->setVisibleButton(2, true);
manageSignalAnimationButton->setValue(getThemeName("SignalAnimation", params));
manageSignalAnimationButton->setVisibleButton(2, true);
manageSoundPackButton->setValue(getThemeName("SoundPack", params));
manageSoundPackButton->setVisibleButton(2, true);
manageWheelIconsButton->setValue(getThemeName("WheelIcon", params));
manageWheelIconsButton->setVisibleButton(2, true);
}
randomThemes = state;
});
QObject::connect(parent, &FrogPilotSettingsWindow::closeSubPanel, [themesLayout, themesPanel, this] {
openDescriptions(forceOpenDescriptions, toggles);
themesLayout->setCurrentWidget(themesPanel);
});
QObject::connect(uiState(), &UIState::uiUpdate, this, &FrogPilotThemesPanel::updateState);
}
void FrogPilotThemesPanel::showEvent(QShowEvent *event) {
colorsDownloaded = params.get("DownloadableColors").empty();
distanceIconsDownloaded = params.get("DownloadableDistanceIcons").empty();
iconsDownloaded = params.get("DownloadableIcons").empty();
signalsDownloaded = params.get("DownloadableSignals").empty();
soundsDownloaded = params.get("DownloadableSounds").empty();
wheelsDownloaded = params.get("DownloadableWheels").empty();
if (params.getBool("RandomThemes")) {
manageColorSchemeButton->setValue("");
manageColorSchemeButton->setVisibleButton(2, false);
manageDistanceIconPackButton->setValue("");
manageDistanceIconPackButton->setVisibleButton(2, false);
manageIconPackButton->setValue("");
manageIconPackButton->setVisibleButton(2, false);
manageSignalAnimationButton->setValue("");
manageSignalAnimationButton->setVisibleButton(2, false);
manageSoundPackButton->setValue("");
manageSoundPackButton->setVisibleButton(2, false);
manageWheelIconsButton->setValue("");
manageWheelIconsButton->setVisibleButton(2, false);
randomThemes = true;
}
updateToggles();
}
void FrogPilotThemesPanel::updateState(const UIState &s, const FrogPilotUIState &fs) {
if (!isVisible() || finalizingDownload) {
return;
}
const FrogPilotUIScene &frogpilot_scene = fs.frogpilot_scene;
if (themeDownloading) {
QString progress = QString::fromStdString(params_memory.get("ThemeDownloadProgress"));
bool downloadFailed = progress.contains(QRegularExpression("cancelled|exists|failed|offline", QRegularExpression::CaseInsensitiveOption));
if (progress != "Downloading...") {
static const QMap<QString, QString> progressTranslations = {
{"Download cancelled...", tr("Download cancelled...")},
{"Download failed...", tr("Download failed...")},
{"Downloaded!", tr("Downloaded!")},
{"GitHub and GitLab are offline...", tr("GitHub and GitLab are offline...")},
{"Repository unavailable", tr("Repository unavailable")},
{"Unpacking theme...", tr("Unpacking theme...")},
{"Verifying authenticity...", tr("Verifying authenticity...")}
};
if (progressTranslations.contains(progress)) {
downloadStatusLabel->setText(progressTranslations[progress]);
} else if (progress.endsWith("%")) {
downloadStatusLabel->setText(progress);
} else {
downloadStatusLabel->setText(tr("Idle"));
}
}
if (progress == "Downloaded!" || downloadFailed) {
finalizingDownload = true;
QTimer::singleShot(2500, [this]() {
colorDownloading = false;
distanceIconDownloading = false;
finalizingDownload = false;
iconDownloading = false;
signalDownloading = false;
soundDownloading = false;
themeDownloading = false;
wheelDownloading = false;
colorsDownloaded = params.get("DownloadableColors").empty();
distanceIconsDownloaded = params.get("DownloadableDistanceIcons").empty();
iconsDownloaded = params.get("DownloadableIcons").empty();
signalsDownloaded = params.get("DownloadableSignals").empty();
soundsDownloaded = params.get("DownloadableSounds").empty();
wheelsDownloaded = params.get("DownloadableWheels").empty();
params_memory.remove("CancelThemeDownload");
params_memory.remove("ThemeDownloadProgress");
downloadStatusLabel->setText(tr("Idle"));
});
}
}
bool parked = !s.scene.started || frogpilot_scene.parked || parent->isFrogsGoMoo;
manageColorSchemeButton->setText(1, colorDownloading ? tr("CANCEL") : tr("DOWNLOAD"));
manageColorSchemeButton->setEnabledButtons(0, !themeDownloading);
manageColorSchemeButton->setEnabledButtons(1, frogpilot_scene.online && (!themeDownloading || colorDownloading) && !cancellingDownload && !finalizingDownload && !colorsDownloaded && parked);
manageColorSchemeButton->setEnabledButtons(2, !themeDownloading);
manageDistanceIconPackButton->setText(1, distanceIconDownloading ? tr("CANCEL") : tr("DOWNLOAD"));
manageDistanceIconPackButton->setEnabledButtons(0, !themeDownloading);
manageDistanceIconPackButton->setEnabledButtons(1, frogpilot_scene.online && (!themeDownloading || distanceIconDownloading) && !cancellingDownload && !finalizingDownload && !distanceIconsDownloaded && parked);
manageDistanceIconPackButton->setEnabledButtons(2, !themeDownloading);
manageIconPackButton->setText(1, iconDownloading ? tr("CANCEL") : tr("DOWNLOAD"));
manageIconPackButton->setEnabledButtons(0, !themeDownloading);
manageIconPackButton->setEnabledButtons(1, frogpilot_scene.online && (!themeDownloading || iconDownloading) && !cancellingDownload && !finalizingDownload && !iconsDownloaded && parked);
manageIconPackButton->setEnabledButtons(2, !themeDownloading);
manageSignalAnimationButton->setText(1, signalDownloading ? tr("CANCEL") : tr("DOWNLOAD"));
manageSignalAnimationButton->setEnabledButtons(0, !themeDownloading);
manageSignalAnimationButton->setEnabledButtons(1, frogpilot_scene.online && (!themeDownloading || signalDownloading) && !cancellingDownload && !finalizingDownload && !signalsDownloaded && parked);
manageSignalAnimationButton->setEnabledButtons(2, !themeDownloading);
manageSoundPackButton->setText(1, soundDownloading ? tr("CANCEL") : tr("DOWNLOAD"));
manageSoundPackButton->setEnabledButtons(0, !themeDownloading);
manageSoundPackButton->setEnabledButtons(1, frogpilot_scene.online && (!themeDownloading || soundDownloading) && !cancellingDownload && !finalizingDownload && !soundsDownloaded && parked);
manageSoundPackButton->setEnabledButtons(2, !themeDownloading);
manageWheelIconsButton->setText(1, wheelDownloading ? tr("CANCEL") : tr("DOWNLOAD"));
manageWheelIconsButton->setEnabledButtons(0, !themeDownloading);
manageWheelIconsButton->setEnabledButtons(1, frogpilot_scene.online && (!themeDownloading || wheelDownloading) && !cancellingDownload && !finalizingDownload && !wheelsDownloaded && parked);
manageWheelIconsButton->setEnabledButtons(2, !themeDownloading);
parent->keepScreenOn = themeDownloading;
}
void FrogPilotThemesPanel::updateToggles() {
for (auto &[key, toggle] : toggles) {
if (parentKeys.contains(key)) {
toggle->setVisible(false);
}
}
for (auto &[key, toggle] : toggles) {
if (parentKeys.contains(key)) {
continue;
}
bool setVisible = parent->tuningLevel >= parent->frogpilotToggleLevels[key].toDouble();
if (key == "DistanceIconPack") {
setVisible &= params.getBool("QOLVisuals") && params.getBool("OnroadDistanceButton");
}
else if (key == "RandomThemes") {
setVisible &= params.getBool("CustomThemes");
}
toggle->setVisible(setVisible);
if (setVisible) {
if (customThemeKeys.contains(key)) {
toggles["CustomThemes"]->setVisible(true);
}
}
}
openDescriptions(forceOpenDescriptions, toggles);
update();
}
+67
View File
@@ -0,0 +1,67 @@
#pragma once
#include "frogpilot/ui/qt/offroad/frogpilot_settings.h"
class FrogPilotThemesPanel : public FrogPilotListWidget {
Q_OBJECT
public:
explicit FrogPilotThemesPanel(FrogPilotSettingsWindow *parent, bool forceOpen = false);
protected:
void showEvent(QShowEvent *event) override;
signals:
void openSubPanel();
private:
void updateState(const UIState &s, const FrogPilotUIState &fs);
void updateToggles();
bool cancellingDownload;
bool colorDownloading;
bool colorsDownloaded;
bool distanceIconDownloading;
bool distanceIconsDownloaded;
bool finalizingDownload;
bool forceOpenDescriptions;
bool iconDownloading;
bool iconsDownloaded;
bool randomThemes;
bool signalDownloading;
bool signalsDownloaded;
bool soundDownloading;
bool soundsDownloaded;
bool themeDownloading;
bool wheelDownloading;
bool wheelsDownloaded;
std::map<QString, AbstractControl*> toggles;
QSet<QString> customThemeKeys = {"ColorScheme", "DistanceIconPack", "DownloadStatusLabel", "IconPack", "SignalAnimation", "SoundPack", "WheelIcon"};
QSet<QString> parentKeys;
FrogPilotButtonsControl *manageColorSchemeButton;
FrogPilotButtonsControl *manageDistanceIconPackButton;
FrogPilotButtonsControl *manageIconPackButton;
FrogPilotButtonsControl *manageSignalAnimationButton;
FrogPilotButtonsControl *manageSoundPackButton;
FrogPilotButtonsControl *manageWheelIconsButton;
FrogPilotSettingsWindow *parent;
LabelControl *downloadStatusLabel;
QDir themePacksDirectory{"/data/themes/theme_packs/"};
QDir wheelsDirectory{"/data/themes/steering_wheels/"};
QString colorSchemeToDownload;
QString distanceIconPackToDownload;
QString iconPackToDownload;
QString signalAnimationToDownload;
QString soundPackToDownload;
QString wheelToDownload;
Params params;
};
+5
View File
@@ -0,0 +1,5 @@
#include "frogpilot/ui/qt/offroad/utilities.h"
FrogPilotUtilitiesPanel::FrogPilotUtilitiesPanel(FrogPilotSettingsWindow *parent, bool forceOpen) : FrogPilotListWidget(parent), parent(parent) {
forceOpenDescriptions = forceOpen;
}
+17
View File
@@ -0,0 +1,17 @@
#pragma once
#include "frogpilot/ui/qt/offroad/frogpilot_settings.h"
class FrogPilotUtilitiesPanel : public FrogPilotListWidget {
Q_OBJECT
public:
explicit FrogPilotUtilitiesPanel(FrogPilotSettingsWindow *parent, bool forceOpen = false);
private:
bool forceOpenDescriptions;
FrogPilotSettingsWindow *parent;
Params params;
};
+281
View File
@@ -0,0 +1,281 @@
#include "frogpilot/ui/qt/offroad/vehicle_settings.h"
FrogPilotVehiclesPanel::FrogPilotVehiclesPanel(FrogPilotSettingsWindow *parent, bool forceOpen) : FrogPilotListWidget(parent), parent(parent) {
forceOpenDescriptions = forceOpen;
QStackedLayout *vehiclesLayout = new QStackedLayout();
addItem(vehiclesLayout);
FrogPilotListWidget *settingsList = new FrogPilotListWidget(this);
ScrollView *vehiclesPanel = new ScrollView(settingsList, this);
vehiclesLayout->addWidget(vehiclesPanel);
FrogPilotListWidget *gmList = new FrogPilotListWidget(this);
FrogPilotListWidget *hkgList = new FrogPilotListWidget(this);
FrogPilotListWidget *subaruList = new FrogPilotListWidget(this);
FrogPilotListWidget *toyotaList = new FrogPilotListWidget(this);
FrogPilotListWidget *vehicleInfoList = new FrogPilotListWidget(this);
ScrollView *gmPanel = new ScrollView(gmList, this);
ScrollView *hkgPanel = new ScrollView(hkgList, this);
ScrollView *subaruPanel = new ScrollView(subaruList, this);
ScrollView *toyotaPanel = new ScrollView(toyotaList, this);
ScrollView *vehicleInfoPanel = new ScrollView(vehicleInfoList, this);
vehiclesLayout->addWidget(gmPanel);
vehiclesLayout->addWidget(hkgPanel);
vehiclesLayout->addWidget(subaruPanel);
vehiclesLayout->addWidget(toyotaPanel);
vehiclesLayout->addWidget(vehicleInfoPanel);
std::vector<std::tuple<QString, QString, QString, QString>> vehicleToggles {
{"GMToggles", tr("General Motors Settings"), tr("<b>FrogPilot features for General Motors vehicles.</b>"), ""},
{"VoltSNG", tr("Stop-and-Go Hack"), tr("<b>Force stop-and-go</b> on the 2017 Chevy Volt."), ""},
{"HKGToggles", tr("Hyundai/Kia/Genesis Settings"), tr("<b>FrogPilot features for Genesis, Hyundai, and Kia vehicles.</b>"), ""},
{"TacoTuneHacks", tr("\"Taco Bell Run\" Torque Hack"), tr("<b>The steering torque hack from comma's 2022 \"Taco Bell Run\".</b> Designed to increase steering torque at low speeds for left and right turns."), ""},
{"SubaruToggles", tr("Subaru Settings"), tr("<b>FrogPilot features for Subaru vehicles.</b>"), ""},
{"SubaruSNG", tr("Stop and Go"), tr("Stop and go for supported Subaru vehicles."), ""},
{"ToyotaToggles", tr("Toyota/Lexus Settings"), tr("<b>FrogPilot features for Lexus and Toyota vehicles.</b>"), ""},
{"ToyotaDoors", tr("Automatically Lock/Unlock Doors"), tr("<b>Automatically lock/unlock doors</b> when shifting in and out of drive."), ""},
{"ClusterOffset", tr("Dashboard Speed Offset"), tr("<b>The speed offset openpilot uses to match the speed on the dashboard display.</b>"), ""},
{"FrogsGoMoosTweak", tr("FrogsGoMoo's Personal Tweaks"), tr("<b>Personal tweaks by FrogsGoMoo for quicker acceleration and smoother braking.</b>"), ""},
{"LockDoorsTimer", tr("Lock Doors On Ignition Off After"), tr("<b>Automatically lock the doors on ignition off</b> when no one is detected in the front seats."), ""},
{"SNGHack", tr("Stop-and-Go Hack"), tr("<b>Force stop-and-go</b> on Lexus/Toyota vehicles without stock stop-and-go functionality."), ""},
{"VehicleInfo", tr("Vehicle Info"), tr("<b>Information about your vehicle in regards to openpilot support and functionality.</b>"), ""},
{"HardwareDetected", tr("3rd Party Hardware Detected"), tr("<b>Detected 3rd party hardware.</b>"), ""},
{"BlindSpotSupport", tr("Blind Spot Support"), tr("<b>Does openpilot use the vehicle's blind spot data?</b>"), ""},
{"PedalSupport", tr("comma Pedal Support"), tr("<b>Does your vehicle support the \"comma pedal\"?</b>"), ""},
{"OpenpilotLongitudinal", tr("openpilot Longitudinal Support"), tr("<b>Can openpilot control the vehicle's acceleration and braking?</b>"), ""},
{"RadarSupport", tr("Radar Support"), tr("<b>Does openpilot use the vehicle's radar data</b> alongside the device's camera for tracking lead vehicles?"), ""},
{"SDSUSupport", tr("SDSU Support"), tr("<b>Does your vehicle support \"SDSUs\"?</b>"), ""},
{"SNGSupport", tr("Stop-and-Go Support"), tr("<b>Does your vehicle support stop-and-go driving?</b>"), ""}
};
for (const auto &[param, title, desc, icon] : vehicleToggles) {
AbstractControl *vehicleToggle;
if (param == "GMToggles") {
ButtonControl *gmButton = new ButtonControl(title, tr("MANAGE"), desc);
QObject::connect(gmButton, &ButtonControl::clicked, [vehiclesLayout, gmPanel, this]() {
openDescriptions(forceOpenDescriptions, toggles);
vehiclesLayout->setCurrentWidget(gmPanel);
});
vehicleToggle = gmButton;
} else if (param == "HKGToggles") {
ButtonControl *hkgButton = new ButtonControl(title, tr("MANAGE"), desc);
QObject::connect(hkgButton, &ButtonControl::clicked, [vehiclesLayout, hkgPanel, this]() {
openDescriptions(forceOpenDescriptions, toggles);
vehiclesLayout->setCurrentWidget(hkgPanel);
});
vehicleToggle = hkgButton;
} else if (param == "SubaruToggles") {
ButtonControl *subaruButton = new ButtonControl(title, tr("MANAGE"), desc);
QObject::connect(subaruButton, &ButtonControl::clicked, [vehiclesLayout, subaruPanel, this]() {
openDescriptions(forceOpenDescriptions, toggles);
vehiclesLayout->setCurrentWidget(subaruPanel);
});
vehicleToggle = subaruButton;
} else if (param == "ToyotaToggles") {
ButtonControl *toyotaButton = new ButtonControl(title, tr("MANAGE"), desc);
QObject::connect(toyotaButton, &ButtonControl::clicked, [vehiclesLayout, toyotaPanel, this]() {
openDescriptions(forceOpenDescriptions, toggles);
vehiclesLayout->setCurrentWidget(toyotaPanel);
});
vehicleToggle = toyotaButton;
} else if (param == "ToyotaDoors") {
std::vector<QString> lockToggles{"LockDoors", "UnlockDoors"};
std::vector<QString> lockToggleNames{tr("Lock"), tr("Unlock")};
vehicleToggle = new FrogPilotButtonToggleControl(param, title, desc, icon, lockToggles, lockToggleNames);
} else if (param == "LockDoorsTimer") {
std::map<float, QString> autoLockLabels;
for (int i = 0; i <= 300; ++i) {
autoLockLabels[i] = i == 0 ? tr("Never") : QString::number(i) + tr(" seconds");
}
vehicleToggle = new FrogPilotParamValueControl(param, title, desc, icon, 0, 300, QString(), autoLockLabels, 5);
} else if (param == "ClusterOffset") {
std::vector<QString> clusterOffsetButton{"Reset"};
FrogPilotParamValueButtonControl *clusterOffsetToggle = new FrogPilotParamValueButtonControl(param, title, desc, icon, 1.000, 1.050, "x", std::map<float, QString>(), 0.001, false, {}, clusterOffsetButton, false, false);
QObject::connect(clusterOffsetToggle, &FrogPilotParamValueButtonControl::buttonClicked, [clusterOffsetToggle, this]() {
params.putFloat("ClusterOffset", std::stof(params.getKeyDefaultValue("ClusterOffset").value()));
clusterOffsetToggle->refresh();
});
vehicleToggle = clusterOffsetToggle;
} else if (param == "VehicleInfo") {
ButtonControl *VehicleInfoButton = new ButtonControl(title, tr("VIEW"), desc);
QObject::connect(VehicleInfoButton, &ButtonControl::clicked, [vehiclesLayout, vehicleInfoPanel, this]() {
openDescriptions(forceOpenDescriptions, toggles);
vehiclesLayout->setCurrentWidget(vehicleInfoPanel);
});
vehicleToggle = VehicleInfoButton;
} else if (vehicleInfoKeys.contains(param)) {
vehicleToggle = new LabelControl(title, "", desc);
} else {
vehicleToggle = new ParamControl(param, title, desc, icon);
}
toggles[param] = vehicleToggle;
if (gmKeys.contains(param)) {
gmList->addItem(vehicleToggle);
} else if (hkgKeys.contains(param)) {
hkgList->addItem(vehicleToggle);
} else if (subaruKeys.contains(param)) {
subaruList->addItem(vehicleToggle);
} else if (toyotaKeys.contains(param)) {
toyotaList->addItem(vehicleToggle);
} else if (vehicleInfoKeys.contains(param)) {
vehicleInfoList->addItem(vehicleToggle);
} else {
settingsList->addItem(vehicleToggle);
parentKeys.insert(param);
}
if (ButtonControl *buttonControl = qobject_cast<ButtonControl*>(vehicleToggle)) {
QObject::connect(buttonControl, &ButtonControl::clicked, this, &FrogPilotVehiclesPanel::openSubPanel);
}
QObject::connect(vehicleToggle, &AbstractControl::hideDescriptionEvent, [this]() {
update();
});
QObject::connect(vehicleToggle, &AbstractControl::showDescriptionEvent, [this]() {
update();
});
}
static_cast<FrogPilotParamValueControl*>(toggles["LockDoorsTimer"])->setWarning("<b>Warning:</b> openpilot can't detect if keys are still inside the car, so ensure you have a spare key to prevent accidental lockouts!");
QSet<QString> rebootKeys = {"TacoTuneHacks"};
for (const QString &key : rebootKeys) {
QObject::connect(static_cast<ToggleControl*>(toggles[key]), &ToggleControl::toggleFlipped, [key, this](bool state) {
if (started) {
if (key == "TacoTuneHacks" && state) {
if (FrogPilotConfirmationDialog::toggleReboot(this)) {
Hardware::reboot();
}
} else if (key != "TacoTuneHacks") {
if (FrogPilotConfirmationDialog::toggleReboot(this)) {
Hardware::reboot();
}
}
}
});
}
openDescriptions(forceOpenDescriptions, toggles);
QObject::connect(parent, &FrogPilotSettingsWindow::closeSubPanel, [vehiclesLayout, vehiclesPanel, this] {
if (forceOpenDescriptions) {
openDescriptions(forceOpenDescriptions, toggles);
}
vehiclesLayout->setCurrentWidget(vehiclesPanel);
});
QObject::connect(uiState(), &UIState::uiUpdate, this, &FrogPilotVehiclesPanel::updateState);
}
void FrogPilotVehiclesPanel::showEvent(QShowEvent *event) {
if (forceOpenDescriptions) {
}
QStringList detected;
if (parent->hasPedal) detected << "comma Pedal";
if (parent->hasSDSU) detected << "SDSU";
if (parent->hasZSS) detected << "ZSS";
static_cast<LabelControl*>(toggles["HardwareDetected"])->setText(detected.isEmpty() ? tr("None") : detected.join(", "));
static_cast<LabelControl*>(toggles["BlindSpotSupport"])->setText(parent->hasBSM ? tr("Yes") : tr("No"));
static_cast<LabelControl*>(toggles["OpenpilotLongitudinal"])->setText(parent->hasOpenpilotLongitudinal ? tr("Yes") : tr("No"));
static_cast<LabelControl*>(toggles["PedalSupport"])->setText(parent->canUsePedal ? tr("Yes") : tr("No"));
static_cast<LabelControl*>(toggles["RadarSupport"])->setText(parent->hasRadar ? tr("Yes") : tr("No"));
static_cast<LabelControl*>(toggles["SDSUSupport"])->setText(parent->canUseSDSU ? tr("Yes") : tr("No"));
static_cast<LabelControl*>(toggles["SNGSupport"])->setText(parent->hasSNG ? tr("Yes") : tr("No"));
updateToggles();
}
void FrogPilotVehiclesPanel::updateState(const UIState &s) {
if (!isVisible()) {
return;
}
started = s.scene.started;
}
void FrogPilotVehiclesPanel::updateToggles() {
for (auto &[key, toggle] : toggles) {
if (parentKeys.contains(key)) {
toggle->setVisible(false);
}
}
for (auto &[key, toggle] : toggles) {
if (parentKeys.contains(key)) {
continue;
}
bool setVisible = parent->tuningLevel >= parent->frogpilotToggleLevels[key].toDouble();
if (gmKeys.contains(key)) {
setVisible &= parent->isGM;
} else if (hkgKeys.contains(key)) {
setVisible &= parent->isHKG;
} else if (subaruKeys.contains(key)) {
setVisible &= parent->isSubaru;
} else if (toyotaKeys.contains(key)) {
setVisible &= parent->isToyota;
} else if (vehicleInfoKeys.contains(key)) {
setVisible = true;
}
if (longitudinalKeys.contains(key)) {
setVisible &= parent->hasOpenpilotLongitudinal;
}
if (key == "SNGHack") {
setVisible &= !parent->hasSNG;
}
else if (key == "SubaruSNG") {
setVisible &= parent->hasSNG;
}
else if (key == "TacoTuneHacks") {
setVisible &= parent->isHKGCanFd;
}
else if (key == "VoltSNG") {
setVisible &= parent->isVolt && !parent->hasSNG;
}
toggle->setVisible(setVisible);
if (setVisible) {
if (gmKeys.contains(key)) {
toggles["GMToggles"]->setVisible(true);
} else if (hkgKeys.contains(key)) {
toggles["HKGToggles"]->setVisible(true);
} else if (subaruKeys.contains(key)) {
toggles["SubaruToggles"]->setVisible(true);
} else if (toyotaKeys.contains(key)) {
toggles["ToyotaToggles"]->setVisible(true);
} else if (vehicleInfoKeys.contains(key)) {
toggles["VehicleInfo"]->setVisible(true);
}
}
}
openDescriptions(forceOpenDescriptions, toggles);
update();
}
@@ -0,0 +1,38 @@
#pragma once
#include "frogpilot/ui/qt/offroad/frogpilot_settings.h"
class FrogPilotVehiclesPanel : public FrogPilotListWidget {
Q_OBJECT
public:
explicit FrogPilotVehiclesPanel(FrogPilotSettingsWindow *parent, bool forceOpen = false);
signals:
void openSubPanel();
protected:
void showEvent(QShowEvent *event) override;
private:
void updateState(const UIState &s);
void updateToggles();
bool forceOpenDescriptions;
bool started;
std::map<QString, AbstractControl*> toggles;
QSet<QString> gmKeys = {"VoltSNG"};
QSet<QString> hkgKeys = {"TacoTuneHacks"};
QSet<QString> longitudinalKeys = {"FrogsGoMoosTweak", "SNGHack", "VoltSNG"};
QSet<QString> subaruKeys = {"SubaruSNG"};
QSet<QString> toyotaKeys = {"ClusterOffset", "FrogsGoMoosTweak", "LockDoorsTimer", "SNGHack", "ToyotaDoors"};
QSet<QString> vehicleInfoKeys = {"BlindSpotSupport", "HardwareDetected", "OpenpilotLongitudinal", "PedalSupport", "RadarSupport", "SDSUSupport", "SNGSupport"};
QSet<QString> parentKeys;
FrogPilotSettingsWindow *parent;
Params params;
};
+328
View File
@@ -0,0 +1,328 @@
#include "frogpilot/ui/qt/offroad/visual_settings.h"
FrogPilotVisualsPanel::FrogPilotVisualsPanel(FrogPilotSettingsWindow *parent, bool forceOpen) : FrogPilotListWidget(parent), parent(parent) {
forceOpenDescriptions = forceOpen;
QStackedLayout *visualsLayout = new QStackedLayout();
addItem(visualsLayout);
FrogPilotListWidget *visualsList = new FrogPilotListWidget(this);
ScrollView *visualsPanel = new ScrollView(visualsList, this);
visualsLayout->addWidget(visualsPanel);
FrogPilotListWidget *advancedCustomList = new FrogPilotListWidget(this);
FrogPilotListWidget *customUIList = new FrogPilotListWidget(this);
FrogPilotListWidget *modelUIList = new FrogPilotListWidget(this);
FrogPilotListWidget *navigationUIList = new FrogPilotListWidget(this);
FrogPilotListWidget *qualityOfLifeList = new FrogPilotListWidget(this);
ScrollView *advancedCustomPanel = new ScrollView(advancedCustomList, this);
ScrollView *customUIPanel = new ScrollView(customUIList, this);
ScrollView *modelUIPanel = new ScrollView(modelUIList, this);
ScrollView *navigationUIPanel = new ScrollView(navigationUIList, this);
ScrollView *qualityOfLifePanel = new ScrollView(qualityOfLifeList, this);
visualsLayout->addWidget(advancedCustomPanel);
visualsLayout->addWidget(customUIPanel);
visualsLayout->addWidget(modelUIPanel);
visualsLayout->addWidget(navigationUIPanel);
visualsLayout->addWidget(qualityOfLifePanel);
const std::vector<std::tuple<QString, QString, QString, QString>> visualToggles {
{"AdvancedCustomUI", tr("Advanced UI Controls"), tr("<b>Advanced visual changes</b> to fine-tune how the driving screen looks."), "../../frogpilot/assets/toggle_icons/icon_advanced_device.png"},
{"HideSpeed", tr("Hide Current Speed"), tr("<b>Hide the current speed</b> from the driving screen."), ""},
{"HideLeadMarker", tr("Hide Lead Marker"), tr("<b>Hide the lead-vehicle marker</b> from the driving screen."), ""},
{"HideMaxSpeed", tr("Hide Max Speed"), tr("<b>Hide the max speed</b> from the driving screen."), ""},
{"HideAlerts", tr("Hide Non-Critical Alerts"), tr("<b>Hide non-critical alerts</b> from the driving screen."), ""},
{"HideSpeedLimit", tr("Hide Speed Limits"), tr("<b>Hide posted speed limits</b> from the driving screen."), ""},
{"WheelSpeed", tr("Use Wheel Speed"), tr("<b>Use the vehicle's wheel speed</b> instead of the cluster speed. This is purely a visual change and doesn't impact how openpilot drives!"), ""},
{"CustomUI", tr("Driving Screen Widgets"), tr("<b>Custom FrogPilot widgets</b> for the driving screen."), "../assets/icons/calibration.png"},
{"AccelerationPath", tr("Acceleration Path"), tr("<b>Color the driving path by planned acceleration and braking.</b>"), ""},
{"AdjacentPath", tr("Adjacent Lanes"), tr("<b>Show the driving paths for the left and right lanes.</b>"), ""},
{"BlindSpotPath", tr("Blind Spot Path"), tr("<b>Show a red path when a vehicle is in that lane's blind spot.</b>"), ""},
{"Compass", tr("Compass"), tr("<b>Show the current driving direction</b> with a simple on-screen compass."), ""},
{"OnroadDistanceButton", tr("Driving Personality Button"), tr("<b>Control and view the current driving personality</b> via a driving screen widget."), ""},
{"PedalsOnUI", tr("Gas / Brake Pedal Indicators"), tr("<b>On-screen gas and brake indicators.</b><br><br><b>Dynamic</b>: Opacity changes according to how much openpilot is accelerating or braking<br><b>Static</b>: Full when active, dim when not"), ""},
{"RotatingWheel", tr("Rotating Steering Wheel"), tr("<b>Rotate the driving screen wheel</b> with the physical steering wheel."), ""},
{"ModelUI", tr("Model UI"), tr("<b>Model visualizations</b> for the driving path, lane lines, path edges, and road edges."), "../../frogpilot/assets/toggle_icons/icon_road.png"},
{"DynamicPathWidth", tr("Dynamic Path Width"), tr("<b>Change the path width based on engagement.</b><br><br><b>Fully Engaged</b>: 100%<br><b>Always On Lateral</b>: 75%<br><b>Disengaged</b>: 50%"), ""},
{"LaneLinesWidth", tr("Lane Lines Width"), tr("<b>Set the lane-line thickness.</b><br><br>Default matches the MUTCD lane-line width standard of 4 inches."), ""},
{"PathEdgeWidth", tr("Path Edges Width"), tr("<b>Set the driving-path edge width</b> that represents different driving modes and statuses.<br><br>Default is 20% of the total path width.<br><br>Color Guide:<br><br>- <b>Light Blue</b>: Always On Lateral<br>- <b>Green</b>: Default<br>- <b>Orange</b>: Experimental Mode<br>- <b>Red</b>: Traffic Mode<br>- <b>Yellow</b>: Conditional Experimental Mode overridden"), ""},
{"PathWidth", tr("Path Width"), tr("<b>Set the driving-path width.</b><br><br>Default (6.1 feet) matches the width of a 2019 Lexus ES 350."), ""},
{"RoadEdgesWidth", tr("Road Edges Width"), tr("<b>Set the road-edge thickness.</b><br><br>Default matches half of the MUTCD lane-line width standard of 4 inches."), ""},
{"NavigationUI", tr("Navigation Widgets"), tr("<b>Speed limits, and other navigation widgets.</b>"), "../../frogpilot/assets/toggle_icons/icon_map.png"},
{"RoadNameUI", tr("Road Name"), tr("<b>Display the road name at the bottom of the driving screen</b> using data from \"OpenStreetMap (OSM)\"."), ""},
{"ShowSpeedLimits", tr("Show Speed Limits"), tr("<b>Show speed limits</b> in the top-left corner of the driving screen. Uses data from the car's dashboard (if supported) and \"OpenStreetMap (OSM)\"."), ""},
{"SLCMapboxFiller", tr("Show Speed Limits from Mapbox"), tr("<b>Use Mapbox speed-limit data when no other source is available.</b>"), ""},
{"UseVienna", tr("Use Vienna-Style Speed Signs"), tr("<b>Show Vienna-style (EU) speed-limit signs</b> instead of MUTCD (US)."), ""},
{"QOLVisuals", tr("Quality of Life"), tr("<b>Miscellaneous visual changes</b> to fine-tune how the driving screen looks."), "../../frogpilot/assets/toggle_icons/icon_quality_of_life.png"},
{"CameraView", tr("Camera View"), tr("<b>Select the active camera view.</b> This is purely a visual change and doesn't impact how openpilot drives!"), ""},
{"DriverCamera", tr("Show Driver Camera When In Reverse"), tr("<b>Show the driver camera feed</b> when the vehicle is in reverse."), ""},
{"StoppedTimer", tr("Stopped Timer"), tr("<b>Show a timer when stopped</b> in place of the current speed to indicate how long the vehicle has been stopped."), ""}
};
for (const auto &[param, title, desc, icon] : visualToggles) {
AbstractControl *visualToggle;
if (param == "AdvancedCustomUI") {
FrogPilotManageControl *advancedCustomUIToggle = new FrogPilotManageControl(param, title, desc, icon);
QObject::connect(advancedCustomUIToggle, &FrogPilotManageControl::manageButtonClicked, [visualsLayout, advancedCustomPanel]() {
visualsLayout->setCurrentWidget(advancedCustomPanel);
});
visualToggle = advancedCustomUIToggle;
} else if (param == "CustomUI") {
FrogPilotManageControl *customUIToggle = new FrogPilotManageControl(param, title, desc, icon);
QObject::connect(customUIToggle, &FrogPilotManageControl::manageButtonClicked, [visualsLayout, customUIPanel]() {
visualsLayout->setCurrentWidget(customUIPanel);
});
visualToggle = customUIToggle;
} else if (param == "PedalsOnUI") {
std::vector<QString> pedalsToggles{"DynamicPedalsOnUI", "StaticPedalsOnUI"};
std::vector<QString> pedalsToggleNames{tr("Dynamic"), tr("Static")};
FrogPilotButtonToggleControl *pedalsToggle = new FrogPilotButtonToggleControl(param, title, desc, icon, pedalsToggles, pedalsToggleNames, true);
QObject::connect(pedalsToggle, &FrogPilotButtonToggleControl::buttonClicked, [this](int id) {
if (id == 0) {
params.putBool("StaticPedalsOnUI", false);
} else if (id == 1) {
params.putBool("DynamicPedalsOnUI", false);
}
});
visualToggle = pedalsToggle;
} else if (param == "ModelUI") {
FrogPilotManageControl *modelUIToggle = new FrogPilotManageControl(param, title, desc, icon);
QObject::connect(modelUIToggle, &FrogPilotManageControl::manageButtonClicked, [visualsLayout, modelUIPanel]() {
visualsLayout->setCurrentWidget(modelUIPanel);
});
visualToggle = modelUIToggle;
} else if (param == "LaneLinesWidth" || param == "RoadEdgesWidth") {
visualToggle = new FrogPilotParamValueControl(param, title, desc, icon, 0, 24, tr(" inches"));
} else if (param == "PathEdgeWidth") {
std::map<float, QString> pathEdgeLabels;
for (int i = 0; i <= 100; ++i) {
pathEdgeLabels[i] = i == 0 ? tr("Off") : QString::number(i) + "%";
}
visualToggle = new FrogPilotParamValueControl(param, title, desc, icon, 0, 100, QString(), pathEdgeLabels);
} else if (param == "PathWidth") {
visualToggle = new FrogPilotParamValueControl(param, title, desc, icon, 0, 10, tr(" feet"), std::map<float, QString>(), 0.1);
} else if (param == "NavigationUI") {
FrogPilotManageControl *navigationUIToggle = new FrogPilotManageControl(param, title, desc, icon);
QObject::connect(navigationUIToggle, &FrogPilotManageControl::manageButtonClicked, [visualsLayout, navigationUIPanel]() {
visualsLayout->setCurrentWidget(navigationUIPanel);
});
visualToggle = navigationUIToggle;
} else if (param == "QOLVisuals") {
FrogPilotManageControl *qolToggle = new FrogPilotManageControl(param, title, desc, icon);
QObject::connect(qolToggle, &FrogPilotManageControl::manageButtonClicked, [visualsLayout, qualityOfLifePanel]() {
visualsLayout->setCurrentWidget(qualityOfLifePanel);
});
visualToggle = qolToggle;
} else if (param == "CameraView") {
std::vector<QString> cameraOptions{tr("Auto"), tr("Driver"), tr("Standard"), tr("Wide")};
ButtonParamControl *cameraSelection = new ButtonParamControl(param, title, desc, icon, cameraOptions);
visualToggle = cameraSelection;
} else {
visualToggle = new ParamControl(param, title, desc, icon);
}
toggles[param] = visualToggle;
if (advancedCustomOnroadUIKeys.contains(param)) {
advancedCustomList->addItem(visualToggle);
} else if (customOnroadUIKeys.contains(param)) {
customUIList->addItem(visualToggle);
} else if (modelUIKeys.contains(param)) {
modelUIList->addItem(visualToggle);
} else if (navigationUIKeys.contains(param)) {
navigationUIList->addItem(visualToggle);
} else if (qualityOfLifeKeys.contains(param)) {
qualityOfLifeList->addItem(visualToggle);
} else {
visualsList->addItem(visualToggle);
parentKeys.insert(param);
}
if (FrogPilotManageControl *frogPilotManageToggle = qobject_cast<FrogPilotManageControl*>(visualToggle)) {
QObject::connect(frogPilotManageToggle, &FrogPilotManageControl::manageButtonClicked, [this]() {
emit openSubPanel();
openDescriptions(forceOpenDescriptions, toggles);
});
}
QObject::connect(visualToggle, &AbstractControl::hideDescriptionEvent, [this]() {
update();
});
QObject::connect(visualToggle, &AbstractControl::showDescriptionEvent, [this]() {
update();
});
}
QSet<QString> forceUpdateKeys = {"HideLeadMarker", "ShowSpeedLimits"};
for (const QString &key : forceUpdateKeys) {
QObject::connect(static_cast<ToggleControl*>(toggles[key]), &ToggleControl::toggleFlipped, this, &FrogPilotVisualsPanel::updateToggles);
}
openDescriptions(forceOpenDescriptions, toggles);
QObject::connect(parent, &FrogPilotSettingsWindow::closeSubPanel, [visualsLayout, visualsPanel, this] {
openDescriptions(forceOpenDescriptions, toggles);
visualsLayout->setCurrentWidget(visualsPanel);
});
QObject::connect(parent, &FrogPilotSettingsWindow::closeSubSubPanel, [this]() {
openDescriptions(forceOpenDescriptions, toggles);
});
QObject::connect(parent, &FrogPilotSettingsWindow::updateMetric, this, &FrogPilotVisualsPanel::updateMetric);
}
void FrogPilotVisualsPanel::showEvent(QShowEvent *event) {
updateToggles();
}
void FrogPilotVisualsPanel::updateMetric(bool metric, bool bootRun) {
static bool previousMetric;
if (metric != previousMetric && !bootRun) {
double distanceConversion = metric ? FOOT_TO_METER : METER_TO_FOOT;
double smallDistanceConversion = metric ? INCH_TO_CM : CM_TO_INCH;
params.putIntNonBlocking("LaneLinesWidth", params.getInt("LaneLinesWidth") * smallDistanceConversion);
params.putIntNonBlocking("RoadEdgesWidth", params.getInt("RoadEdgesWidth") * smallDistanceConversion);
params.putFloatNonBlocking("PathWidth", params.getFloat("PathWidth") * distanceConversion);
}
previousMetric = metric;
static std::map<float, QString> imperialDistanceLabels;
static std::map<float, QString> imperialSmallDistanceLabels;
static std::map<float, QString> metricDistanceLabels;
static std::map<float, QString> metricSmallDistanceLabels;
static bool labelsInitialized = false;
if (!labelsInitialized) {
for (int i = 0; i <= 10; ++i) {
imperialDistanceLabels[i] = i == 0 ? tr("Off") : i == 1 ? QString::number(i) + tr(" foot") : QString::number(i) + tr(" feet");
}
for (int i = 0; i <= 24; ++i) {
imperialSmallDistanceLabels[i] = i == 0 ? tr("Off") : i == 1 ? QString::number(i) + tr(" inch") : QString::number(i) + tr(" inches");
}
for (float i = 0.0f; i <= 3.0f; i += 0.1f) {
metricDistanceLabels[i] = i == 0.0f ? tr("Off") : i == 1.0 ? QString::number(i) + tr(" meter") : QString::number(i, 'f', 1) + tr(" meters");
}
for (int i = 0; i <= 60; ++i) {
metricSmallDistanceLabels[i] = i == 0 ? tr("Off") : i == 1 ? QString::number(i) + tr(" centimeter") : QString::number(i) + tr(" centimeters");
}
labelsInitialized = true;
}
FrogPilotParamValueControl *laneLinesWidthToggle = static_cast<FrogPilotParamValueControl*>(toggles["LaneLinesWidth"]);
FrogPilotParamValueControl *pathWidthToggle = static_cast<FrogPilotParamValueControl*>(toggles["PathWidth"]);
FrogPilotParamValueControl *roadEdgesWidthToggle = static_cast<FrogPilotParamValueControl*>(toggles["RoadEdgesWidth"]);
if (metric) {
laneLinesWidthToggle->setDescription(tr("<b>Set the lane-line thickness.</b><br><br>Default matches the MUTCD lane-line width standard of 10 centimeters."));
pathWidthToggle->setDescription(tr("<b>Set the driving-path width.</b><br><br>Default (1.9 meters) matches the width of a 2019 Lexus ES 350."));
roadEdgesWidthToggle->setDescription(tr("<b>Set the road-edge thickness.</b><br><br>Default matches half of the MUTCD lane-line width standard of 10 centimeters."));
laneLinesWidthToggle->updateControl(0, 60, metricSmallDistanceLabels);
roadEdgesWidthToggle->updateControl(0, 60, metricSmallDistanceLabels);
pathWidthToggle->updateControl(0, 3, metricDistanceLabels);
} else {
laneLinesWidthToggle->setDescription(tr("<b>Set the lane-line thickness.</b><br><br>Default matches the MUTCD lane-line width standard of 4 inches."));
pathWidthToggle->setDescription(tr("<b>Set the driving-path width.</b><br><br>Default (6.1 feet) matches the width of a 2019 Lexus ES 350."));
roadEdgesWidthToggle->setDescription(tr("<b>Set the road-edge thickness.</b><br><br>Default matches half of the MUTCD lane-line width standard of 4 inches."));
laneLinesWidthToggle->updateControl(0, 24, imperialSmallDistanceLabels);
roadEdgesWidthToggle->updateControl(0, 24, imperialSmallDistanceLabels);
pathWidthToggle->updateControl(0, 10, imperialDistanceLabels);
}
}
void FrogPilotVisualsPanel::updateToggles() {
for (auto &[key, toggle] : toggles) {
if (parentKeys.contains(key)) {
toggle->setVisible(false);
}
}
for (auto &[key, toggle] : toggles) {
if (parentKeys.contains(key)) {
continue;
}
bool setVisible = parent->tuningLevel >= parent->frogpilotToggleLevels[key].toDouble();
if (key == "AccelerationPath") {
setVisible &= parent->hasOpenpilotLongitudinal;
}
else if (key == "BlindSpotPath") {
setVisible &= parent->hasBSM;
}
else if (key == "HideLeadMarker") {
setVisible &= parent->hasOpenpilotLongitudinal;
}
else if (key == "HideSpeedLimit") {
setVisible &= parent->hasOpenpilotLongitudinal && params.getBool("SpeedLimitController");
}
else if (key == "OnroadDistanceButton") {
setVisible &= parent->hasOpenpilotLongitudinal;
}
else if (key == "PedalsOnUI") {
setVisible &= parent->hasOpenpilotLongitudinal;
}
else if (key == "ShowSpeedLimits") {
setVisible &= !params.getBool("SpeedLimitController") || !parent->hasOpenpilotLongitudinal;
}
else if (key == "SLCMapboxFiller") {
setVisible &= params.getBool("ShowSpeedLimits");
setVisible &= !params.getBool("SpeedLimitController") || !parent->hasOpenpilotLongitudinal;
setVisible &= !params.get("MapboxSecretKey").empty();
}
else if (key == "UseVienna") {
setVisible &= params.getBool("ShowSpeedLimits") || params.getBool("SpeedLimitController");
}
toggle->setVisible(setVisible);
if (setVisible) {
if (advancedCustomOnroadUIKeys.contains(key)) {
toggles["AdvancedCustomUI"]->setVisible(true);
} else if (customOnroadUIKeys.contains(key)) {
toggles["CustomUI"]->setVisible(true);
} else if (modelUIKeys.contains(key)) {
toggles["ModelUI"]->setVisible(true);
} else if (navigationUIKeys.contains(key)) {
toggles["NavigationUI"]->setVisible(true);
} else if (qualityOfLifeKeys.contains(key)) {
toggles["QOLVisuals"]->setVisible(true);
}
}
}
openDescriptions(forceOpenDescriptions, toggles);
update();
}
+37
View File
@@ -0,0 +1,37 @@
#pragma once
#include "frogpilot/ui/qt/offroad/frogpilot_settings.h"
class FrogPilotVisualsPanel : public FrogPilotListWidget {
Q_OBJECT
public:
explicit FrogPilotVisualsPanel(FrogPilotSettingsWindow *parent, bool forceOpen = false);
signals:
void openSubPanel();
void openSubSubPanel();
protected:
void showEvent(QShowEvent *event) override;
private:
void updateMetric(bool metric, bool bootRun);
void updateToggles();
bool forceOpenDescriptions;
std::map<QString, AbstractControl*> toggles;
QSet<QString> advancedCustomOnroadUIKeys = {"HideAlerts", "HideLeadMarker", "HideMaxSpeed", "HideSpeed", "HideSpeedLimit", "WheelSpeed"};
QSet<QString> customOnroadUIKeys = {"AccelerationPath", "AdjacentPath", "BlindSpotPath", "Compass", "OnroadDistanceButton", "PedalsOnUI", "RotatingWheel"};
QSet<QString> modelUIKeys = {"DynamicPathWidth", "LaneLinesWidth", "PathEdgeWidth", "PathWidth", "RoadEdgesWidth"};
QSet<QString> navigationUIKeys = {"RoadNameUI", "ShowSpeedLimits", "SLCMapboxFiller", "UseVienna"};
QSet<QString> qualityOfLifeKeys = {"CameraView", "DriverCamera", "StoppedTimer"};
QSet<QString> parentKeys;
FrogPilotSettingsWindow *parent;
Params params;
};
+84
View File
@@ -0,0 +1,84 @@
#include "frogpilot/ui/qt/offroad/wheel_settings.h"
FrogPilotWheelPanel::FrogPilotWheelPanel(FrogPilotSettingsWindow *parent, bool forceOpen) : FrogPilotListWidget(parent), parent(parent) {
forceOpenDescriptions = forceOpen;
const std::vector<std::tuple<QString, QString, QString, QString>> wheelToggles {
{"DistanceButtonControl", tr("Distance Button"), tr("<b>Action performed when the \"Distance\" button is pressed.</b>"), "../../frogpilot/assets/toggle_icons/icon_mute.png"},
{"LongDistanceButtonControl", tr("Distance Button (Long Press)"), tr("<b>Action performed when the \"Distance\" button is pressed for more than 0.5 seconds.</b>"), "../../frogpilot/assets/toggle_icons/icon_mute.png"},
{"VeryLongDistanceButtonControl", tr("Distance Button (Very Long Press)"), tr("<b>Action performed when the \"Distance\" button is pressed for more than 2.5 seconds.</b>"), "../../frogpilot/assets/toggle_icons/icon_mute.png"},
{"LKASButtonControl", tr("LKAS Button"), tr("<b>Action performed when the \"LKAS\" button is pressed.</b>"), "../../frogpilot/assets/toggle_icons/icon_mute.png"}
};
for (const auto &[param, title, desc, icon] : wheelToggles) {
QMap<int, QString> functionsMap {
{0, tr("No Action")},
{3, tr("Pause Steering")}
};
QMap<int, QString> longitudinalFunctionsMap {
{1, tr("Change \"Personality Profile\"")},
{2, tr("Force openpilot to Coast")},
{4, tr("Pause Acceleration/Braking")},
{5, tr("Toggle \"Experimental Mode\" On/Off")},
{6, tr("Toggle \"Traffic Mode\" On/Off")}
};
ButtonControl *wheelToggle = new ButtonControl(title, tr("SELECT"), desc);
QObject::connect(wheelToggle, &ButtonControl::clicked, [functionsMap, longitudinalFunctionsMap, key = param, parent, wheelToggle, this]() mutable {
if (parent->hasOpenpilotLongitudinal) {
QMap<int, QString>::const_iterator it;
for (it = longitudinalFunctionsMap.constBegin(); it != longitudinalFunctionsMap.constEnd(); ++it) {
functionsMap[it.key()] = it.value();
}
}
QString selection = MultiOptionDialog::getSelection(tr("Select a function to assign to this button"), functionsMap.values(), functionsMap[params.getInt(key.toStdString())], this);
if (!selection.isEmpty()) {
params.putInt(key.toStdString(), functionsMap.key(selection));
wheelToggle->setValue(selection);
}
});
QMap<int, QString> mergedFunctionsMap = functionsMap;
QMap<int, QString>::const_iterator it;
for (it = longitudinalFunctionsMap.constBegin(); it != longitudinalFunctionsMap.constEnd(); ++it) {
mergedFunctionsMap[it.key()] = it.value();
}
wheelToggle->setValue(mergedFunctionsMap[params.getInt(param.toStdString())]);
toggles[param] = wheelToggle;
addItem(wheelToggle);
QObject::connect(wheelToggle, &AbstractControl::hideDescriptionEvent, [this]() {
update();
});
QObject::connect(wheelToggle, &AbstractControl::showDescriptionEvent, [this]() {
update();
});
}
openDescriptions(forceOpenDescriptions, toggles);
}
void FrogPilotWheelPanel::showEvent(QShowEvent *event) {
updateToggles();
}
void FrogPilotWheelPanel::updateToggles() {
for (auto &[key, toggle] : toggles) {
bool setVisible = parent->tuningLevel >= parent->frogpilotToggleLevels[key].toDouble();
if (key == "LKASButtonControl") {
setVisible &= !parent->isSubaru;
setVisible &= !parent->lkasAllowedForAOL || !(params.getBool("AlwaysOnLateral") && params.getBool("AlwaysOnLateralLKAS"));
}
toggle->setVisible(setVisible);
}
openDescriptions(forceOpenDescriptions, toggles);
update();
}
+24
View File
@@ -0,0 +1,24 @@
#pragma once
#include "frogpilot/ui/qt/offroad/frogpilot_settings.h"
class FrogPilotWheelPanel : public FrogPilotListWidget {
Q_OBJECT
public:
explicit FrogPilotWheelPanel(FrogPilotSettingsWindow *parent, bool forceOpen = false);
protected:
void showEvent(QShowEvent *event) override;
private:
void updateToggles();
bool forceOpenDescriptions;
std::map<QString, AbstractControl*> toggles;
FrogPilotSettingsWindow *parent;
Params params;
};
@@ -2,6 +2,16 @@
#include "frogpilot/ui/frogpilot_ui.h"
bool FrogPilotConfirmationDialog::toggleReboot(QWidget *parent) {
ConfirmationDialog d(tr("Reboot required to take effect."), tr("Reboot Now"), tr("Reboot Later"), false, parent);
return d.exec();
}
bool FrogPilotConfirmationDialog::yesorno(const QString &prompt_text, QWidget *parent) {
ConfirmationDialog d(prompt_text, tr("Yes"), tr("No"), false, parent);
return d.exec();
}
void clearMovie(QSharedPointer<QMovie> &movie, QWidget *parent) {
if (!movie) {
return;
@@ -92,3 +102,13 @@ void loadImage(const QString &basePath, QPixmap &pixmap, QSharedPointer<QMovie>
parent->update();
}
void openDescriptions(bool forceOpenDescriptions, std::map<QString, AbstractControl*> toggles) {
if (forceOpenDescriptions) {
for (auto &[key, toggle] : toggles) {
if (key != "CESpeed") {
toggle->showDescription();
}
}
}
}
@@ -17,3 +17,622 @@
void loadGif(const QString &gifPath, QSharedPointer<QMovie> &movie, const QSize &size, QWidget *parent);
void loadImage(const QString &basePath, QPixmap &pixmap, QSharedPointer<QMovie> &movie, const QSize &size, QWidget *parent);
void openDescriptions(bool forceOpenDescriptions, std::map<QString, AbstractControl*> toggles);
const QString buttonStyle = R"(
QPushButton {
padding: 0px 25px 0px 25px;
border-radius: 50px;
font-size: 35px;
font-weight: 500;
height: 100px;
color: #E4E4E4;
background-color: #393939;
}
QPushButton:pressed {
background-color: #4a4a4a;
}
QPushButton:checked:enabled {
background-color: #33Ab4C;
}
QPushButton:disabled {
color: #33E4E4E4;
}
)";
class FrogPilotConfirmationDialog : public ConfirmationDialog {
Q_OBJECT
public:
explicit FrogPilotConfirmationDialog(const QString &prompt_text, const QString &confirm_text,
const QString &cancel_text, const bool rich, QWidget *parent);
static bool toggleReboot(QWidget *parent);
static bool yesorno(const QString &prompt_text, QWidget *parent);
};
class FrogPilotListWidget : public QWidget {
Q_OBJECT
public:
explicit FrogPilotListWidget(QWidget *parent = 0) : QWidget(parent), outer_layout(this) {
outer_layout.setMargin(0);
outer_layout.setSpacing(0);
outer_layout.addLayout(&inner_layout);
inner_layout.setMargin(0);
inner_layout.setSpacing(25); // default spacing is 25
outer_layout.addStretch();
}
inline void addItem(QWidget *w, bool expanding = false) {
w->setSizePolicy(QSizePolicy::Preferred, expanding ? QSizePolicy::Expanding : QSizePolicy::Fixed);
inner_layout.addWidget(w);
}
inline void addItem(QLayout *layout) { inner_layout.addLayout(layout); }
inline void insertItem(int index, QWidget *w, bool expanding = false) {
w->setSizePolicy(QSizePolicy::Preferred, expanding ? QSizePolicy::Expanding : QSizePolicy::Fixed);
inner_layout.insertWidget(index, w);
}
inline void setSpacing(int spacing) { inner_layout.setSpacing(spacing); }
void clear() {
while (QLayoutItem *child = inner_layout.takeAt(0)) {
if (child->widget()) {
child->widget()->deleteLater();
}
delete child;
}
outer_layout.addStretch();
}
private:
void paintEvent(QPaintEvent *) override {
QPainter p(this);
p.setPen(Qt::gray);
for (int i = 0; i < inner_layout.count() - 1; ++i) {
QWidget *widget = inner_layout.itemAt(i)->widget();
QWidget *nextWidget = nullptr;
for (int j = i + 1; j < inner_layout.count(); ++j) {
nextWidget = inner_layout.itemAt(j)->widget();
if (nextWidget != nullptr && nextWidget->isVisible()) {
break;
}
}
if (widget == nullptr || (widget->isVisible() && nextWidget->isVisible())) {
QRect r = inner_layout.itemAt(i)->geometry();
int bottom = r.bottom() + inner_layout.spacing() / 2;
p.drawLine(r.left() + 40, bottom, r.right() - 40, bottom);
}
}
}
QVBoxLayout outer_layout;
QVBoxLayout inner_layout;
};
class FrogPilotButtonControl : public ParamControl {
Q_OBJECT
public:
FrogPilotButtonControl(const QString &param, const QString &title, const QString &desc, const QString &icon,
const std::vector<QString> &button_texts, bool checkable = false,
bool exclusive = false, int minimum_button_width = 225) : ParamControl(param, title, desc, icon) {
key = param.toStdString();
button_group = new QButtonGroup(this);
button_group->setExclusive(exclusive);
for (int i = 0; i < button_texts.size(); i++) {
QPushButton *button = new QPushButton(button_texts[i], this);
button->setCheckable(checkable);
button->setStyleSheet(buttonStyle);
button->setMinimumWidth(minimum_button_width);
hlayout->addWidget(button);
button_group->addButton(button, i);
}
hlayout->addWidget(&toggle);
QObject::connect(button_group, QOverload<int>::of(&QButtonGroup::buttonClicked), [=](int id) {
emit buttonClicked(id);
});
QObject::connect(this, &ToggleControl::toggleFlipped, this, &FrogPilotButtonControl::refresh);
refresh();
}
virtual void refresh() {
bool state = params.getBool(key);
if (state != toggle.on) {
toggle.togglePosition();
}
for (QAbstractButton *button : button_group->buttons()) {
button->setEnabled(state);
}
}
void clearCheckedButtons() {
bool original_exclusive = button_group->exclusive();
button_group->setExclusive(false);
for (QAbstractButton *button : button_group->buttons()) {
button->setChecked(false);
}
button_group->setExclusive(original_exclusive);
}
void setCheckedButton(int id) {
if (QAbstractButton *button = button_group->button(id)) {
button->setChecked(true);
}
}
void setEnabledButton(int id, bool enable) {
if (QAbstractButton *button = button_group->button(id)) {
button->setEnabled(enable);
}
}
void setVisibleButton(int id, bool visible) {
if (QAbstractButton *button = button_group->button(id)) {
button->setVisible(visible);
}
}
signals:
void buttonClicked(int id);
protected:
std::string key;
Params params;
QButtonGroup *button_group;
};
class FrogPilotButtonsControl : public AbstractControl {
Q_OBJECT
public:
FrogPilotButtonsControl(const QString &title, const QString &desc, const QString &icon,
const std::vector<QString> &button_texts, const bool &checkable = false, const bool &exclusive = true,
const int minimum_button_width = 225) : AbstractControl(title, desc, icon) {
button_group = new QButtonGroup(this);
button_group->setExclusive(exclusive);
for (int i = 0; i < button_texts.size(); i++) {
QPushButton *button = new QPushButton(button_texts[i], this);
button->installEventFilter(this);
button->setCheckable(checkable);
button->setStyleSheet(buttonStyle);
button->setMinimumWidth(minimum_button_width);
hlayout->addWidget(button);
button_group->addButton(button, i);
}
QObject::connect(button_group, QOverload<int>::of(&QButtonGroup::buttonClicked), [=](int id) {
emit buttonClicked(id);
});
}
void clearCheckedButtons() {
bool original_exclusive = button_group->exclusive();
button_group->setExclusive(false);
for (QAbstractButton *button : button_group->buttons()) {
button->setChecked(false);
}
button_group->setExclusive(original_exclusive);
}
void setCheckedButton(int id) {
if (QAbstractButton *button = button_group->button(id)) {
button->setChecked(true);
}
}
void setEnabled(bool enable) {
for (QAbstractButton *button : button_group->buttons()) {
button->setEnabled(enable);
}
}
void setEnabledButtons(int id, bool enable) {
if (QAbstractButton *button = button_group->button(id)) {
button->setEnabled(enable);
}
}
void setText(int id, const QString &text) {
if (QAbstractButton *button = button_group->button(id)) {
button->setText(text);
}
}
void setVisibleButton(int id, bool visible) {
if (QAbstractButton *button = button_group->button(id)) {
button->setVisible(visible);
}
}
signals:
void buttonClicked(int id);
void disabledButtonClicked(int id);
protected:
bool eventFilter(QObject *obj, QEvent *event) override {
if (event->type() == QEvent::MouseButtonPress) {
QPushButton *button = qobject_cast<QPushButton *>(obj);
if (button && !button->isEnabled()) {
emit disabledButtonClicked(button_group->id(button));
}
}
return AbstractControl::eventFilter(obj, event);
}
private:
QButtonGroup *button_group;
};
class FrogPilotButtonToggleControl : public FrogPilotButtonControl {
Q_OBJECT
public:
FrogPilotButtonToggleControl(const QString &param, const QString &title, const QString &desc, const QString &icon,
const std::vector<QString> &button_params, const std::vector<QString> &button_texts,
bool exclusive = false, int minimum_button_width = 225)
: FrogPilotButtonControl(param, title, desc, icon, button_texts, true, exclusive, minimum_button_width), button_params(button_params) {
for (int i = 0; i < button_texts.size(); i++) {
button_group->buttons()[i]->setChecked(params.getBool(button_params[i].toStdString()));
}
QObject::connect(button_group, QOverload<int>::of(&QButtonGroup::buttonClicked), [=](int id) {
params.putBool(button_params[id].toStdString(), button_group->button(id)->isChecked());
emit buttonClicked(id);
});
}
void refresh() override {
FrogPilotButtonControl::refresh();
for (int i = 0; i < button_group->buttons().size(); i++) {
QAbstractButton *button = button_group->button(i);
button->setChecked(params.getBool(button_params[i].toStdString()));
}
}
private:
std::vector<QString> button_params;
};
class FrogPilotManageControl : public ParamControl {
Q_OBJECT
public:
FrogPilotManageControl(const QString &param, const QString &title, const QString &desc, const QString &icon) : ParamControl(param, title, desc, icon) {
key = param.toStdString();
manageButton = new ButtonControl("", tr("MANAGE"), "", this);
hlayout->insertWidget(hlayout->indexOf(&toggle) - 1, manageButton);
QObject::connect(manageButton, &ButtonControl::clicked, this, &FrogPilotManageControl::manageButtonClicked);
QObject::connect(this, &ToggleControl::toggleFlipped, this, &FrogPilotManageControl::refresh);
}
void refresh() {
manageButton->setEnabled(params.getBool(key));
}
void setManageVisibility(bool visible) {
manageButton->setVisible(visible);
}
void showEvent(QShowEvent *event) override {
refresh();
ParamControl::showEvent(event);
}
signals:
void manageButtonClicked();
private:
std::string key;
ButtonControl *manageButton;
Params params;
};
class FrogPilotParamValueControl : public AbstractControl {
Q_OBJECT
public:
FrogPilotParamValueControl(const QString &param, const QString &title, const QString &desc, const QString &icon,
float min_value, float max_value, const QString &label, const std::map<float, QString> &value_labels = {},
float interval = 1.0f, bool fast_increase = false, int label_width = 350)
: AbstractControl(title, desc, icon),
fast_increase(fast_increase), interval(interval), label(label), min_value(min_value), max_value(max_value), value_labels(value_labels) {
factor = std::pow(10, std::ceil(-std::log10(interval)));
key = param.toStdString();
key_type = params.getKeyType(key);
setupButton(decrement_button, "-");
setupButton(increment_button, "+");
value_label = new QLabel(this);
value_label->setAlignment(Qt::AlignRight | Qt::AlignVCenter);
value_label->setFixedSize(QSize(label_width, 100));
value_label->setStyleSheet("QLabel {color: #E0E879;}");
hlayout->addWidget(value_label);
hlayout->addWidget(&decrement_button);
hlayout->addWidget(&increment_button);
QObject::connect(&decrement_button, &QPushButton::pressed, this, &FrogPilotParamValueControl::decrementPressed);
QObject::connect(&increment_button, &QPushButton::pressed, this, &FrogPilotParamValueControl::incrementPressed);
last_action_timer.start();
}
void decrementPressed() {
if (display_warning && !warning_shown) {
showWarning();
}
if (last_action_timer.isValid() && last_action_timer.elapsed() > decrement_button.autoRepeatInterval() + 50) {
decrement_repeating = false;
}
float delta = decrement_repeating && fast_increase ? interval * 5 : interval;
value = std::max(value - delta, min_value);
updateValue();
if (std::lround(value / interval) % 5 == 0) {
decrement_repeating = true;
}
last_action_timer.restart();
}
void hideEvent(QHideEvent *event) override {
AbstractControl::hideEvent(event);
warning_shown = false;
updateParam();
}
void incrementPressed() {
if (display_warning && !warning_shown) {
showWarning();
}
if (last_action_timer.isValid() && last_action_timer.elapsed() > increment_button.autoRepeatInterval() + 50) {
increment_repeating = false;
}
float delta = increment_repeating && fast_increase ? interval * 5 : interval;
value = std::min(value + delta, max_value);
updateValue();
if (std::lround(value / interval) % 5 == 0) {
increment_repeating = true;
}
last_action_timer.restart();
}
void refresh() {
if (key_type == ParamKeyType::INT) {
value = std::clamp(std::round(params.getInt(key) * factor) / factor, min_value, max_value);
} else {
value = std::clamp(std::round(params.getFloat(key) * factor) / factor, min_value, max_value);
}
previous_value = value;
updateDisplay();
updateParam();
}
void setWarning(const QString &newWarning) {
display_warning = true;
warning = newWarning;
}
void setupButton(QPushButton &button, const QString &text) {
button.setAutoRepeat(true);
button.setAutoRepeatDelay(500);
button.setAutoRepeatInterval(150);
button.setFixedSize(150, 100);
button.setStyleSheet(buttonStyle);
if (text == "+" || text == "-") {
button.setStyleSheet(button.styleSheet() + " QPushButton { font-size: 50px; }");
}
button.setText(text);
}
void showEvent(QShowEvent *event) override {
refresh();
}
void showWarning() {
ConfirmationDialog::alert(warning, this);
warning_shown = true;
}
void updateControl(const float &newMinValue, const float &newMaxValue, const std::map<float, QString> &newValueLabels = {}) {
min_value = newMinValue;
max_value = newMaxValue;
value_labels = newValueLabels;
refresh();
}
void updateDisplay() {
QString displayText = QString::number(value) + label;
for (const std::pair<const float, QString> &entry : value_labels) {
if (std::lround(entry.first * factor) == std::lround(value * factor)) {
displayText = entry.second;
break;
}
}
decrement_button.setEnabled(value > min_value);
increment_button.setEnabled(value < max_value);
value_label->setText(displayText);
}
void updateParam() {
if (value == previous_value) {
return;
}
if (key_type == ParamKeyType::INT) {
params.putInt(key, value);
} else {
params.putFloat(key, value);
}
}
void updateValue() {
value = std::round(value * factor) / factor;
emit valueChanged(value);
updateDisplay();
}
signals:
void valueChanged(float value);
protected:
QLabel *value_label;
private:
bool decrement_repeating;
bool display_warning;
bool fast_increase;
bool increment_repeating;
bool warning_shown;
float interval;
float factor;
float max_value;
float min_value;
float previous_value;
float value;
std::map<float, QString> value_labels;
std::string key;
ParamKeyType key_type;
Params params;
QElapsedTimer last_action_timer;
QPushButton decrement_button;
QPushButton increment_button;
QString label;
QString warning;
};
class FrogPilotParamValueButtonControl : public FrogPilotParamValueControl {
Q_OBJECT
public:
FrogPilotParamValueButtonControl(const QString &param, const QString &title, const QString &desc, const QString &icon,
float min_value, float max_value, const QString &label, const std::map<float, QString> &value_labels,
float interval, bool fast_increase, const std::vector<QString> &button_params, const std::vector<QString> &button_texts,
bool left_button = false, bool checkable = true, int minimum_button_width = 225)
: FrogPilotParamValueControl(param, title, desc, icon, min_value, max_value, label, value_labels, interval, fast_increase, 200),
button_params(button_params), checkable(checkable) {
button_group = new QButtonGroup(this);
button_group->setExclusive(false);
for (int i = 0; i < button_texts.size(); i++) {
QPushButton *button = new QPushButton(button_texts[i], this);
button->setCheckable(checkable);
button->setChecked(checkable && params.getBool(button_params[i].toStdString()));
button->setStyleSheet(buttonStyle);
button->setMinimumWidth(minimum_button_width);
if (left_button) {
hlayout->insertWidget(hlayout->indexOf(value_label) - 1, button);
} else {
hlayout->addWidget(button);
}
button_group->addButton(button, i);
}
QObject::connect(button_group, QOverload<int>::of(&QButtonGroup::buttonClicked), [=](int id) {
if (checkable) {
params.putBool(button_params[id].toStdString(), button_group->button(id)->isChecked());
}
emit buttonClicked(id);
});
}
void refresh() {
if (checkable) {
for (int i = 0; i < button_group->buttons().size(); i++) {
QAbstractButton *button = button_group->button(i);
button->setChecked(params.getBool(button_params[i].toStdString()));
}
}
FrogPilotParamValueControl::refresh();
}
void showEvent(QShowEvent *event) override {
refresh();
FrogPilotParamValueControl::showEvent(event);
}
public:
void setEnabledButtons(int id, bool enable) {
if (QAbstractButton *button = button_group->button(id)) {
button->setEnabled(enable);
}
}
signals:
void buttonClicked(int id);
private:
bool checkable;
std::vector<QString> button_params;
Params params;
QButtonGroup *button_group;
};
class FrogPilotDualParamValueControl : public QFrame {
Q_OBJECT
public:
FrogPilotDualParamValueControl(FrogPilotParamValueControl *control1, FrogPilotParamValueControl *control2, QWidget *parent = nullptr) : QFrame(parent), control1(control1), control2(control2) {
QHBoxLayout *hlayout = new QHBoxLayout(this);
hlayout->addWidget(control1);
hlayout->addWidget(control2);
}
void updateControl(const float &newMinValue, const float &newMaxValue, const std::map<float, QString> &newValueLabels = {}) {
control1->updateControl(newMinValue, newMaxValue, newValueLabels);
control2->updateControl(newMinValue, newMaxValue, newValueLabels);
}
void refresh() {
control1->refresh();
control2->refresh();
}
private:
FrogPilotParamValueControl *control1;
FrogPilotParamValueControl *control2;
};
@@ -0,0 +1,62 @@
#include <QJsonArray>
#include <QJsonDocument>
#include <QJsonObject>
#include "frogpilot/ui/qt/widgets/navigation_functions.h"
MapSelectionControl::MapSelectionControl(const QMap<QString, QString> &map, bool isCountry) : selectionType(isCountry ? "nations" : "states") {
mapButtons = new QButtonGroup(this);
mapButtons->setExclusive(false);
QGridLayout *mapLayout = new QGridLayout(this);
QList<QString> keys = map.keys();
for (int i = 0; i < keys.size(); ++i) {
QPushButton *button = new QPushButton(map[keys[i]], this);
button->setCheckable(true);
button->setProperty("mapKey", keys[i]);
button->setStyleSheet(buttonStyle);
mapButtons->addButton(button, i);
mapLayout->addWidget(button, i / 3, i % 3);
QObject::connect(button, &QPushButton::toggled, this, &MapSelectionControl::updateSelectedMaps);
}
loadSelectedMaps();
}
void MapSelectionControl::loadSelectedMaps() {
QJsonObject existingMaps = QJsonDocument::fromJson(QByteArray::fromStdString(params.get("MapsSelected"))).object();
QSet<QString> selectedMaps;
for (const QJsonValue &value : existingMaps.value(selectionType).toArray()) {
selectedMaps.insert(value.toString());
}
for (QAbstractButton *button : mapButtons->buttons()) {
button->setChecked(selectedMaps.contains(button->property("mapKey").toString()));
}
}
void MapSelectionControl::updateSelectedMaps() {
QJsonObject existingMaps = QJsonDocument::fromJson(QByteArray::fromStdString(params.get("MapsSelected"))).object();
QSet<QString> selectedMaps;
for (const QJsonValue &value : existingMaps.value(selectionType).toArray()) {
selectedMaps.insert(value.toString());
}
for (QAbstractButton *button : mapButtons->buttons()) {
QString mapKey = button->property("mapKey").toString();
if (button->isChecked()) {
selectedMaps.insert(mapKey);
} else {
selectedMaps.remove(mapKey);
}
}
existingMaps[selectionType] = QJsonArray::fromStringList(selectedMaps.values());
params.putNonBlocking("MapsSelected", QJsonDocument(existingMaps).toJson(QJsonDocument::Compact).toStdString());
}
@@ -0,0 +1,214 @@
#pragma once
#include <QDateTime>
#include <QDir>
#include <QDirIterator>
#include "frogpilot/ui/qt/widgets/frogpilot_controls.h"
inline QMap<QString, QString> midwestMap = {
{"IL", "Illinois"}, {"IN", "Indiana"}, {"IA", "Iowa"},
{"KS", "Kansas"}, {"MI", "Michigan"}, {"MN", "Minnesota"},
{"MO", "Missouri"}, {"NE", "Nebraska"}, {"ND", "North Dakota"},
{"OH", "Ohio"}, {"SD", "South Dakota"}, {"WI", "Wisconsin"}
};
inline QMap<QString, QString> northeastMap = {
{"CT", "Connecticut"}, {"ME", "Maine"}, {"MA", "Massachusetts"},
{"NH", "New Hampshire"}, {"NJ", "New Jersey"}, {"NY", "New York"},
{"PA", "Pennsylvania"}, {"RI", "Rhode Island"}, {"VT", "Vermont"}
};
inline QMap<QString, QString> southMap = {
{"AL", "Alabama"}, {"AR", "Arkansas"}, {"DE", "Delaware"},
{"DC", "District of Columbia"}, {"FL", "Florida"}, {"GA", "Georgia"},
{"KY", "Kentucky"}, {"LA", "Louisiana"}, {"MD", "Maryland"},
{"MS", "Mississippi"}, {"NC", "North Carolina"}, {"OK", "Oklahoma"},
{"SC", "South Carolina"}, {"TN", "Tennessee"}, {"TX", "Texas"},
{"VA", "Virginia"}, {"WV", "West Virginia"}
};
inline QMap<QString, QString> westMap = {
{"AK", "Alaska"}, {"AZ", "Arizona"}, {"CA", "California"},
{"CO", "Colorado"}, {"HI", "Hawaii"}, {"ID", "Idaho"},
{"MT", "Montana"}, {"NV", "Nevada"}, {"NM", "New Mexico"},
{"OR", "Oregon"}, {"UT", "Utah"}, {"WA", "Washington"},
{"WY", "Wyoming"}
};
inline QMap<QString, QString> territoriesMap = {
{"AS", "American Samoa"}, {"GU", "Guam"}, {"MP", "Northern Mariana Islands"},
{"PR", "Puerto Rico"}, {"VI", "Virgin Islands"}
};
inline QMap<QString, QString> africaMap = {
{"DZ", "Algeria"}, {"AO", "Angola"}, {"BJ", "Benin"},
{"BW", "Botswana"}, {"BF", "Burkina Faso"}, {"BI", "Burundi"},
{"CM", "Cameroon"}, {"CF", "Central African Republic"}, {"TD", "Chad"},
{"KM", "Comoros"}, {"CG", "Congo (Brazzaville)"}, {"CD", "Congo (Kinshasa)"},
{"DJ", "Djibouti"}, {"EG", "Egypt"}, {"GQ", "Equatorial Guinea"},
{"ER", "Eritrea"}, {"ET", "Ethiopia"}, {"GA", "Gabon"},
{"GM", "Gambia"}, {"GH", "Ghana"}, {"GN", "Guinea"},
{"GW", "Guinea-Bissau"}, {"CI", "Ivory Coast"}, {"KE", "Kenya"},
{"LS", "Lesotho"}, {"LR", "Liberia"}, {"LY", "Libya"},
{"MG", "Madagascar"}, {"MW", "Malawi"}, {"ML", "Mali"},
{"MR", "Mauritania"}, {"MA", "Morocco"}, {"MZ", "Mozambique"},
{"NA", "Namibia"}, {"NE", "Niger"}, {"NG", "Nigeria"},
{"RW", "Rwanda"}, {"SN", "Senegal"}, {"SL", "Sierra Leone"},
{"SO", "Somalia"}, {"ZA", "South Africa"}, {"SS", "South Sudan"},
{"SD", "Sudan"}, {"SZ", "Swaziland"}, {"TZ", "Tanzania"},
{"TG", "Togo"}, {"TN", "Tunisia"}, {"UG", "Uganda"},
{"ZM", "Zambia"}, {"ZW", "Zimbabwe"}
};
inline QMap<QString, QString> antarcticaMap = {
{"AQ", "Antarctica"}
};
inline QMap<QString, QString> asiaMap = {
{"AF", "Afghanistan"}, {"AM", "Armenia"}, {"AZ", "Azerbaijan"},
{"BH", "Bahrain"}, {"BD", "Bangladesh"}, {"BT", "Bhutan"},
{"BN", "Brunei"}, {"KH", "Cambodia"}, {"CN", "China"},
{"CY", "Cyprus"}, {"TL", "East Timor"}, {"HK", "Hong Kong"},
{"IN", "India"}, {"ID", "Indonesia"}, {"IR", "Iran"},
{"IQ", "Iraq"}, {"IL", "Israel"}, {"JP", "Japan"},
{"JO", "Jordan"}, {"KZ", "Kazakhstan"}, {"KW", "Kuwait"},
{"KG", "Kyrgyzstan"}, {"LA", "Laos"}, {"LB", "Lebanon"},
{"MY", "Malaysia"}, {"MV", "Maldives"}, {"MO", "Macao"},
{"MN", "Mongolia"}, {"MM", "Myanmar"}, {"NP", "Nepal"},
{"KP", "North Korea"}, {"OM", "Oman"}, {"PK", "Pakistan"},
{"PS", "Palestine"}, {"PH", "Philippines"}, {"QA", "Qatar"},
{"RU", "Russia"}, {"SA", "Saudi Arabia"}, {"SG", "Singapore"},
{"KR", "South Korea"}, {"LK", "Sri Lanka"}, {"SY", "Syria"},
{"TW", "Taiwan"}, {"TJ", "Tajikistan"}, {"TH", "Thailand"},
{"TR", "Turkey"}, {"TM", "Turkmenistan"}, {"AE", "United Arab Emirates"},
{"UZ", "Uzbekistan"}, {"VN", "Vietnam"}, {"YE", "Yemen"}
};
inline QMap<QString, QString> europeMap = {
{"AL", "Albania"}, {"AT", "Austria"}, {"BY", "Belarus"},
{"BE", "Belgium"}, {"BA", "Bosnia and Herzegovina"}, {"BG", "Bulgaria"},
{"HR", "Croatia"}, {"CZ", "Czech Republic"}, {"DK", "Denmark"},
{"EE", "Estonia"}, {"FI", "Finland"}, {"FR", "France"},
{"GE", "Georgia"}, {"DE", "Germany"}, {"GR", "Greece"},
{"HU", "Hungary"}, {"IS", "Iceland"}, {"IE", "Ireland"},
{"IT", "Italy"}, {"KZ", "Kazakhstan"}, {"LV", "Latvia"},
{"LT", "Lithuania"}, {"LU", "Luxembourg"}, {"MK", "Macedonia"},
{"MD", "Moldova"}, {"ME", "Montenegro"}, {"NL", "Netherlands"},
{"NO", "Norway"}, {"PL", "Poland"}, {"PT", "Portugal"},
{"RO", "Romania"}, {"RS", "Serbia"}, {"SK", "Slovakia"},
{"SI", "Slovenia"}, {"ES", "Spain"}, {"SE", "Sweden"},
{"CH", "Switzerland"}, {"TR", "Turkey"}, {"UA", "Ukraine"},
{"GB", "United Kingdom"}
};
inline QMap<QString, QString> northAmericaMap = {
{"BS", "Bahamas"}, {"BZ", "Belize"}, {"CA", "Canada"},
{"CR", "Costa Rica"}, {"CU", "Cuba"}, {"DO", "Dominican Republic"},
{"SV", "El Salvador"}, {"GL", "Greenland"}, {"GD", "Grenada"},
{"GT", "Guatemala"}, {"HT", "Haiti"}, {"HN", "Honduras"},
{"JM", "Jamaica"}, {"MX", "Mexico"}, {"NI", "Nicaragua"},
{"PA", "Panama"}, {"TT", "Trinidad and Tobago"}, {"US", "United States"}
};
inline QMap<QString, QString> oceaniaMap = {
{"AU", "Australia"}, {"FJ", "Fiji"}, {"TF", "French Southern Territories"},
{"NC", "New Caledonia"}, {"NZ", "New Zealand"}, {"PG", "Papua New Guinea"},
{"SB", "Solomon Islands"}, {"VU", "Vanuatu"}
};
inline QMap<QString, QString> southAmericaMap = {
{"AR", "Argentina"}, {"BO", "Bolivia"}, {"BR", "Brazil"},
{"CL", "Chile"}, {"CO", "Colombia"}, {"EC", "Ecuador"},
{"FK", "Falkland Islands"}, {"GY", "Guyana"}, {"PY", "Paraguay"},
{"PE", "Peru"}, {"SR", "Suriname"}, {"UY", "Uruguay"},
{"VE", "Venezuela"}
};
inline QString calculateDirectorySize(const QDir &directory) {
constexpr double MB = 1024.0 * 1024.0;
constexpr double GB = 1024.0 * MB;
if (!directory.exists()) {
return QObject::tr("0 MB");
}
double totalSize = 0;
QDirIterator it(directory.absolutePath(), QDir::Files, QDirIterator::Subdirectories);
while (it.hasNext()) {
it.next();
totalSize += it.fileInfo().size();
}
if (totalSize >= GB) {
return QString::number(totalSize / GB, 'f', 2) + QObject::tr(" GB");
}
return QString::number(totalSize / MB, 'f', 2) + QObject::tr(" MB");
}
inline QString daySuffix(int day) {
if (day % 10 == 1 && day != 11) return "st";
if (day % 10 == 2 && day != 12) return "nd";
if (day % 10 == 3 && day != 13) return "rd";
return "th";
}
inline QString formatCurrentDate() {
QDate currentDate = QDate::currentDate();
return currentDate.toString("MMMM d'") + daySuffix(currentDate.day()) + QString(", %1").arg(currentDate.year());
}
inline QString formatElapsedTime(float elapsedMilliseconds) {
int totalSeconds = elapsedMilliseconds / 1000;
int hours = totalSeconds / 3600;
int minutes = (totalSeconds % 3600) / 60;
int seconds = totalSeconds % 60;
QString formattedTime;
if (hours > 0) {
formattedTime += QString::number(hours) + (hours == 1 ? QObject::tr(" hour ") : QObject::tr(" hours "));
}
if (minutes > 0) {
formattedTime += QString::number(minutes) + (minutes == 1 ? QObject::tr(" minute ") : QObject::tr(" minutes "));
}
formattedTime += QString::number(seconds) + (seconds == 1 ? QObject::tr(" second") : QObject::tr(" seconds"));
return formattedTime.trimmed();
}
inline QString formatETA(float elapsedTime, int downloadedFiles, int previousDownloadedFiles, int totalFiles, QDateTime &startTime) {
static QDateTime estimatedFinishTime;
static float previousElapsedTime;
if (downloadedFiles != previousDownloadedFiles) {
estimatedFinishTime = startTime.addMSecs((elapsedTime * totalFiles) / downloadedFiles);
} else {
estimatedFinishTime = estimatedFinishTime.addSecs((previousElapsedTime - elapsedTime) / 1000);
}
previousElapsedTime = elapsedTime;
int remainingTime = QDateTime::currentDateTime().secsTo(estimatedFinishTime);
QString estimatedFinishTimeStr = estimatedFinishTime.toString("h:mm AP");
QString remainingTimeStr = formatElapsedTime(remainingTime * 1000);
return QString("%1 (%2)").arg(remainingTimeStr).arg(estimatedFinishTimeStr);
}
class MapSelectionControl : public QWidget {
Q_OBJECT
public:
MapSelectionControl(const QMap<QString, QString> &map, bool isCountry = false);
private:
void loadSelectedMaps();
void updateSelectedMaps();
Params params;
QButtonGroup *mapButtons;
QString selectionType;
};
+10 -2
View File
@@ -39,8 +39,16 @@ qt_src = ["main.cc", "ui.cc", "qt/sidebar.cc", "qt/body.cc",
"qt/onroad/buttons.cc", "qt/onroad/alerts.cc", "qt/onroad/driver_monitoring.cc", "qt/onroad/hud.cc"]
# FrogPilot variables
frogpilot_src = ["../../frogpilot/ui/frogpilot_ui.cc", "../../frogpilot/ui/qt/onroad/frogpilot_annotated_camera.cc",
"../../frogpilot/ui/qt/onroad/frogpilot_buttons.cc", "../../frogpilot/ui/qt/onroad/frogpilot_onroad.cc"]
frogpilot_src = ["../../frogpilot/ui/frogpilot_ui.cc", "../../frogpilot/ui/qt/offroad/data_settings.cc",
"../../frogpilot/ui/qt/offroad/device_settings.cc", "../../frogpilot/ui/qt/offroad/frogpilot_settings.cc",
"../../frogpilot/ui/qt/offroad/lateral_settings.cc", "../../frogpilot/ui/qt/offroad/longitudinal_settings.cc",
"../../frogpilot/ui/qt/offroad/maps_settings.cc", "../../frogpilot/ui/qt/offroad/model_settings.cc",
"../../frogpilot/ui/qt/offroad/navigation_settings.cc", "../../frogpilot/ui/qt/offroad/sounds_settings.cc",
"../../frogpilot/ui/qt/offroad/theme_settings.cc", "../../frogpilot/ui/qt/offroad/utilities.cc",
"../../frogpilot/ui/qt/offroad/vehicle_settings.cc", "../../frogpilot/ui/qt/offroad/visual_settings.cc",
"../../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/navigation_functions.cc"]
qt_src += frogpilot_src
# build translation files
+1 -1
View File
@@ -70,6 +70,7 @@ public:
QString getTetheringPassword();
// FrogPilot variables
QString getIp4Address();
private:
QString adapter; // Path to network manager wifi-device
@@ -83,7 +84,6 @@ private:
QString getAdapter(const uint = NM_DEVICE_TYPE_WIFI);
uint getAdapterType(const QDBusObjectPath &path);
QString getIp4Address();
void deactivateConnectionBySsid(const QString &ssid);
void deactivateConnection(const QDBusObjectPath &path);
QVector<QDBusObjectPath> getActiveConnections();
+288 -7
View File
@@ -2,28 +2,39 @@
#include "selfdrive/ui/qt/widgets/ssh_keys.h"
#include "selfdrive/ui/qt/widgets/controls.h"
DeveloperPanel::DeveloperPanel(SettingsWindow *parent) : ListWidget(parent) {
DeveloperPanel::DeveloperPanel(SettingsWindow *parent) : QFrame(parent) {
mainLayout = new QStackedLayout(this);
mainWidget = new QWidget(this);
QVBoxLayout *mainListLayout = new QVBoxLayout(mainWidget);
mainListLayout->setContentsMargins(50, 25, 50, 25);
mainListLayout->setSpacing(20);
FrogPilotListWidget *mainList = new FrogPilotListWidget(mainWidget);
mainListLayout->addWidget(mainList);
adbToggle = new ParamControl("AdbEnabled", tr("Enable ADB"),
tr("ADB (Android Debug Bridge) allows connecting to your device over USB or over the network. See https://docs.comma.ai/how-to/connect-to-comma for more info."), "");
addItem(adbToggle);
mainList->addItem(adbToggle);
// SSH keys
addItem(new SshToggle());
addItem(new SshControl());
mainList->addItem(new SshToggle());
mainList->addItem(new SshControl());
joystickToggle = new ParamControl("JoystickDebugMode", tr("Joystick Debug Mode"), "", "");
QObject::connect(joystickToggle, &ParamControl::toggleFlipped, [=](bool state) {
params.putBool("LongitudinalManeuverMode", false);
longManeuverToggle->refresh();
});
addItem(joystickToggle);
mainList->addItem(joystickToggle);
longManeuverToggle = new ParamControl("LongitudinalManeuverMode", tr("Longitudinal Maneuver Mode"), "", "");
QObject::connect(longManeuverToggle, &ParamControl::toggleFlipped, [=](bool state) {
params.putBool("JoystickDebugMode", false);
joystickToggle->refresh();
});
addItem(longManeuverToggle);
mainList->addItem(longManeuverToggle);
experimentalLongitudinalToggle = new ParamControl(
"AlphaLongitudinalEnabled",
@@ -38,7 +49,7 @@ DeveloperPanel::DeveloperPanel(SettingsWindow *parent) : ListWidget(parent) {
QObject::connect(experimentalLongitudinalToggle, &ParamControl::toggleFlipped, [=]() {
updateToggles(offroad);
});
addItem(experimentalLongitudinalToggle);
mainList->addItem(experimentalLongitudinalToggle);
// Joystick and longitudinal maneuvers should be hidden on release branches
is_release = params.getBool("IsReleaseBranch");
@@ -47,6 +58,201 @@ DeveloperPanel::DeveloperPanel(SettingsWindow *parent) : ListWidget(parent) {
QObject::connect(uiState(), &UIState::offroadTransition, this, &DeveloperPanel::updateToggles);
// FrogPilot variables
QJsonObject shownDescriptions = QJsonDocument::fromJson(QString::fromStdString(params.get("ShownToggleDescriptions")).toUtf8()).object();
QString className = this->metaObject()->className();
if (!shownDescriptions.value(className).toBool(false)) {
forceOpenDescriptions = true;
}
std::vector<std::string> keys = params.allKeys();
for (const std::string &key : keys) {
frogpilotToggleLevels[QString::fromStdString(key)] = params.getTuningLevel(key);
}
developerUIToggle = new FrogPilotManageControl("DeveloperUI", tr("Developer UI"), tr("<b>Detailed information about openpilot's internal operations.</b>"), "");
QObject::connect(developerUIToggle, &FrogPilotManageControl::manageButtonClicked, [this]() {
mainLayout->setCurrentWidget(developerUIPanel);
emit openSubPanel();
});
mainList->insertItem(0, developerUIToggle);
mainLayout->addWidget(mainWidget);
FrogPilotListWidget *developerUIList = new FrogPilotListWidget(this);
developerUIList->setContentsMargins(50, 25, 50, 25);
developerUIPanel = new ScrollView(developerUIList, this);
mainLayout->addWidget(developerUIPanel);
FrogPilotListWidget *developerMetricList = new FrogPilotListWidget(this);
developerMetricList->setContentsMargins(50, 25, 50, 25);
developerMetricPanel = new ScrollView(developerMetricList, this);
mainLayout->addWidget(developerMetricPanel);
FrogPilotListWidget *developerSidebarList = new FrogPilotListWidget(this);
developerSidebarList->setContentsMargins(50, 25, 50, 25);
developerSidebarPanel = new ScrollView(developerSidebarList, this);
mainLayout->addWidget(developerSidebarPanel);
FrogPilotListWidget *developerWidgetList = new FrogPilotListWidget(this);
developerWidgetList->setContentsMargins(50, 25, 50, 25);
developerWidgetPanel = new ScrollView(developerWidgetList, this);
mainLayout->addWidget(developerWidgetPanel);
std::vector<std::tuple<QString, QString, QString, QString>> developerToggles {
{"DeveloperMetrics", tr("Developer Metrics"), tr("<b>Performance data, sensor readings, and system metrics</b> for debugging and optimizing openpilot."), ""},
{"AdjacentPathMetrics", tr("Adjacent Lane Metrics"), tr("<b>Show the width of the adjacent lanes.</b>"), ""},
{"BorderMetrics", tr("Border Metrics"), tr("<b>Show statuses along the border of the driving screen.</b><br><br><b>Blind Spot</b>: The border turns red when a vehicle is in a blind spot<br><b>Steering Torque</b>: The border goes from green to red according to how much steering torque is being used<br><b>Turn Signal</b>: The border flashes yellow when a turn signal is on"), ""},
{"FPSCounter", tr("FPS Display"), tr("<b>Show the frames per second (FPS)</b> at the bottom of the driving screen."), ""},
{"LeadInfo", tr("Lead Info"), tr("<b>Show each tracked vehicle's distance and speed</b> below its marker."), ""},
{"NumericalTemp", tr("Numerical Temperature Gauge"), tr("<b>Show a numerical temperature in the sidebar</b> instead of the status labels."), ""},
{"SidebarMetrics", tr("Sidebar Metrics"), tr("<b>Display system information</b> (CPU, GPU, RAM usage, IP address, device storage) in the sidebar."), ""},
{"UseSI", tr("Use International System of Units"), tr("<b>Display measurements using the \"International System of Units\" (SI)</b> standard."), ""},
{"DeveloperSidebar", tr("Developer Sidebar"), tr("<b>Display debugging info and metrics</b> in a dedicated sidebar on the right side of the screen."), ""},
{"DeveloperSidebarMetric1", tr("Metric #1"), tr("<b>Select the metric shown in the first \"Developer Sidebar\" widget.</b>"), ""},
{"DeveloperSidebarMetric2", tr("Metric #2"), tr("<b>Select the metric shown in the second \"Developer Sidebar\" widget.</b>"), ""},
{"DeveloperSidebarMetric3", tr("Metric #3"), tr("<b>Select the metric shown in the third \"Developer Sidebar\" widget.</b>"), ""},
{"DeveloperSidebarMetric4", tr("Metric #4"), tr("<b>Select the metric shown in the fourth \"Developer Sidebar\" widget.</b>"), ""},
{"DeveloperSidebarMetric5", tr("Metric #5"), tr("<b>Select the metric shown in the fifth \"Developer Sidebar\" widget.</b>"), ""},
{"DeveloperSidebarMetric6", tr("Metric #6"), tr("<b>Select the metric shown in the sixth \"Developer Sidebar\" widget.</b>"), ""},
{"DeveloperSidebarMetric7", tr("Metric #7"), tr("<b>Select the metric shown in the seventh \"Developer Sidebar\" widget.</b>"), ""},
{"DeveloperWidgets", tr("Developer Widgets"), tr("<b>Overlays for debugging visuals, internal states, and model predictions</b> on the driving screen."), ""},
{"AdjacentLeadsUI", tr("Adjacent Leads Tracking"), tr("<b>Display adjacent leads detected by the car's radar</b> to the left and right of the current driving path."), ""},
{"ShowStoppingPoint", tr("Model Stopping Point"), tr("<b>Show a stop-sign marker where the model intends to stop.</b>"), ""},
{"RadarTracksUI", tr("Radar Tracks"), tr("<b>Display all radar points</b> produced by the car's radar."), ""},
};
for (const auto &[param, title, desc, icon] : developerToggles) {
AbstractControl *developerToggle;
if (param == "DeveloperMetrics") {
FrogPilotManageControl *developerMetricsToggle = new FrogPilotManageControl(param, title, desc, icon);
QObject::connect(developerMetricsToggle, &FrogPilotManageControl::manageButtonClicked, [this]() {
mainLayout->setCurrentWidget(developerMetricPanel);
emit openSubSubPanel();
});
developerToggle = developerMetricsToggle;
} else if (param == "BorderMetrics") {
std::vector<QString> borderToggles{"BlindSpotMetrics", "ShowSteering", "SignalMetrics"};
std::vector<QString> borderToggleNames{tr("Blind Spot"), tr("Steering Torque"), tr("Turn Signal")};
borderMetricsButton = new FrogPilotButtonToggleControl(param, title, desc, icon, borderToggles, borderToggleNames);
developerToggle = borderMetricsButton;
} else if (param == "NumericalTemp") {
std::vector<QString> temperatureToggles{"Fahrenheit"};
std::vector<QString> temperatureToggleNames{tr("Fahrenheit")};
developerToggle = new FrogPilotButtonToggleControl(param, title, desc, icon, temperatureToggles, temperatureToggleNames);
} else if (param == "SidebarMetrics") {
sidebarMetricsToggles = {"ShowCPU", "ShowGPU", "ShowIP", "ShowMemoryUsage", "ShowStorageLeft", "ShowStorageUsed"};
std::vector<QString> sidebarMetricsToggleNames{tr("CPU"), tr("GPU"), tr("IP"), tr("RAM"), tr("SSD Left"), tr("SSD Used")};
sidebarMetricsToggle = new FrogPilotButtonsControl(title, desc, icon, sidebarMetricsToggleNames, true, false, 150);
for (int i = 0; i < sidebarMetricsToggles.size(); ++i) {
if (params.getBool(sidebarMetricsToggles[i].toStdString())) {
sidebarMetricsToggle->setCheckedButton(i);
}
}
QObject::connect(sidebarMetricsToggle, &FrogPilotButtonsControl::buttonClicked, [this](int id) {
params.putBool(sidebarMetricsToggles[id].toStdString(), !params.getBool(sidebarMetricsToggles[id].toStdString()));
if (id == 0) {
params.putBool("ShowGPU", false);
} else if (id == 1) {
params.putBool("ShowCPU", false);
} else if (id == 3) {
params.putBool("ShowStorageLeft", false);
params.putBool("ShowStorageUsed", false);
} else if (id == 4) {
params.putBool("ShowMemoryUsage", false);
params.putBool("ShowStorageUsed", false);
} else if (id == 5) {
params.putBool("ShowMemoryUsage", false);
params.putBool("ShowStorageLeft", false);
}
sidebarMetricsToggle->clearCheckedButtons();
for (int i = 0; i < sidebarMetricsToggles.size(); ++i) {
if (params.getBool(sidebarMetricsToggles[i].toStdString())) {
sidebarMetricsToggle->setCheckedButton(i);
}
}
});
developerToggle = sidebarMetricsToggle;
} else if (param == "DeveloperSidebar") {
FrogPilotManageControl *developerSidebarToggle = new FrogPilotManageControl(param, title, desc, icon);
QObject::connect(developerSidebarToggle, &FrogPilotManageControl::manageButtonClicked, [this]() {
mainLayout->setCurrentWidget(developerSidebarPanel);
emit openSubSubPanel();
});
developerToggle = developerSidebarToggle;
} else if (developerSidebarKeys.contains(param)) {
QMap<int, QString> developerSidebarMetricOptions {
{0, tr("None")},
{1, tr("Acceleration: Current")},
{2, tr("Acceleration: Max")},
{3, tr("Auto Tune: Actuator Delay")},
{4, tr("Auto Tune: Friction")},
{5, tr("Auto Tune: Lateral Acceleration")},
{6, tr("Auto Tune: Steer Ratio")},
{7, tr("Auto Tune: Stiffness Factor")},
{8, tr("Engagement %: Lateral")},
{9, tr("Engagement %: Longitudinal")},
{10, tr("Lateral Control: Steering Angle")},
{11, tr("Lateral Control: Torque % Used")},
{12, tr("Longitudinal Control: Actuator Acceleration Output")},
{13, tr("Longitudinal MPC: Danger Factor")},
{14, tr("Longitudinal MPC Jerk: Acceleration")},
{15, tr("Longitudinal MPC Jerk: Danger Zone")},
{16, tr("Longitudinal MPC Jerk: Speed Control")},
};
ButtonControl *metricToggle = new ButtonControl(title, tr("SELECT"), desc);
QObject::connect(metricToggle, &ButtonControl::clicked, [metricToggle, key = param, developerSidebarMetricOptions, this]() mutable {
QString current = developerSidebarMetricOptions.value(params.getInt(key.toStdString()), tr("None"));
QString selection = MultiOptionDialog::getSelection(tr("Select a metric to display"), developerSidebarMetricOptions.values(), current, this);
if (!selection.isEmpty()) {
int selectedMetric = developerSidebarMetricOptions.key(selection);
params.putInt(key.toStdString(), selectedMetric);
metricToggle->setValue(selection);
}
});
metricToggle->setValue(developerSidebarMetricOptions.value(params.getInt(param.toStdString()), tr("None")));
developerToggle = metricToggle;
} else if (param == "DeveloperWidgets") {
FrogPilotManageControl *developerWidgetsToggle = new FrogPilotManageControl(param, title, desc, icon);
QObject::connect(developerWidgetsToggle, &FrogPilotManageControl::manageButtonClicked, [this]() {
mainLayout->setCurrentWidget(developerWidgetPanel);
emit openSubSubPanel();
});
developerToggle = developerWidgetsToggle;
} else if (param == "ShowStoppingPoint") {
std::vector<QString> stoppingPointToggles{"ShowStoppingPointMetrics"};
std::vector<QString> stoppingPointToggleNames{tr("Show Distance")};
developerToggle = new FrogPilotButtonToggleControl(param, title, desc, icon, stoppingPointToggles, stoppingPointToggleNames);
} else {
developerToggle = new ParamControl(param, title, desc, icon);
}
toggles[param] = developerToggle;
if (developerMetricKeys.contains(param)) {
developerMetricList->addItem(developerToggle);
} else if (developerSidebarKeys.contains(param)) {
developerSidebarList->addItem(developerToggle);
} else if (developerWidgetKeys.contains(param)) {
developerWidgetList->addItem(developerToggle);
} else {
developerUIList->addItem(developerToggle);
parentKeys.insert(param);
}
}
QObject::connect(parent, &SettingsWindow::closeSubPanel, [this]() {mainLayout->setCurrentWidget(mainWidget);});
QObject::connect(parent, &SettingsWindow::closeSubSubPanel, [this]() {mainLayout->setCurrentWidget(developerUIPanel);});
}
void DeveloperPanel::updateToggles(bool _offroad) {
@@ -63,6 +269,15 @@ void DeveloperPanel::updateToggles(bool _offroad) {
}
// FrogPilot variables
for (auto &[key, toggle] : toggles) {
if (toggle == btn) {
btn->setEnabled(true);
break;
}
}
if (developerUIToggle == btn) {
btn->setEnabled(true);
}
}
// longManeuverToggle and experimentalLongitudinalToggle should not be toggleable if the car does not have longitudinal control
@@ -87,6 +302,10 @@ void DeveloperPanel::updateToggles(bool _offroad) {
longManeuverToggle->setEnabled(hasLongitudinalControl(CP) && _offroad);
// FrogPilot variables
hasOpenpilotLongitudinal = hasLongitudinalControl(CP);
hasRadar = !CP.getRadarUnavailable();
borderMetricsButton->setVisibleButton(0, CP.getEnableBsm());
} else {
longManeuverToggle->setEnabled(false);
experimentalLongitudinalToggle->setVisible(false);
@@ -96,10 +315,72 @@ void DeveloperPanel::updateToggles(bool _offroad) {
offroad = _offroad;
// FrogPilot variables
tuningLevel = params.getInt("TuningLevel");
for (auto &[key, toggle] : toggles) {
if (parentKeys.contains(key)) {
toggle->setVisible(false);
}
}
for (auto &[key, toggle] : toggles) {
if (parentKeys.contains(key)) {
continue;
}
bool setVisible = tuningLevel >= frogpilotToggleLevels[key].toDouble();
if (key == "AdjacentLeadsUI") {
setVisible &= hasRadar && !(params.getBool("AdvancedCustomUI") && params.getBool("HideLeadMarker"));
}
else if (key == "RadarTracksUI") {
setVisible &= hasRadar;
}
else if (key == "ShowStoppingPoint") {
setVisible &= hasOpenpilotLongitudinal;
}
toggle->setVisible(setVisible);
if (setVisible) {
if (developerMetricKeys.contains(key)) {
toggles["DeveloperMetrics"]->setVisible(true);
} else if (developerSidebarKeys.contains(key)) {
toggles["DeveloperSidebar"]->setVisible(true);
} else if (developerUIKeys.contains(key)) {
toggles["DeveloperUI"]->setVisible(true);
} else if (developerWidgetKeys.contains(key)) {
toggles["DeveloperWidgets"]->setVisible(true);
}
}
}
borderMetricsButton->setVisibleButton(0, hasBSM);
developerUIToggle->setVisible(tuningLevel >= frogpilotToggleLevels["DeveloperUI"].toDouble());
openDescriptions(forceOpenDescriptions, toggles);
update();
}
void DeveloperPanel::showEvent(QShowEvent *event) {
updateToggles(offroad);
// FrogPilot variables
for (int i = 0; i < sidebarMetricsToggles.size(); ++i) {
if (params.getBool(sidebarMetricsToggles[i].toStdString())) {
sidebarMetricsToggle->setCheckedButton(i);
}
}
QJsonObject shownDescriptions = QJsonDocument::fromJson(QString::fromStdString(params.get("ShownToggleDescriptions")).toUtf8()).object();
QString className = this->metaObject()->className();
if (!shownDescriptions.value(className).toBool(false)) {
shownDescriptions.insert(className, true);
params.put("ShownToggleDescriptions", QJsonDocument(shownDescriptions).toJson(QJsonDocument::Compact).toStdString());
}
}
+40 -1
View File
@@ -2,13 +2,18 @@
#include "selfdrive/ui/qt/offroad/settings.h"
class DeveloperPanel : public ListWidget {
#include "frogpilot/ui/qt/offroad/frogpilot_settings.h"
class DeveloperPanel : public QFrame {
Q_OBJECT
public:
explicit DeveloperPanel(SettingsWindow *parent);
void showEvent(QShowEvent *event) override;
// FrogPilot variables
signals:
void openSubPanel();
void openSubSubPanel();
private:
Params params;
@@ -20,6 +25,40 @@ private:
bool offroad = false;
// FrogPilot variables
bool forceOpenDescriptions = false;
bool hasBSM = true;
bool hasOpenpilotLongitudinal = true;
bool hasRadar = true;
int tuningLevel;
std::map<QString, AbstractControl*> toggles;
std::vector<QString> sidebarMetricsToggles;
FrogPilotButtonsControl *sidebarMetricsToggle;
FrogPilotButtonToggleControl *borderMetricsButton;
FrogPilotManageControl *developerUIToggle;
QJsonObject frogpilotToggleLevels;
QSet<QString> developerMetricKeys = {"AdjacentPathMetrics", "BorderMetrics", "FPSCounter", "LeadInfo", "NumericalTemp", "SidebarMetrics", "UseSI"};
QSet<QString> developerSidebarKeys = {"DeveloperSidebarMetric1", "DeveloperSidebarMetric2", "DeveloperSidebarMetric3", "DeveloperSidebarMetric4", "DeveloperSidebarMetric5", "DeveloperSidebarMetric6", "DeveloperSidebarMetric7"};
QSet<QString> developerUIKeys = {"DeveloperMetrics", "DeveloperSidebar", "DeveloperWidgets"};
QSet<QString> developerWidgetKeys = {"AdjacentLeadsUI", "RadarTracksUI", "ShowStoppingPoint"};
QSet<QString> parentKeys;
QStackedLayout *mainLayout;
QWidget *mainWidget;
ScrollView *developerMetricPanel;
ScrollView *developerSidebarPanel;
ScrollView *developerUIPanel;
ScrollView *developerWidgetPanel;
private slots:
void updateToggles(bool _offroad);
+115 -3
View File
@@ -16,6 +16,8 @@
#include "selfdrive/ui/qt/offroad/developer_panel.h"
#include "selfdrive/ui/qt/offroad/firehose.h"
#include "frogpilot/ui/qt/offroad/frogpilot_settings.h"
TogglesPanel::TogglesPanel(SettingsWindow *parent) : ListWidget(parent) {
// param, title, desc, icon, restart needed
std::vector<std::tuple<QString, QString, QString, QString, bool>> toggle_defs{
@@ -121,6 +123,9 @@ TogglesPanel::TogglesPanel(SettingsWindow *parent) : ListWidget(parent) {
toggles["ExperimentalMode"]->setConfirmation(true, true);
// FrogPilot variables
connect(toggles["IsMetric"], &ToggleControl::toggleFlipped, [=](bool isMetric) {
updateMetric(isMetric);
});
}
void TogglesPanel::updateState(const UIState &s) {
@@ -414,6 +419,13 @@ void SettingsWindow::showEvent(QShowEvent *event) {
// FrogPilot variables
void SettingsWindow::hideEvent(QHideEvent *event) {
closePanel();
closeSubPanel();
panelOpen = false;
subPanelOpen = false;
subSubPanelOpen = false;
subSubSubPanelOpen = false;
}
void SettingsWindow::setCurrentPanel(int index, const QString &param) {
@@ -466,7 +478,21 @@ SettingsWindow::SettingsWindow(QWidget *parent) : QFrame(parent) {
sidebar_layout->addWidget(close_btn, 0, Qt::AlignCenter);
QObject::connect(close_btn, &QPushButton::clicked, [this]() {
// FrogPilot variables
closeSettings();
if (subSubSubPanelOpen) {
closeSubSubSubPanel();
subSubSubPanelOpen = false;
} else if (subSubPanelOpen) {
closeSubSubPanel();
subSubPanelOpen = false;
} else if (subPanelOpen) {
closeSubPanel();
subPanelOpen = false;
} else if (panelOpen) {
closePanel();
panelOpen = false;
} else {
closeSettings();
}
});
// setup panels
@@ -482,14 +508,26 @@ SettingsWindow::SettingsWindow(QWidget *parent) : QFrame(parent) {
QObject::connect(uiState()->prime_state, &PrimeState::changed, networking, &Networking::setPrimeType);
// FrogPilot variables
QObject::connect(toggles, &TogglesPanel::updateMetric, this, &SettingsWindow::updateMetric);
FrogPilotSettingsWindow *frogpilotSettingsWindow = new FrogPilotSettingsWindow(this);
QObject::connect(frogpilotSettingsWindow, &FrogPilotSettingsWindow::openPanel, [this]() {panelOpen=true;});
QObject::connect(frogpilotSettingsWindow, &FrogPilotSettingsWindow::openSubPanel, [this]() {subPanelOpen=true;});
QObject::connect(frogpilotSettingsWindow, &FrogPilotSettingsWindow::openSubSubPanel, [this]() {subSubPanelOpen=true;});
QObject::connect(frogpilotSettingsWindow, &FrogPilotSettingsWindow::openSubSubSubPanel, [this]() {subSubSubPanelOpen=true;});
QObject::connect(frogpilotSettingsWindow, &FrogPilotSettingsWindow::tuningLevelChanged, this, &SettingsWindow::updateDeveloperToggle);
DeveloperPanel *developerPanel = new DeveloperPanel(this);
QObject::connect(developerPanel, &DeveloperPanel::openSubPanel, [this]() {subPanelOpen=true;});
QObject::connect(developerPanel, &DeveloperPanel::openSubSubPanel, [this]() {subSubPanelOpen=true;});
QList<QPair<QString, QWidget *>> panels = {
{tr("Device"), device},
{tr("Network"), networking},
{tr("Toggles"), toggles},
{tr("Software"), new SoftwarePanel(this)},
{tr("Firehose"), new FirehosePanel(this)},
{tr("Developer"), new DeveloperPanel(this)},
{tr("Developer"), developerPanel},
{tr("FrogPilot"), frogpilotSettingsWindow},
};
nav_btns = new QButtonGroup(this);
@@ -524,7 +562,68 @@ SettingsWindow::SettingsWindow(QWidget *parent) : QFrame(parent) {
QObject::connect(btn, &QPushButton::clicked, [=, w = panel_frame]() {
// FrogPilot variables
if (w->widget() == frogpilotSettingsWindow) {
bool tuningLevelConfirmed = params.getBool("TuningLevelConfirmed");
if (!tuningLevelConfirmed) {
int frogpilotHours = QJsonDocument::fromJson(QString::fromStdString(params.get("FrogPilotStats")).toUtf8()).object().value("FrogPilotSeconds").toInt() / (60 * 60);
int openpilotHours = params.getInt("openpilotMinutes") / 60;
if (frogpilotHours < 1 && openpilotHours < 100) {
if (openpilotHours < 10) {
if (ConfirmationDialog::alert(tr("Welcome to FrogPilot! Since you're new to openpilot, the \"Minimal\" toggle preset has been applied, but you can change this at any time via the \"Tuning Level\" button!"), this, true)) {
params.putBool("TuningLevelConfirmed", true);
params.putInt("TuningLevel", 0);
}
} else {
if (ConfirmationDialog::alert(tr("Welcome to FrogPilot! Since you're new to FrogPilot, the \"Minimal\" toggle preset has been applied, but you can change this at any time via the \"Tuning Level\" button!"), this, true)) {
params.putBool("TuningLevelConfirmed", true);
params.putInt("TuningLevel", 0);
}
}
} else if (frogpilotHours < 50 && openpilotHours < 100) {
if (ConfirmationDialog::alert(tr("Since you're fairly new to FrogPilot, the \"Minimal\" toggle preset has been applied, but you can change this at any time via the \"Tuning Level\" button!"), this, true)) {
params.putBool("TuningLevelConfirmed", true);
params.putInt("TuningLevel", 0);
}
} else if (frogpilotHours < 100) {
if (openpilotHours >= 100) {
if (ConfirmationDialog::alert(tr("Since you're experienced with openpilot, the \"Standard\" toggle preset has been applied, but you can change this at any time via the \"Tuning Level\" button!"), this, true)) {
params.putBool("TuningLevelConfirmed", true);
params.putInt("TuningLevel", 1);
}
} else {
if (ConfirmationDialog::alert(tr("Since you're experienced with FrogPilot, the \"Standard\" toggle preset has been applied, but you can change this at any time via the \"Tuning Level\" button!"), this, true)) {
params.putBool("TuningLevelConfirmed", true);
params.putInt("TuningLevel", 1);
}
}
} else if (frogpilotHours >= 100) {
if (ConfirmationDialog::alert(tr("Since you're very experienced with FrogPilot, the \"Advanced\" toggle preset has been applied, but you can change this at any time via the \"Tuning Level\" button!"), this, true)) {
params.putBool("TuningLevelConfirmed", true);
params.putInt("TuningLevel", 2);
}
}
updateTuningLevel();
}
}
if (subSubSubPanelOpen) {
closeSubSubSubPanel();
subSubSubPanelOpen = false;
}
if (subSubPanelOpen) {
closeSubSubPanel();
subSubPanelOpen = false;
}
if (subPanelOpen) {
closeSubPanel();
subPanelOpen = false;
}
if (panelOpen) {
closePanel();
panelOpen = false;
}
btn->setChecked(true);
panel_widget->setCurrentWidget(w);
});
@@ -551,4 +650,17 @@ SettingsWindow::SettingsWindow(QWidget *parent) : QFrame(parent) {
border-radius: 30px;
}
)");
// FrogPilot variables
updateDeveloperToggle(params.getInt("TuningLevel"));
}
// FrogPilot variables
void SettingsWindow::updateDeveloperToggle(int tuningLevel) {
for (QAbstractButton *btn : nav_btns->buttons()) {
if (btn->text() == tr("Developer")) {
btn->setVisible(tuningLevel >= 3);
break;
}
}
}
+14
View File
@@ -36,6 +36,12 @@ signals:
void scrollToToggle(const QString &param);
// FrogPilot variables
void closePanel();
void closeSubPanel();
void closeSubSubPanel();
void closeSubSubSubPanel();
void updateMetric(bool isMetric, bool bootRun=false);
void updateTuningLevel();
private:
QPushButton *sidebar_alert_widget;
@@ -44,6 +50,13 @@ private:
QStackedWidget *panel_widget;
// FrogPilot variables
void updateDeveloperToggle(int tuningLevel);
bool panelOpen;
bool subPanelOpen;
bool subSubPanelOpen;
bool subSubSubPanelOpen;
Params params;
};
@@ -75,6 +88,7 @@ public:
signals:
// FrogPilot variables
void updateMetric(bool metric, bool bootRun=false);
public slots:
void expandToggleDescription(const QString &param);
+6 -1
View File
@@ -49,7 +49,12 @@ AbstractControl::AbstractControl(const QString &title, const QString &desc, cons
}
if (!description->text().isEmpty()) {
description->setVisible(!description->isVisible());
if (description->isVisible()) {
emit hideDescriptionEvent();
description->setVisible(false);
} else {
description->setVisible(true);
}
}
});
+7
View File
@@ -68,6 +68,7 @@ signals:
void showDescriptionEvent();
// FrogPilot variables
void hideDescriptionEvent();
protected:
AbstractControl(const QString &title, const QString &desc = "", const QString &icon = "", QWidget *parent = nullptr);
@@ -135,6 +136,12 @@ public:
}
// FrogPilot variables
void forceOn(bool force) {
toggle.setEnabled(!force);
if (force && !toggle.on) {
toggle.togglePosition();
}
}
signals:
void toggleFlipped(bool state);
+21 -6
View File
@@ -143,21 +143,27 @@ InputDialog::InputDialog(const QString &title, QWidget *parent, const QString &s
line->backspace();
// FrogPilot variables
updateMaxLengthSublabel(line->text());
});
QObject::connect(k, &Keyboard::emitKey, this, [=](const QString &key) {
line->insert(key.left(1));
if (line->text().length() < maxLength || maxLength == DEFAULT_MAX_LENGTH) {
line->insert(key.left(1));
updateMaxLengthSublabel(line->text());
}
});
main_layout->addWidget(k, 2, Qt::AlignBottom);
}
QString InputDialog::getText(const QString &prompt, QWidget *parent, const QString &subtitle,
bool secret, int minLength, const QString &defaultText) {
bool secret, int minLength, const QString &defaultText, int maxLength) {
InputDialog d(prompt, parent, subtitle, secret);
d.line->setText(defaultText);
d.setMinLength(minLength);
// FrogPilot variables
d.setMaxLength(maxLength);
d.updateMaxLengthSublabel(defaultText);
const int ret = d.exec();
return ret ? d.text() : QString();
@@ -192,11 +198,20 @@ void InputDialog::setMinLength(int length) {
}
// FrogPilot variables
void InputDialog::setMaxLength(int length) {
maxLength = length;
}
void InputDialog::updateMaxLengthSublabel(const QString &text) {
if (maxLength != DEFAULT_MAX_LENGTH) {
sublabel->setText(tr("Characters: %1/%2").arg(text.length()).arg(maxLength));
}
}
// ConfirmationDialog
ConfirmationDialog::ConfirmationDialog(const QString &prompt_text, const QString &confirm_text, const QString &cancel_text,
const bool rich, QWidget *parent) : DialogBase(parent) {
const bool rich, QWidget *parent, const bool is_long) : DialogBase(parent) {
QFrame *container = new QFrame(this);
container->setStyleSheet(R"(
QFrame { background-color: #1B1B1B; color: #C9C9C9; }
@@ -204,7 +219,7 @@ ConfirmationDialog::ConfirmationDialog(const QString &prompt_text, const QString
#confirm_btn:pressed { background-color: #3049F4; }
)");
QVBoxLayout *main_layout = new QVBoxLayout(container);
main_layout->setContentsMargins(32, rich ? 32 : 120, 32, 32);
main_layout->setContentsMargins(32, rich || is_long ? 32 : 120, 32, 32);
QLabel *prompt = new QLabel(prompt_text, this);
prompt->setWordWrap(true);
@@ -236,8 +251,8 @@ ConfirmationDialog::ConfirmationDialog(const QString &prompt_text, const QString
outer_layout->addWidget(container);
}
bool ConfirmationDialog::alert(const QString &prompt_text, QWidget *parent) {
ConfirmationDialog d(prompt_text, tr("Ok"), "", false, parent);
bool ConfirmationDialog::alert(const QString &prompt_text, QWidget *parent, bool is_long) {
ConfirmationDialog d(prompt_text, tr("Ok"), "", false, parent, is_long);
return d.exec();
}
+8 -3
View File
@@ -9,6 +9,7 @@
#include "selfdrive/ui/qt/widgets/keyboard.h"
const int DEFAULT_MAX_LENGTH = 512;
class DialogBase : public QDialog {
Q_OBJECT
@@ -27,13 +28,14 @@ class InputDialog : public DialogBase {
public:
explicit InputDialog(const QString &title, QWidget *parent, const QString &subtitle = "", bool secret = false);
static QString getText(const QString &title, QWidget *parent, const QString &subtitle = "",
bool secret = false, int minLength = -1, const QString &defaultText = "");
bool secret = false, int minLength = -1, const QString &defaultText = "", int maxLength = DEFAULT_MAX_LENGTH);
QString text();
void setMessage(const QString &message, bool clearInputField = true);
void setMinLength(int length);
void show();
// FrogPilot variables
void setMaxLength(int length);
private:
int minLength;
@@ -45,6 +47,9 @@ private:
QPushButton *eye_btn;
// FrogPilot variables
void updateMaxLengthSublabel(const QString &text);
int maxLength;
private slots:
void handleEnter();
@@ -59,8 +64,8 @@ class ConfirmationDialog : public DialogBase {
public:
explicit ConfirmationDialog(const QString &prompt_text, const QString &confirm_text,
const QString &cancel_text, const bool rich, QWidget* parent);
static bool alert(const QString &prompt_text, QWidget *parent);
const QString &cancel_text, const bool rich, QWidget* parent, const bool is_long=false);
static bool alert(const QString &prompt_text, QWidget *parent, bool isLong=false);
static bool confirm(const QString &prompt_text, const QString &confirm_text, QWidget *parent);
static bool rich(const QString &prompt_text, QWidget *parent);
};
+4
View File
@@ -150,6 +150,10 @@ void UIState::update() {
FrogPilotUIState *fs = frogpilotUIState();
FrogPilotUIScene &frogpilot_scene = fs->frogpilot_scene;
if (frogpilot_scene.frogpilot_panel_active) {
device()->resetInteractiveTimeout();
}
fs->update();
}
+2 -1
View File
@@ -6,6 +6,7 @@ import os
from openpilot.common.basedir import BASEDIR
UI_DIR = os.path.join(BASEDIR, "selfdrive", "ui")
FROGPILOT_UI_DIR = os.path.join(BASEDIR, "frogpilot", "ui")
TRANSLATIONS_DIR = os.path.join(UI_DIR, "translations")
LANGUAGES_FILE = os.path.join(TRANSLATIONS_DIR, "languages.json")
TRANSLATIONS_INCLUDE_FILE = os.path.join(TRANSLATIONS_DIR, "alerts_generated.h")
@@ -31,7 +32,7 @@ def update_translations(vanish: bool = False, translation_files: None | list[str
for file in translation_files:
tr_file = os.path.join(translations_dir, f"{file}.ts")
args = f"lupdate -locations none -recursive {UI_DIR} -ts {tr_file} -I {BASEDIR}"
args = f"lupdate -locations none -recursive {UI_DIR} {FROGPILOT_UI_DIR} -ts {tr_file} -I {BASEDIR}"
if vanish:
args += " -no-obsolete"
if file in PLURAL_ONLY: