Custom themes
Added toggles for the "Custom Themes" configuration. Colors, icons, sounds, and turn signal animations are all individually toggleable.
@@ -119,6 +119,7 @@ struct CarEvent @0x9b1657f34caf3ad3 {
|
||||
|
||||
# FrogPilot events
|
||||
blockUser @123;
|
||||
goatSteerSaturated @125;
|
||||
|
||||
radarCanErrorDEPRECATED @15;
|
||||
communityFeatureDisallowedDEPRECATED @62;
|
||||
@@ -409,6 +410,9 @@ struct CarControl {
|
||||
prompt @6;
|
||||
promptRepeat @7;
|
||||
promptDistracted @8;
|
||||
|
||||
# Other
|
||||
goat @16;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -235,13 +235,19 @@ std::unordered_map<std::string, uint32_t> keys = {
|
||||
{"CEStopLightsLead", PERSISTENT},
|
||||
{"ConditionalExperimental", PERSISTENT},
|
||||
{"CustomAlerts", PERSISTENT},
|
||||
{"CustomColors", PERSISTENT},
|
||||
{"CustomIcons", PERSISTENT},
|
||||
{"CustomPaths", PERSISTENT},
|
||||
{"CustomUI", PERSISTENT},
|
||||
{"CustomSignals", PERSISTENT},
|
||||
{"CustomSounds", PERSISTENT},
|
||||
{"CustomTheme", PERSISTENT},
|
||||
{"DecelerationProfile", PERSISTENT},
|
||||
{"DisengageVolume", PERSISTENT},
|
||||
{"EngageVolume", PERSISTENT},
|
||||
{"FrogPilotTogglesUpdated", PERSISTENT},
|
||||
{"FrogsGoMoo", PERSISTENT},
|
||||
{"GoatScream", PERSISTENT},
|
||||
{"HideAOLStatusBar", PERSISTENT},
|
||||
{"HideCEMStatusBar", PERSISTENT},
|
||||
{"LateralTune", PERSISTENT},
|
||||
|
||||
@@ -648,7 +648,7 @@ class Controls:
|
||||
good_speed = CS.vEgo > 5
|
||||
max_torque = abs(actuators.steer) > 0.99
|
||||
if undershooting and turning and good_speed and max_torque:
|
||||
lac_log.active and self.events.add(EventName.steerSaturated)
|
||||
lac_log.active and self.events.add(EventName.goatSteerSaturated if self.goat_scream else EventName.steerSaturated)
|
||||
elif lac_log.saturated:
|
||||
# TODO probably should not use dpath_points but curvature
|
||||
dpath_points = model_v2.position.y
|
||||
@@ -917,6 +917,11 @@ class Controls:
|
||||
|
||||
custom_alerts = self.params.get_bool("CustomAlerts")
|
||||
|
||||
custom_theme = self.params.get_bool("CustomTheme")
|
||||
custom_sounds = self.params.get_int("CustomSounds") if custom_theme else 0
|
||||
frog_sounds = custom_sounds == 1
|
||||
self.goat_scream = frog_sounds and self.params.get_bool("GoatScream")
|
||||
|
||||
lateral_tune = self.params.get_bool("LateralTune")
|
||||
|
||||
longitudinal_tune = self.CP.openpilotLongitudinalControl and self.params.get_bool("LongitudinalTune")
|
||||
|
||||
@@ -967,6 +967,14 @@ EVENTS: dict[int, dict[str, Alert | AlertCallbackType]] = {
|
||||
"Please don't use the 'Development' branch!",
|
||||
priority=Priority.HIGHEST),
|
||||
},
|
||||
|
||||
EventName.goatSteerSaturated: {
|
||||
ET.WARNING: Alert(
|
||||
"Turn Exceeds Steering Limit",
|
||||
"JESUS TAKE THE WHEEL!!",
|
||||
AlertStatus.userPrompt, AlertSize.mid,
|
||||
Priority.LOW, VisualAlert.steerRequired, AudibleAlert.goat, 2.),
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
After Width: | Height: | Size: 38 KiB |
|
After Width: | Height: | Size: 38 KiB |
|
After Width: | Height: | Size: 2.7 KiB |
|
After Width: | Height: | Size: 63 KiB |
|
After Width: | Height: | Size: 37 KiB |
|
After Width: | Height: | Size: 76 KiB |
|
After Width: | Height: | Size: 83 KiB |
|
After Width: | Height: | Size: 86 KiB |
|
After Width: | Height: | Size: 37 KiB |
|
After Width: | Height: | Size: 37 KiB |
|
After Width: | Height: | Size: 6.5 KiB |
|
After Width: | Height: | Size: 64 KiB |
|
After Width: | Height: | Size: 58 KiB |
|
After Width: | Height: | Size: 62 KiB |
|
After Width: | Height: | Size: 55 KiB |
|
After Width: | Height: | Size: 92 KiB |
|
After Width: | Height: | Size: 60 KiB |
|
After Width: | Height: | Size: 60 KiB |
|
After Width: | Height: | Size: 1.8 KiB |
|
After Width: | Height: | Size: 155 KiB |
|
After Width: | Height: | Size: 150 KiB |
|
After Width: | Height: | Size: 153 KiB |
|
After Width: | Height: | Size: 156 KiB |
|
After Width: | Height: | Size: 156 KiB |
@@ -16,6 +16,12 @@ FrogPilotVisualsPanel::FrogPilotVisualsPanel(SettingsWindow *parent) : FrogPilot
|
||||
{"CustomUI", tr("Custom Onroad UI"), tr("Customize the Onroad UI."), "../assets/offroad/icon_road.png"},
|
||||
{"CustomPaths", tr("Paths"), tr("Show your projected acceleration on the driving path, detected adjacent lanes, or when a vehicle is detected in your blindspot."), ""},
|
||||
|
||||
{"CustomTheme", tr("Custom Themes"), tr("Enable the ability to use custom themes."), "../frogpilot/assets/wheel_images/frog.png"},
|
||||
{"CustomColors", tr("Color Theme"), tr("Switch out the standard openpilot color scheme with themed colors.\n\nWant to submit your own color scheme? Post it in the 'feature-request' channel in the FrogPilot Discord!"), ""},
|
||||
{"CustomIcons", tr("Icon Pack"), tr("Switch out the standard openpilot icons with a set of themed icons.\n\nWant to submit your own icon pack? Post it in the 'feature-request' channel in the FrogPilot Discord!"), ""},
|
||||
{"CustomSignals", tr("Turn Signals"), tr("Add themed animation for your turn signals.\n\nWant to submit your own turn signal animation? Post it in the 'feature-request' channel in the FrogPilot Discord!"), ""},
|
||||
{"CustomSounds", tr("Sound Pack"), tr("Switch out the standard openpilot sounds with a set of themed sounds.\n\nWant to submit your own sound pack? Post it in the 'feature-request' channel in the FrogPilot Discord!"), ""},
|
||||
|
||||
{"QOLVisuals", tr("Quality of Life"), tr("Miscellaneous quality of life changes to improve your overall openpilot experience."), "../frogpilot/assets/toggle_icons/quality_of_life.png"},
|
||||
{"CameraView", tr("Camera View"), tr("Choose your preferred camera view for the onroad UI. This is purely a visual change and doesn't impact how openpilot drives."), ""},
|
||||
};
|
||||
@@ -51,6 +57,32 @@ FrogPilotVisualsPanel::FrogPilotVisualsPanel(SettingsWindow *parent) : FrogPilot
|
||||
});
|
||||
toggle = customAlertsToggle;
|
||||
|
||||
} else if (param == "CustomTheme") {
|
||||
FrogPilotParamManageControl *customThemeToggle = new FrogPilotParamManageControl(param, title, desc, icon, this);
|
||||
QObject::connect(customThemeToggle, &FrogPilotParamManageControl::manageButtonClicked, this, [this]() {
|
||||
openParentToggle();
|
||||
for (auto &[key, toggle] : toggles) {
|
||||
toggle->setVisible(customThemeKeys.find(key.c_str()) != customThemeKeys.end());
|
||||
}
|
||||
});
|
||||
toggle = customThemeToggle;
|
||||
} else if (param == "CustomColors" || param == "CustomIcons" || param == "CustomSignals" || param == "CustomSounds") {
|
||||
std::vector<QString> themeOptions{tr("Stock"), tr("Frog"), tr("Tesla"), tr("Stalin")};
|
||||
FrogPilotButtonParamControl *themeSelection = new FrogPilotButtonParamControl(param, title, desc, icon, themeOptions);
|
||||
toggle = themeSelection;
|
||||
|
||||
if (param == "CustomSounds") {
|
||||
QObject::connect(static_cast<FrogPilotButtonParamControl*>(toggle), &FrogPilotButtonParamControl::buttonClicked, [this](int id) {
|
||||
if (id == 1) {
|
||||
if (FrogPilotConfirmationDialog::yesorno(tr("Do you want to enable the bonus 'Goat' sound effect?"), this)) {
|
||||
params.putBoolNonBlocking("GoatScream", true);
|
||||
} else {
|
||||
params.putBoolNonBlocking("GoatScream", false);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
} else if (param == "CustomUI") {
|
||||
FrogPilotParamManageControl *customUIToggle = new FrogPilotParamManageControl(param, title, desc, icon, this);
|
||||
QObject::connect(customUIToggle, &FrogPilotParamManageControl::manageButtonClicked, this, [this]() {
|
||||
|
||||
@@ -25,7 +25,7 @@ private:
|
||||
std::set<QString> alertVolumeControlKeys = {"DisengageVolume", "EngageVolume", "PromptDistractedVolume", "PromptVolume", "RefuseVolume", "WarningImmediateVolume", "WarningSoftVolume"};
|
||||
std::set<QString> customAlertsKeys = {};
|
||||
std::set<QString> customOnroadUIKeys = {"CustomPaths"};
|
||||
std::set<QString> customThemeKeys = {};
|
||||
std::set<QString> customThemeKeys = {"CustomColors", "CustomIcons", "CustomSignals", "CustomSounds"};
|
||||
std::set<QString> modelUIKeys = {};
|
||||
std::set<QString> qolKeys = {"CameraView"};
|
||||
std::set<QString> screenKeys = {};
|
||||
|
||||
@@ -323,7 +323,7 @@ void AnnotatedCameraWidget::updateState(const UIState &s) {
|
||||
has_eu_speed_limit = (nav_alive && speed_limit_sign == cereal::NavInstruction::SpeedLimitSign::VIENNA);
|
||||
is_metric = s.scene.is_metric;
|
||||
speedUnit = s.scene.is_metric ? tr("km/h") : tr("mph");
|
||||
hideBottomIcons = (cs.getAlertSize() != cereal::ControlsState::AlertSize::NONE);
|
||||
hideBottomIcons = (cs.getAlertSize() != cereal::ControlsState::AlertSize::NONE || customSignals != 0 && (turnSignalLeft || turnSignalRight));
|
||||
status = s.status;
|
||||
|
||||
// update engageability/experimental mode button
|
||||
@@ -490,13 +490,21 @@ void AnnotatedCameraWidget::drawLaneLines(QPainter &painter, const UIState *s) {
|
||||
|
||||
// lanelines
|
||||
for (int i = 0; i < std::size(scene.lane_line_vertices); ++i) {
|
||||
painter.setBrush(QColor::fromRgbF(1.0, 1.0, 1.0, std::clamp<float>(scene.lane_line_probs[i], 0.0, 0.7)));
|
||||
if (customColors != 0) {
|
||||
painter.setBrush(std::get<2>(themeConfiguration[customColors]).begin()->second);
|
||||
} else {
|
||||
painter.setBrush(QColor::fromRgbF(1.0, 1.0, 1.0, std::clamp<float>(scene.lane_line_probs[i], 0.0, 0.7)));
|
||||
}
|
||||
painter.drawPolygon(scene.lane_line_vertices[i]);
|
||||
}
|
||||
|
||||
// road edges
|
||||
for (int i = 0; i < std::size(scene.road_edge_vertices); ++i) {
|
||||
painter.setBrush(QColor::fromRgbF(1.0, 0, 0, std::clamp<float>(1.0 - scene.road_edge_stds[i], 0.0, 1.0)));
|
||||
if (customColors != 0) {
|
||||
painter.setBrush(std::get<2>(themeConfiguration[customColors]).begin()->second);
|
||||
} else {
|
||||
painter.setBrush(QColor::fromRgbF(1.0, 0, 0, std::clamp<float>(1.0 - scene.road_edge_stds[i], 0.0, 1.0)));
|
||||
}
|
||||
painter.drawPolygon(scene.road_edge_vertices[i]);
|
||||
}
|
||||
|
||||
@@ -505,8 +513,14 @@ void AnnotatedCameraWidget::drawLaneLines(QPainter &painter, const UIState *s) {
|
||||
if (experimentalMode || scene.acceleration_path) {
|
||||
// The first half of track_vertices are the points for the right side of the path
|
||||
// and the indices match the positions of accel from uiPlan
|
||||
const auto &acceleration = sm["uiPlan"].getUiPlan().getAccel();
|
||||
const int max_len = std::min<int>(scene.track_vertices.length() / 2, acceleration.size());
|
||||
const auto &acceleration_const = sm["uiPlan"].getUiPlan().getAccel();
|
||||
const int max_len = std::min<int>(scene.track_vertices.length() / 2, acceleration_const.size());
|
||||
|
||||
// Copy of the acceleration vector
|
||||
std::vector<float> acceleration;
|
||||
for (int i = 0; i < acceleration_const.size(); i++) {
|
||||
acceleration.push_back(acceleration_const[i]);
|
||||
}
|
||||
|
||||
for (int i = 0; i < max_len; ++i) {
|
||||
// Some points are out of frame
|
||||
@@ -515,18 +529,33 @@ void AnnotatedCameraWidget::drawLaneLines(QPainter &painter, const UIState *s) {
|
||||
// Flip so 0 is bottom of frame
|
||||
float lin_grad_point = (height() - scene.track_vertices[i].y()) / height();
|
||||
|
||||
// speed up: 120, slow down: 0
|
||||
float path_hue = fmax(fmin(60 + acceleration[i] * 35, 120), 0);
|
||||
// FIXME: painter.drawPolygon can be slow if hue is not rounded
|
||||
path_hue = int(path_hue * 100 + 0.5) / 100;
|
||||
// If acceleration is between -0.25 and 0.25, resort to the theme color
|
||||
if (std::abs(acceleration[i]) < 0.2 && (customColors != 0)) {
|
||||
const auto &colorMap = std::get<2>(themeConfiguration[customColors]);
|
||||
for (const auto &[position, brush] : colorMap) {
|
||||
bg.setColorAt(position, brush.color());
|
||||
}
|
||||
|
||||
float saturation = fmin(fabs(acceleration[i] * 1.5), 1);
|
||||
float lightness = util::map_val(saturation, 0.0f, 1.0f, 0.95f, 0.62f); // lighter when grey
|
||||
float alpha = util::map_val(lin_grad_point, 0.75f / 2.f, 0.75f, 0.4f, 0.0f); // matches previous alpha fade
|
||||
bg.setColorAt(lin_grad_point, QColor::fromHslF(path_hue / 360., saturation, lightness, alpha));
|
||||
} else {
|
||||
// speed up: 120, slow down: 0
|
||||
float path_hue = fmax(fmin(60 + acceleration[i] * 35, 120), 0);
|
||||
// FIXME: painter.drawPolygon can be slow if hue is not rounded
|
||||
path_hue = int(path_hue * 100 + 0.5) / 100;
|
||||
|
||||
// Skip a point, unless next is last
|
||||
i += (i + 2) < max_len ? 1 : 0;
|
||||
float saturation = fmin(fabs(acceleration[i] * 1.5), 1);
|
||||
float lightness = util::map_val(saturation, 0.0f, 1.0f, 0.95f, 0.62f); // lighter when grey
|
||||
float alpha = util::map_val(lin_grad_point, 0.75f / 2.f, 0.75f, 0.4f, 0.0f); // matches previous alpha fade
|
||||
bg.setColorAt(lin_grad_point, QColor::fromHslF(path_hue / 360., saturation, lightness, alpha));
|
||||
|
||||
// Skip a point, unless next is last
|
||||
i += (i + 2) < max_len ? 1 : 0;
|
||||
}
|
||||
}
|
||||
|
||||
} else if (customColors != 0) {
|
||||
const auto &colorMap = std::get<2>(themeConfiguration[customColors]);
|
||||
for (const auto &[position, brush] : colorMap) {
|
||||
bg.setColorAt(position, brush.color());
|
||||
}
|
||||
|
||||
} else {
|
||||
@@ -600,8 +629,8 @@ void AnnotatedCameraWidget::drawDriverState(QPainter &painter, const UIState *s)
|
||||
void AnnotatedCameraWidget::drawLead(QPainter &painter, const cereal::RadarState::LeadData::Reader &lead_data, const QPointF &vd) {
|
||||
painter.save();
|
||||
|
||||
const float speedBuff = 10.;
|
||||
const float leadBuff = 40.;
|
||||
const float speedBuff = customColors != 0 ? 25. : 10.; // Make the center of the chevron appear sooner if a custom theme is active
|
||||
const float leadBuff = customColors != 0 ? 100. : 40.; // Make the center of the chevron appear sooner if a custom theme is active
|
||||
const float d_rel = lead_data.getDRel();
|
||||
const float v_rel = lead_data.getVRel();
|
||||
|
||||
@@ -627,7 +656,11 @@ void AnnotatedCameraWidget::drawLead(QPainter &painter, const cereal::RadarState
|
||||
|
||||
// chevron
|
||||
QPointF chevron[] = {{x + (sz * 1.25), y + sz}, {x, y}, {x - (sz * 1.25), y + sz}};
|
||||
painter.setBrush(redColor(fillAlpha));
|
||||
if (customColors != 0) {
|
||||
painter.setBrush(std::get<2>(themeConfiguration[customColors]).begin()->second);
|
||||
} else {
|
||||
painter.setBrush(redColor(fillAlpha));
|
||||
}
|
||||
painter.drawPolygon(chevron, std::size(chevron));
|
||||
|
||||
painter.restore();
|
||||
@@ -747,6 +780,23 @@ void AnnotatedCameraWidget::initializeFrogPilotWidgets() {
|
||||
bottom_layout->addWidget(map_settings_btn_bottom);
|
||||
|
||||
main_layout->addLayout(bottom_layout);
|
||||
|
||||
themeConfiguration = {
|
||||
{1, {"frog_theme", QColor(23, 134, 68, 242), {{0.0, QBrush(QColor::fromHslF(144 / 360., 0.71, 0.31, 0.9))},
|
||||
{0.5, QBrush(QColor::fromHslF(144 / 360., 0.71, 0.31, 0.5))},
|
||||
{1.0, QBrush(QColor::fromHslF(144 / 360., 0.71, 0.31, 0.1))}}}},
|
||||
{2, {"tesla_theme", QColor(0, 72, 255, 255), {{0.0, QBrush(QColor::fromHslF(223 / 360., 1.0, 0.5, 0.9))},
|
||||
{0.5, QBrush(QColor::fromHslF(223 / 360., 1.0, 0.5, 0.5))},
|
||||
{1.0, QBrush(QColor::fromHslF(223 / 360., 1.0, 0.5, 0.1))}}}},
|
||||
{3, {"stalin_theme", QColor(255, 0, 0, 255), {{0.0, QBrush(QColor::fromHslF(0 / 360., 1.0, 0.5, 0.9))},
|
||||
{0.5, QBrush(QColor::fromHslF(0 / 360., 1.0, 0.5, 0.5))},
|
||||
{1.0, QBrush(QColor::fromHslF(0 / 360., 1.0, 0.5, 0.1))}}}},
|
||||
};
|
||||
|
||||
animationTimer = new QTimer(this);
|
||||
connect(animationTimer, &QTimer::timeout, this, [this] {
|
||||
animationFrameIndex = (animationFrameIndex + 1) % totalFrames;
|
||||
});
|
||||
}
|
||||
|
||||
void AnnotatedCameraWidget::updateFrogPilotWidgets() {
|
||||
@@ -763,9 +813,44 @@ void AnnotatedCameraWidget::updateFrogPilotWidgets() {
|
||||
conditionalStatus = scene.conditional_status;
|
||||
showConditionalExperimentalStatusBar = scene.show_cem_status_bar;
|
||||
|
||||
customColors = scene.custom_colors;
|
||||
|
||||
experimentalMode = scene.experimental_mode;
|
||||
|
||||
mapOpen = scene.map_open;
|
||||
|
||||
turnSignalLeft = scene.turn_signal_left;
|
||||
turnSignalRight = scene.turn_signal_right;
|
||||
|
||||
if (customSignals != scene.custom_signals) {
|
||||
customSignals = scene.custom_signals;
|
||||
|
||||
QString themePath;
|
||||
|
||||
themePath = QString("../frogpilot/assets/custom_themes/%1/images").arg(
|
||||
themeConfiguration.find(customSignals) != themeConfiguration.end() ?
|
||||
std::get<0>(themeConfiguration[customSignals]) : "");
|
||||
|
||||
const QStringList imagePaths = {
|
||||
themePath + "/turn_signal_1.png",
|
||||
themePath + "/turn_signal_2.png",
|
||||
themePath + "/turn_signal_3.png",
|
||||
themePath + "/turn_signal_4.png"
|
||||
};
|
||||
|
||||
signalImgVector.clear();
|
||||
signalImgVector.reserve(2 * imagePaths.size() + 2);
|
||||
|
||||
for (const QString &imagePath : imagePaths) {
|
||||
QPixmap pixmap(imagePath);
|
||||
signalImgVector.push_back(pixmap);
|
||||
signalImgVector.push_back(pixmap.transformed(QTransform().scale(-1, 1)));
|
||||
}
|
||||
|
||||
const QPixmap blindSpotPixmap(themePath + "/turn_signal_1_red.png");
|
||||
signalImgVector.push_back(blindSpotPixmap);
|
||||
signalImgVector.push_back(blindSpotPixmap.transformed(QTransform().scale(-1, 1)));
|
||||
}
|
||||
}
|
||||
|
||||
void AnnotatedCameraWidget::paintFrogPilotWidgets(QPainter &p) {
|
||||
@@ -773,6 +858,15 @@ void AnnotatedCameraWidget::paintFrogPilotWidgets(QPainter &p) {
|
||||
drawStatusBar(p);
|
||||
}
|
||||
|
||||
if (customSignals != 0 && (turnSignalLeft || turnSignalRight)) {
|
||||
if (!animationTimer->isActive()) {
|
||||
animationTimer->start(totalFrames * 11); // 440 milliseconds per loop; syncs up perfectly with my 2019 Lexus ES 350 turn signal clicks
|
||||
}
|
||||
drawTurnSignals(p);
|
||||
} else if (animationTimer->isActive()) {
|
||||
animationTimer->stop();
|
||||
}
|
||||
|
||||
map_settings_btn_bottom->setEnabled(map_settings_btn->isEnabled());
|
||||
if (map_settings_btn_bottom->isEnabled()) {
|
||||
map_settings_btn_bottom->setVisible(!hideBottomIcons);
|
||||
@@ -850,3 +944,28 @@ void AnnotatedCameraWidget::drawStatusBar(QPainter &p) {
|
||||
|
||||
p.restore();
|
||||
}
|
||||
|
||||
void AnnotatedCameraWidget::drawTurnSignals(QPainter &p) {
|
||||
constexpr int signalHeight = 480;
|
||||
constexpr int signalWidth = 360;
|
||||
|
||||
p.setRenderHint(QPainter::Antialiasing);
|
||||
|
||||
int baseYPosition = (height() - signalHeight) / 2 + (showAlwaysOnLateralStatusBar || showConditionalExperimentalStatusBar ? 225 : 300) - alertSize;
|
||||
int leftSignalXPosition = 75 + width() - signalWidth - 300 * (blindSpotLeft ? 0 : animationFrameIndex);
|
||||
int rightSignalXPosition = -75 + 300 * (blindSpotRight ? 0 : animationFrameIndex);
|
||||
|
||||
if (animationFrameIndex < signalImgVector.size()) {
|
||||
auto drawSignal = [&](bool signalActivated, int xPosition, bool flip, bool blindspot) {
|
||||
if (signalActivated) {
|
||||
int uniqueImages = signalImgVector.size() / 4;
|
||||
int index = (blindspot ? 2 * uniqueImages : 2 * animationFrameIndex % totalFrames) + (flip ? 1 : 0);
|
||||
QPixmap &signal = signalImgVector[index];
|
||||
p.drawPixmap(xPosition, baseYPosition, signalWidth, signalHeight, signal);
|
||||
}
|
||||
};
|
||||
|
||||
drawSignal(turnSignalLeft, leftSignalXPosition, false, blindSpotLeft);
|
||||
drawSignal(turnSignalRight, rightSignalXPosition, true, blindSpotRight);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -111,6 +111,7 @@ private:
|
||||
void updateFrogPilotWidgets();
|
||||
|
||||
void drawStatusBar(QPainter &p);
|
||||
void drawTurnSignals(QPainter &p);
|
||||
|
||||
// FrogPilot variables
|
||||
Params paramsMemory{"/dev/shm/params"};
|
||||
@@ -125,10 +126,22 @@ private:
|
||||
bool mapOpen;
|
||||
bool showAlwaysOnLateralStatusBar;
|
||||
bool showConditionalExperimentalStatusBar;
|
||||
bool turnSignalLeft;
|
||||
bool turnSignalRight;
|
||||
|
||||
int alertSize;
|
||||
int cameraView;
|
||||
int conditionalStatus;
|
||||
int customColors;
|
||||
int customSignals;
|
||||
int totalFrames = 8;
|
||||
|
||||
size_t animationFrameIndex;
|
||||
|
||||
std::unordered_map<int, std::tuple<QString, QColor, std::map<double, QBrush>>> themeConfiguration;
|
||||
std::vector<QPixmap> signalImgVector;
|
||||
|
||||
QTimer *animationTimer;
|
||||
|
||||
protected:
|
||||
void paintGL() override;
|
||||
|
||||
@@ -40,6 +40,27 @@ Sidebar::Sidebar(QWidget *parent) : QFrame(parent), onroad(false), flag_pressed(
|
||||
pm = std::make_unique<PubMaster, const std::initializer_list<const char *>>({"userFlag"});
|
||||
|
||||
// FrogPilot variables
|
||||
themeConfiguration = {
|
||||
{0, {"stock", {QColor(255, 255, 255)}}},
|
||||
{1, {"frog_theme", {QColor(23, 134, 68)}}},
|
||||
{2, {"tesla_theme", {QColor(0, 72, 255)}}},
|
||||
{3, {"stalin_theme", {QColor(255, 0, 0)}}}
|
||||
};
|
||||
|
||||
for (auto &[key, themeData] : themeConfiguration) {
|
||||
QString &themeName = themeData.first;
|
||||
QString base = themeName == "stock" ? "../assets/images" : QString("../frogpilot/assets/custom_themes/%1/images").arg(themeName);
|
||||
std::vector<QString> paths = {base + "/button_home.png", base + "/button_flag.png", base + "/button_settings.png"};
|
||||
|
||||
home_imgs[key] = loadPixmap(paths[0], home_btn.size());
|
||||
flag_imgs[key] = loadPixmap(paths[1], home_btn.size());
|
||||
settings_imgs[key] = loadPixmap(paths[2], settings_btn.size(), Qt::IgnoreAspectRatio);
|
||||
}
|
||||
|
||||
home_img = home_imgs[scene.custom_icons];
|
||||
flag_img = flag_imgs[scene.custom_icons];
|
||||
settings_img = settings_imgs[scene.custom_icons];
|
||||
currentColors = themeConfiguration[scene.custom_colors].second;
|
||||
}
|
||||
|
||||
void Sidebar::mousePressEvent(QMouseEvent *event) {
|
||||
@@ -82,6 +103,12 @@ void Sidebar::updateState(const UIState &s) {
|
||||
setProperty("netStrength", strength > 0 ? strength + 1 : 0);
|
||||
|
||||
// FrogPilot properties
|
||||
home_img = home_imgs[scene.custom_icons];
|
||||
flag_img = flag_imgs[scene.custom_icons];
|
||||
settings_img = settings_imgs[scene.custom_icons];
|
||||
|
||||
currentColors = themeConfiguration[scene.custom_colors].second;
|
||||
|
||||
auto frogpilotDeviceState = sm["frogpilotDeviceState"].getFrogpilotDeviceState();
|
||||
|
||||
ItemStatus connectStatus;
|
||||
@@ -90,7 +117,7 @@ void Sidebar::updateState(const UIState &s) {
|
||||
connectStatus = ItemStatus{{tr("CONNECT"), tr("OFFLINE")}, warning_color};
|
||||
} else {
|
||||
connectStatus = nanos_since_boot() - last_ping < 80e9
|
||||
? ItemStatus{{tr("CONNECT"), tr("ONLINE")}, good_color}
|
||||
? ItemStatus{{tr("CONNECT"), tr("ONLINE")}, currentColors[0]}
|
||||
: ItemStatus{{tr("CONNECT"), tr("ERROR")}, danger_color};
|
||||
}
|
||||
setProperty("connectStatus", QVariant::fromValue(connectStatus));
|
||||
@@ -98,13 +125,13 @@ void Sidebar::updateState(const UIState &s) {
|
||||
ItemStatus tempStatus = {{tr("TEMP"), tr("HIGH")}, danger_color};
|
||||
auto ts = deviceState.getThermalStatus();
|
||||
if (ts == cereal::DeviceState::ThermalStatus::GREEN) {
|
||||
tempStatus = {{tr("TEMP"), tr("GOOD")}, good_color};
|
||||
tempStatus = {{tr("TEMP"), tr("GOOD")}, currentColors[0]};
|
||||
} else if (ts == cereal::DeviceState::ThermalStatus::YELLOW) {
|
||||
tempStatus = {{tr("TEMP"), tr("OK")}, warning_color};
|
||||
}
|
||||
setProperty("tempStatus", QVariant::fromValue(tempStatus));
|
||||
|
||||
ItemStatus pandaStatus = {{tr("VEHICLE"), tr("ONLINE")}, good_color};
|
||||
ItemStatus pandaStatus = {{tr("VEHICLE"), tr("ONLINE")}, currentColors[0]};
|
||||
if (s.scene.pandaType == cereal::PandaState::PandaType::UNKNOWN) {
|
||||
pandaStatus = {{tr("NO"), tr("PANDA")}, danger_color};
|
||||
} else if (s.scene.started && !sm["liveLocationKalman"].getLiveLocationKalman().getGpsOK()) {
|
||||
|
||||
@@ -65,4 +65,11 @@ private:
|
||||
// FrogPilot variables
|
||||
Params params;
|
||||
UIScene &scene;
|
||||
|
||||
std::unordered_map<int, std::pair<QString, std::vector<QColor>>> themeConfiguration;
|
||||
std::unordered_map<int, QPixmap> flag_imgs;
|
||||
std::unordered_map<int, QPixmap> home_imgs;
|
||||
std::unordered_map<int, QPixmap> settings_imgs;
|
||||
|
||||
std::vector<QColor> currentColors;
|
||||
};
|
||||
|
||||
@@ -39,6 +39,9 @@ sound_list: dict[int, tuple[str, int | None, float]] = {
|
||||
|
||||
AudibleAlert.warningSoft: ("warning_soft.wav", None, MAX_VOLUME),
|
||||
AudibleAlert.warningImmediate: ("warning_immediate.wav", None, MAX_VOLUME),
|
||||
|
||||
# Other
|
||||
AudibleAlert.goat: ("goat.wav", None, MAX_VOLUME),
|
||||
}
|
||||
|
||||
def check_controls_timeout_alert(sm):
|
||||
@@ -57,6 +60,8 @@ class Soundd:
|
||||
self.params = Params()
|
||||
self.params_memory = Params("/dev/shm/params")
|
||||
|
||||
self.previous_sound_directory = None
|
||||
|
||||
self.update_frogpilot_params()
|
||||
|
||||
self.current_alert = AudibleAlert.none
|
||||
@@ -72,9 +77,15 @@ class Soundd:
|
||||
|
||||
# Load all sounds
|
||||
for sound in sound_list:
|
||||
if sound == AudibleAlert.goat and not self.goat_scream:
|
||||
continue
|
||||
|
||||
filename, play_count, volume = sound_list[sound]
|
||||
|
||||
wavefile = wave.open(BASEDIR + "/selfdrive/assets/sounds/" + filename, 'r')
|
||||
try:
|
||||
wavefile = wave.open(self.sound_directory + filename, 'r')
|
||||
except FileNotFoundError:
|
||||
wavefile = wave.open(BASEDIR + "/selfdrive/assets/sounds/" + filename, 'r')
|
||||
|
||||
assert wavefile.getnchannels() == 1
|
||||
assert wavefile.getsampwidth() == 2
|
||||
@@ -181,9 +192,27 @@ class Soundd:
|
||||
|
||||
AudibleAlert.warningSoft: self.params.get_int("WarningSoftVolume"),
|
||||
AudibleAlert.warningImmediate: self.params.get_int("WarningImmediateVolume"),
|
||||
|
||||
AudibleAlert.goat: self.params.get_int("PromptVolume"),
|
||||
}
|
||||
|
||||
self.load_sounds()
|
||||
custom_theme = self.params.get_bool("CustomTheme")
|
||||
custom_sounds = self.params.get_int("CustomSounds") if custom_theme else 0
|
||||
self.goat_scream = custom_sounds == 1 and self.params.get_bool("GoatScream")
|
||||
|
||||
theme_configuration = {
|
||||
1: "frog_theme",
|
||||
2: "tesla_theme",
|
||||
3: "stalin_theme"
|
||||
}
|
||||
|
||||
theme_name = theme_configuration.get(custom_sounds)
|
||||
self.sound_directory = BASEDIR + ("/selfdrive/frogpilot/assets/custom_themes/" + theme_name + "/sounds/" if custom_sounds != 0 else "/selfdrive/assets/sounds/")
|
||||
|
||||
if self.sound_directory != self.previous_sound_directory:
|
||||
self.load_sounds()
|
||||
|
||||
self.previous_sound_directory = self.sound_directory
|
||||
|
||||
def main():
|
||||
s = Soundd()
|
||||
|
||||
@@ -214,6 +214,8 @@ static void update_state(UIState *s) {
|
||||
scene.acceleration = carState.getAEgo();
|
||||
scene.blind_spot_left = carState.getLeftBlindspot();
|
||||
scene.blind_spot_right = carState.getRightBlindspot();
|
||||
scene.turn_signal_left = carState.getLeftBlinker();
|
||||
scene.turn_signal_right = carState.getRightBlinker();
|
||||
}
|
||||
if (sm.updated("controlsState")) {
|
||||
auto controlsState = sm["controlsState"].getControlsState();
|
||||
@@ -273,6 +275,11 @@ void ui_update_frogpilot_params(UIState *s) {
|
||||
scene.acceleration_path = custom_paths && params.getBool("AccelerationPath");
|
||||
scene.blind_spot_path = custom_paths && params.getBool("BlindSpotPath");
|
||||
|
||||
bool custom_theme = params.getBool("CustomTheme");
|
||||
scene.custom_colors = custom_theme ? params.getInt("CustomColors") : 0;
|
||||
scene.custom_icons = custom_theme ? params.getInt("CustomIcons") : 0;
|
||||
scene.custom_signals = custom_theme ? params.getInt("CustomSignals") : 0;
|
||||
|
||||
bool quality_of_life_controls = params.getBool("QOLControls");
|
||||
|
||||
bool quality_of_life_visuals = params.getBool("QOLVisuals");
|
||||
|
||||
@@ -183,6 +183,8 @@ typedef struct UIScene {
|
||||
bool right_hand_drive;
|
||||
bool show_aol_status_bar;
|
||||
bool show_cem_status_bar;
|
||||
bool turn_signal_left;
|
||||
bool turn_signal_right;
|
||||
|
||||
float acceleration;
|
||||
float lane_width_left;
|
||||
@@ -193,6 +195,9 @@ typedef struct UIScene {
|
||||
int conditional_speed;
|
||||
int conditional_speed_lead;
|
||||
int conditional_status;
|
||||
int custom_colors;
|
||||
int custom_icons;
|
||||
int custom_signals;
|
||||
|
||||
QPolygonF track_adjacent_vertices[6];
|
||||
|
||||
|
||||