UI: move onroad hud to Qt (#23040)

* onroad hud

* improve boudingrect, same as nanvg

* QRect

* Update selfdrive/ui/paint.cc

* stacked with nvgWindow

* cleanup ui.h

* rename to road_view_layout

* no lambda

Co-authored-by: Adeeb Shihadeh <adeebshihadeh@gmail.com>
pull/23132/head
Dean Lee 3 years ago committed by GitHub
parent 1f73192695
commit ad09507e1b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 135
      selfdrive/ui/paint.cc
  2. 4
      selfdrive/ui/paint.h
  3. 101
      selfdrive/ui/qt/onroad.cc
  4. 38
      selfdrive/ui/qt/onroad.h
  5. 16
      selfdrive/ui/ui.h

@ -16,16 +16,7 @@
#include <nanovg_gl.h>
#include <nanovg_gl_utils.h>
#include "selfdrive/common/util.h"
#include "selfdrive/hardware/hw.h"
#include "selfdrive/ui/ui.h"
static void ui_draw_text(const UIState *s, float x, float y, const char *string, float size, NVGcolor color, const char *font_name) {
nvgFontFace(s->vg, font_name);
nvgFontSize(s->vg, size);
nvgFillColor(s->vg, color);
nvgText(s->vg, x, y, string, NULL);
}
static void draw_chevron(UIState *s, float x, float y, float sz, NVGcolor fillColor, NVGcolor glowColor) {
// glow
@ -49,21 +40,6 @@ static void draw_chevron(UIState *s, float x, float y, float sz, NVGcolor fillCo
nvgFill(s->vg);
}
static void ui_draw_circle_image(const UIState *s, int center_x, int center_y, int radius, const char *image, NVGcolor color, float img_alpha) {
nvgBeginPath(s->vg);
nvgCircle(s->vg, center_x, center_y, radius);
nvgFillColor(s->vg, color);
nvgFill(s->vg);
const int img_size = radius * 1.5;
ui_draw_image(s, {center_x - (img_size / 2), center_y - (img_size / 2), img_size, img_size}, image, img_alpha);
}
static void ui_draw_circle_image(const UIState *s, int center_x, int center_y, int radius, const char *image, bool active) {
float bg_alpha = active ? 0.3f : 0.1f;
float img_alpha = active ? 1.0f : 0.15f;
ui_draw_circle_image(s, center_x, center_y, radius, image, nvgRGBA(0, 0, 0, (255 * bg_alpha)), img_alpha);
}
static void draw_lead(UIState *s, const cereal::RadarState::LeadData::Reader &lead_data, const vertex_data &vd) {
// Draw lead car indicator
auto [x, y] = vd;
@ -151,60 +127,13 @@ static void ui_draw_world(UIState *s) {
nvgResetScissor(s->vg);
}
static void ui_draw_vision_maxspeed(UIState *s) {
const int SET_SPEED_NA = 255;
float maxspeed = (*s->sm)["controlsState"].getControlsState().getVCruise();
const bool is_cruise_set = maxspeed != 0 && maxspeed != SET_SPEED_NA;
if (is_cruise_set && !s->scene.is_metric) { maxspeed *= KM_TO_MILE; }
const Rect rect = {bdr_s * 2, int(bdr_s * 1.5), 184, 202};
ui_fill_rect(s->vg, rect, COLOR_BLACK_ALPHA(100), 30.);
ui_draw_rect(s->vg, rect, COLOR_WHITE_ALPHA(100), 10, 20.);
nvgTextAlign(s->vg, NVG_ALIGN_CENTER | NVG_ALIGN_BASELINE);
ui_draw_text(s, rect.centerX(), 118, "MAX", 26 * 2.5, COLOR_WHITE_ALPHA(is_cruise_set ? 200 : 100), "sans-regular");
if (is_cruise_set) {
const std::string maxspeed_str = std::to_string((int)std::nearbyint(maxspeed));
ui_draw_text(s, rect.centerX(), 212, maxspeed_str.c_str(), 48 * 2.5, COLOR_WHITE, "sans-bold");
} else {
ui_draw_text(s, rect.centerX(), 212, "N/A", 42 * 2.5, COLOR_WHITE_ALPHA(100), "sans-semibold");
}
}
static void ui_draw_vision_speed(UIState *s) {
const float speed = std::max(0.0, (*s->sm)["carState"].getCarState().getVEgo() * (s->scene.is_metric ? MS_TO_KPH : MS_TO_MPH));
const std::string speed_str = std::to_string((int)std::nearbyint(speed));
nvgTextAlign(s->vg, NVG_ALIGN_CENTER | NVG_ALIGN_BASELINE);
ui_draw_text(s, s->fb_w/2, 210, speed_str.c_str(), 96 * 2.5, COLOR_WHITE, "sans-bold");
ui_draw_text(s, s->fb_w/2, 290, s->scene.is_metric ? "km/h" : "mph", 36 * 2.5, COLOR_WHITE_ALPHA(200), "sans-regular");
}
static void ui_draw_vision_event(UIState *s) {
if (s->scene.engageable) {
// draw steering wheel
const int radius = 96;
const int center_x = s->fb_w - radius - bdr_s * 2;
const int center_y = radius + (bdr_s * 1.5);
const QColor &color = bg_colors[s->status];
NVGcolor nvg_color = nvgRGBA(color.red(), color.green(), color.blue(), color.alpha());
ui_draw_circle_image(s, center_x, center_y, radius, "wheel", nvg_color, 1.0f);
}
}
static void ui_draw_vision_face(UIState *s) {
const int radius = 96;
const int center_x = radius + (bdr_s * 2);
const int center_y = s->fb_h - footer_h / 2;
ui_draw_circle_image(s, center_x, center_y, radius, "driver_face", s->scene.dm_active);
}
static void ui_draw_vision_header(UIState *s) {
NVGpaint gradient = nvgLinearGradient(s->vg, 0, header_h - (header_h / 2.5), 0, header_h,
nvgRGBAf(0, 0, 0, 0.45), nvgRGBAf(0, 0, 0, 0));
ui_fill_rect(s->vg, {0, 0, s->fb_w , header_h}, gradient);
ui_draw_vision_maxspeed(s);
ui_draw_vision_speed(s);
ui_draw_vision_event(s);
nvgBeginPath(s->vg);
nvgRect(s->vg, 0, 0, s->fb_w, header_h);
nvgFillPaint(s->vg, gradient);
nvgFill(s->vg);
}
static void ui_draw_vision(UIState *s) {
@ -213,11 +142,8 @@ static void ui_draw_vision(UIState *s) {
if (scene->world_objects_visible) {
ui_draw_world(s);
}
// Set Speed, Current Speed, Status/Events
// TODO: move this to Qt
ui_draw_vision_header(s);
if ((*s->sm)["controlsState"].getControlsState().getAlertSize() == cereal::ControlsState::AlertSize::NONE) {
ui_draw_vision_face(s);
}
}
void ui_draw(UIState *s, int w, int h) {
@ -233,61 +159,10 @@ void ui_draw(UIState *s, int w, int h) {
glDisable(GL_BLEND);
}
void ui_draw_image(const UIState *s, const Rect &r, const char *name, float alpha) {
nvgBeginPath(s->vg);
NVGpaint imgPaint = nvgImagePattern(s->vg, r.x, r.y, r.w, r.h, 0, s->images.at(name), alpha);
nvgRect(s->vg, r.x, r.y, r.w, r.h);
nvgFillPaint(s->vg, imgPaint);
nvgFill(s->vg);
}
void ui_draw_rect(NVGcontext *vg, const Rect &r, NVGcolor color, int width, float radius) {
nvgBeginPath(vg);
radius > 0 ? nvgRoundedRect(vg, r.x, r.y, r.w, r.h, radius) : nvgRect(vg, r.x, r.y, r.w, r.h);
nvgStrokeColor(vg, color);
nvgStrokeWidth(vg, width);
nvgStroke(vg);
}
static inline void fill_rect(NVGcontext *vg, const Rect &r, const NVGcolor *color, const NVGpaint *paint, float radius) {
nvgBeginPath(vg);
radius > 0 ? nvgRoundedRect(vg, r.x, r.y, r.w, r.h, radius) : nvgRect(vg, r.x, r.y, r.w, r.h);
if (color) nvgFillColor(vg, *color);
if (paint) nvgFillPaint(vg, *paint);
nvgFill(vg);
}
void ui_fill_rect(NVGcontext *vg, const Rect &r, const NVGcolor &color, float radius) {
fill_rect(vg, r, &color, nullptr, radius);
}
void ui_fill_rect(NVGcontext *vg, const Rect &r, const NVGpaint &paint, float radius) {
fill_rect(vg, r, nullptr, &paint, radius);
}
void ui_nvg_init(UIState *s) {
// on EON, we enable MSAA
s->vg = Hardware::EON() ? nvgCreate(0) : nvgCreate(NVG_ANTIALIAS | NVG_STENCIL_STROKES | NVG_DEBUG);
assert(s->vg);
// init fonts
std::pair<const char *, const char *> fonts[] = {
{"sans-regular", "../assets/fonts/opensans_regular.ttf"},
{"sans-semibold", "../assets/fonts/opensans_semibold.ttf"},
{"sans-bold", "../assets/fonts/opensans_bold.ttf"},
};
for (auto [name, file] : fonts) {
int font_id = nvgCreateFont(s->vg, name, file);
assert(font_id >= 0);
}
// init images
std::vector<std::pair<const char *, const char *>> images = {
{"wheel", "../assets/img_chffr_wheel.png"},
{"driver_face", "../assets/img_driver_face.png"},
};
for (auto [name, file] : images) {
s->images[name] = nvgCreateImage(s->vg, file, 1);
assert(s->images[name] != 0);
}
}
void ui_resize(UIState *s, int width, int height) {

@ -3,10 +3,6 @@
#include "selfdrive/ui/ui.h"
void ui_draw(UIState *s, int w, int h);
void ui_draw_image(const UIState *s, const Rect &r, const char *name, float alpha);
void ui_draw_rect(NVGcontext *vg, const Rect &r, NVGcolor color, int width, float radius = 0);
void ui_fill_rect(NVGcontext *vg, const Rect &r, const NVGpaint &paint, float radius = 0);
void ui_fill_rect(NVGcontext *vg, const Rect &r, const NVGcolor &color, float radius = 0);
void ui_nvg_init(UIState *s);
void ui_resize(UIState *s, int width, int height);
void ui_update_params(UIState *s);

@ -17,13 +17,18 @@ OnroadWindow::OnroadWindow(QWidget *parent) : QWidget(parent) {
stacked_layout->setStackingMode(QStackedLayout::StackAll);
main_layout->addLayout(stacked_layout);
QStackedLayout *road_view_layout = new QStackedLayout;
road_view_layout->setStackingMode(QStackedLayout::StackAll);
nvg = new NvgWindow(VISION_STREAM_RGB_BACK, this);
road_view_layout->addWidget(nvg);
hud = new OnroadHud(this);
road_view_layout->addWidget(hud);
QWidget * split_wrapper = new QWidget;
split = new QHBoxLayout(split_wrapper);
split->setContentsMargins(0, 0, 0, 0);
split->setSpacing(0);
split->addWidget(nvg);
split->addLayout(road_view_layout);
stacked_layout->addWidget(split_wrapper);
@ -48,6 +53,9 @@ void OnroadWindow::updateState(const UIState &s) {
}
alerts->updateAlert(alert, bgColor);
}
hud->updateState(s);
if (bg != bgColor) {
// repaint border
bg = bgColor;
@ -91,6 +99,7 @@ void OnroadWindow::paintEvent(QPaintEvent *event) {
// ***** onroad widgets *****
// OnroadAlerts
void OnroadAlerts::updateAlert(const Alert &a, const QColor &color) {
if (!alert.equal(a) || color != bg) {
alert = a;
@ -150,6 +159,96 @@ void OnroadAlerts::paintEvent(QPaintEvent *event) {
}
}
// OnroadHud
OnroadHud::OnroadHud(QWidget *parent) : QWidget(parent) {
engage_img = QPixmap("../assets/img_chffr_wheel.png").scaled(img_size, img_size, Qt::KeepAspectRatio, Qt::SmoothTransformation);
dm_img = QPixmap("../assets/img_driver_face.png").scaled(img_size, img_size, Qt::KeepAspectRatio, Qt::SmoothTransformation);
connect(this, &OnroadHud::valueChanged, [=] { update(); });
}
void OnroadHud::updateState(const UIState &s) {
const int SET_SPEED_NA = 255;
const SubMaster &sm = *(s.sm);
const auto cs = sm["controlsState"].getControlsState();
float maxspeed = cs.getVCruise();
bool cruise_set = maxspeed > 0 && (int)maxspeed != SET_SPEED_NA;
if (cruise_set && !s.scene.is_metric) {
maxspeed *= KM_TO_MILE;
}
QString maxspeed_str = cruise_set ? QString::number(std::nearbyint(maxspeed)) : "N/A";
float cur_speed = std::max(0.0, sm["carState"].getCarState().getVEgo() * (s.scene.is_metric ? MS_TO_KPH : MS_TO_MPH));
setProperty("is_cruise_set", cruise_set);
setProperty("speed", QString::number(std::nearbyint(cur_speed)));
setProperty("maxSpeed", maxspeed_str);
setProperty("speedUnit", s.scene.is_metric ? "km/h" : "mph");
setProperty("dmActive", sm["driverMonitoringState"].getDriverMonitoringState().getIsActiveMode());
setProperty("hideDM", cs.getAlertSize() != cereal::ControlsState::AlertSize::NONE);
setProperty("engageable", cs.getEngageable());
setProperty("status", s.status);
}
void OnroadHud::paintEvent(QPaintEvent *event) {
QPainter p(this);
p.setRenderHint(QPainter::Antialiasing);
// max speed
QRect rc(bdr_s * 2, bdr_s * 1.5, 184, 202);
p.setPen(QPen(QColor(0xff, 0xff, 0xff, 100), 10));
p.setBrush(QColor(0, 0, 0, 100));
p.drawRoundedRect(rc, 20, 20);
p.setPen(Qt::NoPen);
configFont(p, "Open Sans", 48, "Regular");
drawText(p, rc.center().x(), 118, "MAX", is_cruise_set ? 200 : 100);
if (is_cruise_set) {
configFont(p, "Open Sans", 88, is_cruise_set ? "Bold" : "SemiBold");
drawText(p, rc.center().x(), 212, maxSpeed, 255);
} else {
configFont(p, "Open Sans", 80, "SemiBold");
drawText(p, rc.center().x(), 212, maxSpeed, 100);
}
// current speed
configFont(p, "Open Sans", 176, "Bold");
drawText(p, rect().center().x(), 210, speed);
configFont(p, "Open Sans", 66, "Regular");
drawText(p, rect().center().x(), 290, speedUnit, 200);
// engage-ability icon
if (engageable) {
drawIcon(p, rect().right() - radius / 2 - bdr_s * 2, radius / 2 + int(bdr_s * 1.5),
engage_img, bg_colors[status], 1.0);
}
// dm icon
if (!hideDM) {
drawIcon(p, radius / 2 + (bdr_s * 2), rect().bottom() - footer_h / 2,
dm_img, QColor(0, 0, 0, 70), dmActive ? 1.0 : 0.2);
}
}
void OnroadHud::drawText(QPainter &p, int x, int y, const QString &text, int alpha) {
QFontMetrics fm(p.font());
QRect init_rect = fm.boundingRect(text);
QRect real_rect = fm.boundingRect(init_rect, 0, text);
real_rect.moveCenter({x, y - real_rect.height() / 2});
p.setPen(QColor(0xff, 0xff, 0xff, alpha));
p.drawText(real_rect.x(), real_rect.bottom(), text);
}
void OnroadHud::drawIcon(QPainter &p, int x, int y, QPixmap &img, QBrush bg, float opacity) {
p.setPen(Qt::NoPen);
p.setBrush(bg);
p.drawEllipse(x - radius / 2, y - radius / 2, radius, radius);
p.setOpacity(opacity);
p.drawPixmap(x - img_size / 2, y - img_size / 2, img);
}
// NvgWindow
void NvgWindow::initializeGL() {
CameraViewWidget::initializeGL();
qInfo() << "OpenGL version:" << QString((const char*)glGetString(GL_VERSION));

@ -9,6 +9,43 @@
// ***** onroad widgets *****
class OnroadHud : public QWidget {
Q_OBJECT
Q_PROPERTY(QString speed MEMBER speed NOTIFY valueChanged);
Q_PROPERTY(QString speedUnit MEMBER speedUnit NOTIFY valueChanged);
Q_PROPERTY(QString maxSpeed MEMBER maxSpeed NOTIFY valueChanged);
Q_PROPERTY(bool is_cruise_set MEMBER is_cruise_set NOTIFY valueChanged);
Q_PROPERTY(bool engageable MEMBER engageable NOTIFY valueChanged);
Q_PROPERTY(bool dmActive MEMBER dmActive NOTIFY valueChanged);
Q_PROPERTY(bool hideDM MEMBER hideDM NOTIFY valueChanged);
Q_PROPERTY(int status MEMBER status NOTIFY valueChanged);
public:
explicit OnroadHud(QWidget *parent);
void updateState(const UIState &s);
private:
void drawIcon(QPainter &p, int x, int y, QPixmap &img, QBrush bg, float opacity);
void drawText(QPainter &p, int x, int y, const QString &text, int alpha = 255);
void paintEvent(QPaintEvent *event) override;
QPixmap engage_img;
QPixmap dm_img;
const int radius = 192;
const int img_size = (radius / 2) * 1.5;
QString speed;
QString speedUnit;
QString maxSpeed;
bool is_cruise_set = false;
bool engageable = false;
bool dmActive = false;
bool hideDM = false;
int status = STATUS_DISENGAGED;
signals:
void valueChanged();
};
class OnroadAlerts : public QWidget {
Q_OBJECT
@ -49,6 +86,7 @@ public:
private:
void paintEvent(QPaintEvent *event);
void mousePressEvent(QMouseEvent* e) override;
OnroadHud *hud;
OnroadAlerts *alerts;
NvgWindow *nvg;
QColor bg = bg_colors[STATUS_DISENGAGED];

@ -19,8 +19,6 @@
#include "selfdrive/common/params.h"
#include "selfdrive/common/util.h"
#define COLOR_BLACK nvgRGBA(0, 0, 0, 255)
#define COLOR_BLACK_ALPHA(x) nvgRGBA(0, 0, 0, x)
#define COLOR_WHITE nvgRGBA(255, 255, 255, 255)
#define COLOR_WHITE_ALPHA(x) nvgRGBA(255, 255, 255, x)
#define COLOR_RED_ALPHA(x) nvgRGBA(201, 34, 49, x)
@ -39,17 +37,6 @@ typedef cereal::CarControl::HUDControl::AudibleAlert AudibleAlert;
const float y_offset = Hardware::EON() ? 0.0 : 150.0;
const float ZOOM = Hardware::EON() ? 2138.5 : 2912.8;
typedef struct Rect {
int x, y, w, h;
int centerX() const { return x + w / 2; }
int centerY() const { return y + h / 2; }
int right() const { return x + w; }
int bottom() const { return y + h; }
bool ptInRect(int px, int py) const {
return px >= x && px < (x + w) && py >= y && py < (y + h);
}
} Rect;
struct Alert {
QString text1;
QString text2;
@ -136,9 +123,6 @@ typedef struct UIState {
int fb_w = 0, fb_h = 0;
NVGcontext *vg;
// images
std::map<std::string, int> images;
std::unique_ptr<SubMaster> sm;
UIStatus status;

Loading…
Cancel
Save