diff --git a/common/params_keys.h b/common/params_keys.h index 0ff0354b32..3b5d02a429 100644 --- a/common/params_keys.h +++ b/common/params_keys.h @@ -168,6 +168,7 @@ inline static std::unordered_map keys = { {"QuietMode", {PERSISTENT | BACKUP, BOOL, "0"}}, {"RainbowMode", {PERSISTENT | BACKUP, BOOL, "0"}}, {"ShowAdvancedControls", {PERSISTENT | BACKUP, BOOL, "0"}}, + {"ShowTurnSignals", {PERSISTENT | BACKUP, BOOL, "0"}}, {"StandstillTimer", {PERSISTENT | BACKUP, BOOL, "0"}}, {"TrueVEgoUI", {PERSISTENT | BACKUP, BOOL, "0"}}, diff --git a/selfdrive/ui/sunnypilot/qt/offroad/settings/visuals_panel.cc b/selfdrive/ui/sunnypilot/qt/offroad/settings/visuals_panel.cc index efd5e99f3d..0324cd70f0 100644 --- a/selfdrive/ui/sunnypilot/qt/offroad/settings/visuals_panel.cc +++ b/selfdrive/ui/sunnypilot/qt/offroad/settings/visuals_panel.cc @@ -82,7 +82,14 @@ VisualsPanel::VisualsPanel(QWidget *parent) : QWidget(parent) { tr("When enabled, the speedometer on the onroad screen is not displayed."), "", false, - } + }, + { + "ShowTurnSignals", + tr("Display Turn Signals"), + tr("When enabled, visual turn indicators are drawn on the HUD."), + "", + false, + }, }; // Add regular toggles first diff --git a/selfdrive/ui/sunnypilot/qt/onroad/hud.cc b/selfdrive/ui/sunnypilot/qt/onroad/hud.cc index 4bb2aca28c..d98ae89147 100644 --- a/selfdrive/ui/sunnypilot/qt/onroad/hud.cc +++ b/selfdrive/ui/sunnypilot/qt/onroad/hud.cc @@ -122,6 +122,12 @@ void HudRendererSP::updateState(const UIState &s) { float v_ego = (v_ego_cluster_seen && !s.scene.trueVEgoUI) ? car_state.getVEgoCluster() : car_state.getVEgo(); speed = std::max(0.0f, v_ego * (is_metric ? MS_TO_KPH : MS_TO_MPH)); hideVEgoUI = s.scene.hideVEgoUI; + + leftBlinkerOn = car_state.getLeftBlinker(); + rightBlinkerOn = car_state.getRightBlinker(); + leftBlindspot = car_state.getLeftBlindspot(); + rightBlindspot = car_state.getRightBlindspot(); + showTurnSignals = s.scene.turn_signals; } void HudRendererSP::draw(QPainter &p, const QRect &surface_rect) { @@ -239,6 +245,11 @@ void HudRendererSP::draw(QPainter &p, const QRect &surface_rect) { } else { e2eAlertFrame = 0; } + + // Blinker + if (showTurnSignals) { + drawBlinker(p, surface_rect); + } } p.restore(); @@ -752,3 +763,73 @@ void HudRendererSP::drawCurrentSpeedSP(QPainter &p, const QRect &surface_rect) { p.setFont(InterFont(66)); HudRenderer::drawText(p, surface_rect.center().x(), 290, is_metric ? tr("km/h") : tr("mph"), 200); } + +void HudRendererSP::drawBlinker(QPainter &p, const QRect &surface_rect) { + if (!leftBlinkerOn && !rightBlinkerOn) { + blinkerFrameCounter = 0; + return; + } + ++blinkerFrameCounter; + + const int circleRadius = 44; + const int arrowLength = 44; + const int x_gap = 180; + const int y_offset = 272; + + const int centerX = surface_rect.center().x(); + const bool hazard = leftBlinkerOn && rightBlinkerOn; + + const QPen bgBorder(Qt::white, 5); + const QPen arrowPen(Qt::NoPen); + + p.save(); + + auto drawArrow = [&](int cx, int cy, int dir, const QBrush &arrowBrush) { + const int bodyLength = arrowLength / 2; + const int bodyWidth = arrowLength / 2; + const int headLength = arrowLength / 2; + const int headWidth = arrowLength; + + QPolygon arrow; + arrow.reserve(7); + arrow << QPoint(cx - dir * bodyLength, cy - bodyWidth / 2) + << QPoint(cx, cy - bodyWidth / 2) + << QPoint(cx, cy - headWidth / 2) + << QPoint(cx + dir * headLength, cy) + << QPoint(cx, cy + headWidth / 2) + << QPoint(cx, cy + bodyWidth / 2) + << QPoint(cx - dir * bodyLength, cy + bodyWidth / 2); + + p.setPen(arrowPen); + p.setBrush(arrowBrush); + p.drawPolygon(arrow); + }; + + auto drawCircle = [&](int cx, int cy, const QBrush &bgBrush) { + p.setPen(bgBorder); + p.setBrush(bgBrush); + p.drawEllipse(QPoint(cx, cy), circleRadius, circleRadius); + }; + + struct BlinkerSide { bool on; int dir; bool blocked; int cx; }; + const std::array sides = {{ + {leftBlinkerOn, -1, hazard ? true : (leftBlinkerOn && leftBlindspot), centerX - x_gap}, + {rightBlinkerOn, 1, hazard ? true : (rightBlinkerOn && rightBlindspot), centerX + x_gap}, + }}; + + for (const auto &s: sides) { + if (!s.on) continue; + + QColor bgColor = s.blocked ? QColor(135, 23, 23) : QColor(23, 134, 68); + QColor arrowColor = s.blocked ? QColor(66, 12, 12) : QColor(12, 67, 34); + if (pulseElement(blinkerFrameCounter)) arrowColor = Qt::white; + + const QBrush bgBrush(bgColor); + const QBrush arrowBrush(arrowColor); + + drawCircle(s.cx, y_offset, bgBrush); + drawArrow(s.cx, y_offset, s.dir, arrowBrush); + } + + p.restore(); +} diff --git a/selfdrive/ui/sunnypilot/qt/onroad/hud.h b/selfdrive/ui/sunnypilot/qt/onroad/hud.h index 7a4940a286..a5462fba9e 100644 --- a/selfdrive/ui/sunnypilot/qt/onroad/hud.h +++ b/selfdrive/ui/sunnypilot/qt/onroad/hud.h @@ -38,6 +38,7 @@ private: void drawSetSpeedSP(QPainter &p, const QRect &surface_rect); void drawE2eAlert(QPainter &p, const QRect &surface_rect); void drawCurrentSpeedSP(QPainter &p, const QRect &surface_rect); + void drawBlinker(QPainter &p, const QRect &surface_rect); bool lead_status; float lead_d_rel; @@ -109,4 +110,10 @@ private: QString alert_text; QPixmap alert_img; bool hideVEgoUI; + bool leftBlinkerOn; + bool rightBlinkerOn; + bool leftBlindspot; + bool rightBlindspot; + int blinkerFrameCounter; + bool showTurnSignals; }; diff --git a/selfdrive/ui/sunnypilot/ui.cc b/selfdrive/ui/sunnypilot/ui.cc index e4d5b41adb..c021e0e95b 100644 --- a/selfdrive/ui/sunnypilot/ui.cc +++ b/selfdrive/ui/sunnypilot/ui.cc @@ -72,6 +72,8 @@ void ui_update_params_sp(UIStateSP *s) { s->scene.onroadScreenOffControl = params.getBool("OnroadScreenOffControl"); s->scene.onroadScreenOffTimerParam = std::atoi(params.get("OnroadScreenOffTimer").c_str()); s->reset_onroad_sleep_timer(); + + s->scene.turn_signals = params.getBool("ShowTurnSignals"); } void UIStateSP::reset_onroad_sleep_timer() { diff --git a/selfdrive/ui/sunnypilot/ui_scene.h b/selfdrive/ui/sunnypilot/ui_scene.h index 5bf1156648..233f1e15de 100644 --- a/selfdrive/ui/sunnypilot/ui_scene.h +++ b/selfdrive/ui/sunnypilot/ui_scene.h @@ -17,4 +17,5 @@ typedef struct UISceneSP : UIScene { int onroadScreenOffTimerParam; bool trueVEgoUI; bool hideVEgoUI; + bool turn_signals = false; } UISceneSP;