diff --git a/common/params_keys.h b/common/params_keys.h index be722ec64..9d826e44f 100644 --- a/common/params_keys.h +++ b/common/params_keys.h @@ -137,7 +137,6 @@ inline static std::unordered_map keys = { {"Brightness", PERSISTENT | BACKUP}, {"ModelRunnerTypeCache", CLEAR_ON_ONROAD_TRANSITION}, {"OffroadMode", CLEAR_ON_MANAGER_START}, - {"OffroadMode_Status", CLEAR_ON_MANAGER_START}, {"QuietMode", PERSISTENT | BACKUP}, // MADS params diff --git a/selfdrive/selfdrived/alerts_offroad.json b/selfdrive/selfdrived/alerts_offroad.json index 6ea1bcf23..1668bf17f 100644 --- a/selfdrive/selfdrived/alerts_offroad.json +++ b/selfdrive/selfdrived/alerts_offroad.json @@ -45,10 +45,6 @@ "text": "sunnypilot detected a change in the device's mounting position. Ensure the device is fully seated in the mount and the mount is firmly secured to the windshield.", "severity": 0 }, - "OffroadMode_Status": { - "text": "sunnypilot is now in Always Offroad mode. sunnypilot won't start until Always Offroad mode is disabled. Go to \"Settings\" -> \"Device\" to exit Always Offroad mode.", - "severity": 1 - }, "Offroad_OSMUpdateRequired": { "text": "OpenStreetMap database is out of date. New maps must be downloaded if you wish to continue using OpenStreetMap data for Enhanced Speed Control and road name display.\n\n%1", "severity": 0 diff --git a/selfdrive/ui/qt/offroad/offroad_home.cc b/selfdrive/ui/qt/offroad/offroad_home.cc index 6fcb6a71a..9ca41e556 100644 --- a/selfdrive/ui/qt/offroad/offroad_home.cc +++ b/selfdrive/ui/qt/offroad/offroad_home.cc @@ -18,7 +18,7 @@ OffroadHome::OffroadHome(QWidget* parent) : QFrame(parent) { main_layout->setContentsMargins(40, 40, 40, 40); // top header - QHBoxLayout* header_layout = new QHBoxLayout(); + header_layout = new QHBoxLayout(); header_layout->setContentsMargins(0, 0, 0, 0); header_layout->setSpacing(16); diff --git a/selfdrive/ui/qt/offroad/offroad_home.h b/selfdrive/ui/qt/offroad/offroad_home.h index 40b63ce8d..cac37d58c 100644 --- a/selfdrive/ui/qt/offroad/offroad_home.h +++ b/selfdrive/ui/qt/offroad/offroad_home.h @@ -39,11 +39,13 @@ public: protected: QHBoxLayout *home_layout; + QHBoxLayout *header_layout; + + void showEvent(QShowEvent *event) override; + void refresh(); private: - void showEvent(QShowEvent *event) override; void hideEvent(QHideEvent *event) override; - void refresh(); Params params; diff --git a/selfdrive/ui/sunnypilot/SConscript b/selfdrive/ui/sunnypilot/SConscript index de7f3efbc..5d67f4157 100644 --- a/selfdrive/ui/sunnypilot/SConscript +++ b/selfdrive/ui/sunnypilot/SConscript @@ -19,6 +19,7 @@ qt_src = [ "sunnypilot/qt/sidebar.cc", "sunnypilot/qt/window.cc", "sunnypilot/qt/home.cc", + "sunnypilot/qt/offroad/exit_offroad_button.cc", "sunnypilot/qt/offroad/offroad_home.cc", "sunnypilot/qt/offroad/settings/device_panel.cc", "sunnypilot/qt/offroad/settings/lateral_panel.cc", @@ -80,6 +81,7 @@ brand_settings_qt_src = [ "sunnypilot/qt/offroad/settings/vehicle/volkswagen_settings.cc", ] + sp_widgets_src = widgets_src + network_src sp_qt_src = qt_src + lateral_panel_qt_src + vehicle_panel_qt_src + brand_settings_qt_src + osm_panel_qt_src sp_qt_util = qt_util diff --git a/selfdrive/ui/sunnypilot/qt/offroad/exit_offroad_button.cc b/selfdrive/ui/sunnypilot/qt/offroad/exit_offroad_button.cc new file mode 100644 index 000000000..0c7902476 --- /dev/null +++ b/selfdrive/ui/sunnypilot/qt/offroad/exit_offroad_button.cc @@ -0,0 +1,100 @@ +#include +#include +#include +#include +#include + +#include "selfdrive/ui/ui.h" +#include "selfdrive/ui/qt/widgets/input.h" +#include "selfdrive/ui/sunnypilot/qt/offroad/exit_offroad_button.h" + + +ExitOffroadButton::ExitOffroadButton(QWidget *parent) : QPushButton(parent), glowTimer(new QTimer(this)) { + setMouseTracking(true); + + connect(glowTimer, &QTimer::timeout, this, [this]() { + // Pulse alpha up and down + glowAlpha += glowDelta; + if (glowAlpha > 220 || glowAlpha < 10) { + glowDelta *= -1; + } + update(); // trigger repaint + }); + + glowTimer->start(45); + + pixmap = QPixmap("../../sunnypilot/selfdrive/assets/offroad/icon_exit_offroad.png").scaledToWidth(img_width, Qt::SmoothTransformation); + + // go to toggles and expand experimental mode description + connect(this, &QPushButton::clicked, [=]() { + if (ConfirmationDialog::confirm(tr("Are you sure you want to exit Always Offroad mode?"), tr("Confirm"), this)) { + params.remove("OffroadMode"); + } + }); + + setFixedHeight(125); + QHBoxLayout *main_layout = new QHBoxLayout; + main_layout->setContentsMargins(horizontal_padding, 0, horizontal_padding, 0); + + mode_label = new QLabel(tr("EXIT ALWAYS OFFROAD MODE")); + mode_icon = new QLabel; + mode_icon->setSizePolicy(QSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed)); + mode_icon->setPixmap(pixmap); + + main_layout->addWidget(mode_label, 1, Qt::AlignLeft); + main_layout->addWidget(mode_icon, 0, Qt::AlignRight); + + setLayout(main_layout); + + setStyleSheet(R"( + QPushButton { + border: none; + } + + QLabel { + font-size: 45px; + font-weight: 300; + text-align: left; + font-family: JetBrainsMono; + color: #000000; + } + )"); +} + +void drawPulsingGlowOverlay(QPainter &p, QPainterPath path, int glowAlpha) { + // Draw pulsing glow effect clipped to button area + p.save(); + p.setClipPath(path); + p.setCompositionMode(QPainter::CompositionMode_HardLight); + + const QColor animatedGlowColor(255, 255, 255, std::min(255, glowAlpha)); + QPen glowPen(animatedGlowColor, 8); + glowPen.setJoinStyle(Qt::RoundJoin); + p.setPen(glowPen); + p.drawPath(path); + + p.restore(); +} + +void ExitOffroadButton::paintEvent(QPaintEvent *event) { + QPainter p(this); + p.setRenderHint(QPainter::Antialiasing); + + QPainterPath path; + path.addRoundedRect(rect(), 10, 10); + + // gradient + bool pressed = isDown(); + QLinearGradient gradient(rect().left(), 0, rect().right(), 0); + gradient.setColorAt(0, QColor(35, 149, 255, pressed ? 0xcc : 0xff)); + gradient.setColorAt(0.3, QColor(35, 149, 255, pressed ? 0xcc : 0xff)); + gradient.setColorAt(1, QColor(20, 255, 171, pressed ? 0xcc : 0xff)); + p.fillPath(path, gradient); + + drawPulsingGlowOverlay(p, path, glowAlpha); + + // vertical line + p.setPen(QPen(QColor(0, 0, 0, 0x4d), 3, Qt::SolidLine)); + int line_x = rect().right() - img_width - (2 * horizontal_padding); + p.drawLine(line_x, rect().bottom(), line_x, rect().top()); +} diff --git a/selfdrive/ui/sunnypilot/qt/offroad/exit_offroad_button.h b/selfdrive/ui/sunnypilot/qt/offroad/exit_offroad_button.h new file mode 100644 index 000000000..673d9fff7 --- /dev/null +++ b/selfdrive/ui/sunnypilot/qt/offroad/exit_offroad_button.h @@ -0,0 +1,29 @@ +#pragma once + +#include +#include + +#include "common/params.h" + +class ExitOffroadButton : public QPushButton { + Q_OBJECT + +private: + QTimer *glowTimer; + int glowAlpha = 100; // Current alpha of glow + int glowDelta = 10; // Change per tick + +public: + explicit ExitOffroadButton(QWidget* parent = 0); + + Params params; + bool offroad_mode; + int img_width = 100; + int horizontal_padding = 30; + QPixmap pixmap; + QLabel *mode_label; + QLabel *mode_icon; + +protected: + void paintEvent(QPaintEvent *event) override; +}; diff --git a/selfdrive/ui/sunnypilot/qt/offroad/offroad_home.cc b/selfdrive/ui/sunnypilot/qt/offroad/offroad_home.cc index 05b1d8084..cc82e6ddb 100644 --- a/selfdrive/ui/sunnypilot/qt/offroad/offroad_home.cc +++ b/selfdrive/ui/sunnypilot/qt/offroad/offroad_home.cc @@ -5,16 +5,45 @@ * See the LICENSE.md file in the root directory for more details. */ -#include "selfdrive/ui/sunnypilot/qt/offroad/offroad_home.h" - #include +#include "selfdrive/ui/sunnypilot/qt/offroad/offroad_home.h" #include "selfdrive/ui/sunnypilot/qt/widgets/drive_stats.h" OffroadHomeSP::OffroadHomeSP(QWidget *parent) : OffroadHome(parent) { - QStackedWidget *left_widget = new QStackedWidget(this); - left_widget->addWidget(new DriveStats(this)); + QFrame *left_widget = new QFrame(this); + QVBoxLayout *left_layout = new QVBoxLayout(left_widget); + left_layout->setContentsMargins(0, 0, 0, 0); + left_layout->setSpacing(30); + + btn_exit_offroad = new ExitOffroadButton(this); + QObject::connect(btn_exit_offroad, &ExitOffroadButton::clicked, [=]() { + refreshOffroadStatus(); + }); + left_layout->addWidget(btn_exit_offroad); + + left_layout->addWidget(new DriveStats(this)); left_widget->setStyleSheet("border-radius: 10px;"); home_layout->insertWidget(0, left_widget); + + offroad_notif = new QPushButton(tr("ALWAYS OFFROAD ACTIVE")); + offroad_notif->setVisible(false); + offroad_notif->setStyleSheet("background-color: #E22C2C;"); + header_layout->insertWidget(0, offroad_notif, 0, Qt::AlignHCenter | Qt::AlignLeft); + + QObject::connect(deviceSP(), &DeviceSP::displayPowerChanged, this, &OffroadHomeSP::refreshOffroadStatus); + +} + +void OffroadHomeSP::showEvent(QShowEvent *event) { + refreshOffroadStatus(); + OffroadHome::showEvent(event); +} + +void OffroadHomeSP::refreshOffroadStatus() { + bool is_offroad = params.getBool("OffroadMode"); + btn_exit_offroad->setVisible(is_offroad); + offroad_notif->setVisible(is_offroad); + OffroadHome::refresh(); } diff --git a/selfdrive/ui/sunnypilot/qt/offroad/offroad_home.h b/selfdrive/ui/sunnypilot/qt/offroad/offroad_home.h index 9710150d5..30816f666 100644 --- a/selfdrive/ui/sunnypilot/qt/offroad/offroad_home.h +++ b/selfdrive/ui/sunnypilot/qt/offroad/offroad_home.h @@ -8,10 +8,19 @@ #pragma once #include "selfdrive/ui/qt/offroad/offroad_home.h" +#include "selfdrive/ui/sunnypilot/qt/offroad/exit_offroad_button.h" class OffroadHomeSP : public OffroadHome { Q_OBJECT public: explicit OffroadHomeSP(QWidget *parent = 0); + +private: + ExitOffroadButton *btn_exit_offroad; + QPushButton *offroad_notif; + Params params; + + void showEvent(QShowEvent *event) override; + void refreshOffroadStatus(); }; diff --git a/selfdrive/ui/sunnypilot/qt/widgets/drive_stats.cc b/selfdrive/ui/sunnypilot/qt/widgets/drive_stats.cc index 032abdbbf..bfdcac256 100644 --- a/selfdrive/ui/sunnypilot/qt/widgets/drive_stats.cc +++ b/selfdrive/ui/sunnypilot/qt/widgets/drive_stats.cc @@ -34,7 +34,7 @@ DriveStats::DriveStats(QWidget* parent) : QFrame(parent) { int row = 0; grid_layout->addWidget(newLabel(title, "title"), row++, 0, 1, 3); - grid_layout->addItem(new QSpacerItem(0, 50), row++, 0, 1, 1); + grid_layout->addItem(new QSpacerItem(0, 30), row++, 0, 1, 1); grid_layout->addWidget(labels.routes = newLabel("0", "number"), row, 0, Qt::AlignLeft); grid_layout->addWidget(labels.distance = newLabel("0", "number"), row, 1, Qt::AlignLeft); diff --git a/sunnypilot/selfdrive/assets/offroad/icon_exit_offroad.png b/sunnypilot/selfdrive/assets/offroad/icon_exit_offroad.png new file mode 100644 index 000000000..1b8308a0a --- /dev/null +++ b/sunnypilot/selfdrive/assets/offroad/icon_exit_offroad.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:d44f71f7603b106986fff7835f35c4c18c4c619a29e6292f8626ae3eedd85e57 +size 7811 diff --git a/system/hardware/hardwared.py b/system/hardware/hardwared.py index 38cbe8c99..223faeaaf 100755 --- a/system/hardware/hardwared.py +++ b/system/hardware/hardwared.py @@ -323,7 +323,6 @@ def hardware_thread(end_event, hw_queue) -> None: offroad_mode = params.get_bool("OffroadMode") startup_conditions["not_always_offroad"] = not offroad_mode onroad_conditions["not_always_offroad"] = not offroad_mode - set_offroad_alert("OffroadMode_Status", offroad_mode) # if the temperature enters the danger zone, go offroad to cool down onroad_conditions["device_temp_good"] = thermal_status < ThermalStatus.danger