From 5e224f8c9ac29700d5c48556e59ead47183dd90b Mon Sep 17 00:00:00 2001
From: James <91348155+FrogAi@users.noreply.github.com>
Date: Mon, 9 Feb 2026 13:19:50 -0700
Subject: [PATCH] Fixes
---
common/api.py | 2 +-
frogpilot/assets/theme_manager.py | 9 +-
frogpilot/common/frogpilot_backups.py | 2 +-
frogpilot/common/frogpilot_variables.py | 3 +-
frogpilot/controls/frogpilot_planner.py | 2 +-
frogpilot/controls/lib/frogpilot_events.py | 6 +-
frogpilot/system/frogpilot_tracking.py | 2 +-
frogpilot/ui/qt/offroad/data_settings.cc | 2 +-
frogpilot/ui/qt/offroad/device_settings.cc | 8 +-
frogpilot/ui/qt/offroad/lateral_settings.cc | 5 +-
frogpilot/ui/qt/offroad/theme_settings.cc | 1739 +++++++++---------
frogpilot/ui/qt/widgets/frogpilot_controls.h | 2 +-
tools/lib/api.py | 2 +-
tools/lib/auth.py | 2 +-
tools/scripts/fetch_image_from_route.py | 2 +-
15 files changed, 888 insertions(+), 900 deletions(-)
diff --git a/common/api.py b/common/api.py
index 22fb7f65..c9e89a16 100644
--- a/common/api.py
+++ b/common/api.py
@@ -7,7 +7,7 @@ from openpilot.system.version import get_version
from openpilot.frogpilot.common.frogpilot_utilities import use_konik_server
-API_HOST = os.getenv('API_HOST', f'https://api.{'konik.ai' if use_konik_server() else 'commadotai.com'}')
+API_HOST = os.getenv('API_HOST', f"https://api.{'konik.ai' if use_konik_server() else 'commadotai.com'}")
# name: jwt signature algorithm
KEYS = {"id_rsa": "RS256",
diff --git a/frogpilot/assets/theme_manager.py b/frogpilot/assets/theme_manager.py
index 52a5ebb7..8f18e463 100644
--- a/frogpilot/assets/theme_manager.py
+++ b/frogpilot/assets/theme_manager.py
@@ -270,13 +270,8 @@ class ThemeManager:
if "~" in base:
base, creator = base.split("~", 1)
- parts = base.replace("_", "-").split("-")
- capitalized_parts = [part.capitalize() for part in parts if part]
-
- if len(capitalized_parts) > 1 and component != "steering_wheels":
- display = f"{capitalized_parts[0]} ({' '.join(capitalized_parts[1:])})"
- else:
- display = " ".join(capitalized_parts)
+ parts = base.replace("_", " ").replace("-", " ").split()
+ display = " ".join(part.capitalize() for part in parts)
if creator:
return f"{display} - by: {creator}"
diff --git a/frogpilot/common/frogpilot_backups.py b/frogpilot/common/frogpilot_backups.py
index 8bd82bc9..e505cdc5 100644
--- a/frogpilot/common/frogpilot_backups.py
+++ b/frogpilot/common/frogpilot_backups.py
@@ -54,7 +54,7 @@ def backup_toggles(params, boot_run=False):
print("Toggles are identical to the previous backup. Aborting...")
return
- destination = TOGGLE_BACKUPS / f"{datetime.datetime.now().strftime("%Y-%m-%d_%H-%M-%S")}_auto"
+ destination = TOGGLE_BACKUPS / f"{datetime.datetime.now().strftime('%Y-%m-%d_%H-%M-%S')}_auto"
create_backup(Path(params_backup.get_param_path()), destination, "Successfully backed up toggles!", "Failed to backup toggles...", params)
diff --git a/frogpilot/common/frogpilot_variables.py b/frogpilot/common/frogpilot_variables.py
index f02ecca5..8c4ed850 100644
--- a/frogpilot/common/frogpilot_variables.py
+++ b/frogpilot/common/frogpilot_variables.py
@@ -277,7 +277,7 @@ class FrogPilotVariables:
for source in (theme_colors, self.stock_colors):
color = source.get(key)
if isinstance(color, dict):
- return f"#{color.get("alpha", 255):02X}{color.get("red", 255):02X}{color.get("green", 255):02X}{color.get("blue", 255):02X}"
+ return f"#{color.get('alpha', 255):02X}{color.get('red', 255):02X}{color.get('green', 255):02X}{color.get('blue', 255):02X}"
return "#FFFFFFFF"
def get_value(self, key, cast=bool, condition=True, conversion=None, default=None, min=None, max=None):
@@ -739,4 +739,5 @@ class FrogPilotVariables:
toggle.volt_sng = self.get_value("VoltSNG", condition=toggle.car_model == "CHEVROLET_VOLT")
+ process_frogpilot_toggles.cache_clear()
self.params_memory.remove("FrogPilotTogglesUpdated")
diff --git a/frogpilot/controls/frogpilot_planner.py b/frogpilot/controls/frogpilot_planner.py
index defac891..2753e082 100644
--- a/frogpilot/controls/frogpilot_planner.py
+++ b/frogpilot/controls/frogpilot_planner.py
@@ -74,7 +74,7 @@ class FrogPilotPlanner:
self.driving_in_curve = abs(self.lateral_acceleration) >= MINIMUM_LATERAL_ACCELERATION
- self.frogpilot_events.update(v_cruise, sm, frogpilot_toggles)
+ self.frogpilot_events.update(long_control_active, v_cruise, sm, frogpilot_toggles)
self.frogpilot_following.update(long_control_active, v_ego, sm, frogpilot_toggles)
diff --git a/frogpilot/controls/lib/frogpilot_events.py b/frogpilot/controls/lib/frogpilot_events.py
index deab7059..2b5f3599 100644
--- a/frogpilot/controls/lib/frogpilot_events.py
+++ b/frogpilot/controls/lib/frogpilot_events.py
@@ -36,7 +36,7 @@ class FrogPilotEvents:
self.error_log = error_log
- def update(self, v_cruise, sm, frogpilot_toggles):
+ def update(self, long_control_active, v_cruise, sm, frogpilot_toggles):
current_alert = sm["selfdriveState"].alertType
current_frogpilot_alert = sm["selfdriveState"].alertType
@@ -44,9 +44,9 @@ class FrogPilotEvents:
self.events.clear()
- acceleration = sm["carState"].aEgo
+ acceleration = sm["carControl"].actuators.accel
- if not sm["carState"].gasPressed:
+ if long_control_active:
self.max_acceleration = max(acceleration, self.max_acceleration)
else:
self.max_acceleration = 0
diff --git a/frogpilot/system/frogpilot_tracking.py b/frogpilot/system/frogpilot_tracking.py
index 42e87051..5ba913e3 100644
--- a/frogpilot/system/frogpilot_tracking.py
+++ b/frogpilot/system/frogpilot_tracking.py
@@ -92,7 +92,7 @@ class FrogPilotTracking:
self.previous_sound = sm["frogpilotSelfdriveState"].alertSound
- self.frogpilot_stats["HighestAcceleration"] = max(self.frogpilot_events.max_acceleration, self.frogpilot_stats.get("HighestAcceleration", 0))
+ self.frogpilot_stats["MaxAcceleration"] = max(self.frogpilot_events.max_acceleration, self.frogpilot_stats.get("MaxAcceleration", 0))
if sm["carControl"].latActive:
self.frogpilot_stats["LateralTime"] = self.frogpilot_stats.get("LateralTime", 0) + DT_MDL
diff --git a/frogpilot/ui/qt/offroad/data_settings.cc b/frogpilot/ui/qt/offroad/data_settings.cc
index 8bcfd89b..b4939096 100644
--- a/frogpilot/ui/qt/offroad/data_settings.cc
+++ b/frogpilot/ui/qt/offroad/data_settings.cc
@@ -657,10 +657,10 @@ void FrogPilotDataPanel::updateStatsLabels(FrogPilotListWidget *labelsList) {
{"FrogPilotSeconds", {tr("Total Driving Time"), "time"}},
{"FrogSqueaks", {tr("Total Frog Squeaks"), "count"}},
{"GoatScreams", {tr("Total Goat Screams"), "count"}},
- {"HighestAcceleration", {tr("Highest Acceleration Rate"), "accel"}},
{"LateralTime", {tr("Time Using Lateral Control"), "timePercent"}},
{"LongestDistanceWithoutOverride", {tr("Longest Distance Without an Override"), "distance"}},
{"LongitudinalTime", {tr("Time Using Longitudinal Control"), "timePercent"}},
+ {"MaxAcceleration", {tr("Highest Acceleration Rate"), "accel"}},
{"ModelTimes", {tr("Driving Models:"), "parent"}},
{"Month", {tr("Month"), "other"}},
{"NightTime", {tr("Time Driving (Nighttime)"), "timePercent"}},
diff --git a/frogpilot/ui/qt/offroad/device_settings.cc b/frogpilot/ui/qt/offroad/device_settings.cc
index f390a708..42d03dbc 100644
--- a/frogpilot/ui/qt/offroad/device_settings.cc
+++ b/frogpilot/ui/qt/offroad/device_settings.cc
@@ -41,13 +41,7 @@ FrogPilotDevicePanel::FrogPilotDevicePanel(FrogPilotSettingsWindow *parent, bool
{"ScreenRecorder", tr("Screen Recorder"), tr("Add a button to the driving screen to record the display."), ""},
{"ScreenTimeout", tr("Screen Timeout (Offroad)"), tr("How long the screen stays on after being tapped while not driving."), ""},
{"ScreenTimeoutOnroad", tr("Screen Timeout (Onroad)"), tr("How long the screen stays on after being tapped while driving."), ""},
- {"StandbyMode", tr("Standby Mode"), tr("Turn the screen off while driving and automatically wake it up for alerts or engagement state changes."), ""},
-
- {"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.", ""}
+ {"StandbyMode", tr("Standby Mode"), tr("Turn the screen off while driving and automatically wake it up for alerts or engagement state changes."), ""}
};
for (const auto &[param, title, desc, icon] : deviceToggles) {
diff --git a/frogpilot/ui/qt/offroad/lateral_settings.cc b/frogpilot/ui/qt/offroad/lateral_settings.cc
index 2430e46f..021f4ddb 100644
--- a/frogpilot/ui/qt/offroad/lateral_settings.cc
+++ b/frogpilot/ui/qt/offroad/lateral_settings.cc
@@ -58,10 +58,7 @@ FrogPilotLateralPanel::FrogPilotLateralPanel(FrogPilotSettingsWindow *parent, bo
{"NNFFLite", tr("Neural Network Feedforward (NNFF) Lite"), tr("A lightweight version of Twilsonco's \"Neural Network FeedForward\" controller. 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("Steering control changes to fine-tune how openpilot drives."), "../../frogpilot/assets/toggle_icons/icon_quality_of_life.png"},
- {"PauseLateralSpeed", tr("Pause Steering Below"), tr("Pause steering below the set speed."), ""},
-
- {"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.", ""}
+ {"PauseLateralSpeed", tr("Pause Steering Below"), tr("Pause steering below the set speed."), ""}
};
for (const auto &[param, title, desc, icon] : lateralToggles) {
diff --git a/frogpilot/ui/qt/offroad/theme_settings.cc b/frogpilot/ui/qt/offroad/theme_settings.cc
index 5a9d90a9..250bac1e 100644
--- a/frogpilot/ui/qt/offroad/theme_settings.cc
+++ b/frogpilot/ui/qt/offroad/theme_settings.cc
@@ -1,869 +1,870 @@
-#include "frogpilot/ui/qt/offroad/theme_settings.h"
-
-bool isUserCreatedTheme(const QString &themeName) {
- return themeName.endsWith("-user_created");
-}
-
-void updateAssetParam(const QString &assetParam, Params ¶ms, 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 ¶ms) {
- 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 ¶mKey, const QString &assetParam, Params ¶ms, Params ¶ms_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 ¶ms) {
- 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 ¶mKey, Params ¶ms) {
- 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 ¶mKey, Params ¶ms) {
- 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> themeToggles {
- {"CustomThemes", tr("Custom Themes"), tr("The overall look and feel of openpilot. 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("The color scheme used throughout openpilot. Use the \"Theme Maker\" in \"The Pond\" to create and share your own themes!"), ""},
- {"DistanceIconPack", tr("Distance Button"), tr("The distance button icons shown on the driving screen. Use the \"Theme Maker\" in \"The Pond\" to create and share your own themes!"), ""},
- {"IconPack", tr("Icon Pack"), tr("The icon style used across openpilot. Use the \"Theme Maker\" in \"The Pond\" to create and share your own themes!"), ""},
- {"SignalAnimation", tr("Turn Signal"), tr("Themed turn-signal animations. Use the \"Theme Maker\" in \"The Pond\" to create and share your own themes!"), ""},
- {"SoundPack", tr("Sound Pack"), tr("The sound pack used by openpilot. Use the \"Theme Maker\" in \"The Pond\" to create and share your own themes!"), ""},
- {"WheelIcon", tr("Steering Wheel"), tr("The steering-wheel icon 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("Themes based on U.S. holidays. 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("Color the driving path like a Mario Kartโstyle \"Rainbow Road\"."), "../../frogpilot/assets/toggle_icons/icon_rainbow.png"},
- {"RandomEvents", tr("Random Events"), tr("Occasional on-screen effects triggered by driving conditions. These are purely a visual and don't impact how openpilot drives!"), "../../frogpilot/assets/toggle_icons/icon_random.png"},
- {"RandomThemes", tr("Random Themes"), tr("Pick a random theme between each drive 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("Customize the \"Startup Alert\" message 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 randomThemesToggles{"RandomThemesHolidays"};
- std::vector 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(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(toggles["CustomThemes"]), &ToggleControl::toggleFlipped, this, &FrogPilotThemesPanel::updateToggles);
- QObject::connect(static_cast(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 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();
-}
+#include "frogpilot/ui/qt/offroad/theme_settings.h"
+
+bool isUserCreatedTheme(const QString &themeName) {
+ return themeName.endsWith("-user_created");
+}
+
+void updateAssetParam(const QString &assetParam, Params ¶ms, 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 ¶ms) {
+ 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 ¶mKey, const QString &assetParam, Params ¶ms, Params ¶ms_memory) {
+ QString output = input;
+ output.replace(" - by: ", "~");
+ 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 ¶ms) {
+ 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 || !baseName.contains("-")) ? 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 ¶mKey, Params ¶ms) {
+ 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 ¶mKey, Params ¶ms) {
+ 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> themeToggles {
+ {"CustomThemes", tr("Custom Themes"), tr("The overall look and feel of openpilot. 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("The color scheme used throughout openpilot. Use the \"Theme Maker\" in \"The Pond\" to create and share your own themes!"), ""},
+ {"DistanceIconPack", tr("Distance Button"), tr("The distance button icons shown on the driving screen. Use the \"Theme Maker\" in \"The Pond\" to create and share your own themes!"), ""},
+ {"IconPack", tr("Icon Pack"), tr("The icon style used across openpilot. Use the \"Theme Maker\" in \"The Pond\" to create and share your own themes!"), ""},
+ {"SignalAnimation", tr("Turn Signal"), tr("Themed turn-signal animations. Use the \"Theme Maker\" in \"The Pond\" to create and share your own themes!"), ""},
+ {"SoundPack", tr("Sound Pack"), tr("The sound pack used by openpilot. Use the \"Theme Maker\" in \"The Pond\" to create and share your own themes!"), ""},
+ {"WheelIcon", tr("Steering Wheel"), tr("The steering-wheel icon 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("Themes based on U.S. holidays. 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("Color the driving path like a Mario Kartโstyle \"Rainbow Road\"."), "../../frogpilot/assets/toggle_icons/icon_rainbow.png"},
+ {"RandomEvents", tr("Random Events"), tr("Occasional on-screen effects triggered by driving conditions. These are purely a visual and don't impact how openpilot drives!"), "../../frogpilot/assets/toggle_icons/icon_random.png"},
+ {"RandomThemes", tr("Random Themes"), tr("Pick a random theme between each drive 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("Customize the \"Startup Alert\" message 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 randomThemesToggles{"RandomThemesHolidays"};
+ std::vector 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(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(toggles["CustomThemes"]), &ToggleControl::toggleFlipped, this, &FrogPilotThemesPanel::updateToggles);
+ QObject::connect(static_cast(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 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();
+}
diff --git a/frogpilot/ui/qt/widgets/frogpilot_controls.h b/frogpilot/ui/qt/widgets/frogpilot_controls.h
index c2a1a414..44cc39bc 100644
--- a/frogpilot/ui/qt/widgets/frogpilot_controls.h
+++ b/frogpilot/ui/qt/widgets/frogpilot_controls.h
@@ -68,7 +68,7 @@ class FrogPilotListWidget : public QWidget {
outer_layout.addStretch();
}
inline void addItem(QWidget *w, bool expanding = false) {
- w->setSizePolicy(QSizePolicy::Preferred, expanding ? QSizePolicy::Expanding : QSizePolicy::Fixed);
+ w->setSizePolicy(QSizePolicy::Preferred, expanding ? QSizePolicy::Expanding : QSizePolicy::Maximum);
inner_layout.addWidget(w);
}
inline void addItem(QLayout *layout) { inner_layout.addLayout(layout); }
diff --git a/tools/lib/api.py b/tools/lib/api.py
index 2780481d..db5f94cd 100644
--- a/tools/lib/api.py
+++ b/tools/lib/api.py
@@ -3,7 +3,7 @@ import requests
from openpilot.frogpilot.common.frogpilot_utilities import use_konik_server
-API_HOST = os.getenv('API_HOST', f'https://api.{'konik.ai' if use_konik_server() else 'commadotai.com'}')
+API_HOST = os.getenv('API_HOST', f"https://api.{'konik.ai' if use_konik_server() else 'commadotai.com'}")
# TODO: this should be merged into common.api
diff --git a/tools/lib/auth.py b/tools/lib/auth.py
index 39a9e04a..2e1de97c 100755
--- a/tools/lib/auth.py
+++ b/tools/lib/auth.py
@@ -35,7 +35,7 @@ from openpilot.tools.lib.auth_config import set_token, get_token
from openpilot.frogpilot.common.frogpilot_utilities import use_konik_server
-API_HOST = os.getenv('API_HOST', f'https://api.{'konik' if use_konik_server() else 'comma'}.ai')
+API_HOST = os.getenv('API_HOST', f"https://api.{'konik' if use_konik_server() else 'comma'}.ai")
PORT = 3000
diff --git a/tools/scripts/fetch_image_from_route.py b/tools/scripts/fetch_image_from_route.py
index c1dbe4f7..40e4a316 100755
--- a/tools/scripts/fetch_image_from_route.py
+++ b/tools/scripts/fetch_image_from_route.py
@@ -26,7 +26,7 @@ segment = int(sys.argv[2])
frame = int(sys.argv[3])
camera = cameras[sys.argv[4]] if len(sys.argv) > 4 and sys.argv[4] in cameras else "cameras"
-url = f'https://api.{'konik.ai' if use_konik_server() else 'commadotai.com'}/v1/route/{route}/files'
+url = f"https://api.{'konik.ai' if use_konik_server() else 'commadotai.com'}/v1/route/{route}/files"
r = requests.get(url, headers={"Authorization": f"JWT {jwt}"}, timeout=10)
assert r.status_code == 200
print("got api response")