diff --git a/common/params.cc b/common/params.cc index 1d9411956fe638..98310657f87651 100644 --- a/common/params.cc +++ b/common/params.cc @@ -129,6 +129,7 @@ std::unordered_map keys = { {"FrogColors", PERSISTENT}, {"FrogIcons", PERSISTENT}, {"FrogPilotTogglesUpdated", CLEAR_ON_MANAGER_START | CLEAR_ON_OFFROAD_TRANSITION}, + {"FrogSignals", PERSISTENT}, {"FrogSounds", PERSISTENT}, {"FrogTheme", PERSISTENT}, {"GitBranch", PERSISTENT}, diff --git a/selfdrive/assets/images/frog_turn_signal_1.png b/selfdrive/assets/images/frog_turn_signal_1.png new file mode 100644 index 00000000000000..43e0b446f8ea9d Binary files /dev/null and b/selfdrive/assets/images/frog_turn_signal_1.png differ diff --git a/selfdrive/assets/images/frog_turn_signal_1_red.png b/selfdrive/assets/images/frog_turn_signal_1_red.png new file mode 100644 index 00000000000000..7c102456e6efb6 Binary files /dev/null and b/selfdrive/assets/images/frog_turn_signal_1_red.png differ diff --git a/selfdrive/assets/images/frog_turn_signal_2.png b/selfdrive/assets/images/frog_turn_signal_2.png new file mode 100644 index 00000000000000..e8e147976b7330 Binary files /dev/null and b/selfdrive/assets/images/frog_turn_signal_2.png differ diff --git a/selfdrive/assets/images/frog_turn_signal_3.png b/selfdrive/assets/images/frog_turn_signal_3.png new file mode 100644 index 00000000000000..b59b003f95eb2f Binary files /dev/null and b/selfdrive/assets/images/frog_turn_signal_3.png differ diff --git a/selfdrive/assets/images/frog_turn_signal_4.png b/selfdrive/assets/images/frog_turn_signal_4.png new file mode 100644 index 00000000000000..c3c1d204e68375 Binary files /dev/null and b/selfdrive/assets/images/frog_turn_signal_4.png differ diff --git a/selfdrive/ui/qt/offroad/frogpilot_settings.cc b/selfdrive/ui/qt/offroad/frogpilot_settings.cc index e31d681a0dc097..5dc2d81b79e408 100644 --- a/selfdrive/ui/qt/offroad/frogpilot_settings.cc +++ b/selfdrive/ui/qt/offroad/frogpilot_settings.cc @@ -83,6 +83,7 @@ FrogPilotVisualsPanel::FrogPilotVisualsPanel(QWidget *parent) : FrogPilotPanel(p {"FrogColors", "Colors"}, {"FrogIcons", "Icons"}, {"FrogSounds", "Sounds"}, + {"FrogSignals", "Turn Signals"} }, mainLayout); } else if (key == "CustomRoadUI") { createSubControl(key, label, desc, icon, { diff --git a/selfdrive/ui/qt/onroad.cc b/selfdrive/ui/qt/onroad.cc index b7c9fbf682d391..b3f0b379ba4598 100644 --- a/selfdrive/ui/qt/onroad.cc +++ b/selfdrive/ui/qt/onroad.cc @@ -4,6 +4,7 @@ #include #include +#include #include "common/timing.h" #include "selfdrive/ui/qt/util.h" @@ -363,6 +364,30 @@ AnnotatedCameraWidget::AnnotatedCameraWidget(VisionStreamType type, QWidget* par {3, loadPixmap("../assets/frog.png", {img_size, img_size})}, {4, loadPixmap("../assets/rocket.png", {img_size, img_size})} }; + + // Turn signal images + const QStringList imagePaths = { + "../assets/images/frog_turn_signal_1.png", + "../assets/images/frog_turn_signal_2.png", + "../assets/images/frog_turn_signal_3.png", + "../assets/images/frog_turn_signal_4.png" + }; + signalImgVector.reserve(2 * imagePaths.size() + 1); + for (int i = 0; i < 2; ++i) { + for (const QString& path : imagePaths) { + signalImgVector.push_back(QPixmap(path)); + } + } + // Add the blindspot signal image to the vector + signalImgVector.push_back(QPixmap("../assets/images/frog_turn_signal_1_red.png")); + + // Initialize the timer for the turn signal animation + auto animationTimer = new QTimer(this); + connect(animationTimer, &QTimer::timeout, this, [this] { + animationFrameIndex = (animationFrameIndex + 1) % totalFrames; + update(); + }); + animationTimer->start(totalFrames * 11); // 50 milliseconds per frame; syncs up perfectly with my 2019 Lexus ES 350 turn signal clicks } void AnnotatedCameraWidget::updateState(const UIState &s) { @@ -408,7 +433,7 @@ void AnnotatedCameraWidget::updateState(const UIState &s) { setProperty("speed", cur_speed); setProperty("setSpeed", set_speed); setProperty("speedUnit", s.scene.is_metric ? tr("km/h") : tr("mph")); - setProperty("hideBottomIcons", (cs.getAlertSize() != cereal::ControlsState::AlertSize::NONE)); + setProperty("hideBottomIcons", (cs.getAlertSize() != cereal::ControlsState::AlertSize::NONE) || frogSignals && (turnSignalLeft || turnSignalRight)); setProperty("status", s.status); // update engageability/experimental mode button @@ -437,10 +462,13 @@ void AnnotatedCameraWidget::updateState(const UIState &s) { setProperty("compass", s.scene.compass); setProperty("experimentalMode", s.scene.experimental_mode); setProperty("frogColors", s.scene.frog_colors); + setProperty("frogSignals", s.scene.frog_signals); setProperty("muteDM", s.scene.mute_dm); setProperty("rotatingWheel", s.scene.rotating_wheel); setProperty("steeringAngleDeg", s.scene.steering_angle_deg); setProperty("steeringWheel", s.scene.steering_wheel); + setProperty("turnSignalLeft", s.scene.turn_signal_left); + setProperty("turnSignalRight", s.scene.turn_signal_right); } void AnnotatedCameraWidget::drawHud(QPainter &p) { @@ -551,6 +579,11 @@ void AnnotatedCameraWidget::drawHud(QPainter &p) { drawCompass(p); } + // Frog turn signal animation + if (frogSignals && (turnSignalLeft || turnSignalRight)) { + drawFrogSignals(p); + } + // Rotating steering wheel if (rotatingWheel) { const auto &scene = uiState()->scene; @@ -999,6 +1032,37 @@ void AnnotatedCameraWidget::drawCompass(QPainter &p) { p.restore(); } +void AnnotatedCameraWidget::drawFrogSignals(QPainter &p) { + // Declare the turn signal size + constexpr int signalHeight = 480; + constexpr int signalWidth = 360; + + // Calculate the vertical position for the turn signals + const int baseYPosition = (height() - signalHeight) / 2 + 300; + // Calculate the x-coordinates for the turn signals + int leftSignalXPosition = 75 + width() - signalWidth - 300 * (blindSpotLeft ? 0 : animationFrameIndex); + int rightSignalXPosition = -75 + 300 * (blindSpotRight ? 0 : animationFrameIndex); + + // Enable Antialiasing + p.setRenderHint(QPainter::Antialiasing); + + // Draw the turn signals + if (animationFrameIndex < static_cast(signalImgVector.size())) { + const auto drawSignal = [&](const bool signalActivated, const int xPosition, const bool flip, const bool blindspot) { + if (signalActivated) { + // Get the appropriate image from the signalImgVector + QPixmap signal = signalImgVector[(blindspot ? signalImgVector.size()-1 : animationFrameIndex % totalFrames)].transformed(QTransform().scale(flip ? -1 : 1, 1)); + // Draw the image + p.drawPixmap(xPosition, baseYPosition, signalWidth, signalHeight, signal); + } + }; + + // Display the animation based on which signal is activated + drawSignal(turnSignalLeft, leftSignalXPosition, false, blindSpotLeft); + drawSignal(turnSignalRight, rightSignalXPosition, true, blindSpotRight); + } +} + void AnnotatedCameraWidget::drawStatusBar(QPainter &p) { p.save(); diff --git a/selfdrive/ui/qt/onroad.h b/selfdrive/ui/qt/onroad.h index 1905af2e00527b..aa5b6f5bfa817f 100644 --- a/selfdrive/ui/qt/onroad.h +++ b/selfdrive/ui/qt/onroad.h @@ -96,8 +96,11 @@ class AnnotatedCameraWidget : public CameraWidget { Q_PROPERTY(bool compass MEMBER compass); Q_PROPERTY(bool experimentalMode MEMBER experimentalMode); Q_PROPERTY(bool frogColors MEMBER frogColors); + Q_PROPERTY(bool frogSignals MEMBER frogSignals); Q_PROPERTY(bool muteDM MEMBER muteDM); Q_PROPERTY(bool rotatingWheel MEMBER rotatingWheel); + Q_PROPERTY(bool turnSignalLeft MEMBER turnSignalLeft); + Q_PROPERTY(bool turnSignalRight MEMBER turnSignalRight); Q_PROPERTY(int bearingDeg MEMBER bearingDeg); Q_PROPERTY(int steeringAngleDeg MEMBER steeringAngleDeg); Q_PROPERTY(int steeringWheel MEMBER steeringWheel); @@ -113,6 +116,7 @@ class AnnotatedCameraWidget : public CameraWidget { // FrogPilot widgets void drawCompass(QPainter &p); + void drawFrogSignals(QPainter &p); void drawStatusBar(QPainter &p); QVBoxLayout *main_layout; @@ -143,15 +147,21 @@ class AnnotatedCameraWidget : public CameraWidget { bool compass; bool experimentalMode; bool frogColors; + bool frogSignals; bool muteDM; bool rotatingWheel; + bool turnSignalLeft; + bool turnSignalRight; + int animationFrameIndex; int bearingDeg; int steeringAngleDeg; int steeringWheel; QPixmap compass_inner_img; QPixmap engage_img; QPixmap experimental_img; + static constexpr int totalFrames = 8; std::map wheel_images; + std::vector signalImgVector; protected: void paintGL() override; diff --git a/selfdrive/ui/ui.cc b/selfdrive/ui/ui.cc index 5103228032a935..f254c237c41a1a 100644 --- a/selfdrive/ui/ui.cc +++ b/selfdrive/ui/ui.cc @@ -216,10 +216,14 @@ static void update_state(UIState *s) { } if (sm.updated("carState")) { const auto carState = sm["carState"].getCarState(); - if (scene.blind_spot_path) { + if (scene.blind_spot_path || scene.frog_signals) { scene.blind_spot_left = carState.getLeftBlindspot(); scene.blind_spot_right = carState.getRightBlindspot(); } + if (scene.frog_signals) { + scene.turn_signal_left = carState.getLeftBlinker(); + scene.turn_signal_right = carState.getRightBlinker(); + } if (scene.blind_spot_path || scene.rotating_wheel) { scene.steering_angle_deg = carState.getSteeringAngleDeg(); } @@ -275,6 +279,7 @@ void ui_update_params(UIState *s) { scene.blind_spot_path = scene.custom_road_ui && params.getBool("BlindSpotPath"); scene.frog_theme = params.getBool("FrogTheme"); scene.frog_colors = scene.frog_theme && params.getBool("FrogColors"); + scene.frog_signals = scene.frog_theme && params.getBool("FrogSignals"); scene.lane_line_width = params.getInt("LaneLinesWidth") / 12.0 * 0.1524; // Convert from inches to meters scene.mute_dm = params.getBool("FireTheBabysitter") && params.getBool("MuteDM"); scene.path_edge_width = params.getInt("PathEdgeWidth"); diff --git a/selfdrive/ui/ui.h b/selfdrive/ui/ui.h index fb80d1187ddb18..bbcb330b05015c 100644 --- a/selfdrive/ui/ui.h +++ b/selfdrive/ui/ui.h @@ -153,11 +153,14 @@ typedef struct UIScene { bool enabled; bool experimental_mode; bool frog_colors; + bool frog_signals; bool frog_theme; bool frogpilot_toggles_updated; bool mute_dm; bool rotating_wheel; bool toyota_car = true; + bool turn_signal_left; + bool turn_signal_right; bool unlimited_road_ui_length; bool wide_camera_disabled; float lane_line_width;