1027 lines
42 KiB
C++
1027 lines
42 KiB
C++
#include <QMovie>
|
||
|
||
#include "frogpilot/ui/qt/onroad/frogpilot_annotated_camera.h"
|
||
|
||
FrogPilotAnnotatedCameraWidget::FrogPilotAnnotatedCameraWidget(QWidget *parent) : QWidget(parent) {
|
||
animationTimer = new QTimer(this);
|
||
|
||
brakePedalImg = loadPixmap("../../frogpilot/assets/other_images/brake_pedal.png", {btn_size, btn_size});
|
||
curveSpeedIcon = loadPixmap("../../frogpilot/assets/other_images/curve_speed.png", {btn_size, btn_size});
|
||
dashboardIcon = loadPixmap("../../frogpilot/assets/other_images/dashboard_icon.png", {btn_size / 2, btn_size / 2});
|
||
gasPedalImg = loadPixmap("../../frogpilot/assets/other_images/gas_pedal.png", {btn_size, btn_size});
|
||
mapDataIcon = loadPixmap("../../frogpilot/assets/other_images/offline_maps_icon.png", {btn_size / 2, btn_size / 2});
|
||
navigationIcon = loadPixmap("../../frogpilot/assets/other_images/navigation_icon.png", {btn_size / 2, btn_size / 2});
|
||
nextMapsIcon = loadPixmap("../../frogpilot/assets/other_images/next_maps_icon.png", {btn_size / 2, btn_size / 2});
|
||
pausedIcon = loadPixmap("../../frogpilot/assets/other_images/paused_icon.png", {btn_size / 2, btn_size / 2});
|
||
speedIcon = loadPixmap("../../frogpilot/assets/other_images/speed_icon.png", {btn_size / 2, btn_size / 2});
|
||
stopSignImg = loadPixmap("../../frogpilot/assets/other_images/stop_sign.png", {btn_size, btn_size});
|
||
turnIcon = loadPixmap("../../frogpilot/assets/other_images/turn_icon.png", {btn_size / 2, btn_size / 2});
|
||
|
||
loadGif("../../frogpilot/assets/other_images/curve_icon.gif", cemCurveIcon, QSize(btn_size / 2, btn_size / 2), this);
|
||
loadGif("../../frogpilot/assets/other_images/lead_icon.gif", cemLeadIcon, QSize(btn_size / 2, btn_size / 2), this);
|
||
loadGif("../../frogpilot/assets/other_images/speed_icon.gif", cemSpeedIcon, QSize(btn_size / 2, btn_size / 2), this);
|
||
loadGif("../../frogpilot/assets/other_images/light_icon.gif", cemStopIcon, QSize(btn_size / 2, btn_size / 2), this);
|
||
loadGif("../../frogpilot/assets/other_images/turn_icon.gif", cemTurnIcon, QSize(btn_size / 2, btn_size / 2), this);
|
||
loadGif("../../frogpilot/assets/other_images/chill_mode_icon.gif", chillModeIcon, QSize(btn_size / 2, btn_size / 2), this);
|
||
loadGif("../../frogpilot/assets/other_images/experimental_mode_icon.gif", experimentalModeIcon, QSize(btn_size / 2, btn_size / 2), this);
|
||
loadGif("../../frogpilot/assets/other_images/weather_clear_day.gif", weatherClearDay, QSize(btn_size / 2, btn_size / 2), this);
|
||
loadGif("../../frogpilot/assets/other_images/weather_clear_night.gif", weatherClearNight, QSize(btn_size / 2, btn_size / 2), this);
|
||
loadGif("../../frogpilot/assets/other_images/weather_rain.gif", weatherRain, QSize(btn_size / 2, btn_size / 2), this);
|
||
loadGif("../../frogpilot/assets/other_images/weather_snow.gif", weatherSnow, QSize(btn_size / 2, btn_size / 2), this);
|
||
|
||
QObject::connect(animationTimer, &QTimer::timeout, [this] {
|
||
animationFrameIndex = (animationFrameIndex + 1) % totalFrames;
|
||
});
|
||
QObject::connect(frogpilotUIState(), &FrogPilotUIState::themeUpdated, this, &FrogPilotAnnotatedCameraWidget::updateSignals);
|
||
QObject::connect(uiState(), &UIState::offroadTransition, [this] {
|
||
standstillTimer.invalidate();
|
||
|
||
QJsonObject stats = QJsonDocument::fromJson(QString::fromStdString(params.get("FrogPilotStats")).toUtf8()).object();
|
||
stats["FrogHops"] = stats.value("FrogHops").toInt(0) + frogHopCount;
|
||
params.putNonBlocking("FrogPilotStats", QJsonDocument(stats).toJson(QJsonDocument::Compact).toStdString());
|
||
|
||
frogHopCount = 0;
|
||
});
|
||
}
|
||
|
||
void FrogPilotAnnotatedCameraWidget::showEvent(QShowEvent *event) {
|
||
FrogPilotUIState &fs = *frogpilotUIState();
|
||
QJsonObject &frogpilot_toggles = fs.frogpilot_toggles;
|
||
UIState &s = *uiState();
|
||
UIScene &scene = s.scene;
|
||
|
||
if (scene.is_metric || frogpilot_toggles.value("use_si_metrics").toBool()) {
|
||
accelerationUnit = tr(" m/s²");
|
||
leadDistanceUnit = tr(" meters");
|
||
leadSpeedUnit = frogpilot_toggles.value("use_si_metrics").toBool() ? tr(" m/s") : tr(" km/h");
|
||
|
||
distanceConversion = 1.0f;
|
||
speedConversion = scene.is_metric ? MS_TO_KPH : MS_TO_MPH;
|
||
speedConversionMetrics = frogpilot_toggles.value("use_si_metrics").toBool() ? 1.0f : MS_TO_KPH;
|
||
} else {
|
||
accelerationUnit = tr(" ft/s²");
|
||
leadDistanceUnit = tr(" feet");
|
||
leadSpeedUnit = tr(" mph");
|
||
|
||
distanceConversion = METER_TO_FOOT;
|
||
speedConversion = MS_TO_MPH;
|
||
speedConversionMetrics = MS_TO_MPH;
|
||
}
|
||
|
||
updateSignals();
|
||
}
|
||
|
||
void FrogPilotAnnotatedCameraWidget::updateSignals() {
|
||
QVector<QPixmap>().swap(blindspotImages);
|
||
QVector<QPixmap>().swap(signalImages);
|
||
|
||
bool isGif = false;
|
||
|
||
QFileInfoList files = QDir("../../frogpilot/assets/active_theme/signals/").entryInfoList(QDir::Files | QDir::NoDotAndDotDot, QDir::Name);
|
||
for (const QFileInfo &fileInfo : files) {
|
||
QString fileName = fileInfo.fileName();
|
||
QString filePath = fileInfo.absoluteFilePath();
|
||
|
||
if (fileName.endsWith(".gif", Qt::CaseInsensitive)) {
|
||
isGif = true;
|
||
|
||
QMovie movie(filePath);
|
||
movie.setCacheMode(QMovie::CacheNone);
|
||
movie.start();
|
||
|
||
int frameCount = movie.frameCount();
|
||
signalImages.reserve(frameCount);
|
||
|
||
for (int i = 0; i < frameCount; ++i) {
|
||
movie.jumpToFrame(i);
|
||
|
||
QImage image = movie.currentPixmap().toImage().convertToFormat(QImage::Format_Indexed8);
|
||
QPixmap frame = QPixmap::fromImage(image);
|
||
signalImages.append(frame);
|
||
}
|
||
|
||
movie.stop();
|
||
} else if (fileName.endsWith(".png", Qt::CaseInsensitive)) {
|
||
QVector<QPixmap> &targetList = fileName.contains("blindspot", Qt::CaseInsensitive) ? blindspotImages : signalImages;
|
||
targetList.append(QPixmap::fromImage(QImage(filePath).convertToFormat(QImage::Format_Indexed8)));
|
||
} else {
|
||
QStringList parts = fileName.split('_');
|
||
if (parts.size() == 2) {
|
||
signalStyle = parts[0];
|
||
signalAnimationLength = parts[1].toInt();
|
||
}
|
||
}
|
||
}
|
||
|
||
if (!signalImages.isEmpty()) {
|
||
QPixmap &firstImage = signalImages.front();
|
||
signalHeight = firstImage.height();
|
||
signalWidth = firstImage.width();
|
||
totalFrames = signalImages.size();
|
||
|
||
if (isGif && signalStyle == "traditional") {
|
||
signalMovement = (width() + signalWidth * 2) / totalFrames;
|
||
|
||
signalStyle = "traditional_gif";
|
||
} else {
|
||
signalMovement = 0;
|
||
}
|
||
} else {
|
||
signalAnimationLength = 0;
|
||
signalHeight = 0;
|
||
signalMovement = 0;
|
||
signalWidth = 0;
|
||
totalFrames = 0;
|
||
|
||
signalStyle = "None";
|
||
}
|
||
}
|
||
|
||
void FrogPilotAnnotatedCameraWidget::updateState(const FrogPilotUIState &fs, const QJsonObject &frogpilot_toggles) {
|
||
const FrogPilotUIScene &frogpilot_scene = fs.frogpilot_scene;
|
||
const SubMaster &fpsm = *(fs.sm);
|
||
|
||
const cereal::FrogPilotPlan::Reader &frogpilotPlan = fpsm["frogpilotPlan"].getFrogpilotPlan();
|
||
|
||
float speedLimitOffset = frogpilotPlan.getSlcSpeedLimitOffset() * speedConversion;
|
||
|
||
cscSpeedStr = QString::number(std::nearbyint(fmin(speed, frogpilotPlan.getCscSpeed() * speedConversion))) + speedUnit;
|
||
speedLimitOffsetStr = (speedLimitOffset != 0) ? QString::number(speedLimitOffset, 'f', 0).prepend((speedLimitOffset > 0) ? "+" : "-") : "–";
|
||
|
||
if (frogpilot_scene.standstill && frogpilot_toggles.value("stopped_timer").toBool()) {
|
||
if (!standstillTimer.isValid()) {
|
||
standstillTimer.start();
|
||
} else {
|
||
standstillDuration = frogpilot_scene.map_open || frogpilot_scene.started_timer / UI_FREQ < 60 ? 0 : standstillTimer.elapsed() / 1000;
|
||
}
|
||
} else {
|
||
standstillDuration = 0;
|
||
|
||
standstillTimer.invalidate();
|
||
}
|
||
|
||
static int lastFrameIndex;
|
||
if (lastFrameIndex > animationFrameIndex && frogpilot_toggles.value("signal_icons").toString() == "frog") {
|
||
frogHopCount++;
|
||
}
|
||
lastFrameIndex = animationFrameIndex;
|
||
|
||
update();
|
||
}
|
||
|
||
void FrogPilotAnnotatedCameraWidget::paintFrogPilotWidgets(QPainter &p, UIState &s, FrogPilotUIState &fs, SubMaster &sm, SubMaster &fpsm, QJsonObject &frogpilot_toggles) {
|
||
FrogPilotUIScene &frogpilot_scene = fs.frogpilot_scene;
|
||
UIScene &scene = s.scene;
|
||
|
||
const cereal::CarState::Reader &carState = fpsm["carState"].getCarState();
|
||
const cereal::FrogPilotCarState::Reader &frogpilotCarState = fpsm["frogpilotCarState"].getFrogpilotCarState();
|
||
const cereal::FrogPilotNavigation::Reader &frogpilotNavigation = fpsm["frogpilotNavigation"].getFrogpilotNavigation();
|
||
const cereal::FrogPilotPlan::Reader &frogpilotPlan = fpsm["frogpilotPlan"].getFrogpilotPlan();
|
||
const cereal::ModelDataV2::Reader &model = sm["modelV2"].getModelV2();
|
||
|
||
if (!hideBottomIcons && frogpilot_toggles.value("cem_status").toBool()) {
|
||
paintCEMStatus(p, frogpilotPlan, frogpilot_scene, sm);
|
||
} else {
|
||
cemStatusPosition.setX(0);
|
||
cemStatusPosition.setY(0);
|
||
}
|
||
|
||
if (!frogpilot_scene.map_open && !hideBottomIcons && frogpilot_toggles.value("compass").toBool()) {
|
||
paintCompass(p, frogpilot_toggles);
|
||
} else {
|
||
compassPosition.setX(0);
|
||
compassPosition.setY(0);
|
||
}
|
||
|
||
if (!frogpilot_scene.map_open && !frogpilotPlan.getSpeedLimitChanged() && !(signalStyle == "static" && carState.getLeftBlinker()) && frogpilot_toggles.value("csc_status").toBool()) {
|
||
if (frogpilotPlan.getCscTraining()) {
|
||
paintSmartControllerTraining(p, frogpilotPlan);
|
||
} else {
|
||
glowTimer.invalidate();
|
||
|
||
if (isCruiseSet && frogpilotPlan.getCscControllingSpeed()) {
|
||
paintCurveSpeedControl(p, frogpilotPlan);
|
||
}
|
||
}
|
||
} else {
|
||
glowTimer.invalidate();
|
||
}
|
||
|
||
if (!frogpilot_scene.map_open && frogpilotCarState.getPauseLateral() && !hideBottomIcons) {
|
||
paintLateralPaused(p, frogpilot_scene);
|
||
} else {
|
||
lateralPausedPosition.setX(0);
|
||
lateralPausedPosition.setY(0);
|
||
}
|
||
|
||
if (!frogpilot_scene.map_open && (frogpilotCarState.getForceCoast() || frogpilotCarState.getPauseLongitudinal()) && !hideBottomIcons) {
|
||
paintLongitudinalPaused(p, frogpilot_scene);
|
||
}
|
||
|
||
if (!bigMapOpen && frogpilot_toggles.value("pedals_on_ui").toBool()) {
|
||
paintPedalIcons(p, carState, frogpilotCarState, frogpilot_scene, frogpilot_toggles);
|
||
}
|
||
|
||
if (frogpilotPlan.getSpeedLimitChanged()) {
|
||
paintPendingSpeedLimit(p, frogpilotPlan);
|
||
} else {
|
||
pendingLimitTimer.invalidate();
|
||
}
|
||
|
||
if (frogpilot_toggles.value("radar_tracks").toBool()) {
|
||
paintRadarTracks(p, model, s, frogpilot_scene, sm, fpsm);
|
||
}
|
||
|
||
if (frogpilot_toggles.value("road_name_ui").toBool()) {
|
||
paintRoadName(p);
|
||
}
|
||
|
||
if (!bigMapOpen && (mutcdSpeedLimit || viennaSpeedLimit) && frogpilot_toggles.value("speed_limit_sources").toBool()) {
|
||
paintSpeedLimitSources(p, frogpilotCarState, frogpilotNavigation, frogpilotPlan);
|
||
}
|
||
|
||
if (!frogpilot_scene.map_open && standstillDuration != 0 && frogpilot_scene.started_timer / UI_FREQ >= 60) {
|
||
paintStandstillTimer(p);
|
||
}
|
||
|
||
if (scene.track_vertices.length() >= 1 && frogpilotPlan.getRedLight() && frogpilot_toggles.value("show_stopping_point").toBool()) {
|
||
paintStoppingPoint(p, scene, frogpilot_scene, frogpilot_toggles);
|
||
}
|
||
|
||
if (!bigMapOpen && (carState.getLeftBlinker() || carState.getRightBlinker()) && signalStyle != "None") {
|
||
if (!animationTimer->isActive()) {
|
||
animationTimer->start(signalAnimationLength);
|
||
}
|
||
paintTurnSignals(p, carState);
|
||
} else if (animationTimer->isActive()) {
|
||
animationTimer->stop();
|
||
}
|
||
|
||
if (!frogpilot_scene.map_open && !hideBottomIcons) {
|
||
paintWeather(p, frogpilotPlan, frogpilot_scene);
|
||
}
|
||
}
|
||
|
||
void FrogPilotAnnotatedCameraWidget::paintAdjacentPaths(QPainter &p, const cereal::CarState::Reader &carState, const FrogPilotUIScene &frogpilot_scene, const QJsonObject &frogpilot_toggles) {
|
||
std::function<void(bool, float, float, const QPolygonF &)> drawAdjacentPath = [&p, &frogpilot_toggles, this](bool isBlindSpot, float width, float requirement, const QPolygonF &polygon) {
|
||
QLinearGradient gradient(0, height(), 0, 0);
|
||
if (isBlindSpot && frogpilot_toggles.value("blind_spot_path").toBool()) {
|
||
gradient.setColorAt(0.0f, QColor::fromHslF(0 / 360.0f, 0.75f, 0.5f, 0.6f));
|
||
gradient.setColorAt(0.5f, QColor::fromHslF(0 / 360.0f, 0.75f, 0.5f, 0.4f));
|
||
gradient.setColorAt(1.0f, QColor::fromHslF(0 / 360.0f, 0.75f, 0.5f, 0.2f));
|
||
} else {
|
||
float ratio = std::clamp(width / requirement, 0.0f, 1.0f);
|
||
float hue = ratio * (120.0f / 360.0f);
|
||
|
||
gradient.setColorAt(0.0f, QColor::fromHslF(hue, 0.75f, 0.5f, 0.6f));
|
||
gradient.setColorAt(0.5f, QColor::fromHslF(hue, 0.75f, 0.5f, 0.4f));
|
||
gradient.setColorAt(1.0f, QColor::fromHslF(hue, 0.75f, 0.5f, 0.2f));
|
||
}
|
||
|
||
p.setBrush(gradient);
|
||
p.drawPolygon(polygon);
|
||
};
|
||
|
||
std::function<void(bool, float, const QPolygonF &)> drawAdjacentPathMetric = [&p, &frogpilot_toggles, this](bool isBlindSpot, float width, const QPolygonF &polygon) {
|
||
QString text = isBlindSpot && frogpilot_toggles.value("blind_spot_path").toBool() ? tr("Vehicle in blind spot") : QString::number(width * distanceConversion, 'f', 2) + leadDistanceUnit;
|
||
|
||
p.setFont(InterFont(40, QFont::DemiBold));
|
||
p.setPen(QPen(whiteColor()));
|
||
p.drawText(polygon.boundingRect(), Qt::AlignCenter, text);
|
||
};
|
||
|
||
if (frogpilot_scene.lane_width_left >= frogpilot_toggles.value("lane_detection_width").toDouble()) {
|
||
p.save();
|
||
|
||
drawAdjacentPath(carState.getLeftBlindspot(), frogpilot_scene.lane_width_left, frogpilot_toggles.value("lane_detection_width").toDouble(), frogpilot_scene.track_adjacent_vertices[0]);
|
||
|
||
if (frogpilot_toggles.value("adjacent_path_metrics").toBool()) {
|
||
drawAdjacentPathMetric(carState.getLeftBlindspot(), frogpilot_scene.lane_width_left, frogpilot_scene.track_adjacent_vertices[0]);
|
||
}
|
||
|
||
p.restore();
|
||
}
|
||
|
||
if (frogpilot_scene.lane_width_right >= frogpilot_toggles.value("lane_detection_width").toDouble()) {
|
||
p.save();
|
||
|
||
drawAdjacentPath(carState.getRightBlindspot(), frogpilot_scene.lane_width_right, frogpilot_toggles.value("lane_detection_width").toDouble(), frogpilot_scene.track_adjacent_vertices[1]);
|
||
|
||
if (frogpilot_toggles.value("adjacent_path_metrics").toBool()) {
|
||
drawAdjacentPathMetric(carState.getRightBlindspot(), frogpilot_scene.lane_width_right, frogpilot_scene.track_adjacent_vertices[1]);
|
||
}
|
||
|
||
p.restore();
|
||
}
|
||
}
|
||
|
||
void FrogPilotAnnotatedCameraWidget::paintBlindSpotPath(QPainter &p, const cereal::CarState::Reader &carState, const FrogPilotUIScene &frogpilot_scene) {
|
||
p.save();
|
||
|
||
QLinearGradient bs(0, height(), 0, 0);
|
||
bs.setColorAt(0.0f, QColor::fromHslF(0 / 360.0f, 0.75f, 0.5f, 0.6f));
|
||
bs.setColorAt(0.5f, QColor::fromHslF(0 / 360.0f, 0.75f, 0.5f, 0.4f));
|
||
bs.setColorAt(1.0f, QColor::fromHslF(0 / 360.0f, 0.75f, 0.5f, 0.2f));
|
||
|
||
p.setBrush(bs);
|
||
if (frogpilot_scene.lane_width_left != 0 && carState.getLeftBlindspot()) {
|
||
p.drawPolygon(frogpilot_scene.track_adjacent_vertices[0]);
|
||
}
|
||
if (frogpilot_scene.lane_width_right != 0 && carState.getRightBlindspot()) {
|
||
p.drawPolygon(frogpilot_scene.track_adjacent_vertices[1]);
|
||
}
|
||
|
||
p.restore();
|
||
}
|
||
|
||
void FrogPilotAnnotatedCameraWidget::paintCEMStatus(QPainter &p, const cereal::FrogPilotPlan::Reader &frogpilotPlan, FrogPilotUIScene &frogpilot_scene, SubMaster &sm) {
|
||
if (dmIconPosition == QPoint(0, 0)) {
|
||
return;
|
||
}
|
||
|
||
p.save();
|
||
|
||
cemStatusPosition.rx() = dmIconPosition.x();
|
||
cemStatusPosition.ry() = dmIconPosition.y() - widget_size / 2;
|
||
cemStatusPosition.rx() += (rightHandDM ? -img_size - widget_size : widget_size) / (frogpilot_scene.map_open ? 1.25 : 1);
|
||
|
||
QRect cemWidget(cemStatusPosition, QSize(widget_size, widget_size));
|
||
|
||
p.setBrush(blackColor(166));
|
||
if (frogpilot_scene.conditional_status == 1) {
|
||
p.setPen(QPen(QColor(bg_colors[STATUS_CONDITIONAL_OVERRIDDEN]), 10));
|
||
} else if (frogpilot_scene.enabled && sm["controlsState"].getControlsState().getExperimentalMode()) {
|
||
p.setPen(QPen(QColor(bg_colors[STATUS_EXPERIMENTAL_MODE_ENABLED]), 10));
|
||
} else {
|
||
p.setPen(QPen(blackColor(), 10));
|
||
}
|
||
p.drawRoundedRect(cemWidget, 24, 24);
|
||
|
||
QSharedPointer<QMovie> icon = chillModeIcon;
|
||
if (frogpilot_scene.enabled && sm["controlsState"].getControlsState().getExperimentalMode()) {
|
||
if (frogpilot_scene.conditional_status == 1) {
|
||
icon = chillModeIcon;
|
||
} else if (frogpilot_scene.conditional_status == 2) {
|
||
icon = experimentalModeIcon;
|
||
} else if (frogpilot_scene.conditional_status == 3 || frogpilot_scene.conditional_status == 4) {
|
||
icon = cemSpeedIcon;
|
||
} else if (frogpilot_scene.conditional_status == 5 || frogpilot_scene.conditional_status == 7) {
|
||
icon = cemTurnIcon;
|
||
} else if (frogpilot_scene.conditional_status == 6 || frogpilot_scene.conditional_status == 11 || frogpilot_scene.conditional_status == 12) {
|
||
icon = cemStopIcon;
|
||
} else if (frogpilot_scene.conditional_status == 8) {
|
||
icon = cemCurveIcon;
|
||
} else if (frogpilot_scene.conditional_status == 9 || frogpilot_scene.conditional_status == 10) {
|
||
icon = cemLeadIcon;
|
||
} else {
|
||
icon = experimentalModeIcon;
|
||
}
|
||
}
|
||
p.drawPixmap(cemWidget, icon->currentPixmap());
|
||
|
||
p.restore();
|
||
}
|
||
|
||
void FrogPilotAnnotatedCameraWidget::paintCompass(QPainter &p, QJsonObject &frogpilot_toggles) {
|
||
p.save();
|
||
|
||
compassPosition.rx() = rightHandDM ? UI_BORDER_SIZE + widget_size / 2 : width() - UI_BORDER_SIZE - btn_size;
|
||
if (mapButtonVisible) {
|
||
if (rightHandDM) {
|
||
compassPosition.rx() += btn_size - UI_BORDER_SIZE;
|
||
} else {
|
||
compassPosition.rx() -= btn_size + UI_BORDER_SIZE;
|
||
}
|
||
}
|
||
compassPosition.ry() = dmIconPosition.y() - widget_size / 2;
|
||
|
||
QRect compassWidget(compassPosition, QSize(widget_size, widget_size));
|
||
|
||
p.setBrush(blackColor(166));
|
||
p.setPen(QPen(blackColor(), 10));
|
||
p.drawRoundedRect(compassWidget, 24, 24);
|
||
|
||
QPainterPath clipPath;
|
||
clipPath.addRoundedRect(compassWidget.adjusted(5, 5, -5, -5), 24, 24);
|
||
p.setClipPath(clipPath);
|
||
|
||
QFont font = InterFont(65, QFont::Bold);
|
||
QFontMetrics fm(font);
|
||
p.setFont(font);
|
||
p.setPen(QPen(whiteColor()));
|
||
|
||
const double pixelsPerDegree = 2.5;
|
||
|
||
const int baseRibbonWidth = qRound(360 * pixelsPerDegree);
|
||
|
||
static QPixmap ribbonPixmap;
|
||
if (!ribbonPixmap) {
|
||
int ribbonHeight = compassWidget.height();
|
||
int ribbonWidth = baseRibbonWidth * 2;
|
||
|
||
ribbonPixmap = QPixmap(ribbonWidth, ribbonHeight);
|
||
ribbonPixmap.fill(Qt::transparent);
|
||
|
||
QPainter ribbonPainter(&ribbonPixmap);
|
||
ribbonPainter.setRenderHints(QPainter::Antialiasing | QPainter::TextAntialiasing);
|
||
ribbonPainter.setFont(font);
|
||
|
||
QMap<int, QString> directionLabels = {{0, "N"}, {45, "NE"}, {90, "E"}, {135, "SE"}, {180, "S"}, {225, "SW"}, {270, "W"}, {315, "NW"}, {360, "N"}};
|
||
|
||
for (int i = 0; i < 2; ++i) {
|
||
for (int degree = 0; degree < 360; ++degree) {
|
||
int x = qRound((i * 360 + degree) * pixelsPerDegree);
|
||
|
||
if (directionLabels.contains(degree)) {
|
||
const QString &label = directionLabels[degree];
|
||
int textX = x - fm.horizontalAdvance(label) / 2;
|
||
|
||
ribbonPainter.setPen(whiteColor());
|
||
ribbonPainter.drawText(textX, fm.ascent(), label);
|
||
}
|
||
|
||
int notchHeight = (degree % 45 == 0) ? 35 : (degree % 15 == 0) ? 25 : 15;
|
||
int notchWidth = (degree % 45 == 0) ? 5 : (degree % 15 == 0) ? 4 : 3;
|
||
|
||
ribbonPainter.setPen(QPen(whiteColor(), notchWidth));
|
||
ribbonPainter.drawLine(x, ribbonHeight - notchHeight - 5, x, ribbonHeight);
|
||
}
|
||
}
|
||
}
|
||
|
||
double rawBearing = QJsonDocument::fromJson(QString::fromStdString(params_memory.get("LastGPSPosition")).toUtf8()).object().value("bearing").toDouble(0.0);
|
||
|
||
int bearing = qRound(fmod(rawBearing + 360.0, 360.0));
|
||
int offset = qRound(bearing * pixelsPerDegree) % baseRibbonWidth;
|
||
int drawX = compassWidget.center().x() - offset;
|
||
|
||
p.drawPixmap(drawX - baseRibbonWidth, compassWidget.top() + 5, ribbonPixmap);
|
||
p.drawPixmap(drawX, compassWidget.top() + 5, ribbonPixmap);
|
||
|
||
int triangleSize = 40;
|
||
int triangleX = compassWidget.center().x();
|
||
int triangleY = compassWidget.bottom() - triangleSize;
|
||
|
||
QPolygon triangle(QVector<QPoint>{
|
||
QPoint(triangleX, triangleY - triangleSize),
|
||
QPoint(triangleX - triangleSize / 1.5, triangleY),
|
||
QPoint(triangleX + triangleSize / 1.5, triangleY)
|
||
});
|
||
|
||
p.setBrush(whiteColor());
|
||
p.setPen(Qt::NoPen);
|
||
p.drawPolygon(triangle);
|
||
|
||
p.restore();
|
||
}
|
||
|
||
void FrogPilotAnnotatedCameraWidget::paintCurveSpeedControl(QPainter &p, const cereal::FrogPilotPlan::Reader &frogpilotPlan) {
|
||
p.save();
|
||
|
||
QRect curveSpeedRect(QPoint(setSpeedRect.right() + UI_BORDER_SIZE, setSpeedRect.top()), QSize(defaultSize.width() * 1.25, defaultSize.width() * 1.25));
|
||
|
||
QPixmap curveSpeedImage = frogpilotPlan.getRoadCurvature() < 0 ? curveSpeedIcon : curveSpeedIcon.transformed(QTransform().scale(-1, 1));
|
||
QSize curveSpeedSize = curveSpeedImage.size();
|
||
QPoint curveSpeedPoint(curveSpeedRect.x() + (curveSpeedRect.width() - curveSpeedSize.width()) / 2, curveSpeedRect.y() + (curveSpeedRect.height() - curveSpeedSize.height()) / 2);
|
||
|
||
p.setOpacity(1.0);
|
||
|
||
QRect cscRect(curveSpeedRect.topLeft() + QPoint(0, curveSpeedRect.height() + 10), QSize(curveSpeedRect.width(), 100));
|
||
|
||
p.setBrush(blueColor(166));
|
||
p.setFont(InterFont(45, QFont::Bold));
|
||
p.setPen(QPen(blueColor(), 10));
|
||
|
||
p.drawRoundedRect(cscRect, 24, 24);
|
||
p.setPen(QPen(whiteColor(), 6));
|
||
p.drawText(cscRect.adjusted(20, 0, 0, 0), Qt::AlignVCenter | Qt::AlignLeft, cscSpeedStr);
|
||
|
||
p.drawPixmap(curveSpeedPoint, curveSpeedImage);
|
||
|
||
p.restore();
|
||
}
|
||
|
||
void FrogPilotAnnotatedCameraWidget::paintLateralPaused(QPainter &p, FrogPilotUIScene &frogpilot_scene) {
|
||
if (dmIconPosition == QPoint(0, 0)) {
|
||
return;
|
||
}
|
||
|
||
p.save();
|
||
|
||
if (cemStatusPosition != QPoint(0, 0)) {
|
||
lateralPausedPosition = cemStatusPosition;
|
||
} else {
|
||
lateralPausedPosition.rx() = dmIconPosition.x();
|
||
lateralPausedPosition.ry() = dmIconPosition.y() - widget_size / 2;
|
||
}
|
||
lateralPausedPosition.rx() += (rightHandDM ? -UI_BORDER_SIZE - widget_size - UI_BORDER_SIZE : UI_BORDER_SIZE + widget_size + UI_BORDER_SIZE) / (frogpilot_scene.map_open ? 1.25 : 1);
|
||
|
||
QRect lateralWidget(lateralPausedPosition, QSize(widget_size, widget_size));
|
||
|
||
p.setBrush(blackColor(166));
|
||
p.setPen(QPen(QColor(bg_colors[STATUS_TRAFFIC_MODE_ENABLED]), 10));
|
||
p.drawRoundedRect(lateralWidget, 24, 24);
|
||
|
||
p.setOpacity(0.5);
|
||
p.drawPixmap(lateralWidget, turnIcon);
|
||
p.setOpacity(0.75);
|
||
p.drawPixmap(lateralWidget, pausedIcon);
|
||
|
||
p.restore();
|
||
}
|
||
|
||
void FrogPilotAnnotatedCameraWidget::paintLeadMetrics(QPainter &p, bool adjacent, QPointF *chevron, const cereal::FrogPilotPlan::Reader &frogpilotPlan, const cereal::RadarState::LeadData::Reader &lead_data) {
|
||
float leadDistance = lead_data.getDRel() + (adjacent ? fabs(lead_data.getYRel()) : 0);
|
||
float leadSpeed = std::max(lead_data.getVLead(), 0.0f);
|
||
|
||
p.setFont(InterFont(40, QFont::Bold));
|
||
p.setPen(QPen(whiteColor()));
|
||
|
||
QString text;
|
||
if (adjacent) {
|
||
text = QString("%1 %2 | %3 %4")
|
||
.arg(qRound(leadDistance * distanceConversion))
|
||
.arg(leadDistanceUnit)
|
||
.arg(qRound(leadSpeed * speedConversionMetrics))
|
||
.arg(leadSpeedUnit);
|
||
} else {
|
||
text = QString("%1 %2 (%3) | %4 %5 | %6 %7")
|
||
.arg(qRound(leadDistance * distanceConversion))
|
||
.arg(leadDistanceUnit)
|
||
.arg(QString(tr("Desired: %1")).arg(frogpilotPlan.getDesiredFollowDistance() * distanceConversion))
|
||
.arg(qRound(leadSpeed * speedConversionMetrics))
|
||
.arg(leadSpeedUnit)
|
||
.arg(QString::number(leadDistance / std::max(speed / speedConversion, 1.0f), 'f', 2))
|
||
.arg(tr("s"));
|
||
}
|
||
|
||
QFontMetrics metrics(p.font());
|
||
int textHeight = metrics.height();
|
||
int textWidth = metrics.horizontalAdvance(text);
|
||
|
||
int textX = ((chevron[2].x() + chevron[0].x()) / 2) - textWidth / 2;
|
||
int textY = chevron[0].y() + textHeight + 5;
|
||
|
||
if (!adjacent) {
|
||
int xMargin = textWidth * 0.25;
|
||
int yMargin = textHeight * 0.25;
|
||
|
||
leadTextRect = QRect(textX, textY - textHeight, textWidth, textHeight).adjusted(-xMargin, -yMargin, xMargin, yMargin);
|
||
p.drawText(textX, textY, text);
|
||
} else {
|
||
QRect adjacentTextRect(textX, textY - textHeight, textWidth, textHeight);
|
||
if (!adjacentTextRect.intersects(leadTextRect)) {
|
||
p.drawText(textX, textY, text);
|
||
}
|
||
}
|
||
}
|
||
|
||
void FrogPilotAnnotatedCameraWidget::paintLongitudinalPaused(QPainter &p, FrogPilotUIScene &frogpilot_scene) {
|
||
if (dmIconPosition == QPoint(0, 0)) {
|
||
return;
|
||
}
|
||
|
||
p.save();
|
||
|
||
QPoint longitudinalIconPosition;
|
||
if (lateralPausedPosition != QPoint(0, 0)) {
|
||
longitudinalIconPosition = lateralPausedPosition;
|
||
} else if (cemStatusPosition != QPoint(0, 0)) {
|
||
longitudinalIconPosition = cemStatusPosition;
|
||
} else {
|
||
longitudinalIconPosition.rx() = dmIconPosition.x();
|
||
longitudinalIconPosition.ry() = dmIconPosition.y() - widget_size / 2;
|
||
}
|
||
longitudinalIconPosition.rx() += (rightHandDM ? -UI_BORDER_SIZE - widget_size - UI_BORDER_SIZE : UI_BORDER_SIZE + widget_size + UI_BORDER_SIZE) / (frogpilot_scene.map_open ? 1.25 : 1);
|
||
|
||
QRect longitudinalWidget(longitudinalIconPosition, QSize(widget_size, widget_size));
|
||
|
||
p.setBrush(blackColor(166));
|
||
p.setPen(QPen(QColor(bg_colors[STATUS_TRAFFIC_MODE_ENABLED]), 10));
|
||
p.drawRoundedRect(longitudinalWidget, 24, 24);
|
||
|
||
p.setOpacity(0.5);
|
||
p.drawPixmap(longitudinalWidget, speedIcon);
|
||
p.setOpacity(0.75);
|
||
p.drawPixmap(longitudinalWidget, pausedIcon);
|
||
|
||
p.restore();
|
||
}
|
||
|
||
void FrogPilotAnnotatedCameraWidget::paintPathEdges(QPainter &p, const cereal::NavInstruction::Reader &navInstruction, const UIScene &scene, const FrogPilotUIScene &frogpilot_scene, SubMaster &sm) {
|
||
p.save();
|
||
|
||
std::function<void(QLinearGradient&, const QColor&)> setPathEdgeColors = [&](QLinearGradient &gradient, QColor baseColor) {
|
||
baseColor.setAlphaF(1.0f); gradient.setColorAt(0.0f, baseColor);
|
||
baseColor.setAlphaF(0.5f); gradient.setColorAt(0.5f, baseColor);
|
||
baseColor.setAlphaF(0.1f); gradient.setColorAt(1.0f, baseColor);
|
||
};
|
||
|
||
QLinearGradient pe(0, height(), 0, 0);
|
||
if (frogpilot_scene.always_on_lateral_active) {
|
||
setPathEdgeColors(pe, bg_colors[STATUS_ALWAYS_ON_LATERAL_ACTIVE]);
|
||
} else if (frogpilot_scene.conditional_status == 1) {
|
||
setPathEdgeColors(pe, bg_colors[STATUS_CONDITIONAL_OVERRIDDEN]);
|
||
} else if (sm["controlsState"].getControlsState().getExperimentalMode()) {
|
||
setPathEdgeColors(pe, bg_colors[STATUS_EXPERIMENTAL_MODE_ENABLED]);
|
||
} else if (frogpilot_scene.traffic_mode_enabled) {
|
||
setPathEdgeColors(pe, bg_colors[STATUS_TRAFFIC_MODE_ENABLED]);
|
||
} else if (frogpilot_scene.model_length > navInstruction.getManeuverDistance() && navInstruction.getManeuverDistance() >= 1) {
|
||
setPathEdgeColors(pe, bg_colors[STATUS_NAVIGATION_ACTIVE]);
|
||
} else if (!frogpilot_scene.use_stock_colors) {
|
||
setPathEdgeColors(pe, frogpilot_scene.path_edges_color);
|
||
} else {
|
||
pe.setColorAt(0.0f, QColor::fromHslF(148 / 360.0f, 0.94f, 0.51f, 1.0f));
|
||
pe.setColorAt(0.5f, QColor::fromHslF(112 / 360.0f, 1.00f, 0.68f, 0.5f));
|
||
pe.setColorAt(1.0f, QColor::fromHslF(112 / 360.0f, 1.00f, 0.68f, 0.1f));
|
||
}
|
||
|
||
QPainterPath path;
|
||
path.addPolygon(scene.track_vertices);
|
||
path.addPolygon(frogpilot_scene.track_edge_vertices);
|
||
p.setBrush(pe);
|
||
p.drawPath(path);
|
||
|
||
p.restore();
|
||
}
|
||
|
||
void FrogPilotAnnotatedCameraWidget::paintPedalIcons(QPainter &p, const cereal::CarState::Reader &carState, const cereal::FrogPilotCarState::Reader &frogpilotCarState, FrogPilotUIScene &frogpilot_scene, QJsonObject &frogpilot_toggles) {
|
||
p.save();
|
||
|
||
float brakeOpacity = 1.0f;
|
||
float gasOpacity = 1.0f;
|
||
|
||
if (frogpilot_toggles.value("dynamic_pedals_on_ui").toBool()) {
|
||
brakeOpacity = frogpilot_scene.standstill ? 1.0f : carState.getAEgo() < -0.25f ? std::max(0.25f, std::abs(carState.getAEgo())) : 0.25f;
|
||
gasOpacity = std::max(0.25f, carState.getAEgo());
|
||
} else if (frogpilot_toggles.value("static_pedals_on_ui").toBool()) {
|
||
brakeOpacity = frogpilot_scene.standstill || frogpilotCarState.getBrakeLights() || carState.getAEgo() < -0.25f ? 1.0f : 0.25f;
|
||
gasOpacity = carState.getAEgo() > 0.25 ? 1.0f : 0.25f;
|
||
}
|
||
|
||
int startX = experimentalButtonPosition.x();
|
||
int startY = experimentalButtonPosition.y() + btn_size + UI_BORDER_SIZE;
|
||
|
||
p.setOpacity(brakeOpacity);
|
||
p.drawPixmap(startX, startY, brakePedalImg);
|
||
|
||
p.setOpacity(gasOpacity);
|
||
p.drawPixmap(startX + btn_size / 2, startY, gasPedalImg);
|
||
|
||
p.restore();
|
||
}
|
||
|
||
void FrogPilotAnnotatedCameraWidget::paintPendingSpeedLimit(QPainter &p, const cereal::FrogPilotPlan::Reader &frogpilotPlan) {
|
||
p.save();
|
||
|
||
if (!pendingLimitTimer.isValid()) {
|
||
pendingLimitTimer.start();
|
||
}
|
||
|
||
QString newSpeedLimitStr = (frogpilotPlan.getUnconfirmedSlcSpeedLimit() > 1) ? QString::number(std::nearbyint(frogpilotPlan.getUnconfirmedSlcSpeedLimit() * speedConversion)) : "–";
|
||
newSpeedLimitRect = speedLimitRect.translated(speedLimitRect.width() + UI_BORDER_SIZE, 0);
|
||
|
||
if (!viennaSpeedLimit) {
|
||
newSpeedLimitRect.setWidth(newSpeedLimitStr.size() >= 3 ? 200 : 175);
|
||
|
||
p.setBrush(whiteColor());
|
||
p.setPen(Qt::NoPen);
|
||
p.drawRoundedRect(newSpeedLimitRect, 24, 24);
|
||
p.setPen(pendingLimitTimer.elapsed() % 1000 < 500 ? QPen(blackColor(), 6) : QPen(redColor(), 6));
|
||
p.drawRoundedRect(newSpeedLimitRect.adjusted(9, 9, -9, -9), 16, 16);
|
||
|
||
p.setFont(InterFont(28, QFont::DemiBold));
|
||
p.drawText(newSpeedLimitRect.adjusted(0, 22, 0, 0), Qt::AlignTop | Qt::AlignHCenter, tr("PENDING"));
|
||
p.drawText(newSpeedLimitRect.adjusted(0, 51, 0, 0), Qt::AlignTop | Qt::AlignHCenter, tr("LIMIT"));
|
||
p.setFont(InterFont(70, QFont::Bold));
|
||
p.drawText(newSpeedLimitRect.adjusted(0, 85, 0, 0), Qt::AlignTop | Qt::AlignHCenter, newSpeedLimitStr);
|
||
} else {
|
||
p.setBrush(whiteColor());
|
||
p.setPen(Qt::NoPen);
|
||
p.drawEllipse(newSpeedLimitRect);
|
||
p.setPen(QPen(Qt::red, 20));
|
||
p.drawEllipse(newSpeedLimitRect.adjusted(16, 16, -16, -16));
|
||
|
||
p.setPen(pendingLimitTimer.elapsed() % 1000 < 500 ? QPen(blackColor(), 6) : QPen(redColor(), 6));
|
||
p.setFont(InterFont((newSpeedLimitStr.size() >= 3) ? 60 : 70, QFont::Bold));
|
||
p.drawText(newSpeedLimitRect, Qt::AlignCenter, newSpeedLimitStr);
|
||
}
|
||
|
||
p.restore();
|
||
}
|
||
|
||
void FrogPilotAnnotatedCameraWidget::paintRadarTracks(QPainter &p, const cereal::ModelDataV2::Reader &model, UIState &s, FrogPilotUIScene &frogpilot_scene, SubMaster &sm, SubMaster &fpsm) {
|
||
p.save();
|
||
|
||
capnp::List<cereal::LiveTracks>::Reader liveTracks = fpsm["liveTracks"].getLiveTracks();
|
||
update_radar_tracks(liveTracks, model.getPosition(), s, sm);
|
||
|
||
int diameter = 25;
|
||
|
||
QRect viewport = p.viewport();
|
||
|
||
for (std::size_t i = 0; i < frogpilot_scene.live_radar_tracks.size(); ++i) {
|
||
const RadarTrackData &track = frogpilot_scene.live_radar_tracks[i];
|
||
|
||
float x = std::clamp(static_cast<float>(track.calibrated_point.x()), 0.0f, float(viewport.width() - diameter));
|
||
float y = std::clamp(static_cast<float>(track.calibrated_point.y()), 0.0f, float(viewport.height() - diameter));
|
||
|
||
p.setBrush(redColor());
|
||
p.drawEllipse(QPointF(x + diameter / 2.0f, y + diameter / 2.0f), diameter / 2.0f, diameter / 2.0f);
|
||
}
|
||
|
||
p.restore();
|
||
}
|
||
|
||
void FrogPilotAnnotatedCameraWidget::paintRainbowPath(QPainter &p, QLinearGradient &bg, float lin_grad_point, SubMaster &sm) {
|
||
p.save();
|
||
|
||
static float hueOffset = 0.0;
|
||
if (sm["carState"].getCarState().getVEgo() > 0) {
|
||
hueOffset += powf(sm["carState"].getCarState().getVEgo(), 0.5f) / sqrtf(145.0f / MS_TO_KPH);
|
||
}
|
||
|
||
float alpha = util::map_val(lin_grad_point, 0.0f, 1.0f, 0.5f, 0.1f);
|
||
float pathHue = fmodf((lin_grad_point * 360.0f) + hueOffset, 360.0f);
|
||
|
||
bg.setColorAt(lin_grad_point, QColor::fromHslF(pathHue / 360.0f, 1.0f, 0.5f, alpha));
|
||
bg.setSpread(QGradient::RepeatSpread);
|
||
|
||
p.restore();
|
||
}
|
||
|
||
void FrogPilotAnnotatedCameraWidget::paintRoadName(QPainter &p) {
|
||
QString roadName = QString::fromStdString(params_memory.get("RoadName"));
|
||
if (roadName.isEmpty()) {
|
||
return;
|
||
}
|
||
|
||
alertHeight = std::max(50, alertHeight);
|
||
|
||
p.save();
|
||
|
||
QFont font = InterFont(40, QFont::DemiBold);
|
||
|
||
int textWidth = QFontMetrics(font).horizontalAdvance(roadName);
|
||
|
||
QRect roadNameRect((width() - (textWidth + 100)) / 2, rect().bottom() - 55 + 1, textWidth + 100, 50);
|
||
|
||
p.setBrush(blackColor(166));
|
||
p.setOpacity(1.0);
|
||
p.setPen(QPen(blackColor(), 10));
|
||
p.drawRoundedRect(roadNameRect, 24, 24);
|
||
|
||
p.setFont(font);
|
||
p.setPen(QPen(whiteColor(), 6));
|
||
p.drawText(roadNameRect, Qt::AlignCenter, roadName);
|
||
|
||
p.restore();
|
||
}
|
||
|
||
void FrogPilotAnnotatedCameraWidget::paintSmartControllerTraining(QPainter &p, const cereal::FrogPilotPlan::Reader &frogpilotPlan) {
|
||
p.save();
|
||
|
||
if (!glowTimer.isValid()) {
|
||
glowTimer.start();
|
||
}
|
||
|
||
QRect curveSpeedRect(QPoint(setSpeedRect.right() + UI_BORDER_SIZE, setSpeedRect.top()), QSize(defaultSize.width() * 1.25, defaultSize.width() * 1.25));
|
||
QPixmap curveSpeedImage = frogpilotPlan.getRoadCurvature() < 0 ? curveSpeedIcon : curveSpeedIcon.transformed(QTransform().scale(-1, 1));
|
||
|
||
qreal phase = (glowTimer.elapsed() % 2000) / 2000.0 * 2 * M_PI;
|
||
qreal alphaFactor = 0.5 + 0.5 * sin(phase);
|
||
|
||
QColor glowColor = blueColor();
|
||
glowColor.setAlphaF(0.3 + 0.7 * alphaFactor);
|
||
|
||
int glowWidth = 8 + static_cast<int>(2 * alphaFactor);
|
||
|
||
p.setOpacity(1.0);
|
||
|
||
p.setBrush(blackColor(166));
|
||
p.setPen(QPen(glowColor, glowWidth));
|
||
p.drawRoundedRect(curveSpeedRect, 24, 24);
|
||
|
||
QSize curveSpeedSize = curveSpeedImage.size();
|
||
QPoint curveSpeedPoint(curveSpeedRect.x() + (curveSpeedRect.width() - curveSpeedSize.width()) / 2, curveSpeedRect.y() + (curveSpeedRect.height() - curveSpeedSize.height()) / 2);
|
||
p.drawPixmap(curveSpeedPoint, curveSpeedImage);
|
||
|
||
QRect textRect(curveSpeedRect.topLeft() + QPoint(0, curveSpeedRect.height() + 10), QSize(curveSpeedRect.width(), 50));
|
||
p.setBrush(blackColor(166));
|
||
p.setPen(QPen(blackColor(), 10));
|
||
p.drawRoundedRect(textRect, 24, 24);
|
||
|
||
p.setFont(InterFont(35, QFont::Bold));
|
||
p.setPen(QPen(whiteColor(), 6));
|
||
p.drawText(textRect.adjusted(20, 0, 0, 0), Qt::AlignVCenter | Qt::AlignLeft, "Training...");
|
||
|
||
p.restore();
|
||
}
|
||
|
||
void FrogPilotAnnotatedCameraWidget::paintSpeedLimitSources(QPainter &p, const cereal::FrogPilotCarState::Reader &frogpilotCarState, const cereal::FrogPilotNavigation::Reader &frogpilotNavigation, const cereal::FrogPilotPlan::Reader &frogpilotPlan) {
|
||
p.save();
|
||
|
||
std::function<void(QRect&, QPixmap&, const QString&, const double)> drawSource = [&](QRect &rect, QPixmap &icon, QString title, double speedLimitValue) {
|
||
if (QString::fromUtf8(frogpilotPlan.getSlcSpeedLimitSource().cStr()) == "Mapbox" && title == "Navigation") {
|
||
speedLimitValue = frogpilotPlan.getSlcMapboxSpeedLimit() * speedConversion;
|
||
|
||
title = "Mapbox";
|
||
}
|
||
|
||
if (QString::fromUtf8(frogpilotPlan.getSlcSpeedLimitSource().cStr()) == title && speedLimitValue != 0) {
|
||
p.setBrush(redColor(166));
|
||
p.setFont(InterFont(35, QFont::Bold));
|
||
p.setPen(QPen(redColor(), 10));
|
||
} else {
|
||
p.setBrush(blackColor(166));
|
||
p.setFont(InterFont(35, QFont::DemiBold));
|
||
p.setPen(QPen(blackColor(), 10));
|
||
}
|
||
|
||
QRect iconRect(rect.x() + 20, rect.y() + (rect.height() - img_size / 4) / 2, img_size / 4, img_size / 4);
|
||
QPixmap scaledIcon = icon.scaled(iconRect.size(), Qt::KeepAspectRatio, Qt::SmoothTransformation);
|
||
|
||
QString speedText;
|
||
if (speedLimitValue != 0) {
|
||
speedText = QString::number(std::nearbyint(speedLimitValue)) + speedUnit;
|
||
} else {
|
||
speedText = "N/A";
|
||
}
|
||
|
||
QString fullText = tr(title.toUtf8().constData()) + " - " + speedText;
|
||
|
||
p.setOpacity(1.0);
|
||
p.drawRoundedRect(rect, 24, 24);
|
||
p.drawPixmap(iconRect, scaledIcon);
|
||
|
||
p.setPen(QPen(whiteColor(), 6));
|
||
QRect textRect(iconRect.right() + 10, rect.y(), rect.width() - iconRect.width() - 30, rect.height());
|
||
p.drawText(textRect, Qt::AlignVCenter | Qt::AlignLeft, fullText);
|
||
};
|
||
|
||
QRect dashboardRect(speedLimitRect.x() - signMargin, speedLimitRect.y() + speedLimitRect.height() + UI_BORDER_SIZE, 450, 60);
|
||
QRect mapDataRect(dashboardRect.x(), dashboardRect.y() + dashboardRect.height() + UI_BORDER_SIZE / 2, 450, 60);
|
||
QRect navigationRect(mapDataRect.x(), mapDataRect.y() + mapDataRect.height() + UI_BORDER_SIZE / 2, 450, 60);
|
||
QRect nextLimitRect(navigationRect.x(), navigationRect.y() + navigationRect.height() + UI_BORDER_SIZE / 2, 450, 60);
|
||
|
||
drawSource(dashboardRect, dashboardIcon, "Dashboard", frogpilotCarState.getDashboardSpeedLimit() * speedConversion);
|
||
drawSource(mapDataRect, mapDataIcon, "Map Data", frogpilotPlan.getSlcMapSpeedLimit() * speedConversion);
|
||
drawSource(navigationRect, navigationIcon, "Navigation", frogpilotNavigation.getNavigationSpeedLimit() * speedConversion);
|
||
drawSource(nextLimitRect, nextMapsIcon, "Upcoming", frogpilotPlan.getSlcNextSpeedLimit() * speedConversion);
|
||
|
||
p.restore();
|
||
}
|
||
|
||
void FrogPilotAnnotatedCameraWidget::paintStandstillTimer(QPainter &p) {
|
||
p.save();
|
||
|
||
float transition = 0.0f;
|
||
|
||
QColor startColor, endColor;
|
||
if (standstillDuration < 60) {
|
||
startColor = endColor = bg_colors[STATUS_ENGAGED];
|
||
} else if (standstillDuration < 150) {
|
||
startColor = bg_colors[STATUS_ENGAGED];
|
||
endColor = bg_colors[STATUS_CONDITIONAL_OVERRIDDEN];
|
||
|
||
transition = (standstillDuration - 60) / 150.0f;
|
||
} else if (standstillDuration < 300) {
|
||
startColor = bg_colors[STATUS_CONDITIONAL_OVERRIDDEN];
|
||
endColor = bg_colors[STATUS_TRAFFIC_MODE_ENABLED];
|
||
|
||
transition = (standstillDuration - 150) / 150.0f;
|
||
} else {
|
||
startColor = endColor = bg_colors[STATUS_TRAFFIC_MODE_ENABLED];
|
||
|
||
transition = 0.0f;
|
||
}
|
||
|
||
QColor blendedColor(
|
||
startColor.red() + transition * (endColor.red() - startColor.red()),
|
||
startColor.green() + transition * (endColor.green() - startColor.green()),
|
||
startColor.blue() + transition * (endColor.blue() - startColor.blue())
|
||
);
|
||
|
||
int minutes = standstillDuration / 60;
|
||
int seconds = standstillDuration % 60;
|
||
|
||
p.setFont(InterFont(176, QFont::Bold));
|
||
{
|
||
QString minuteStr = (minutes == 1) ? tr("1 minute") : QString(tr("%1 minutes")).arg(minutes);
|
||
QRect textRect = p.fontMetrics().boundingRect(minuteStr);
|
||
textRect.moveCenter({rect().center().x(), 210 - textRect.height() / 2});
|
||
p.setPen(QPen(blendedColor));
|
||
p.drawText(textRect.x(), textRect.bottom(), minuteStr);
|
||
}
|
||
|
||
p.setFont(InterFont(66));
|
||
{
|
||
QString secondStr = (seconds == 1) ? tr("1 second") : QString(tr("%1 seconds")).arg(seconds);
|
||
QRect textRect = p.fontMetrics().boundingRect(secondStr);
|
||
textRect.moveCenter({rect().center().x(), 290 - textRect.height() / 2});
|
||
p.setPen(QPen(whiteColor()));
|
||
p.drawText(textRect.x(), textRect.bottom(), secondStr);
|
||
}
|
||
|
||
p.restore();
|
||
}
|
||
|
||
void FrogPilotAnnotatedCameraWidget::paintStoppingPoint(QPainter &p, UIScene &scene, FrogPilotUIScene &frogpilot_scene, QJsonObject &frogpilot_toggles) {
|
||
p.save();
|
||
|
||
QPointF centerPoint = (scene.track_vertices.first() + scene.track_vertices.last()) / 2.0;
|
||
QPointF adjustedPoint = centerPoint - QPointF(stopSignImg.width() / 2, stopSignImg.height());
|
||
p.drawPixmap(adjustedPoint, stopSignImg);
|
||
|
||
if (frogpilot_toggles.value("show_stopping_point_metrics").toBool()) {
|
||
QFont font = InterFont(35, QFont::DemiBold);
|
||
QString text = QString::number(std::nearbyint(frogpilot_scene.model_length * distanceConversion)) + leadDistanceUnit;
|
||
QPointF textPosition = centerPoint - QPointF(QFontMetrics(font).horizontalAdvance(text) / 2, stopSignImg.height() + 35);
|
||
|
||
p.setFont(font);
|
||
p.setPen(QPen(whiteColor()));
|
||
p.drawText(textPosition, text);
|
||
}
|
||
|
||
p.restore();
|
||
}
|
||
|
||
void FrogPilotAnnotatedCameraWidget::paintTurnSignals(QPainter &p, const cereal::CarState::Reader &carState) {
|
||
p.save();
|
||
|
||
bool leftBlinker = carState.getLeftBlinker();
|
||
bool blindspotActive = leftBlinker ? carState.getLeftBlindspot() : carState.getRightBlindspot();
|
||
|
||
if (signalStyle == "static") {
|
||
int signalXPosition = leftBlinker ? (rect().center().x() * 0.75) - signalWidth : rect().center().x() * 1.25;
|
||
int signalYPosition = signalHeight / 2;
|
||
|
||
if (blindspotActive && !blindspotImages.empty()) {
|
||
p.drawPixmap(signalXPosition, signalYPosition, signalWidth, signalHeight, blindspotImages[0].transformed(QTransform().scale(leftBlinker ? 1 : -1, 1)));
|
||
} else {
|
||
p.drawPixmap(signalXPosition, signalYPosition, signalWidth, signalHeight, signalImages[animationFrameIndex].transformed(QTransform().scale(leftBlinker ? 1 : -1, 1)));
|
||
}
|
||
} else {
|
||
int signalXPosition;
|
||
if (signalStyle == "traditional_gif") {
|
||
signalXPosition = leftBlinker ? width() - (animationFrameIndex * signalMovement) + signalWidth : (animationFrameIndex * signalMovement) - signalWidth;
|
||
} else {
|
||
signalXPosition = leftBlinker ? width() - ((animationFrameIndex + 1) * signalWidth) : animationFrameIndex * signalWidth;
|
||
}
|
||
|
||
int signalYPosition = height() - signalHeight - alertHeight;
|
||
|
||
if (blindspotActive && !blindspotImages.empty()) {
|
||
p.drawPixmap(leftBlinker ? width() - signalWidth : 0, signalYPosition, signalWidth, signalHeight, blindspotImages[0].transformed(QTransform().scale(leftBlinker ? 1 : -1, 1)));
|
||
} else {
|
||
p.drawPixmap(signalXPosition, signalYPosition, signalWidth, signalHeight, signalImages[animationFrameIndex].transformed(QTransform().scale(leftBlinker ? 1 : -1, 1)));
|
||
}
|
||
}
|
||
|
||
p.restore();
|
||
}
|
||
|
||
void FrogPilotAnnotatedCameraWidget::paintWeather(QPainter &p, const cereal::FrogPilotPlan::Reader &frogpilotPlan, FrogPilotUIScene &frogpilot_scene) {
|
||
int weatherId = frogpilotPlan.getWeatherId();
|
||
if (weatherId == 0) {
|
||
return;
|
||
}
|
||
|
||
p.save();
|
||
|
||
QPoint weatherIconPosition;
|
||
if (compassPosition != QPoint(0, 0)) {
|
||
weatherIconPosition = compassPosition;
|
||
weatherIconPosition.rx() += (rightHandDM ? UI_BORDER_SIZE + widget_size + UI_BORDER_SIZE : -UI_BORDER_SIZE - widget_size - UI_BORDER_SIZE) / (frogpilot_scene.map_open ? 1.25 : 1);
|
||
} else {
|
||
weatherIconPosition.rx() = rightHandDM ? UI_BORDER_SIZE + widget_size / 2 : width() - UI_BORDER_SIZE - btn_size;
|
||
if (mapButtonVisible) {
|
||
if (rightHandDM) {
|
||
weatherIconPosition.rx() += btn_size - UI_BORDER_SIZE;
|
||
} else {
|
||
weatherIconPosition.rx() -= btn_size + UI_BORDER_SIZE;
|
||
}
|
||
}
|
||
weatherIconPosition.ry() = dmIconPosition.y() - widget_size / 2;
|
||
}
|
||
|
||
QRect weatherRect(weatherIconPosition, QSize(widget_size, widget_size));
|
||
|
||
p.setBrush(blackColor(166));
|
||
p.setPen(QPen(blackColor(), 10));
|
||
p.drawRoundedRect(weatherRect, 24, 24);
|
||
|
||
QSharedPointer<QMovie> icon = weatherClearDay;
|
||
if ((weatherId >= 200 && weatherId <= 232) || (weatherId >= 300 && weatherId <= 321) || (weatherId >= 500 && weatherId <= 531)) {
|
||
icon = weatherRain;
|
||
} else if (weatherId >= 600 && weatherId <= 622) {
|
||
icon = weatherSnow;
|
||
} else if (weatherId == 800) {
|
||
icon = frogpilotPlan.getWeatherDaytime() ? weatherClearDay : weatherClearNight;
|
||
}
|
||
|
||
p.drawPixmap(weatherRect, icon->currentPixmap());
|
||
|
||
p.restore();
|
||
}
|