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>pull/20778/head
parent
a57f10c202
commit
db9319405b
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