Skip to content

Commit

Permalink
UI: new set speed design, show speed limits (commaai#24736)
Browse files Browse the repository at this point in the history
* basic US design

* place based on center position

* fix typo

* eu sign without rounded box

* same as steering wheel icon

* proper rounded bottom for eu sign

* add border

* proper placement/sizes

* needs to be semi bold

* color changes

* only when engaged

* move helpers into util.h

* Fix MAX placement

* only change color when at least 5 over

* implement override state

* pixel perfect spacing around us sign
  • Loading branch information
pd0wm authored and spektor56 committed Jul 1, 2022
1 parent aec55ef commit 41cf433
Show file tree
Hide file tree
Showing 4 changed files with 216 additions and 26 deletions.
162 changes: 140 additions & 22 deletions selfdrive/ui/qt/onroad.cc
Original file line number Diff line number Diff line change
Expand Up @@ -174,17 +174,24 @@ void NvgWindow::updateState(const UIState &s) {
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;
float set_speed = cs.getVCruise();
bool cruise_set = set_speed > 0 && (int)set_speed != SET_SPEED_NA;
if (cruise_set && !s.scene.is_metric) {
maxspeed *= KM_TO_MILE;
set_speed *= 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));

auto speed_limit_sign = sm["navInstruction"].getNavInstruction().getSpeedLimitSign();
float speed_limit = sm["navInstruction"].getValid() ? sm["navInstruction"].getNavInstruction().getSpeedLimit() : 0.0;
speed_limit *= (s.scene.is_metric ? MS_TO_KPH : MS_TO_MPH);

setProperty("speedLimit", speed_limit);
setProperty("has_us_speed_limit", speed_limit > 1 && speed_limit_sign == cereal::NavInstruction::SpeedLimitSign::MUTCD);
setProperty("has_eu_speed_limit", speed_limit > 1 && speed_limit_sign == cereal::NavInstruction::SpeedLimitSign::VIENNA);

setProperty("is_cruise_set", cruise_set);
setProperty("speed", QString::number(std::nearbyint(cur_speed)));
setProperty("maxSpeed", maxspeed_str);
setProperty("speed", cur_speed);
setProperty("setSpeed", set_speed);
setProperty("speedUnit", s.scene.is_metric ? "km/h" : "mph");
setProperty("hideDM", cs.getAlertSize() != cereal::ControlsState::AlertSize::NONE);
setProperty("status", s.status);
Expand All @@ -205,26 +212,139 @@ void NvgWindow::drawHud(QPainter &p) {
bg.setColorAt(1, QColor::fromRgbF(0, 0, 0, 0));
p.fillRect(0, 0, width(), header_h, bg);

// 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);
QString speedLimitStr = QString::number(std::nearbyint(speedLimit));
QString speedStr = QString::number(std::nearbyint(speed));
QString setSpeedStr = is_cruise_set ? QString::number(std::nearbyint(setSpeed)) : "";

// Draw outer box + border to contain set speed and speed limit
int default_rect_width = 172;
int rect_width = default_rect_width;
if (has_us_speed_limit && speedLimitStr.size() >= 3) rect_width = 223;
else if (has_eu_speed_limit) rect_width = 200;

int rect_height = 204;
if (has_us_speed_limit) rect_height = 402;
else if (has_eu_speed_limit) rect_height = 392;

configFont(p, "Open Sans", 48, "Regular");
drawText(p, rc.center().x(), 118, "MAX", is_cruise_set ? 200 : 100);
int top_radius = 32;
int bottom_radius = has_eu_speed_limit ? 100 : 32;

QRect set_speed_rect(60 + default_rect_width / 2 - rect_width / 2, 45, rect_width, rect_height);
p.setPen(QPen(QColor(255, 255, 255, 75), 6));
p.setBrush(QColor(0, 0, 0, 166));
drawRoundedRect(p, set_speed_rect, top_radius, top_radius, bottom_radius, bottom_radius);

// Draw set speed
if (is_cruise_set) {
configFont(p, "Open Sans", 88, "Bold");
drawText(p, rc.center().x(), 212, maxSpeed, 255);
if (speedLimit > 0 && status != STATUS_DISENGAGED && status != STATUS_OVERRIDE) {
p.setPen(interpColor(
setSpeed,
{speedLimit + 5, speedLimit + 15, speedLimit + 25},
{QColor(0xff, 0xff, 0xff, 0xff), QColor(0xff, 0x95, 0x00, 0xff), QColor(0xff, 0x00, 0x00, 0xff)}
));
} else {
p.setPen(QColor(0xff, 0xff, 0xff, 0xff));
}
} else {
configFont(p, "Open Sans", 80, "SemiBold");
drawText(p, rc.center().x(), 212, maxSpeed, 100);
p.setPen(QColor(0x72, 0x72, 0x72, 0xff));
}
configFont(p, "Open Sans", 90, "Bold");
QRect speed_rect = getTextRect(p, Qt::AlignCenter, setSpeedStr);
speed_rect.moveCenter({set_speed_rect.center().x(), 0});
speed_rect.moveTop(set_speed_rect.top() + 8);
p.drawText(speed_rect, Qt::AlignCenter, setSpeedStr);

// Draw MAX
if (is_cruise_set) {
if (status == STATUS_DISENGAGED) {
p.setPen(QColor(0xff, 0xff, 0xff, 0xff));
} else if (status == STATUS_OVERRIDE) {
p.setPen(QColor(0x91, 0x9b, 0x95, 0xff));
} else if (speedLimit > 0) {
p.setPen(interpColor(
setSpeed,
{speedLimit + 5, speedLimit + 15, speedLimit + 25},
{QColor(0x80, 0xd8, 0xa6, 0xff), QColor(0xff, 0xe4, 0xbf, 0xff), QColor(0xff, 0xbf, 0xbf, 0xff)}
));
} else {
p.setPen(QColor(0x80, 0xd8, 0xa6, 0xff));
}
} else {
p.setPen(QColor(0xa6, 0xa6, 0xa6, 0xff));
}
configFont(p, "Open Sans", 40, "SemiBold");
QRect max_rect = getTextRect(p, Qt::AlignCenter, "MAX");
max_rect.moveCenter({set_speed_rect.center().x(), 0});
max_rect.moveTop(set_speed_rect.top() + 123);
p.drawText(max_rect, Qt::AlignCenter, "MAX");

// US/Canada (MUTCD style) sign
if (has_us_speed_limit) {
const int border_width = 6;
const int sign_width = (speedLimitStr.size() >= 3) ? 199 : 148;
const int sign_height = 186;

// White outer square
QRect sign_rect_outer(set_speed_rect.left() + 12, set_speed_rect.bottom() - 11 - sign_height, sign_width, sign_height);
p.setPen(Qt::NoPen);
p.setBrush(QColor(255, 255, 255, 255));
p.drawRoundedRect(sign_rect_outer, 24, 24);

// Smaller white square with black border
QRect sign_rect(sign_rect_outer.left() + 1.5 * border_width, sign_rect_outer.top() + 1.5 * border_width, sign_width - 3 * border_width, sign_height - 3 * border_width);
p.setPen(QPen(QColor(0, 0, 0, 255), border_width));
p.setBrush(QColor(255, 255, 255, 255));
p.drawRoundedRect(sign_rect, 16, 16);

// "SPEED"
configFont(p, "Open Sans", 28, "SemiBold");
QRect text_speed_rect = getTextRect(p, Qt::AlignCenter, "SPEED");
text_speed_rect.moveCenter({sign_rect.center().x(), 0});
text_speed_rect.moveTop(sign_rect_outer.top() + 20);
p.drawText(text_speed_rect, Qt::AlignCenter, "SPEED");

// "LIMIT"
QRect text_limit_rect = getTextRect(p, Qt::AlignCenter, "LIMIT");
text_limit_rect.moveCenter({sign_rect.center().x(), 0});
text_limit_rect.moveTop(sign_rect_outer.top() + 48);
p.drawText(text_limit_rect, Qt::AlignCenter, "LIMIT");

// Speed limit value
configFont(p, "Open Sans", 70, "Bold");
QRect speed_limit_rect = getTextRect(p, Qt::AlignCenter, speedLimitStr);
speed_limit_rect.moveCenter({sign_rect.center().x(), 0});
speed_limit_rect.moveTop(sign_rect.top() + 70);
p.drawText(speed_limit_rect, Qt::AlignCenter, speedLimitStr);
}

// EU (Vienna style) sign
if (has_eu_speed_limit) {
int outer_radius = 176 / 2;
int inner_radius_1 = outer_radius - 6; // White outer border
int inner_radius_2 = inner_radius_1 - 20; // Red circle

// Draw white circle with red border
QPoint center(set_speed_rect.center().x() + 1, set_speed_rect.top() + 204 + outer_radius);
p.setPen(Qt::NoPen);
p.setBrush(QColor(255, 255, 255, 255));
p.drawEllipse(center, outer_radius, outer_radius);
p.setBrush(QColor(255, 0, 0, 255));
p.drawEllipse(center, inner_radius_1, inner_radius_1);
p.setBrush(QColor(255, 255, 255, 255));
p.drawEllipse(center, inner_radius_2, inner_radius_2);

// Speed limit value
int font_size = (speedLimitStr.size() >= 3) ? 62 : 70;
configFont(p, "Open Sans", font_size, "Bold");
QRect speed_limit_rect = getTextRect(p, Qt::AlignCenter, speedLimitStr);
speed_limit_rect.moveCenter(center);
p.setPen(QColor(0, 0, 0, 255));
p.drawText(speed_limit_rect, Qt::AlignCenter, speedLimitStr);
}

// current speed
configFont(p, "Open Sans", 176, "Bold");
drawText(p, rect().center().x(), 210, speed);
drawText(p, rect().center().x(), 210, speedStr);
configFont(p, "Open Sans", 66, "Regular");
drawText(p, rect().center().x(), 290, speedUnit, 200);

Expand All @@ -243,9 +363,7 @@ void NvgWindow::drawHud(QPainter &p) {
}

void NvgWindow::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);
QRect real_rect = getTextRect(p, 0, text);
real_rect.moveCenter({x, y - real_rect.height() / 2});

p.setPen(QColor(0xff, 0xff, 0xff, alpha));
Expand Down
15 changes: 11 additions & 4 deletions selfdrive/ui/qt/onroad.h
Original file line number Diff line number Diff line change
Expand Up @@ -27,10 +27,14 @@ class OnroadAlerts : public QWidget {
// container window for the NVG UI
class NvgWindow : public CameraViewWidget {
Q_OBJECT
Q_PROPERTY(QString speed MEMBER speed);
Q_PROPERTY(float speed MEMBER speed);
Q_PROPERTY(QString speedUnit MEMBER speedUnit);
Q_PROPERTY(QString maxSpeed MEMBER maxSpeed);
Q_PROPERTY(float setSpeed MEMBER setSpeed);
Q_PROPERTY(float speedLimit MEMBER speedLimit);
Q_PROPERTY(bool is_cruise_set MEMBER is_cruise_set);
Q_PROPERTY(bool has_eu_speed_limit MEMBER has_eu_speed_limit);
Q_PROPERTY(bool has_us_speed_limit MEMBER has_us_speed_limit);

Q_PROPERTY(bool engageable MEMBER engageable);
Q_PROPERTY(bool dmActive MEMBER dmActive);
Q_PROPERTY(bool hideDM MEMBER hideDM);
Expand All @@ -48,13 +52,16 @@ class NvgWindow : public CameraViewWidget {
QPixmap dm_img;
const int radius = 192;
const int img_size = (radius / 2) * 1.5;
QString speed;
float speed;
QString speedUnit;
QString maxSpeed;
float setSpeed;
float speedLimit;
bool is_cruise_set = false;
bool engageable = false;
bool dmActive = false;
bool hideDM = false;
bool has_us_speed_limit = false;
bool has_eu_speed_limit = false;
int status = STATUS_DISENGAGED;

protected:
Expand Down
61 changes: 61 additions & 0 deletions selfdrive/ui/qt/util.cc
Original file line number Diff line number Diff line change
Expand Up @@ -138,3 +138,64 @@ QPixmap loadPixmap(const QString &fileName, const QSize &size, Qt::AspectRatioMo
return QPixmap(fileName).scaled(size, aspectRatioMode, Qt::SmoothTransformation);
}
}

QRect getTextRect(QPainter &p, int flags, QString text) {
QFontMetrics fm(p.font());
QRect init_rect = fm.boundingRect(text);
return fm.boundingRect(init_rect, flags, text);
}

void drawRoundedRect(QPainter &painter, const QRectF &rect, qreal xRadiusTop, qreal yRadiusTop, qreal xRadiusBottom, qreal yRadiusBottom){
qreal w_2 = rect.width() / 2;
qreal h_2 = rect.height() / 2;

xRadiusTop = 100 * qMin(xRadiusTop, w_2) / w_2;
yRadiusTop = 100 * qMin(yRadiusTop, h_2) / h_2;

xRadiusBottom = 100 * qMin(xRadiusBottom, w_2) / w_2;
yRadiusBottom = 100 * qMin(yRadiusBottom, h_2) / h_2;

qreal x = rect.x();
qreal y = rect.y();
qreal w = rect.width();
qreal h = rect.height();

qreal rxx2Top = w*xRadiusTop/100;
qreal ryy2Top = h*yRadiusTop/100;

qreal rxx2Bottom = w*xRadiusBottom/100;
qreal ryy2Bottom = h*yRadiusBottom/100;

QPainterPath path;
path.arcMoveTo(x, y, rxx2Top, ryy2Top, 180);
path.arcTo(x, y, rxx2Top, ryy2Top, 180, -90);
path.arcTo(x+w-rxx2Top, y, rxx2Top, ryy2Top, 90, -90);
path.arcTo(x+w-rxx2Bottom, y+h-ryy2Bottom, rxx2Bottom, ryy2Bottom, 0, -90);
path.arcTo(x, y+h-ryy2Bottom, rxx2Bottom, ryy2Bottom, 270, -90);
path.closeSubpath();

painter.drawPath(path);
}

QColor interpColor(float xv, std::vector<float> xp, std::vector<QColor> fp) {
assert(xp.size() == fp.size());

int N = xp.size();
int hi = 0;

while (hi < N and xv > xp[hi]) hi++;
int low = hi - 1;

if (hi == N && xv > xp[low]) {
return fp[fp.size() - 1];
} else if (hi == 0){
return fp[0];
} else {
return QColor(
(xv - xp[low]) * (fp[hi].red() - fp[low].red()) / (xp[hi] - xp[low]) + fp[low].red(),
(xv - xp[low]) * (fp[hi].green() - fp[low].green()) / (xp[hi] - xp[low]) + fp[low].green(),
(xv - xp[low]) * (fp[hi].blue() - fp[low].blue()) / (xp[hi] - xp[low]) + fp[low].blue(),
(xv - xp[low]) * (fp[hi].alpha() - fp[low].alpha()) / (xp[hi] - xp[low]) + fp[low].alpha()
);
}
}
4 changes: 4 additions & 0 deletions selfdrive/ui/qt/util.h
Original file line number Diff line number Diff line change
Expand Up @@ -22,3 +22,7 @@ void swagLogMessageHandler(QtMsgType type, const QMessageLogContext &context, co
void initApp(int argc, char *argv[]);
QWidget* topWidget (QWidget* widget);
QPixmap loadPixmap(const QString &fileName, const QSize &size = {}, Qt::AspectRatioMode aspectRatioMode = Qt::KeepAspectRatio);

QRect getTextRect(QPainter &p, int flags, QString text);
void drawRoundedRect(QPainter &painter, const QRectF &rect, qreal xRadiusTop, qreal yRadiusTop, qreal xRadiusBottom, qreal yRadiusBottom);
QColor interpColor(float xv, std::vector<float> xp, std::vector<QColor> fp);

0 comments on commit 41cf433

Please sign in to comment.