ui/map: move `MapInstructions` & `MapETA` to separate files (#28976)
old-commit-hash: 942a2f9760
beeps
parent
b120aec2f5
commit
7d5d1b1e70
7 changed files with 255 additions and 227 deletions
@ -0,0 +1,55 @@ |
||||
#include "selfdrive/ui/qt/maps/map_eta.h" |
||||
|
||||
#include <QDateTime> |
||||
#include <QPainter> |
||||
|
||||
#include "selfdrive/ui/ui.h" |
||||
|
||||
const float MANEUVER_TRANSITION_THRESHOLD = 10; |
||||
|
||||
MapETA::MapETA(QWidget *parent) : QWidget(parent) { |
||||
setVisible(false); |
||||
setAttribute(Qt::WA_TranslucentBackground); |
||||
eta_doc.setUndoRedoEnabled(false); |
||||
eta_doc.setDefaultStyleSheet("body {font-family:Inter;font-size:60px;color:white;} b{font-size:70px;font-weight:600}"); |
||||
} |
||||
|
||||
void MapETA::paintEvent(QPaintEvent *event) { |
||||
if (!eta_doc.isEmpty()) { |
||||
QPainter p(this); |
||||
p.setRenderHint(QPainter::Antialiasing); |
||||
p.setPen(Qt::NoPen); |
||||
p.setBrush(QColor(0, 0, 0, 150)); |
||||
QSizeF txt_size = eta_doc.size(); |
||||
p.drawRoundedRect((width() - txt_size.width()) / 2 - UI_BORDER_SIZE, 0, txt_size.width() + UI_BORDER_SIZE * 2, height() + 25, 25, 25); |
||||
p.translate((width() - txt_size.width()) / 2, (height() - txt_size.height()) / 2); |
||||
eta_doc.drawContents(&p); |
||||
} |
||||
} |
||||
|
||||
void MapETA::updateETA(float s, float s_typical, float d) { |
||||
// ETA
|
||||
auto eta_t = QDateTime::currentDateTime().addSecs(s).time(); |
||||
auto eta = format_24h ? std::array{eta_t.toString("HH:mm"), tr("eta")} |
||||
: std::array{eta_t.toString("h:mm a").split(' ')[0], eta_t.toString("a")}; |
||||
|
||||
// Remaining time
|
||||
auto remaining = s < 3600 ? std::array{QString::number(int(s / 60)), tr("min")} |
||||
: std::array{QString("%1:%2").arg((int)s / 3600).arg(((int)s % 3600) / 60, 2, 10, QLatin1Char('0')), tr("hr")}; |
||||
QString color = "#25DA6E"; |
||||
if (s / s_typical > 1.5) |
||||
color = "#DA3025"; |
||||
else if (s / s_typical > 1.2) |
||||
color = "#DAA725"; |
||||
|
||||
// Distance
|
||||
float num = uiState()->scene.is_metric ? (d / 1000.0) : (d * METER_TO_MILE); |
||||
auto distance = std::array{QString::number(num, 'f', num < 100 ? 1 : 0), |
||||
uiState()->scene.is_metric ? tr("km") : tr("mi")}; |
||||
|
||||
eta_doc.setHtml(QString(R"(<body><b>%1</b>%2 <span style="color:%3"><b>%4</b>%5</span> <b>%6</b>%7</body>)") |
||||
.arg(eta[0], eta[1], color, remaining[0], remaining[1], distance[0], distance[1])); |
||||
|
||||
setVisible(d >= MANEUVER_TRANSITION_THRESHOLD); |
||||
update(); |
||||
} |
@ -0,0 +1,23 @@ |
||||
#pragma once |
||||
|
||||
#include <QPaintEvent> |
||||
#include <QTextDocument> |
||||
#include <QWidget> |
||||
|
||||
#include "common/params.h" |
||||
|
||||
class MapETA : public QWidget { |
||||
Q_OBJECT |
||||
|
||||
public: |
||||
MapETA(QWidget * parent=nullptr); |
||||
void updateETA(float seconds, float seconds_typical, float distance); |
||||
|
||||
private: |
||||
void paintEvent(QPaintEvent *event) override; |
||||
void showEvent(QShowEvent *event) override { format_24h = param.getBool("NavSettingTime24h"); } |
||||
|
||||
bool format_24h = false; |
||||
QTextDocument eta_doc; |
||||
Params param; |
||||
}; |
@ -0,0 +1,146 @@ |
||||
#include "selfdrive/ui/qt/maps/map_instructions.h" |
||||
|
||||
#include <QDir> |
||||
#include <QVBoxLayout> |
||||
|
||||
#include "selfdrive/ui/ui.h" |
||||
|
||||
const QString ICON_SUFFIX = ".png"; |
||||
|
||||
MapInstructions::MapInstructions(QWidget *parent) : QWidget(parent) { |
||||
is_rhd = Params().getBool("IsRhdDetected"); |
||||
QHBoxLayout *main_layout = new QHBoxLayout(this); |
||||
main_layout->setContentsMargins(11, 50, 11, 11); |
||||
main_layout->addWidget(icon_01 = new QLabel, 0, Qt::AlignTop); |
||||
|
||||
QWidget *right_container = new QWidget(this); |
||||
right_container->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Preferred); |
||||
QVBoxLayout *layout = new QVBoxLayout(right_container); |
||||
|
||||
layout->addWidget(distance = new QLabel); |
||||
distance->setStyleSheet(R"(font-size: 90px;)"); |
||||
|
||||
layout->addWidget(primary = new QLabel); |
||||
primary->setStyleSheet(R"(font-size: 60px;)"); |
||||
primary->setWordWrap(true); |
||||
|
||||
layout->addWidget(secondary = new QLabel); |
||||
secondary->setStyleSheet(R"(font-size: 50px;)"); |
||||
secondary->setWordWrap(true); |
||||
|
||||
layout->addLayout(lane_layout = new QHBoxLayout); |
||||
main_layout->addWidget(right_container); |
||||
|
||||
setStyleSheet("color:white"); |
||||
QPalette pal = palette(); |
||||
pal.setColor(QPalette::Background, QColor(0, 0, 0, 150)); |
||||
setAutoFillBackground(true); |
||||
setPalette(pal); |
||||
|
||||
buildPixmapCache(); |
||||
} |
||||
|
||||
void MapInstructions::buildPixmapCache() { |
||||
QDir dir("../assets/navigation"); |
||||
for (QString fn : dir.entryList({"*" + ICON_SUFFIX}, QDir::Files)) { |
||||
QPixmap pm(dir.filePath(fn)); |
||||
QString key = fn.left(fn.size() - ICON_SUFFIX.length()); |
||||
pm = pm.scaledToWidth(200, Qt::SmoothTransformation); |
||||
|
||||
// Maneuver icons
|
||||
pixmap_cache[key] = pm; |
||||
// lane direction icons
|
||||
if (key.contains("turn_")) { |
||||
pixmap_cache["lane_" + key] = pm.scaled({125, 125}, Qt::IgnoreAspectRatio, Qt::SmoothTransformation); |
||||
} |
||||
|
||||
// for rhd, reflect direction and then flip
|
||||
if (key.contains("_left")) { |
||||
pixmap_cache["rhd_" + key.replace("_left", "_right")] = pm.transformed(QTransform().scale(-1, 1)); |
||||
} else if (key.contains("_right")) { |
||||
pixmap_cache["rhd_" + key.replace("_right", "_left")] = pm.transformed(QTransform().scale(-1, 1)); |
||||
} |
||||
} |
||||
} |
||||
|
||||
QString MapInstructions::getDistance(float d) { |
||||
d = std::max(d, 0.0f); |
||||
if (uiState()->scene.is_metric) { |
||||
return (d > 500) ? QString::number(d / 1000, 'f', 1) + tr(" km") |
||||
: QString::number(50 * int(d / 50)) + tr(" m"); |
||||
} else { |
||||
float feet = d * METER_TO_FOOT; |
||||
return (feet > 500) ? QString::number(d * METER_TO_MILE, 'f', 1) + tr(" mi") |
||||
: QString::number(50 * int(feet / 50)) + tr(" ft"); |
||||
} |
||||
} |
||||
|
||||
void MapInstructions::updateInstructions(cereal::NavInstruction::Reader instruction) { |
||||
setUpdatesEnabled(false); |
||||
|
||||
// Show instruction text
|
||||
QString primary_str = QString::fromStdString(instruction.getManeuverPrimaryText()); |
||||
QString secondary_str = QString::fromStdString(instruction.getManeuverSecondaryText()); |
||||
|
||||
primary->setText(primary_str); |
||||
secondary->setVisible(secondary_str.length() > 0); |
||||
secondary->setText(secondary_str); |
||||
distance->setText(getDistance(instruction.getManeuverDistance())); |
||||
|
||||
// Show arrow with direction
|
||||
QString type = QString::fromStdString(instruction.getManeuverType()); |
||||
QString modifier = QString::fromStdString(instruction.getManeuverModifier()); |
||||
if (!type.isEmpty()) { |
||||
QString fn = "direction_" + type; |
||||
if (!modifier.isEmpty()) { |
||||
fn += "_" + modifier; |
||||
} |
||||
fn = fn.replace(' ', '_'); |
||||
bool rhd = is_rhd && (fn.contains("_left") || fn.contains("_right")); |
||||
icon_01->setPixmap(pixmap_cache[!rhd ? fn : "rhd_" + fn]); |
||||
icon_01->setSizePolicy(QSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed)); |
||||
icon_01->setVisible(true); |
||||
} |
||||
|
||||
// Show lanes
|
||||
auto lanes = instruction.getLanes(); |
||||
for (int i = 0; i < lanes.size(); ++i) { |
||||
bool active = lanes[i].getActive(); |
||||
|
||||
// TODO: only use active direction if active
|
||||
bool left = false, straight = false, right = false; |
||||
for (auto const &direction : lanes[i].getDirections()) { |
||||
left |= direction == cereal::NavInstruction::Direction::LEFT; |
||||
right |= direction == cereal::NavInstruction::Direction::RIGHT; |
||||
straight |= direction == cereal::NavInstruction::Direction::STRAIGHT; |
||||
} |
||||
|
||||
// TODO: Make more images based on active direction and combined directions
|
||||
QString fn = "lane_direction_"; |
||||
if (left) { |
||||
fn += "turn_left"; |
||||
} else if (right) { |
||||
fn += "turn_right"; |
||||
} else if (straight) { |
||||
fn += "turn_straight"; |
||||
} |
||||
|
||||
if (!active) { |
||||
fn += "_inactive"; |
||||
} |
||||
|
||||
QLabel *label = (i < lane_labels.size()) ? lane_labels[i] : lane_labels.emplace_back(new QLabel); |
||||
if (!label->parentWidget()) { |
||||
lane_layout->addWidget(label); |
||||
} |
||||
label->setPixmap(pixmap_cache[fn]); |
||||
label->setVisible(true); |
||||
} |
||||
|
||||
for (int i = lanes.size(); i < lane_labels.size(); ++i) { |
||||
lane_labels[i]->setVisible(false); |
||||
} |
||||
|
||||
setUpdatesEnabled(true); |
||||
setVisible(true); |
||||
} |
@ -0,0 +1,27 @@ |
||||
#pragma once |
||||
|
||||
#include <QHash> |
||||
#include <QHBoxLayout> |
||||
#include <QLabel> |
||||
|
||||
#include "cereal/gen/cpp/log.capnp.h" |
||||
|
||||
class MapInstructions : public QWidget { |
||||
Q_OBJECT |
||||
|
||||
private: |
||||
QLabel *distance; |
||||
QLabel *primary; |
||||
QLabel *secondary; |
||||
QLabel *icon_01; |
||||
QHBoxLayout *lane_layout; |
||||
bool is_rhd = false; |
||||
std::vector<QLabel *> lane_labels; |
||||
QHash<QString, QPixmap> pixmap_cache; |
||||
|
||||
public: |
||||
MapInstructions(QWidget * parent=nullptr); |
||||
void buildPixmapCache(); |
||||
QString getDistance(float d); |
||||
void updateInstructions(cereal::NavInstruction::Reader instruction); |
||||
}; |
Loading…
Reference in new issue