diff --git a/selfdrive/frogpilot/assets/custom_themes/frog_theme/images/turn_signal_1.png b/selfdrive/frogpilot/assets/custom_themes/frog_theme/images/turn_signal_1.png new file mode 100644 index 00000000..43e0b446 Binary files /dev/null and b/selfdrive/frogpilot/assets/custom_themes/frog_theme/images/turn_signal_1.png differ diff --git a/selfdrive/frogpilot/assets/custom_themes/frog_theme/images/turn_signal_1_red.png b/selfdrive/frogpilot/assets/custom_themes/frog_theme/images/turn_signal_1_red.png new file mode 100644 index 00000000..7c102456 Binary files /dev/null and b/selfdrive/frogpilot/assets/custom_themes/frog_theme/images/turn_signal_1_red.png differ diff --git a/selfdrive/frogpilot/assets/custom_themes/frog_theme/images/turn_signal_2.png b/selfdrive/frogpilot/assets/custom_themes/frog_theme/images/turn_signal_2.png new file mode 100644 index 00000000..e8e14797 Binary files /dev/null and b/selfdrive/frogpilot/assets/custom_themes/frog_theme/images/turn_signal_2.png differ diff --git a/selfdrive/frogpilot/assets/custom_themes/frog_theme/images/turn_signal_3.png b/selfdrive/frogpilot/assets/custom_themes/frog_theme/images/turn_signal_3.png new file mode 100644 index 00000000..b59b003f Binary files /dev/null and b/selfdrive/frogpilot/assets/custom_themes/frog_theme/images/turn_signal_3.png differ diff --git a/selfdrive/frogpilot/assets/custom_themes/frog_theme/images/turn_signal_4.png b/selfdrive/frogpilot/assets/custom_themes/frog_theme/images/turn_signal_4.png new file mode 100644 index 00000000..c3c1d204 Binary files /dev/null and b/selfdrive/frogpilot/assets/custom_themes/frog_theme/images/turn_signal_4.png differ diff --git a/selfdrive/frogpilot/assets/custom_themes/stalin_theme/images/turn_signal_1.png b/selfdrive/frogpilot/assets/custom_themes/stalin_theme/images/turn_signal_1.png new file mode 100644 index 00000000..f5116df8 Binary files /dev/null and b/selfdrive/frogpilot/assets/custom_themes/stalin_theme/images/turn_signal_1.png differ diff --git a/selfdrive/frogpilot/assets/custom_themes/stalin_theme/images/turn_signal_1_red.png b/selfdrive/frogpilot/assets/custom_themes/stalin_theme/images/turn_signal_1_red.png new file mode 100644 index 00000000..4c948135 Binary files /dev/null and b/selfdrive/frogpilot/assets/custom_themes/stalin_theme/images/turn_signal_1_red.png differ diff --git a/selfdrive/frogpilot/assets/custom_themes/stalin_theme/images/turn_signal_2.png b/selfdrive/frogpilot/assets/custom_themes/stalin_theme/images/turn_signal_2.png new file mode 100644 index 00000000..8ceed238 Binary files /dev/null and b/selfdrive/frogpilot/assets/custom_themes/stalin_theme/images/turn_signal_2.png differ diff --git a/selfdrive/frogpilot/assets/custom_themes/stalin_theme/images/turn_signal_3.png b/selfdrive/frogpilot/assets/custom_themes/stalin_theme/images/turn_signal_3.png new file mode 100644 index 00000000..48dbe09b Binary files /dev/null and b/selfdrive/frogpilot/assets/custom_themes/stalin_theme/images/turn_signal_3.png differ diff --git a/selfdrive/frogpilot/assets/custom_themes/stalin_theme/images/turn_signal_4.png b/selfdrive/frogpilot/assets/custom_themes/stalin_theme/images/turn_signal_4.png new file mode 100644 index 00000000..3e462798 Binary files /dev/null and b/selfdrive/frogpilot/assets/custom_themes/stalin_theme/images/turn_signal_4.png differ diff --git a/selfdrive/frogpilot/assets/custom_themes/tesla_theme/images/turn_signal_1.png b/selfdrive/frogpilot/assets/custom_themes/tesla_theme/images/turn_signal_1.png new file mode 100644 index 00000000..b635c577 Binary files /dev/null and b/selfdrive/frogpilot/assets/custom_themes/tesla_theme/images/turn_signal_1.png differ diff --git a/selfdrive/frogpilot/assets/custom_themes/tesla_theme/images/turn_signal_1_red.png b/selfdrive/frogpilot/assets/custom_themes/tesla_theme/images/turn_signal_1_red.png new file mode 100644 index 00000000..a2f508f7 Binary files /dev/null and b/selfdrive/frogpilot/assets/custom_themes/tesla_theme/images/turn_signal_1_red.png differ diff --git a/selfdrive/frogpilot/assets/custom_themes/tesla_theme/images/turn_signal_2.png b/selfdrive/frogpilot/assets/custom_themes/tesla_theme/images/turn_signal_2.png new file mode 100644 index 00000000..5158c0f3 Binary files /dev/null and b/selfdrive/frogpilot/assets/custom_themes/tesla_theme/images/turn_signal_2.png differ diff --git a/selfdrive/frogpilot/assets/custom_themes/tesla_theme/images/turn_signal_3.png b/selfdrive/frogpilot/assets/custom_themes/tesla_theme/images/turn_signal_3.png new file mode 100644 index 00000000..c0e2a877 Binary files /dev/null and b/selfdrive/frogpilot/assets/custom_themes/tesla_theme/images/turn_signal_3.png differ diff --git a/selfdrive/frogpilot/assets/custom_themes/tesla_theme/images/turn_signal_4.png b/selfdrive/frogpilot/assets/custom_themes/tesla_theme/images/turn_signal_4.png new file mode 100644 index 00000000..5932e83b Binary files /dev/null and b/selfdrive/frogpilot/assets/custom_themes/tesla_theme/images/turn_signal_4.png differ diff --git a/selfdrive/ui/qt/onroad/annotated_camera.cc b/selfdrive/ui/qt/onroad/annotated_camera.cc index 95173e5e..17ec2da1 100644 --- a/selfdrive/ui/qt/onroad/annotated_camera.cc +++ b/selfdrive/ui/qt/onroad/annotated_camera.cc @@ -77,7 +77,7 @@ void AnnotatedCameraWidget::updateState(const UIState &s) { has_eu_speed_limit = (nav_alive && speed_limit_sign == cereal::NavInstruction::SpeedLimitSign::VIENNA) && !(speedLimitController && !useViennaSLCSign) || (speedLimitController && useViennaSLCSign); 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 @@ -652,6 +652,11 @@ void AnnotatedCameraWidget::initializeFrogPilotWidgets() { {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::paintFrogPilotWidgets(QPainter &painter, const UIScene &scene) { @@ -746,6 +751,49 @@ void AnnotatedCameraWidget::paintFrogPilotWidgets(QPainter &painter, const UISce } trafficModeActive = scene.traffic_mode_active; + + turnSignalLeft = scene.turn_signal_left; + turnSignalRight = scene.turn_signal_right; + 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(painter); + } else if (animationTimer->isActive()) { + animationTimer->stop(); + } + + 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))); + + totalFrames = 8; + } } Compass::Compass(QWidget *parent) : QWidget(parent) { @@ -1025,3 +1073,27 @@ 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 || 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 index = (blindspot ? totalFrames : 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); + } +} diff --git a/selfdrive/ui/qt/onroad/annotated_camera.h b/selfdrive/ui/qt/onroad/annotated_camera.h index 86d7727f..3561105d 100644 --- a/selfdrive/ui/qt/onroad/annotated_camera.h +++ b/selfdrive/ui/qt/onroad/annotated_camera.h @@ -93,6 +93,7 @@ private: void drawSLCConfirmation(QPainter &p); void drawStatusBar(QPainter &p); + void drawTurnSignals(QPainter &p); // FrogPilot variables Params paramsMemory{"/dev/shm/params"}; @@ -119,6 +120,8 @@ private: bool speedLimitChanged; bool speedLimitController; bool trafficModeActive; + bool turnSignalLeft; + bool turnSignalRight; bool useViennaSLCSign; bool vtscControllingCurve; @@ -137,6 +140,8 @@ private: int conditionalSpeedLead; int conditionalStatus; int customColors; + int customSignals; + int totalFrames; QPixmap stopSignImg; @@ -144,7 +149,12 @@ private: QString leadDistanceUnit; QString leadSpeedUnit; + size_t animationFrameIndex; + std::unordered_map>> themeConfiguration; + std::vector signalImgVector; + + QTimer *animationTimer; inline QColor blueColor(int alpha = 255) { return QColor(0, 150, 255, alpha); } inline QColor greenColor(int alpha = 242) { return QColor(23, 134, 68, alpha); } diff --git a/selfdrive/ui/ui.cc b/selfdrive/ui/ui.cc index 7f4bd127..40437d2d 100644 --- a/selfdrive/ui/ui.cc +++ b/selfdrive/ui/ui.cc @@ -226,6 +226,8 @@ static void update_state(UIState *s) { scene.reverse = carState.getGearShifter() == cereal::CarState::GearShifter::REVERSE; scene.standstill = carState.getStandstill() && !scene.reverse; scene.steering_angle_deg = -carState.getSteeringAngleDeg(); + scene.turn_signal_left = carState.getLeftBlinker(); + scene.turn_signal_right = carState.getRightBlinker(); } if (sm.updated("controlsState")) { auto controlsState = sm["controlsState"].getControlsState(); @@ -332,6 +334,7 @@ void ui_update_frogpilot_params(UIState *s, Params ¶ms) { 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; scene.disable_smoothing_mtsc = params.getBool("MTSCEnabled") && params.getBool("DisableMTSCSmoothing"); scene.disable_smoothing_vtsc = params.getBool("VisionTurnControl") && params.getBool("DisableVTSCSmoothing"); diff --git a/selfdrive/ui/ui.h b/selfdrive/ui/ui.h index 2cc9f00d..506f5f5b 100644 --- a/selfdrive/ui/ui.h +++ b/selfdrive/ui/ui.h @@ -166,6 +166,8 @@ typedef struct UIScene { bool tethering_enabled; bool traffic_mode; bool traffic_mode_active; + bool turn_signal_left; + bool turn_signal_right; bool use_kaofui_icons; bool use_vienna_slc_sign; bool vtsc_controlling_curve; @@ -187,6 +189,7 @@ typedef struct UIScene { int conditional_status; int custom_colors; int custom_icons; + int custom_signals; int model_length; int steering_angle_deg; int tethering_config;