Qt Sidebar (#20775)
* revive #19877
* looks like a sidebar
* more like a sidebar
* cleanup
* fix qcom2
* style
* that's the sidebar
* more space
Co-authored-by: Comma Device <device@comma.ai>
old-commit-hash: db9319405b
commatwo_master
parent
002057e1f1
commit
f5e6a0c9a0
13 changed files with 286 additions and 189 deletions
@ -0,0 +1,177 @@ |
||||
#include "common/util.h" |
||||
#include "sidebar.hpp" |
||||
#include "qt_window.hpp" |
||||
|
||||
StatusWidget::StatusWidget(QString label, QString msg, QColor c, QWidget* parent) : QFrame(parent) { |
||||
layout.setSpacing(0); |
||||
|
||||
if(msg.length() > 0){ |
||||
layout.setContentsMargins(50, 24, 16, 24); |
||||
status.setAlignment(Qt::AlignLeft | Qt::AlignHCenter); |
||||
status.setStyleSheet(R"(font-size: 65px; font-weight: 500;)"); |
||||
|
||||
substatus.setAlignment(Qt::AlignLeft | Qt::AlignHCenter); |
||||
substatus.setStyleSheet(R"(font-size: 30px; font-weight: 400;)"); |
||||
|
||||
layout.addWidget(&status, 0, Qt::AlignLeft); |
||||
layout.addWidget(&substatus, 0, Qt::AlignLeft); |
||||
} 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); |
||||
} |
||||
|
||||
update(label, msg, c); |
||||
|
||||
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); |
||||
|
||||
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); |
||||
} |
||||
|
||||
void StatusWidget::update(QString label, QString msg, QColor c) { |
||||
status.setText(label); |
||||
substatus.setText(msg); |
||||
color = c; |
||||
return; |
||||
} |
||||
|
||||
SignalWidget::SignalWidget(QString text, int strength, QWidget* parent) : QFrame(parent), _strength(strength) { |
||||
layout.setMargin(0); |
||||
layout.setSpacing(0); |
||||
layout.insertSpacing(0, 45); |
||||
|
||||
label.setText(text); |
||||
layout.addWidget(&label, 0, Qt::AlignLeft); |
||||
label.setStyleSheet(R"(font-size: 35px; font-weight: 400;)"); |
||||
|
||||
setFixedWidth(177); |
||||
setLayout(&layout); |
||||
} |
||||
|
||||
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)); |
||||
} |
||||
} |
||||
|
||||
void SignalWidget::update(QString text, int strength){ |
||||
label.setText(text); |
||||
_strength = strength; |
||||
} |
||||
|
||||
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("--", 0, this); |
||||
layout->addWidget(signal, 0, Qt::AlignTop | Qt::AlignHCenter); |
||||
|
||||
temp = new StatusWidget("0°C", "TEMP", QColor(255, 255, 255), this); |
||||
layout->addWidget(temp, 0, Qt::AlignTop); |
||||
|
||||
panda = new StatusWidget("NO\nPANDA", "", QColor(201, 34, 49), this); |
||||
layout->addWidget(panda, 0, Qt::AlignTop); |
||||
|
||||
connect = new StatusWidget("CONNECT\nOFFLINE", "", QColor(218, 202, 37), 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<NetStatus, std::pair<QString, QColor>> 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<cereal::DeviceState::ThermalStatus, QColor> 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", temp_severity_map[s.scene.deviceState.getThermalStatus()]); |
||||
|
||||
static std::map<cereal::DeviceState::NetworkType, const char *> 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<cereal::DeviceState::NetworkStrength, int> 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"; |
||||
} |
||||
#ifdef QCOM2 |
||||
else if (s.scene.started) { |
||||
panda_color = s.scene.gpsOK ? COLOR_GOOD : COLOR_WARNING; |
||||
panda_message = QString("SAT CNT\n%1").arg(s.scene.satelliteCount); |
||||
} |
||||
#endif |
||||
panda->update(panda_message, "", panda_color); |
||||
} |
@ -0,0 +1,65 @@ |
||||
#pragma once |
||||
|
||||
#include <QtWidgets> |
||||
|
||||
#include <ui.hpp> |
||||
|
||||
#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(QString text, int strength, QWidget* parent = 0); |
||||
void update(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(QString label, QString msg, QColor c, QWidget* parent = 0); |
||||
void update(QString label, QString msg, QColor c); |
||||
|
||||
protected: |
||||
void paintEvent(QPaintEvent*) override; |
||||
|
||||
private: |
||||
QColor color = COLOR_WARNING; |
||||
QLabel status; |
||||
QLabel substatus; |
||||
QVBoxLayout layout; |
||||
}; |
||||
|
||||
class Sidebar : public QFrame { |
||||
Q_OBJECT |
||||
|
||||
public: |
||||
explicit Sidebar(QWidget* parent = 0); |
||||
|
||||
signals: |
||||
void openSettings(); |
||||
|
||||
public slots: |
||||
void update(const UIState &s); |
||||
|
||||
private: |
||||
SignalWidget *signal; |
||||
StatusWidget *temp; |
||||
StatusWidget *panda; |
||||
StatusWidget *connect; |
||||
}; |
@ -1,143 +0,0 @@ |
||||
#include <stdio.h> |
||||
#include <string.h> |
||||
#include <math.h> |
||||
#include <map> |
||||
#include "common/util.h" |
||||
#include "paint.hpp" |
||||
#include "sidebar.hpp" |
||||
|
||||
static void draw_background(UIState *s) { |
||||
const NVGcolor color = nvgRGBA(0x39, 0x39, 0x39, 0xff); |
||||
ui_fill_rect(s->vg, {0, 0, sbr_w, s->fb_h}, color); |
||||
} |
||||
|
||||
static void draw_settings_button(UIState *s) { |
||||
ui_draw_image(s, settings_btn, "button_settings", 0.65f); |
||||
} |
||||
|
||||
static void draw_home_button(UIState *s) { |
||||
ui_draw_image(s, home_btn, "button_home", 1.0f); |
||||
} |
||||
|
||||
static void draw_network_strength(UIState *s) { |
||||
static std::map<cereal::DeviceState::NetworkStrength, int> 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()]; |
||||
ui_draw_image(s, {58, 196, 176, 27}, util::string_format("network_%d", img_idx).c_str(), 1.0f); |
||||
} |
||||
|
||||
static void draw_network_type(UIState *s) { |
||||
static std::map<cereal::DeviceState::NetworkType, const char *> 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 int network_x = 50; |
||||
const int network_y = 273; |
||||
const int network_w = 100; |
||||
const char *network_type = network_type_map[s->scene.deviceState.getNetworkType()]; |
||||
nvgFillColor(s->vg, COLOR_WHITE); |
||||
nvgFontSize(s->vg, 48); |
||||
nvgFontFace(s->vg, "sans-regular"); |
||||
nvgTextAlign(s->vg, NVG_ALIGN_CENTER | NVG_ALIGN_MIDDLE); |
||||
nvgTextBox(s->vg, network_x, network_y, network_w, network_type ? network_type : "--", NULL); |
||||
} |
||||
|
||||
static void draw_metric(UIState *s, const char *label_str, const char *value_str, const int severity, const int y_offset, const char *message_str) { |
||||
NVGcolor status_color; |
||||
|
||||
if (severity == 0) { |
||||
status_color = COLOR_WHITE; |
||||
} else if (severity == 1) { |
||||
status_color = COLOR_YELLOW; |
||||
} else if (severity > 1) { |
||||
status_color = COLOR_RED; |
||||
} |
||||
|
||||
const Rect rect = {30, 338 + y_offset, 240, message_str ? strchr(message_str, '\n') ? 124 : 100 : 148}; |
||||
ui_draw_rect(s->vg, rect, severity > 0 ? COLOR_WHITE : COLOR_WHITE_ALPHA(85), 2, 20.); |
||||
|
||||
nvgBeginPath(s->vg); |
||||
nvgRoundedRectVarying(s->vg, rect.x + 6, rect.y + 6, 18, rect.h - 12, 25, 0, 0, 25); |
||||
nvgFillColor(s->vg, status_color); |
||||
nvgFill(s->vg); |
||||
|
||||
if (!message_str) { |
||||
nvgFillColor(s->vg, COLOR_WHITE); |
||||
nvgFontSize(s->vg, 78); |
||||
nvgFontFace(s->vg, "sans-bold"); |
||||
nvgTextAlign(s->vg, NVG_ALIGN_LEFT | NVG_ALIGN_MIDDLE); |
||||
nvgTextBox(s->vg, rect.x + 50, rect.y + 50, rect.w - 60, value_str, NULL); |
||||
|
||||
nvgFillColor(s->vg, COLOR_WHITE); |
||||
nvgFontSize(s->vg, 48); |
||||
nvgFontFace(s->vg, "sans-regular"); |
||||
nvgTextAlign(s->vg, NVG_ALIGN_LEFT | NVG_ALIGN_MIDDLE); |
||||
nvgTextBox(s->vg, rect.x + 50, rect.y + 50 + 66, rect.w - 60, label_str, NULL); |
||||
} else { |
||||
nvgFillColor(s->vg, COLOR_WHITE); |
||||
nvgFontSize(s->vg, 48); |
||||
nvgFontFace(s->vg, "sans-bold"); |
||||
nvgTextAlign(s->vg, NVG_ALIGN_CENTER | NVG_ALIGN_MIDDLE); |
||||
nvgTextBox(s->vg, rect.x + 35, rect.y + (strchr(message_str, '\n') ? 40 : 50), rect.w - 50, message_str, NULL); |
||||
} |
||||
} |
||||
|
||||
static void draw_temp_metric(UIState *s) { |
||||
static std::map<cereal::DeviceState::ThermalStatus, const int> temp_severity_map = { |
||||
{cereal::DeviceState::ThermalStatus::GREEN, 0}, |
||||
{cereal::DeviceState::ThermalStatus::YELLOW, 1}, |
||||
{cereal::DeviceState::ThermalStatus::RED, 2}, |
||||
{cereal::DeviceState::ThermalStatus::DANGER, 3}}; |
||||
std::string temp_val = std::to_string((int)s->scene.deviceState.getAmbientTempC()) + "°C"; |
||||
draw_metric(s, "TEMP", temp_val.c_str(), temp_severity_map[s->scene.deviceState.getThermalStatus()], 0, NULL); |
||||
} |
||||
|
||||
static void draw_panda_metric(UIState *s) { |
||||
const int panda_y_offset = 32 + 148; |
||||
|
||||
int panda_severity = 0; |
||||
std::string panda_message = "VEHICLE\nONLINE"; |
||||
if (s->scene.pandaType == cereal::PandaState::PandaType::UNKNOWN) { |
||||
panda_severity = 2; |
||||
panda_message = "NO\nPANDA"; |
||||
} |
||||
#ifdef QCOM2 |
||||
else if (s->scene.started) { |
||||
panda_severity = s->scene.gpsOK ? 0 : 1; |
||||
panda_message = util::string_format("SAT CNT\n%d", s->scene.satelliteCount); |
||||
} |
||||
#endif |
||||
|
||||
draw_metric(s, NULL, NULL, panda_severity, panda_y_offset, panda_message.c_str()); |
||||
} |
||||
|
||||
static void draw_connectivity(UIState *s) { |
||||
static std::map<NetStatus, std::pair<const char *, int>> connectivity_map = { |
||||
{NET_ERROR, {"CONNECT\nERROR", 2}}, |
||||
{NET_CONNECTED, {"CONNECT\nONLINE", 0}}, |
||||
{NET_DISCONNECTED, {"CONNECT\nOFFLINE", 1}}, |
||||
}; |
||||
auto net_params = connectivity_map[s->scene.athenaStatus]; |
||||
draw_metric(s, NULL, NULL, net_params.second, 180 + 158, net_params.first); |
||||
} |
||||
|
||||
void ui_draw_sidebar(UIState *s) { |
||||
if (s->sidebar_collapsed) { |
||||
return; |
||||
} |
||||
draw_background(s); |
||||
draw_settings_button(s); |
||||
draw_home_button(s); |
||||
draw_network_strength(s); |
||||
draw_network_type(s); |
||||
draw_temp_metric(s); |
||||
draw_panda_metric(s); |
||||
draw_connectivity(s); |
||||
} |
@ -1,4 +0,0 @@ |
||||
#pragma once |
||||
#include "ui.hpp" |
||||
|
||||
void ui_draw_sidebar(UIState *s); |
Loading…
Reference in new issue