From ad09507e1ba2d758fe435d06a3dc28def33c14e8 Mon Sep 17 00:00:00 2001 From: Dean Lee Date: Sun, 5 Dec 2021 15:02:10 +0800 Subject: [PATCH] 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 --- selfdrive/ui/paint.cc | 135 ++------------------------------------ selfdrive/ui/paint.h | 4 -- selfdrive/ui/qt/onroad.cc | 101 +++++++++++++++++++++++++++- selfdrive/ui/qt/onroad.h | 38 +++++++++++ selfdrive/ui/ui.h | 16 ----- 5 files changed, 143 insertions(+), 151 deletions(-) diff --git a/selfdrive/ui/paint.cc b/selfdrive/ui/paint.cc index 612b924f32..aa53f4d527 100644 --- a/selfdrive/ui/paint.cc +++ b/selfdrive/ui/paint.cc @@ -16,16 +16,7 @@ #include #include -#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 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> 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) { diff --git a/selfdrive/ui/paint.h b/selfdrive/ui/paint.h index 4ce7cbd133..c888e3cb65 100644 --- a/selfdrive/ui/paint.h +++ b/selfdrive/ui/paint.h @@ -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); diff --git a/selfdrive/ui/qt/onroad.cc b/selfdrive/ui/qt/onroad.cc index 5abcf61047..c5704007a0 100644 --- a/selfdrive/ui/qt/onroad.cc +++ b/selfdrive/ui/qt/onroad.cc @@ -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)); diff --git a/selfdrive/ui/qt/onroad.h b/selfdrive/ui/qt/onroad.h index 1076ed01e6..315aff54f0 100644 --- a/selfdrive/ui/qt/onroad.h +++ b/selfdrive/ui/qt/onroad.h @@ -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]; diff --git a/selfdrive/ui/ui.h b/selfdrive/ui/ui.h index 4c94a83038..ef02a30f26 100644 --- a/selfdrive/ui/ui.h +++ b/selfdrive/ui/ui.h @@ -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 images; - std::unique_ptr sm; UIStatus status;