diff --git a/selfdrive/ui/qt/sidebar.cc b/selfdrive/ui/qt/sidebar.cc index 1ab7facbf2..c3b8cb2fd8 100644 --- a/selfdrive/ui/qt/sidebar.cc +++ b/selfdrive/ui/qt/sidebar.cc @@ -3,185 +3,116 @@ #include "qt_window.h" #include "selfdrive/hardware/hw.h" -StatusWidget::StatusWidget(bool has_substatus, QWidget *parent) : QFrame(parent) { - layout = new QVBoxLayout(); - layout->setSpacing(0); - - status = new QLabel(this); - - if (has_substatus) { - layout->setContentsMargins(50, 24, 16, 24); - status->setAlignment(Qt::AlignLeft | Qt::AlignHCenter); - status->setStyleSheet(R"(font-size: 65px; font-weight: 500;)"); +void configFont(QPainter &p, QString family, int size, int weight) { + QFont f(family); + f.setPixelSize(size); + f.setWeight(weight); + p.setFont(f); +} - substatus = new QLabel(this); - substatus->setAlignment(Qt::AlignLeft | Qt::AlignHCenter); - substatus->setStyleSheet(R"(font-size: 30px; font-weight: 400;)"); +void Sidebar::drawMetric(QPainter &p, const QString &label, const QString &val, QColor c, int y) { + const QRect rect = {30, y, 240, val.isEmpty() ? (label.contains("\n") ? 124 : 100) : 148}; - layout->addWidget(status, 0, Qt::AlignLeft); - layout->addWidget(substatus, 0, Qt::AlignLeft); + p.setPen(Qt::NoPen); + p.setBrush(QBrush(c)); + p.setClipRect(rect.x() + 6, rect.y(), 18, rect.height(), Qt::ClipOperation::ReplaceClip); + p.drawRoundedRect(QRect(rect.x() + 6, rect.y() + 6, 100, rect.height() - 12), 10, 10); + p.setClipping(false); + + QPen pen = QPen(QColor(0xff, 0xff, 0xff, 0x55)); + pen.setWidth(2); + p.setPen(pen); + p.setBrush(Qt::NoBrush); + p.drawRoundedRect(rect, 20, 20); + + p.setPen(QColor(0xff, 0xff, 0xff)); + if (val.isEmpty()) { + configFont(p, "Open Sans", 35, 500); + const QRect r = QRect(rect.x() + 35, rect.y(), rect.width() - 50, rect.height()); + p.drawText(r, Qt::AlignCenter, label); } else { - layout->setContentsMargins(40, 24, 16, 24); - - status->setAlignment(Qt::AlignCenter); - status->setStyleSheet(R"(font-size: 38px; font-weight: 500;)"); - layout->addWidget(status, 0, Qt::AlignCenter); + configFont(p, "Open Sans", 58, 500); + p.drawText(rect.x() + 50, rect.y() + 71, val); + configFont(p, "Open Sans", 35, 400); + p.drawText(rect.x() + 50, rect.y() + 50 + 77, label); } - - setMinimumHeight(124); - setStyleSheet("background-color: transparent;"); - setLayout(layout); } -void StatusWidget::paintEvent(QPaintEvent *e) { - QPainter p(this); - p.setRenderHint(QPainter::Antialiasing, true); - p.setPen(QPen(QColor(0xb2b2b2), 3, Qt::SolidLine, Qt::FlatCap)); - // origin at 1.5,1.5 because qt issues with pixel perfect borders - p.drawRoundedRect(QRectF(1.5, 1.5, size().width()-3, size().height()-3), 30, 30); +Sidebar::Sidebar(QWidget *parent) : QFrame(parent) { + home_img = QImage("../assets/images/button_home.png").scaled(180, 180, Qt::KeepAspectRatio, Qt::SmoothTransformation); + settings_img = QImage("../assets/images/button_settings.png").scaled(settings_btn.width(), settings_btn.height(), Qt::IgnoreAspectRatio, Qt::SmoothTransformation);; - p.setPen(Qt::NoPen); - p.setBrush(color); - p.setClipRect(0,0,25+6,size().height()-6,Qt::ClipOperation::ReplaceClip); - p.drawRoundedRect(QRectF(6, 6, size().width()-12, size().height()-12), 25, 25); + setFixedWidth(300); + setMinimumHeight(vwp_h); + setStyleSheet("background-color: rgb(57, 57, 57);"); } -void StatusWidget::update(const QString &label, const QColor &c, const QString &msg) { - status->setText(label); - if (substatus != nullptr) { - substatus->setText(msg); +void Sidebar::mousePressEvent(QMouseEvent *event) { + if (settings_btn.contains(event->pos())) { + emit openSettings(); } - if (color != c) { - color = c; - repaint(); - } - return; } -SignalWidget::SignalWidget(QWidget *parent) : QFrame(parent), _strength(0) { - layout = new QVBoxLayout(); - layout->setMargin(0); - layout->setSpacing(0); - layout->insertSpacing(0, 45); +void Sidebar::update(const UIState &s) { + if (s.sm->frame % (6*UI_FREQ) == 0) { + connect_str = "OFFLINE"; + connect_status = warning_color; + auto last_ping = params.get("LastAthenaPingTime"); + if (last_ping) { + bool online = nanos_since_boot() - *last_ping < 70e9; + connect_str = online ? "ONLINE" : "ERROR"; + connect_status = online ? good_color : danger_color; + } + repaint(); + } - label = new QLabel(this); - label->setStyleSheet(R"(font-size: 35px; font-weight: 400;)"); - layout->addWidget(label, 0, Qt::AlignLeft); + net_type = s.scene.deviceState.getNetworkType(); + strength = s.scene.deviceState.getNetworkStrength(); - setFixedWidth(177); - setLayout(layout); -} + temp_status = danger_color; + auto ts = s.scene.deviceState.getThermalStatus(); + if (ts == cereal::DeviceState::ThermalStatus::GREEN) { + temp_status = good_color; + } else if (ts == cereal::DeviceState::ThermalStatus::YELLOW) { + temp_status = warning_color; + } + temp_val = (int)s.scene.deviceState.getAmbientTempC(); -void SignalWidget::paintEvent(QPaintEvent *e) { - QPainter p(this); - p.setRenderHint(QPainter::Antialiasing, true); - p.setPen(Qt::NoPen); - p.setBrush(Qt::white); - for (int i = 0; i < 5; i++) { - if (i == _strength) { - p.setPen(Qt::NoPen); - p.setBrush(Qt::darkGray); - } - p.drawEllipse(QRectF(_dotspace * i, _top, _dia, _dia)); + panda_str = "VEHICLE\nONLINE"; + panda_status = good_color; + if (s.scene.pandaType == cereal::PandaState::PandaType::UNKNOWN) { + panda_status = danger_color; + panda_str = "NO\nPANDA"; + } else if (Hardware::TICI() && s.scene.started) { + panda_str = QString("SAT CNT\n%1").arg(s.scene.satelliteCount); + panda_status = s.scene.gpsOK ? good_color : warning_color; } -} -void SignalWidget::update(const QString &text, int strength) { - label->setText(text); - if (_strength != strength) { - _strength = strength; + if (s.sm->updated("deviceState") || s.sm->updated("pandaState")) { repaint(); } } -Sidebar::Sidebar(QWidget *parent) : QFrame(parent) { - QVBoxLayout *layout = new QVBoxLayout(); - layout->setContentsMargins(25, 50, 25, 50); - layout->setSpacing(35); - setFixedSize(300, vwp_h); - - QPushButton *s_btn = new QPushButton; - s_btn->setStyleSheet(R"( - border-image: url(../assets/images/button_settings.png); - )"); - s_btn->setFixedSize(200, 117); - layout->addWidget(s_btn, 0, Qt::AlignHCenter); - QObject::connect(s_btn, &QPushButton::pressed, this, &Sidebar::openSettings); - - signal = new SignalWidget(this); - layout->addWidget(signal, 0, Qt::AlignTop | Qt::AlignHCenter); - - temp = new StatusWidget(true, this); - layout->addWidget(temp, 0, Qt::AlignTop); - - panda = new StatusWidget(false, this); - layout->addWidget(panda, 0, Qt::AlignTop); - - connect = new StatusWidget(false, this); - layout->addWidget(connect, 0, Qt::AlignTop); - - QImage image = QImageReader("../assets/images/button_home.png").read(); - QLabel *comma = new QLabel(this); - comma->setPixmap(QPixmap::fromImage(image)); - comma->setAlignment(Qt::AlignCenter); - layout->addWidget(comma, 1, Qt::AlignHCenter | Qt::AlignVCenter); - - layout->addStretch(1); - - setStyleSheet(R"( - Sidebar { - background-color: #393939; - } - * { - color: white; - } - )"); - setLayout(layout); -} - -void Sidebar::update(const UIState &s) { - static std::map> connectivity_map = { - {NET_ERROR, {"CONNECT\nERROR", COLOR_DANGER}}, - {NET_CONNECTED, {"CONNECT\nONLINE", COLOR_GOOD}}, - {NET_DISCONNECTED, {"CONNECT\nOFFLINE", COLOR_WARNING}}, - }; - auto net_params = connectivity_map[s.scene.athenaStatus]; - connect->update(net_params.first, net_params.second); - - static std::map temp_severity_map = { - {cereal::DeviceState::ThermalStatus::GREEN, COLOR_GOOD}, - {cereal::DeviceState::ThermalStatus::YELLOW, COLOR_WARNING}, - {cereal::DeviceState::ThermalStatus::RED, COLOR_DANGER}, - {cereal::DeviceState::ThermalStatus::DANGER, COLOR_DANGER}}; - QString temp_val = QString("%1 °C").arg((int)s.scene.deviceState.getAmbientTempC()); - temp->update(temp_val, temp_severity_map[s.scene.deviceState.getThermalStatus()], "TEMP"); - - static std::map network_type_map = { - {cereal::DeviceState::NetworkType::NONE, "--"}, - {cereal::DeviceState::NetworkType::WIFI, "WiFi"}, - {cereal::DeviceState::NetworkType::CELL2_G, "2G"}, - {cereal::DeviceState::NetworkType::CELL3_G, "3G"}, - {cereal::DeviceState::NetworkType::CELL4_G, "4G"}, - {cereal::DeviceState::NetworkType::CELL5_G, "5G"}}; - const char *network_type = network_type_map[s.scene.deviceState.getNetworkType()]; - static std::map network_strength_map = { - {cereal::DeviceState::NetworkStrength::UNKNOWN, 1}, - {cereal::DeviceState::NetworkStrength::POOR, 2}, - {cereal::DeviceState::NetworkStrength::MODERATE, 3}, - {cereal::DeviceState::NetworkStrength::GOOD, 4}, - {cereal::DeviceState::NetworkStrength::GREAT, 5}}; - const int img_idx = s.scene.deviceState.getNetworkType() == cereal::DeviceState::NetworkType::NONE ? 0 : network_strength_map[s.scene.deviceState.getNetworkStrength()]; - signal->update(network_type, img_idx); - - QColor panda_color = COLOR_GOOD; - QString panda_message = "VEHICLE\nONLINE"; - if (s.scene.pandaType == cereal::PandaState::PandaType::UNKNOWN) { - panda_color = COLOR_DANGER; - panda_message = "NO\nPANDA"; - } - else if (Hardware::TICI() && s.scene.started) { - panda_color = s.scene.gpsOK ? COLOR_GOOD : COLOR_WARNING; - panda_message = QString("SAT CNT\n%1").arg(s.scene.satelliteCount); - } - panda->update(panda_message, panda_color); +void Sidebar::paintEvent(QPaintEvent *event) { + QPainter p(this); + p.setPen(Qt::NoPen); + p.setRenderHint(QPainter::Antialiasing); + + // static imgs + p.setOpacity(0.65); + p.drawImage(settings_btn.x(), settings_btn.y(), settings_img); + p.setOpacity(1.0); + p.drawImage(60, 1080 - 180 - 40, home_img); + + // network + p.drawImage(58, 196, signal_imgs[strength]); + configFont(p, "Open Sans", 35, 400); + p.setPen(QColor(0xff, 0xff, 0xff)); + const QRect r = QRect(50, 247, 100, 50); + p.drawText(r, Qt::AlignCenter, network_type[net_type]); + + // metrics + drawMetric(p, "TEMP", QString("%1°C").arg(temp_val), temp_status, 338); + drawMetric(p, panda_str, "", panda_status, 518); + drawMetric(p, "CONNECT\n" + connect_str, "", connect_status, 676); } diff --git a/selfdrive/ui/qt/sidebar.h b/selfdrive/ui/qt/sidebar.h index 7ef34de2b2..e108a6a5fe 100644 --- a/selfdrive/ui/qt/sidebar.h +++ b/selfdrive/ui/qt/sidebar.h @@ -2,48 +2,7 @@ #include -#include - -#define COLOR_GOOD QColor(255, 255, 255) -#define COLOR_WARNING QColor(218, 202, 37) -#define COLOR_DANGER QColor(201, 34, 49) - -class SignalWidget : public QFrame { - Q_OBJECT - -public: - SignalWidget(QWidget* parent = 0); - void update(const QString &text, int strength); - QLabel *label; - int _strength = 0; - -protected: - void paintEvent(QPaintEvent*) override; - -private: - QVBoxLayout *layout; - - const float _dotspace = 37; // spacing between dots - const float _top = 10; - const float _dia = 28; // dot diameter -}; - -class StatusWidget : public QFrame { - Q_OBJECT - -public: - StatusWidget(bool has_substatus, QWidget* parent = 0); - void update(const QString &label, const QColor &c, const QString &msg = ""); - -protected: - void paintEvent(QPaintEvent*) override; - -private: - QLabel *status; - QLabel *substatus = nullptr; - QColor color = COLOR_WARNING; - QVBoxLayout *layout; -}; +#include "selfdrive/ui/ui.h" class Sidebar : public QFrame { Q_OBJECT @@ -57,9 +16,42 @@ signals: public slots: void update(const UIState &s); +protected: + void paintEvent(QPaintEvent *event) override; + void mousePressEvent(QMouseEvent *event) override; + private: - SignalWidget *signal; - StatusWidget *temp; - StatusWidget *panda; - StatusWidget *connect; + void drawMetric(QPainter &p, const QString &label, const QString &val, QColor c, int y); + + QImage home_img, settings_img; + const QMap network_type = { + {cereal::DeviceState::NetworkType::NONE, "--"}, + {cereal::DeviceState::NetworkType::WIFI, "WiFi"}, + {cereal::DeviceState::NetworkType::CELL2_G, "2G"}, + {cereal::DeviceState::NetworkType::CELL3_G, "3G"}, + {cereal::DeviceState::NetworkType::CELL4_G, "4G"}, + {cereal::DeviceState::NetworkType::CELL5_G, "5G"} + }; + const QMap signal_imgs = { + {cereal::DeviceState::NetworkStrength::UNKNOWN, QImage("../assets/images/network_0.png")}, + {cereal::DeviceState::NetworkStrength::POOR, QImage("../assets/images/network_1.png")}, + {cereal::DeviceState::NetworkStrength::MODERATE, QImage("../assets/images/network_2.png")}, + {cereal::DeviceState::NetworkStrength::GOOD, QImage("../assets/images/network_3.png")}, + {cereal::DeviceState::NetworkStrength::GREAT, QImage("../assets/images/network_4.png")}, + }; + + const QRect settings_btn = QRect(50, 35, 200, 117); + const QColor good_color = QColor(255, 255, 255); + const QColor warning_color = QColor(218, 202, 37); + const QColor danger_color = QColor(201, 34, 49); + + Params params; + QString connect_str = "OFFLINE"; + QColor connect_status = warning_color; + QString panda_str = "NO\nPANDA"; + QColor panda_status = warning_color; + int temp_val = 0; + QColor temp_status = warning_color; + cereal::DeviceState::NetworkType net_type; + cereal::DeviceState::NetworkStrength strength; }; diff --git a/selfdrive/ui/qt/window.cc b/selfdrive/ui/qt/window.cc index 235339067a..52b9be1600 100644 --- a/selfdrive/ui/qt/window.cc +++ b/selfdrive/ui/qt/window.cc @@ -32,6 +32,11 @@ MainWindow::MainWindow(QWidget *parent) : QWidget(parent) { QObject::connect(&qs, &QUIState::offroadTransition, this, &MainWindow::offroadTransition); QObject::connect(&device, &Device::displayPowerChanged, this, &MainWindow::closeSettings); + // load fonts + QFontDatabase::addApplicationFont("../assets/fonts/opensans_regular.ttf"); + QFontDatabase::addApplicationFont("../assets/fonts/opensans_bold.ttf"); + QFontDatabase::addApplicationFont("../assets/fonts/opensans_semibold.ttf"); + // no outline to prevent the focus rectangle setLayout(main_layout); setStyleSheet(R"( diff --git a/selfdrive/ui/ui.cc b/selfdrive/ui/ui.cc index b8ea1f58c6..3bd59fbaf4 100644 --- a/selfdrive/ui/ui.cc +++ b/selfdrive/ui/ui.cc @@ -219,14 +219,8 @@ static void update_state(UIState *s) { static void update_params(UIState *s) { const uint64_t frame = s->sm->frame; UIScene &scene = s->scene; - Params params; if (frame % (5*UI_FREQ) == 0) { - scene.is_metric = params.getBool("IsMetric"); - } else if (frame % (6*UI_FREQ) == 0) { - scene.athenaStatus = NET_DISCONNECTED; - if (auto last_ping = params.get("LastAthenaPingTime"); last_ping) { - scene.athenaStatus = nanos_since_boot() - *last_ping < 70e9 ? NET_CONNECTED : NET_ERROR; - } + scene.is_metric = Params().getBool("IsMetric"); } } diff --git a/selfdrive/ui/ui.h b/selfdrive/ui/ui.h index 2eb7cc6696..3c31540c66 100644 --- a/selfdrive/ui/ui.h +++ b/selfdrive/ui/ui.h @@ -48,12 +48,6 @@ const int footer_h = 280; const int UI_FREQ = 20; // Hz -typedef enum NetStatus { - NET_CONNECTED, - NET_DISCONNECTED, - NET_ERROR, -} NetStatus; - typedef enum UIStatus { STATUS_DISENGAGED, STATUS_ENGAGED, @@ -86,7 +80,6 @@ typedef struct UIScene { bool driver_view; cereal::PandaState::PandaType pandaType; - NetStatus athenaStatus; cereal::DeviceState::Reader deviceState; cereal::RadarState::LeadData::Reader lead_data[2];