Files
StarPilot/starpilot/ui/qt/offroad/theme_settings.cc
T
2026-06-19 00:39:48 -05:00

952 lines
43 KiB
C++
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#include "starpilot/ui/qt/offroad/theme_settings.h"
#include "system/hardware/hw.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) {
if (paramKey == "BootLogoToDownload") {
params_memory.put(paramKey, input.trimmed().toStdString());
return;
}
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 &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 || !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 &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);
}
StarPilotThemesPanel::StarPilotThemesPanel(StarPilotSettingsWindow *parent, bool forceOpen) : StarPilotListWidget(parent), parent(parent) {
forceOpenDescriptions = forceOpen;
if (Hardware::PC()) {
const QString themesRoot = QString::fromStdString(Path::comma_home() + "/starpilot/data/themes/");
bootLogosDirectory.setPath(themesRoot + "bootlogos/");
themePacksDirectory.setPath(themesRoot + "theme_packs/");
wheelsDirectory.setPath(themesRoot + "steering_wheels/");
}
QDir().mkpath(bootLogosDirectory.path());
QDir().mkpath(themePacksDirectory.path());
QDir().mkpath(wheelsDirectory.path());
QStackedLayout *themesLayout = new QStackedLayout();
addItem(themesLayout);
StarPilotListWidget *themesList = new StarPilotListWidget(this);
ScrollView *themesPanel = new ScrollView(themesList, this);
themesLayout->addWidget(themesPanel);
StarPilotListWidget *customThemesList = new StarPilotListWidget(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 Galaxy\" to create and share your own themes!"), "../../starpilot/assets/toggle_icons/icon_frog.png"},
{"BootLogo", tr("Boot Logo"), tr("<b>The boot logo shown while the device starts.</b>"), ""},
{"ColorScheme", tr("Color Scheme"), tr("<b>The color scheme used throughout openpilot.</b> Use the \"Theme Maker\" in \"The Galaxy\" 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 Galaxy\" 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 Galaxy\" to create and share your own themes!"), ""},
{"SignalAnimation", tr("Turn Signal"), tr("<b>Themed turn-signal animations.</b> Use the \"Theme Maker\" in \"The Galaxy\" 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 Galaxy\" 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 Galaxy\" 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."), "../../starpilot/assets/toggle_icons/icon_calendar.png"},
{"RainbowPath", tr("Rainbow Path"), tr("<b>Color the driving path like a Mario Kartstyle \"Rainbow Road\".</b>"), "../../starpilot/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!"), "../../starpilot/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."), "../../starpilot/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."), "../../starpilot/assets/toggle_icons/icon_message.png"}
};
for (const auto &[param, title, desc, icon] : themeToggles) {
AbstractControl *themeToggle;
if (param == "CustomThemes") {
StarPilotManageControl *personalizeOpenpilotToggle = new StarPilotManageControl(param, title, desc, icon);
QObject::connect(personalizeOpenpilotToggle, &StarPilotManageControl::manageButtonClicked, [customThemesPanel, themesLayout]() {
themesLayout->setCurrentWidget(customThemesPanel);
});
themeToggle = personalizeOpenpilotToggle;
} else if (param == "BootLogo") {
manageBootLogosButton = new StarPilotButtonsControl(title, desc, icon, {tr("DELETE"), tr("DOWNLOAD"), tr("SELECT")});
QObject::connect(manageBootLogosButton, &StarPilotButtonsControl::buttonClicked, [this](int id) {
// Show all downloaded boot logos, including the currently selected one.
QStringList bootLogos = getThemeList(true, QDir(bootLogosDirectory.path()), "", "BootLogo", params);
if (id == 0) {
QString bootLogoToDelete = MultiOptionDialog::getSelection(tr("Select a boot logo to delete"), bootLogos, "", this);
if (!bootLogoToDelete.isEmpty() && ConfirmationDialog::confirm(tr("Delete the \"%1\" boot logo?").arg(bootLogoToDelete), tr("Delete"), this)) {
bootLogosDownloaded = false;
deleteThemeAsset(bootLogosDirectory, "", "DownloadableBootLogos", bootLogoToDelete, params);
}
} else if (id == 1) {
if (bootLogoDownloading) {
cancellingDownload = true;
params_memory.putBool("CancelThemeDownload", true);
QTimer::singleShot(2500, [this]() {
bootLogoDownloading = false;
cancellingDownload = false;
themeDownloading = false;
params_memory.putBool("CancelThemeDownload", false);
});
} else {
QStringList downloadableBootLogos = QString::fromStdString(params.get("DownloadableBootLogos")).split(",");
bootLogoToDownload = MultiOptionDialog::getSelection(tr("Select a boot logo to download"), downloadableBootLogos, "", this);
if (!bootLogoToDownload.isEmpty()) {
manageBootLogosButton->setValue(storeThemeName(bootLogoToDownload, "BootLogo", params));
bootLogoDownloading = true;
themeDownloading = true;
params_memory.put("ThemeDownloadProgress", "Downloading...");
downloadThemeAsset(bootLogoToDownload, "BootLogoToDownload", "DownloadableBootLogos", params, params_memory);
downloadStatusLabel->setText(tr("Downloading..."));
}
}
} else if (id == 2) {
QString bootLogoToSelect = MultiOptionDialog::getSelection(tr("Select a boot logo"), bootLogos, getThemeName("BootLogo", params), this);
if (!bootLogoToSelect.isEmpty()) {
manageBootLogosButton->setValue(storeThemeName(bootLogoToSelect, "BootLogo", params));
}
}
});
manageBootLogosButton->setValue(getThemeName(param.toStdString(), params));
themeToggle = manageBootLogosButton;
} else if (param == "ColorScheme") {
manageColorSchemeButton = new StarPilotButtonsControl(title, desc, icon, {tr("DELETE"), tr("DOWNLOAD"), tr("SELECT")});
QObject::connect(manageColorSchemeButton, &StarPilotButtonsControl::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 StarPilotButtonsControl(title, desc, icon, {tr("DELETE"), tr("DOWNLOAD"), tr("SELECT")});
QObject::connect(manageDistanceIconPackButton, &StarPilotButtonsControl::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 StarPilotButtonsControl(title, desc, icon, {tr("DELETE"), tr("DOWNLOAD"), tr("SELECT")});
QObject::connect(manageIconPackButton, &StarPilotButtonsControl::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 StarPilotButtonsControl(title, desc, icon, {tr("DELETE"), tr("DOWNLOAD"), tr("SELECT")});
QObject::connect(manageSignalAnimationButton, &StarPilotButtonsControl::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 StarPilotButtonsControl(title, desc, icon, {tr("DELETE"), tr("DOWNLOAD"), tr("SELECT")});
QObject::connect(manageSoundPackButton, &StarPilotButtonsControl::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 StarPilotButtonsControl(title, desc, icon, {tr("DELETE"), tr("DOWNLOAD"), tr("SELECT")});
QObject::connect(manageWheelIconsButton, &StarPilotButtonsControl::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 StarPilotButtonToggleControl(param, title, desc, icon, randomThemesToggles, randomThemesToggleNames);
} else if (param == "StartupAlert") {
StarPilotButtonsControl *startupAlertButton = new StarPilotButtonsControl(title, desc, icon, {tr("STOCK"), tr("STARPILOT"), 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 starpilotTop = "Hop in and buckle up!";
QString starpilotBottom = "Human-tested, frog-approved 🐸";
if (currentTop == stockTop && currentBottom == stockBottom) {
startupAlertButton->setCheckedButton(0);
} else if (currentTop == starpilotTop && currentBottom == starpilotBottom) {
startupAlertButton->setCheckedButton(1);
} else if (!currentTop.isEmpty() || !currentBottom.isEmpty()) {
startupAlertButton->setCheckedButton(2);
}
QObject::connect(startupAlertButton, &StarPilotButtonsControl::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", starpilotTop.toStdString());
params.put("StartupMessageBottom", starpilotBottom.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 (StarPilotConfirmationDialog::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 (StarPilotManageControl *frogPilotManageToggle = qobject_cast<StarPilotManageControl*>(themeToggle)) {
QObject::connect(frogPilotManageToggle, &StarPilotManageControl::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, &StarPilotThemesPanel::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, &StarPilotSettingsWindow::closeSubPanel, [themesLayout, themesPanel, this] {
openDescriptions(forceOpenDescriptions, toggles);
themesLayout->setCurrentWidget(themesPanel);
});
QObject::connect(uiState(), &UIState::uiUpdate, this, &StarPilotThemesPanel::updateState);
}
void StarPilotThemesPanel::showEvent(QShowEvent *event) {
bootLogosDownloaded = params.get("DownloadableBootLogos").empty();
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 StarPilotThemesPanel::updateState(const UIState &s, const StarPilotUIState &fs) {
if (!isVisible() || finalizingDownload) {
return;
}
const StarPilotUIScene &starpilot_scene = fs.starpilot_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]() {
bootLogoDownloading = false;
colorDownloading = false;
distanceIconDownloading = false;
finalizingDownload = false;
iconDownloading = false;
signalDownloading = false;
soundDownloading = false;
themeDownloading = false;
wheelDownloading = false;
bootLogosDownloaded = params.get("DownloadableBootLogos").empty();
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 || starpilot_scene.parked || parent->isFrogsGoMoo;
manageBootLogosButton->setText(1, bootLogoDownloading ? tr("CANCEL") : tr("DOWNLOAD"));
manageBootLogosButton->setEnabledButtons(0, !themeDownloading);
manageBootLogosButton->setEnabledButtons(1, starpilot_scene.online && (!themeDownloading || bootLogoDownloading) && !cancellingDownload && !finalizingDownload && !bootLogosDownloaded && parked);
manageBootLogosButton->setEnabledButtons(2, !themeDownloading);
manageColorSchemeButton->setText(1, colorDownloading ? tr("CANCEL") : tr("DOWNLOAD"));
manageColorSchemeButton->setEnabledButtons(0, !themeDownloading);
manageColorSchemeButton->setEnabledButtons(1, starpilot_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, starpilot_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, starpilot_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, starpilot_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, starpilot_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, starpilot_scene.online && (!themeDownloading || wheelDownloading) && !cancellingDownload && !finalizingDownload && !wheelsDownloaded && parked);
manageWheelIconsButton->setEnabledButtons(2, !themeDownloading);
parent->keepScreenOn = themeDownloading;
}
void StarPilotThemesPanel::updateToggles() {
const bool showAllToggles = parent->showAllTogglesEnabled();
for (auto &[key, toggle] : toggles) {
if (parentKeys.contains(key)) {
toggle->setVisible(showAllToggles);
}
}
for (auto &[key, toggle] : toggles) {
if (parentKeys.contains(key)) {
continue;
}
bool setVisible = showAllToggles || parent->tuningLevel >= parent->starpilotToggleLevels[key].toDouble();
if (!showAllToggles) {
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();
}