Visuals - Custom Themes - Turn Signals

Add themed animation for your turn signals.

Want to submit your own turn signal animation? Post it in the 'feature-request' channel in the FrogPilot Discord!
This commit is contained in:
FrogAi 2024-05-11 16:37:37 -07:00
parent d507acf585
commit 17de87484a
19 changed files with 89 additions and 1 deletions

Binary file not shown.

After

Width:  |  Height:  |  Size: 63 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 37 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 76 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 83 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 86 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 64 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 58 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 62 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 55 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 92 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 155 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 150 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 153 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 156 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 156 KiB

View File

@ -438,7 +438,7 @@ void AnnotatedCameraWidget::updateState(const UIState &s) {
has_eu_speed_limit = (nav_alive && speed_limit_sign == cereal::NavInstruction::SpeedLimitSign::VIENNA) && !(speedLimitController && !useViennaSLCSign) || (speedLimitController && useViennaSLCSign); has_eu_speed_limit = (nav_alive && speed_limit_sign == cereal::NavInstruction::SpeedLimitSign::VIENNA) && !(speedLimitController && !useViennaSLCSign) || (speedLimitController && useViennaSLCSign);
is_metric = s.scene.is_metric; is_metric = s.scene.is_metric;
speedUnit = s.scene.is_metric ? tr("km/h") : tr("mph"); 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; status = s.status;
// update engageability/experimental mode button // update engageability/experimental mode button
@ -984,6 +984,11 @@ void AnnotatedCameraWidget::initializeFrogPilotWidgets() {
{0.5, QBrush(QColor::fromHslF(0 / 360., 1.0, 0.5, 0.5))}, {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))}}}}, {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() { void AnnotatedCameraWidget::updateFrogPilotWidgets() {
@ -1043,6 +1048,39 @@ void AnnotatedCameraWidget::updateFrogPilotWidgets() {
useViennaSLCSign = scene.use_vienna_slc_sign; useViennaSLCSign = scene.use_vienna_slc_sign;
trafficModeActive = scene.traffic_mode_active; trafficModeActive = scene.traffic_mode_active;
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) { void AnnotatedCameraWidget::paintFrogPilotWidgets(QPainter &p) {
@ -1050,6 +1088,15 @@ void AnnotatedCameraWidget::paintFrogPilotWidgets(QPainter &p) {
drawStatusBar(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();
}
if (scene.speed_limit_changed) { if (scene.speed_limit_changed) {
drawSLCConfirmation(p); drawSLCConfirmation(p);
} }
@ -1432,3 +1479,28 @@ void AnnotatedCameraWidget::drawStatusBar(QPainter &p) {
p.restore(); 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 || roadNameUI ? 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);
}
}

View File

@ -190,6 +190,7 @@ private:
void drawSLCConfirmation(QPainter &p); void drawSLCConfirmation(QPainter &p);
void drawStatusBar(QPainter &p); void drawStatusBar(QPainter &p);
void drawTurnSignals(QPainter &p);
// FrogPilot variables // FrogPilot variables
Params paramsMemory{"/dev/shm/params"}; Params paramsMemory{"/dev/shm/params"};
@ -215,6 +216,8 @@ private:
bool slcOverridden; bool slcOverridden;
bool speedLimitController; bool speedLimitController;
bool trafficModeActive; bool trafficModeActive;
bool turnSignalLeft;
bool turnSignalRight;
bool useViennaSLCSign; bool useViennaSLCSign;
bool vtscControllingCurve; bool vtscControllingCurve;
@ -230,12 +233,19 @@ private:
int alertSize; int alertSize;
int conditionalStatus; int conditionalStatus;
int customColors; int customColors;
int customSignals;
int totalFrames = 8;
QString accelerationUnit; QString accelerationUnit;
QString leadDistanceUnit; QString leadDistanceUnit;
QString leadSpeedUnit; QString leadSpeedUnit;
size_t animationFrameIndex;
std::unordered_map<int, std::tuple<QString, QColor, std::map<double, QBrush>>> themeConfiguration; std::unordered_map<int, std::tuple<QString, QColor, std::map<double, QBrush>>> themeConfiguration;
std::vector<QPixmap> signalImgVector;
QTimer *animationTimer;
inline QColor blueColor(int alpha = 255) { return QColor(0, 150, 255, alpha); } inline QColor blueColor(int alpha = 255) { return QColor(0, 150, 255, alpha); }
inline QColor greenColor(int alpha = 242) { return QColor(23, 134, 68, alpha); } inline QColor greenColor(int alpha = 242) { return QColor(23, 134, 68, alpha); }

View File

@ -225,6 +225,8 @@ static void update_state(UIState *s) {
scene.parked = carState.getGearShifter() == cereal::CarState::GearShifter::PARK; scene.parked = carState.getGearShifter() == cereal::CarState::GearShifter::PARK;
scene.standstill = carState.getStandstill(); scene.standstill = carState.getStandstill();
scene.steering_angle_deg = -carState.getSteeringAngleDeg(); scene.steering_angle_deg = -carState.getSteeringAngleDeg();
scene.turn_signal_left = carState.getLeftBlinker();
scene.turn_signal_right = carState.getRightBlinker();
} }
if (sm.updated("controlsState")) { if (sm.updated("controlsState")) {
auto controlsState = sm["controlsState"].getControlsState(); auto controlsState = sm["controlsState"].getControlsState();
@ -317,6 +319,7 @@ void ui_update_frogpilot_params(UIState *s) {
bool custom_theme = params.getBool("CustomTheme"); bool custom_theme = params.getBool("CustomTheme");
scene.custom_colors = custom_theme ? params.getInt("CustomColors") : 0; scene.custom_colors = custom_theme ? params.getInt("CustomColors") : 0;
scene.custom_icons = custom_theme ? params.getInt("CustomIcons") : 0; scene.custom_icons = custom_theme ? params.getInt("CustomIcons") : 0;
scene.custom_signals = custom_theme ? params.getInt("CustomSignals") : 0;
scene.disable_smoothing_mtsc = params.getBool("MTSCEnabled") && params.getBool("DisableMTSCSmoothing"); scene.disable_smoothing_mtsc = params.getBool("MTSCEnabled") && params.getBool("DisableMTSCSmoothing");
scene.disable_smoothing_vtsc = params.getBool("VisionTurnControl") && params.getBool("DisableVTSCSmoothing"); scene.disable_smoothing_vtsc = params.getBool("VisionTurnControl") && params.getBool("DisableVTSCSmoothing");

View File

@ -224,6 +224,8 @@ typedef struct UIScene {
bool tethering_enabled; bool tethering_enabled;
bool traffic_mode; bool traffic_mode;
bool traffic_mode_active; bool traffic_mode_active;
bool turn_signal_left;
bool turn_signal_right;
bool use_kaofui_icons; bool use_kaofui_icons;
bool use_vienna_slc_sign; bool use_vienna_slc_sign;
bool vtsc_controlling_curve; bool vtsc_controlling_curve;
@ -245,6 +247,7 @@ typedef struct UIScene {
int conditional_status; int conditional_status;
int custom_colors; int custom_colors;
int custom_icons; int custom_icons;
int custom_signals;
int steering_angle_deg; int steering_angle_deg;
int wheel_icon; int wheel_icon;