mirror of
https://github.com/firestar5683/StarPilot.git
synced 2026-07-15 06:12:11 +08:00
1178 lines
45 KiB
C++
1178 lines
45 KiB
C++
#include <QPainterPath>
|
||
|
||
#include "frogpilot/ui/qt/onroad/frogpilot_annotated_camera.h"
|
||
|
||
FrogPilotAnnotatedCameraWidget::FrogPilotAnnotatedCameraWidget(QWidget *parent) : QWidget(parent) {
|
||
animationTimer = new QTimer(this);
|
||
|
||
QSize iconSize(img_size / 4, img_size / 4);
|
||
|
||
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});
|
||
curveSpeedIconFlipped = curveSpeedIcon.transformed(QTransform().scale(-1, 1));
|
||
dashboardIcon = loadPixmap("../../frogpilot/assets/other_images/dashboard_icon.png", {btn_size / 2, btn_size / 2}).scaled(iconSize, Qt::KeepAspectRatio, Qt::SmoothTransformation);
|
||
gasPedalImg = loadPixmap("../../frogpilot/assets/other_images/gas_pedal.png", {btn_size, btn_size});
|
||
mapboxIcon = loadPixmap("../../frogpilot/assets/other_images/mapbox_icon.png", {btn_size / 2, btn_size / 2}).scaled(iconSize, Qt::KeepAspectRatio, Qt::SmoothTransformation);
|
||
mapDataIcon = loadPixmap("../../frogpilot/assets/other_images/offline_maps_icon.png", {btn_size / 2, btn_size / 2}).scaled(iconSize, Qt::KeepAspectRatio, Qt::SmoothTransformation);
|
||
nextMapsIcon = loadPixmap("../../frogpilot/assets/other_images/next_maps_icon.png", {btn_size / 2, btn_size / 2}).scaled(iconSize, Qt::KeepAspectRatio, Qt::SmoothTransformation);
|
||
pausedIcon = loadPixmap("../../frogpilot/assets/other_images/paused_icon.png", {widget_size, widget_size});
|
||
speedIcon = loadPixmap("../../frogpilot/assets/other_images/speed_icon.png", {widget_size, widget_size});
|
||
stopSignImg = loadPixmap("../../frogpilot/assets/other_images/stop_sign.png", {btn_size, btn_size});
|
||
turnIcon = loadPixmap("../../frogpilot/assets/other_images/turn_icon.png", {widget_size, widget_size});
|
||
|
||
loadGif("../../frogpilot/assets/other_images/curve_icon.gif", cemCurveIcon, QSize(widget_size, widget_size), this);
|
||
loadGif("../../frogpilot/assets/other_images/lead_icon.gif", cemLeadIcon, QSize(widget_size, widget_size), this);
|
||
loadGif("../../frogpilot/assets/other_images/speed_icon.gif", cemSpeedIcon, QSize(widget_size, widget_size), this);
|
||
loadGif("../../frogpilot/assets/other_images/light_icon.gif", cemStopIcon, QSize(widget_size, widget_size), this);
|
||
loadGif("../../frogpilot/assets/other_images/turn_icon.gif", cemTurnIcon, QSize(widget_size, widget_size), this);
|
||
loadGif("../../frogpilot/assets/other_images/chill_mode_icon.gif", chillModeIcon, QSize(widget_size, widget_size), this);
|
||
loadGif("../../frogpilot/assets/other_images/experimental_mode_icon.gif", experimentalModeIcon, QSize(widget_size, widget_size), this);
|
||
loadGif("../../frogpilot/assets/other_images/weather_clear_day.gif", weatherClearDay, QSize(widget_size, widget_size), this);
|
||
loadGif("../../frogpilot/assets/other_images/weather_clear_night.gif", weatherClearNight, QSize(widget_size, widget_size), this);
|
||
loadGif("../../frogpilot/assets/other_images/weather_low_visibility.gif", weatherLowVisibility, QSize(widget_size, widget_size), this);
|
||
loadGif("../../frogpilot/assets/other_images/weather_rain.gif", weatherRain, QSize(widget_size, widget_size), this);
|
||
loadGif("../../frogpilot/assets/other_images/weather_snow.gif", weatherSnow, QSize(widget_size, widget_size), 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) {
|
||
updateSignals();
|
||
}
|
||
|
||
void FrogPilotAnnotatedCameraWidget::updateSignals() {
|
||
QVector<QPixmap>().swap(blindspotImages);
|
||
QVector<QPixmap>().swap(blindspotImagesRight);
|
||
QVector<QPixmap>().swap(signalImages);
|
||
QVector<QPixmap>().swap(signalImagesRight);
|
||
|
||
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);
|
||
signalImagesRight.reserve(frameCount);
|
||
|
||
for (int i = 0; i < frameCount; ++i) {
|
||
movie.jumpToFrame(i);
|
||
|
||
QPixmap frame = movie.currentPixmap();
|
||
signalImages.append(frame);
|
||
signalImagesRight.append(frame.transformed(QTransform().scale(-1, 1)));
|
||
}
|
||
|
||
movie.stop();
|
||
} else if (fileName.endsWith(".png", Qt::CaseInsensitive)) {
|
||
QPixmap img(filePath);
|
||
if (fileName.contains("blindspot", Qt::CaseInsensitive)) {
|
||
blindspotImages.append(img);
|
||
blindspotImagesRight.append(img.transformed(QTransform().scale(-1, 1)));
|
||
} else {
|
||
signalImages.append(img);
|
||
signalImagesRight.append(img.transformed(QTransform().scale(-1, 1)));
|
||
}
|
||
} 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 UIState &s, const FrogPilotUIState &fs) {
|
||
const UIScene &scene = s.scene;
|
||
|
||
const SubMaster &sm = *(s.sm);
|
||
const SubMaster &fpsm = *(fs.sm);
|
||
|
||
const cereal::CarState::Reader &carState = sm["carState"].getCarState();
|
||
const cereal::FrogPilotCarState::Reader &frogpilotCarState = fpsm["frogpilotCarState"].getFrogpilotCarState();
|
||
const cereal::FrogPilotPlan::Reader &frogpilotPlan = fpsm["frogpilotPlan"].getFrogpilotPlan();
|
||
const cereal::FrogPilotSelfdriveState::Reader &frogpilotSelfdriveState = fpsm["frogpilotSelfdriveState"].getFrogpilotSelfdriveState();
|
||
const cereal::MapdOut::Reader &mapdOut = fpsm["mapdOut"].getMapdOut();
|
||
const cereal::ModelDataV2::Reader &modelV2 = sm["modelV2"].getModelV2();
|
||
const cereal::SelfdriveState::Reader &selfdriveState = sm["selfdriveState"].getSelfdriveState();
|
||
|
||
if (scene.is_metric || frogpilot_toggles.value("use_si_metrics").toBool()) {
|
||
leadDistanceUnit = tr(" meters");
|
||
leadSpeedUnit = frogpilot_toggles.value("use_si_metrics").toBool() ? tr(" m/s") : tr(" km/h");
|
||
speedUnit = scene.is_metric ? tr("km/h") : tr("mph");
|
||
|
||
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 {
|
||
leadDistanceUnit = tr(" feet");
|
||
leadSpeedUnit = tr(" mph");
|
||
speedUnit = tr("mph");
|
||
|
||
distanceConversion = METER_TO_FOOT;
|
||
speedConversion = MS_TO_MPH;
|
||
speedConversionMetrics = MS_TO_MPH;
|
||
}
|
||
|
||
accelerationEgo = carState.getAEgo();
|
||
blindspotLeft = carState.getLeftBlindspot();
|
||
blindspotRight = carState.getRightBlindspot();
|
||
blinkerLeft = carState.getLeftBlinker();
|
||
blinkerRight = carState.getRightBlinker();
|
||
brakeLights = frogpilotCarState.getBrakeLights();
|
||
cscControllingSpeed = frogpilotPlan.getCscControllingSpeed();
|
||
cscSpeed = frogpilotPlan.getCscSpeed();
|
||
cscTraining = frogpilotPlan.getCscTraining();
|
||
dashboardSpeedLimit = frogpilotCarState.getDashboardSpeedLimit();
|
||
desiredFollowDistance = frogpilotPlan.getDesiredFollowDistance();
|
||
experimentalMode = selfdriveState.getExperimentalMode();
|
||
forceCoast = frogpilotCarState.getForceCoast();
|
||
laneWidthLeft = frogpilotPlan.getLaneWidthLeft();
|
||
laneWidthRight = frogpilotPlan.getLaneWidthRight();
|
||
lateralPaused = frogpilotCarState.getPauseLateral();
|
||
longitudinalPaused = frogpilotCarState.getPauseLongitudinal();
|
||
mapSpeedLimit = frogpilotPlan.getSlcMapSpeedLimit();
|
||
mapboxSpeedLimit = frogpilotPlan.getSlcMapboxSpeedLimit();
|
||
nextSpeedLimit = frogpilotPlan.getSlcNextSpeedLimit();
|
||
redLight = frogpilotPlan.getRedLight();
|
||
roadCurvature = frogpilotPlan.getRoadCurvature();
|
||
roadName = QString::fromStdString(mapdOut.getRoadName());
|
||
slcOverriddenSpeed = frogpilotPlan.getSlcOverriddenSpeed();
|
||
speedLimit = slcOverriddenSpeed != 0 ? slcOverriddenSpeed : frogpilotPlan.getSlcSpeedLimit();
|
||
speedLimitChanged = frogpilotPlan.getSpeedLimitChanged();
|
||
speedLimitSource = frogpilotPlan.getSlcSpeedLimitSource();
|
||
stoppingDistance = modelV2.getPosition().getX().size() > 33 - 1 ? modelV2.getPosition().getX()[33 - 1] : 0.0;
|
||
unconfirmedSpeedLimit = frogpilotPlan.getUnconfirmedSlcSpeedLimit();
|
||
weatherDaytime = frogpilotPlan.getWeatherDaytime();
|
||
weatherId = frogpilotPlan.getWeatherId();
|
||
|
||
hideBottomIcons = selfdriveState.getAlertSize() != cereal::SelfdriveState::AlertSize::NONE;
|
||
hideBottomIcons |= frogpilotSelfdriveState.getAlertSize() != cereal::FrogPilotSelfdriveState::AlertSize::NONE;
|
||
hideBottomIcons |= signalStyle.startsWith("traditional") && (blinkerLeft || blinkerRight);
|
||
|
||
if (slcOverriddenSpeed == 0 && !frogpilot_toggles.value("show_speed_limit_offset").toBool()) {
|
||
speedLimit += frogpilotPlan.getSlcSpeedLimitOffset();
|
||
}
|
||
speedLimit *= (scene.is_metric ? MS_TO_KPH : MS_TO_MPH);
|
||
float speedLimitOffset = frogpilotPlan.getSlcSpeedLimitOffset() * speedConversion;
|
||
speedLimitOffsetStr = (speedLimitOffset != 0) ? QString::number(speedLimitOffset, 'f', 0).prepend((speedLimitOffset > 0) ? "+" : "-") : "–";
|
||
|
||
static int lastFrameIndex;
|
||
if (lastFrameIndex > animationFrameIndex && frogpilot_toggles.value("signal_icons").toString() == "frog") {
|
||
frogHopCount++;
|
||
}
|
||
lastFrameIndex = animationFrameIndex;
|
||
|
||
if ((blinkerLeft || blinkerRight) && signalStyle != "None") {
|
||
if (!animationTimer->isActive()) {
|
||
animationTimer->start(signalAnimationLength);
|
||
}
|
||
} else if (animationTimer->isActive()) {
|
||
animationFrameIndex = 0;
|
||
animationTimer->stop();
|
||
}
|
||
|
||
if (cscTraining) {
|
||
if (!glowTimer.isValid()) {
|
||
glowTimer.start();
|
||
}
|
||
} else {
|
||
glowTimer.invalidate();
|
||
}
|
||
|
||
if (speedLimitChanged) {
|
||
if (!pendingLimitTimer.isValid()) {
|
||
pendingLimitTimer.start();
|
||
}
|
||
} else {
|
||
pendingLimitTimer.invalidate();
|
||
}
|
||
|
||
if (frogpilot_scene.standstill && frogpilot_toggles.value("stopped_timer").toBool()) {
|
||
if (!standstillTimer.isValid()) {
|
||
standstillTimer.start();
|
||
} else {
|
||
standstillDuration = frogpilot_scene.started_timer / UI_FREQ < 60 ? 0 : standstillTimer.elapsed() / 1000;
|
||
}
|
||
} else {
|
||
standstillDuration = 0;
|
||
standstillTimer.invalidate();
|
||
}
|
||
}
|
||
|
||
void FrogPilotAnnotatedCameraWidget::mousePressEvent(QMouseEvent *mouseEvent) {
|
||
if (speedLimitChanged && newSpeedLimitRect.contains(mouseEvent->pos())) {
|
||
params_memory.putBool("SpeedLimitAccepted", true);
|
||
mouseEvent->accept();
|
||
return;
|
||
}
|
||
|
||
mouseEvent->ignore();
|
||
}
|
||
|
||
void FrogPilotAnnotatedCameraWidget::paintFrogPilotWidgets(QPainter &p, UIState &s) {
|
||
if (!hideBottomIcons && frogpilot_toggles.value("cem_status").toBool()) {
|
||
paintCEMStatus(p);
|
||
} else {
|
||
cemStatusPosition.setX(0);
|
||
cemStatusPosition.setY(0);
|
||
}
|
||
|
||
if (!hideBottomIcons && frogpilot_toggles.value("compass").toBool()) {
|
||
paintCompass(p);
|
||
} else {
|
||
compassPosition.setX(0);
|
||
compassPosition.setY(0);
|
||
}
|
||
|
||
if (!speedLimitChanged && !(signalStyle == "static" && blinkerLeft) && frogpilot_toggles.value("csc_status").toBool()) {
|
||
if (cscTraining) {
|
||
paintCurveSpeedControlTraining(p);
|
||
} else if (isCruiseSet && cscControllingSpeed) {
|
||
paintCurveSpeedControl(p);
|
||
}
|
||
}
|
||
|
||
if (!hideBottomIcons && lateralPaused) {
|
||
paintLateralPaused(p);
|
||
} else {
|
||
lateralPausedPosition.setX(0);
|
||
lateralPausedPosition.setY(0);
|
||
}
|
||
|
||
if (!hideBottomIcons && (forceCoast || longitudinalPaused)) {
|
||
paintLongitudinalPaused(p);
|
||
}
|
||
|
||
if (frogpilot_toggles.value("pedals_on_ui").toBool()) {
|
||
paintPedalIcons(p);
|
||
}
|
||
|
||
if (speedLimitChanged) {
|
||
paintPendingSpeedLimit(p);
|
||
}
|
||
|
||
if (frogpilot_toggles.value("radar_tracks").toBool()) {
|
||
paintRadarTracks(p);
|
||
}
|
||
|
||
if (frogpilot_toggles.value("road_name_ui").toBool()) {
|
||
paintRoadName(p);
|
||
}
|
||
|
||
bool hideSpeedLimit = !speedLimitChanged && frogpilot_toggles.value("hide_speed_limit").toBool();
|
||
if (!hideSpeedLimit && (frogpilot_toggles.value("show_speed_limits").toBool() || frogpilot_toggles.value("speed_limit_controller").toBool())) {
|
||
paintSpeedLimit(p);
|
||
} else {
|
||
speedLimitHeight = 0;
|
||
}
|
||
|
||
if (frogpilot_toggles.value("speed_limit_sources").toBool()) {
|
||
paintSpeedLimitSources(p);
|
||
}
|
||
|
||
if (standstillDuration != 0) {
|
||
paintStandstillTimer(p);
|
||
}
|
||
|
||
if (track_vertices.length() >= 1 && redLight && frogpilot_toggles.value("show_stopping_point").toBool()) {
|
||
paintStoppingPoint(p);
|
||
}
|
||
|
||
if ((blinkerLeft || blinkerRight) && signalStyle != "None" && (standstillDuration == 0 || signalStyle != "static")) {
|
||
paintTurnSignals(p);
|
||
}
|
||
|
||
if (!hideBottomIcons) {
|
||
paintWeather(p);
|
||
}
|
||
}
|
||
|
||
void FrogPilotAnnotatedCameraWidget::paintAdjacentPaths(QPainter &p) {
|
||
std::function<void(const QPolygonF&, bool, bool, float)> paintPath = [&](const QPolygonF &path, bool isLeft, bool isBlindSpot, float laneWidth) {
|
||
if (laneWidth == 0.0f) {
|
||
return;
|
||
}
|
||
|
||
p.save();
|
||
|
||
QLinearGradient gradient(0, height(), 0, 0);
|
||
if (isBlindSpot && frogpilot_toggles.value("blind_spot_path").toBool()) {
|
||
gradient.setColorAt(0.0f, QColor::fromHslF(0.0f, 0.75f, 0.5f, 0.4f));
|
||
gradient.setColorAt(0.5f, QColor::fromHslF(0.0f, 0.75f, 0.5f, 0.35f));
|
||
gradient.setColorAt(1.0f, QColor::fromHslF(0.0f, 0.75f, 0.5f, 0.0f));
|
||
} else {
|
||
float ratio = std::clamp(laneWidth / frogpilot_toggles.value("lane_detection_width").toDouble(), 0.0, 1.0);
|
||
float hue = (ratio * ratio) * (120.0f / 360.0f);
|
||
|
||
gradient.setColorAt(0.0f, QColor::fromHslF(hue, 0.75f, 0.5f, 0.4f));
|
||
gradient.setColorAt(0.5f, QColor::fromHslF(hue, 0.75f, 0.5f, 0.35f));
|
||
gradient.setColorAt(1.0f, QColor::fromHslF(hue, 0.75f, 0.5f, 0.0f));
|
||
}
|
||
|
||
p.setBrush(gradient);
|
||
p.drawPolygon(path);
|
||
|
||
if (frogpilot_toggles.value("adjacent_path_metrics").toBool()) {
|
||
QString text;
|
||
if (isBlindSpot && frogpilot_toggles.value("blind_spot_path").toBool()) {
|
||
text = tr("Vehicle in blind spot");
|
||
} else {
|
||
text = QString::number(laneWidth * distanceConversion, 'f', 2) + leadDistanceUnit;
|
||
}
|
||
|
||
int midIndex = path.size() / 2;
|
||
QPointF anchorPoint = isLeft ? path[midIndex / 2] : path[midIndex + (path.size() - midIndex) / 2];
|
||
|
||
p.setFont(InterFont(45, QFont::DemiBold));
|
||
QFontMetrics metrics(p.font());
|
||
|
||
int textXPosition = isLeft ? anchorPoint.x() - metrics.horizontalAdvance(text) : anchorPoint.x();
|
||
int textYPosition = anchorPoint.y() - metrics.height() / 2 + metrics.ascent();
|
||
|
||
QPainterPath textPath;
|
||
textPath.addText(textXPosition, textYPosition, p.font(), text);
|
||
p.strokePath(textPath, QPen(Qt::black, 3, Qt::SolidLine, Qt::RoundCap, Qt::RoundJoin));
|
||
|
||
p.setPen(whiteColor());
|
||
p.drawText(textXPosition, textYPosition, text);
|
||
}
|
||
|
||
p.restore();
|
||
};
|
||
|
||
paintPath(track_adjacent_vertices[0], true, blindspotLeft, laneWidthLeft);
|
||
paintPath(track_adjacent_vertices[1], false, blindspotRight, laneWidthRight);
|
||
}
|
||
|
||
void FrogPilotAnnotatedCameraWidget::paintBlindSpotPath(QPainter &p) {
|
||
p.save();
|
||
|
||
QLinearGradient bs(0, height(), 0, 0);
|
||
bs.setColorAt(0.0f, QColor::fromHslF(0 / 360.0f, 0.75f, 0.5f, 0.4f));
|
||
bs.setColorAt(0.5f, QColor::fromHslF(0 / 360.0f, 0.75f, 0.5f, 0.35f));
|
||
bs.setColorAt(1.0f, QColor::fromHslF(0 / 360.0f, 0.75f, 0.5f, 0.0f));
|
||
p.setBrush(bs);
|
||
|
||
if (track_adjacent_vertices[0].boundingRect().width() > 0 && blindspotLeft) {
|
||
p.drawPolygon(track_adjacent_vertices[0]);
|
||
}
|
||
if (track_adjacent_vertices[1].boundingRect().width() > 0 && blindspotRight) {
|
||
p.drawPolygon(track_adjacent_vertices[1]);
|
||
}
|
||
|
||
p.restore();
|
||
}
|
||
|
||
void FrogPilotAnnotatedCameraWidget::paintCEMStatus(QPainter &p) {
|
||
if (dmIconPosition == QPoint(0, 0)) {
|
||
return;
|
||
}
|
||
|
||
p.save();
|
||
|
||
cemStatusPosition.setX(dmIconPosition.x() + (rightHandDM ? -img_size - widget_size : widget_size));
|
||
cemStatusPosition.setY(dmIconPosition.y() - widget_size / 2);
|
||
|
||
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_CEM_DISABLED]), 10));
|
||
} else if (experimentalMode) {
|
||
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 (experimentalMode) {
|
||
if (frogpilot_scene.conditional_status == 1) {
|
||
icon = chillModeIcon;
|
||
} else if (frogpilot_scene.conditional_status == 2) {
|
||
icon = experimentalModeIcon;
|
||
} else if (frogpilot_scene.conditional_status == 3) {
|
||
icon = cemCurveIcon;
|
||
} else if (frogpilot_scene.conditional_status == 4) {
|
||
icon = cemLeadIcon;
|
||
} else if (frogpilot_scene.conditional_status == 5) {
|
||
icon = cemTurnIcon;
|
||
} else if (frogpilot_scene.conditional_status == 6 || frogpilot_scene.conditional_status == 7) {
|
||
icon = cemSpeedIcon;
|
||
} else if (frogpilot_scene.conditional_status == 8) {
|
||
icon = cemStopIcon;
|
||
} else {
|
||
icon = experimentalModeIcon;
|
||
}
|
||
}
|
||
p.drawPixmap(cemWidget, icon->currentPixmap());
|
||
|
||
p.restore();
|
||
}
|
||
|
||
void FrogPilotAnnotatedCameraWidget::paintCompass(QPainter &p) {
|
||
if (dmIconPosition == QPoint(0, 0)) {
|
||
return;
|
||
}
|
||
|
||
p.save();
|
||
|
||
constexpr double PIXELS_PER_DEGREE = 2.5;
|
||
|
||
constexpr int BASE_RIBBON_WIDTH = static_cast<int>(360 * PIXELS_PER_DEGREE);
|
||
constexpr int BORDER_WIDTH = 10;
|
||
constexpr int MARGIN = 5;
|
||
constexpr int TRIANGLE_SIZE = 40;
|
||
|
||
static QPixmap compassRibbon = [&]() {
|
||
QPixmap ribbon(BASE_RIBBON_WIDTH * 2, widget_size);
|
||
ribbon.fill(Qt::transparent);
|
||
|
||
QPainter ribbonPainter(&ribbon);
|
||
ribbonPainter.setRenderHints(QPainter::Antialiasing | QPainter::TextAntialiasing);
|
||
|
||
QFont font = InterFont(65, QFont::Bold);
|
||
ribbonPainter.setFont(font);
|
||
QFontMetrics fm(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 cycle = 0; cycle < 2; ++cycle) {
|
||
int xOffset = cycle * 360;
|
||
|
||
for (int degree = 0; degree < 360; ++degree) {
|
||
int x = qRound((xOffset + degree) * PIXELS_PER_DEGREE);
|
||
|
||
if (directionLabels.contains(degree)) {
|
||
QString label = directionLabels[degree];
|
||
ribbonPainter.setPen(whiteColor());
|
||
ribbonPainter.drawText(x - fm.horizontalAdvance(label) / 2, 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, widget_size - notchHeight - MARGIN, x, widget_size);
|
||
}
|
||
}
|
||
|
||
return ribbon;
|
||
}();
|
||
|
||
compassPosition.rx() = rightHandDM ? UI_BORDER_SIZE + widget_size / 2 : width() - UI_BORDER_SIZE - btn_size;
|
||
compassPosition.ry() = dmIconPosition.y() - widget_size / 2;
|
||
|
||
QRect compassWidget(compassPosition, QSize(widget_size, widget_size));
|
||
|
||
p.setBrush(blackColor(166));
|
||
p.setPen(QPen(blackColor(), BORDER_WIDTH));
|
||
p.drawRoundedRect(compassWidget, 24, 24);
|
||
|
||
QPainterPath clipPath;
|
||
clipPath.addRoundedRect(compassWidget.adjusted(MARGIN, MARGIN, -MARGIN, -MARGIN), 24, 24);
|
||
p.setClipPath(clipPath);
|
||
|
||
double rawBearing = QJsonDocument::fromJson(QByteArray::fromStdString(params_memory.get("LastGPSPosition"))).object().value("bearing").toDouble(0.0);
|
||
int bearing = qRound(fmod(rawBearing + 360.0, 360.0));
|
||
int offset = qRound(bearing * PIXELS_PER_DEGREE) % BASE_RIBBON_WIDTH;
|
||
int drawX = compassWidget.center().x() - offset;
|
||
|
||
p.drawPixmap(drawX - BASE_RIBBON_WIDTH, compassWidget.top() + MARGIN, compassRibbon);
|
||
p.drawPixmap(drawX, compassWidget.top() + MARGIN, compassRibbon);
|
||
|
||
int triangleX = compassWidget.center().x();
|
||
int triangleY = compassWidget.bottom() - TRIANGLE_SIZE;
|
||
QPolygon triangle({
|
||
QPoint(triangleX, triangleY - TRIANGLE_SIZE),
|
||
QPoint(triangleX - TRIANGLE_SIZE / 1.5, triangleY),
|
||
QPoint(triangleX + TRIANGLE_SIZE / 1.5, triangleY)
|
||
});
|
||
|
||
p.setBrush(whiteColor());
|
||
p.setPen(Qt::NoPen);
|
||
p.drawPolygon(triangle);
|
||
|
||
p.restore();
|
||
}
|
||
|
||
void FrogPilotAnnotatedCameraWidget::paintCurveSpeedControl(QPainter &p) {
|
||
p.save();
|
||
|
||
QRect curveSpeedRect(QPoint(setSpeedRect.right() + UI_BORDER_SIZE, setSpeedRect.top()), QSize(defaultSize.width() * 1.25, defaultSize.width() * 1.25));
|
||
|
||
QPixmap &curveSpeedImage = roadCurvature < 0 ? curveSpeedIcon : curveSpeedIconFlipped;
|
||
QSize curveSpeedSize = curveSpeedImage.size();
|
||
QPoint curveSpeedPoint = QStyle::alignedRect(Qt::LeftToRight, Qt::AlignCenter, curveSpeedSize, curveSpeedRect).topLeft();
|
||
|
||
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, QString::number(std::nearbyint(fmin(speed, cscSpeed * speedConversion))) + speedUnit);
|
||
p.drawPixmap(curveSpeedPoint, curveSpeedImage);
|
||
|
||
p.restore();
|
||
}
|
||
|
||
void FrogPilotAnnotatedCameraWidget::paintCurveSpeedControlTraining(QPainter &p) {
|
||
p.save();
|
||
|
||
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);
|
||
|
||
QRect curveSpeedRect(QPoint(setSpeedRect.right() + UI_BORDER_SIZE, setSpeedRect.top()), QSize(defaultSize.width() * 1.25, defaultSize.width() * 1.25));
|
||
|
||
QPixmap &curveSpeedImage = roadCurvature < 0 ? curveSpeedIcon : curveSpeedIconFlipped;
|
||
QSize curveSpeedSize = curveSpeedImage.size();
|
||
QPoint curveSpeedPoint = QStyle::alignedRect(Qt::LeftToRight, Qt::AlignCenter, curveSpeedSize, curveSpeedRect).topLeft();
|
||
|
||
p.setOpacity(1.0);
|
||
|
||
p.setBrush(blackColor(166));
|
||
p.setPen(QPen(glowColor, glowWidth));
|
||
p.drawRoundedRect(curveSpeedRect, 24, 24);
|
||
p.drawPixmap(curveSpeedPoint, curveSpeedImage);
|
||
p.setBrush(blackColor(166));
|
||
p.setFont(InterFont(35, QFont::Bold));
|
||
p.setPen(QPen(blackColor(), 10));
|
||
|
||
QRect textRect(curveSpeedRect.topLeft() + QPoint(0, curveSpeedRect.height() + 10), QSize(curveSpeedRect.width(), 50));
|
||
p.drawRoundedRect(textRect, 24, 24);
|
||
p.setPen(QPen(whiteColor(), 6));
|
||
p.drawText(textRect.adjusted(20, 0, 0, 0), Qt::AlignVCenter | Qt::AlignLeft, "Training...");
|
||
|
||
p.restore();
|
||
}
|
||
|
||
void FrogPilotAnnotatedCameraWidget::paintLateralPaused(QPainter &p) {
|
||
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;
|
||
|
||
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::RadarState::LeadData::Reader &lead_data) {
|
||
float leadDistance = lead_data.getDRel() + (adjacent ? std::abs(lead_data.getYRel()) : 0.0f);
|
||
float leadSpeed = std::max(lead_data.getVLead(), 0.0f);
|
||
|
||
QString distanceString = QString::number(qRound(leadDistance * distanceConversion));
|
||
QString speedString = QString::number(qRound(leadSpeed * speedConversionMetrics));
|
||
|
||
QVector<QString> textLines;
|
||
textLines.reserve(3);
|
||
if (adjacent) {
|
||
textLines.append(QString("%1 %2").arg(distanceString, leadDistanceUnit));
|
||
textLines.append(QString("%1 %2").arg(speedString, leadSpeedUnit));
|
||
} else {
|
||
if (frogpilot_toggles.value("openpilot_longitudinal").toBool()) {
|
||
int desiredDistance = std::max(0, qRound(desiredFollowDistance * distanceConversion));
|
||
textLines.append(QString("%1 %2 (%3)").arg(distanceString, leadDistanceUnit, tr("Desired: %1").arg(desiredDistance)));
|
||
} else {
|
||
textLines.append(QString("%1 %2").arg(distanceString, leadDistanceUnit));
|
||
}
|
||
textLines.append(QString("%1 %2").arg(speedString, leadSpeedUnit));
|
||
|
||
float timeGap = leadDistance / std::max(speed / speedConversion, 1.0f);
|
||
textLines.append(QString("%1 %2").arg(QString::number(timeGap, 'f', 2), tr("seconds")));
|
||
}
|
||
|
||
p.setFont(InterFont(45, QFont::DemiBold));
|
||
p.setPen(whiteColor());
|
||
|
||
QFontMetrics metrics(p.font());
|
||
int lineHeight = metrics.lineSpacing();
|
||
|
||
int maxTextWidth = 0;
|
||
for (QString &line : textLines) {
|
||
maxTextWidth = std::max(maxTextWidth, metrics.horizontalAdvance(line));
|
||
}
|
||
|
||
int centerX = (chevron[2].x() + chevron[0].x()) / 2;
|
||
int startY = chevron[0].y() + lineHeight + 5;
|
||
|
||
int xMargin = maxTextWidth * 0.1;
|
||
int yMargin = lineHeight * 0.1;
|
||
|
||
QRect textRect(centerX - maxTextWidth / 2, startY - lineHeight, maxTextWidth, textLines.size() * lineHeight);
|
||
textRect.adjust(-xMargin, -yMargin, xMargin, yMargin);
|
||
|
||
if (adjacent) {
|
||
if (textRect.intersects(adjacentLeadTextRect) || textRect.intersects(leadTextRect)) {
|
||
return;
|
||
}
|
||
adjacentLeadTextRect = textRect;
|
||
} else {
|
||
leadTextRect = textRect;
|
||
}
|
||
|
||
for (int i = 0; i < textLines.size(); ++i) {
|
||
int lineX = centerX - metrics.horizontalAdvance(textLines[i]) / 2;
|
||
int lineY = startY + (i * lineHeight);
|
||
|
||
QPainterPath path;
|
||
path.addText(lineX, lineY, p.font(), textLines[i]);
|
||
p.strokePath(path, QPen(Qt::black, 3, Qt::SolidLine, Qt::RoundCap, Qt::RoundJoin));
|
||
|
||
p.setPen(whiteColor());
|
||
p.drawText(lineX, lineY, textLines[i]);
|
||
}
|
||
}
|
||
|
||
void FrogPilotAnnotatedCameraWidget::paintLongitudinalPaused(QPainter &p) {
|
||
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;
|
||
|
||
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, int height) {
|
||
p.save();
|
||
|
||
QLinearGradient gradient(0, height, 0, 0);
|
||
|
||
std::function<void(const QColor &)> setPathEdgeColors = [&gradient](const QColor &baseColor) {
|
||
gradient.setColorAt(0.0f, QColor(baseColor.red(), baseColor.green(), baseColor.blue(), 255.0f * 0.4f));
|
||
gradient.setColorAt(0.5f, QColor(baseColor.red(), baseColor.green(), baseColor.blue(), 255.0f * 0.35f));
|
||
gradient.setColorAt(1.0f, QColor(baseColor.red(), baseColor.green(), baseColor.blue(), 255.0f * 0.0f));
|
||
};
|
||
|
||
if (frogpilot_scene.always_on_lateral_active) {
|
||
setPathEdgeColors(bg_colors[STATUS_ALWAYS_ON_LATERAL_ACTIVE]);
|
||
} else if (frogpilot_scene.conditional_status == 1) {
|
||
setPathEdgeColors(bg_colors[STATUS_CEM_DISABLED]);
|
||
} else if (experimentalMode) {
|
||
setPathEdgeColors(bg_colors[STATUS_EXPERIMENTAL_MODE_ENABLED]);
|
||
} else if (frogpilot_scene.traffic_mode_enabled) {
|
||
setPathEdgeColors(bg_colors[STATUS_TRAFFIC_MODE_ENABLED]);
|
||
} else if (frogpilot_toggles.value("color_scheme").toString() != "stock") {
|
||
setPathEdgeColors(QColor(frogpilot_toggles.value("path_edges_color").toString()));
|
||
} else {
|
||
gradient.setColorAt(0.0f, QColor::fromHslF(148.0f / 360.0f, 0.94f, 0.41f, 0.4f));
|
||
gradient.setColorAt(0.5f, QColor::fromHslF(112.0f / 360.0f, 1.0f, 0.54f, 0.35f));
|
||
gradient.setColorAt(1.0f, QColor::fromHslF(112.0f / 360.0f, 1.0f, 0.54f, 0.0f));
|
||
}
|
||
|
||
p.setBrush(gradient);
|
||
|
||
QPainterPath path;
|
||
path.addPolygon(track_vertices);
|
||
path.addPolygon(track_edge_vertices);
|
||
p.drawPath(path);
|
||
|
||
p.restore();
|
||
}
|
||
|
||
void FrogPilotAnnotatedCameraWidget::paintPedalIcons(QPainter &p) {
|
||
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 : accelerationEgo < -0.25f ? std::max(0.25f, std::abs(accelerationEgo)) : 0.25f;
|
||
gasOpacity = std::max(0.25f, accelerationEgo);
|
||
} else if (frogpilot_toggles.value("static_pedals_on_ui").toBool()) {
|
||
brakeOpacity = frogpilot_scene.standstill || brakeLights || accelerationEgo < -0.25f ? 1.0f : 0.25f;
|
||
gasOpacity = accelerationEgo > 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) {
|
||
p.save();
|
||
|
||
QString newSpeedLimitStr = (unconfirmedSpeedLimit > 1) ? QString::number(std::nearbyint(unconfirmedSpeedLimit * speedConversion)) : "–";
|
||
newSpeedLimitRect = speedLimitRect.translated(speedLimitRect.width() + UI_BORDER_SIZE, 0);
|
||
|
||
if (!frogpilot_toggles.value("speed_limit_vienna").toBool()) {
|
||
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::paintRainbowPath(QPainter &p, QLinearGradient &bg, float lin_grad_point) {
|
||
p.save();
|
||
|
||
static float hueOffset = 0.0f;
|
||
if (speed > 0) {
|
||
hueOffset += speed / speedConversion * 0.02f;
|
||
|
||
if (hueOffset >= 360.0f) {
|
||
hueOffset = fmodf(hueOffset, 360.0f);
|
||
}
|
||
}
|
||
|
||
float alpha = util::map_val(lin_grad_point, 0.0f, 1.0f, 0.5f, 0.1f);
|
||
float pathHue = fmodf(lin_grad_point * 120.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::paintRadarTracks(QPainter &p) {
|
||
if (radar_tracks.empty()) {
|
||
return;
|
||
}
|
||
|
||
p.save();
|
||
|
||
int diameter = 25;
|
||
|
||
float radius = diameter / 2.0f;
|
||
float track_x = p.viewport().width() - diameter;
|
||
float track_y = p.viewport().height() - diameter;
|
||
|
||
p.setBrush(redColor());
|
||
|
||
for (const QPointF &track : radar_tracks) {
|
||
float x = std::clamp(static_cast<float>(track.x()), 0.0f, track_x);
|
||
float y = std::clamp(static_cast<float>(track.y()), 0.0f, track_y);
|
||
|
||
p.drawEllipse(QPointF(x + radius, y + radius), radius, radius);
|
||
}
|
||
|
||
p.restore();
|
||
}
|
||
|
||
void FrogPilotAnnotatedCameraWidget::paintRoadName(QPainter &p) {
|
||
if (roadName.isEmpty()) {
|
||
return;
|
||
}
|
||
|
||
p.save();
|
||
|
||
QFont font = InterFont(40, QFont::DemiBold);
|
||
|
||
int textWidth = QFontMetrics(font).horizontalAdvance(roadName);
|
||
|
||
QSize size(textWidth + 100, 50);
|
||
QRect roadNameRect = QStyle::alignedRect(Qt::LeftToRight, Qt::AlignHCenter | Qt::AlignBottom, size, rect().adjusted(0, 0, 0, -5));
|
||
|
||
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::paintSpeedLimit(QPainter &p) {
|
||
if (setSpeedRect.isEmpty()) {
|
||
return;
|
||
}
|
||
|
||
p.save();
|
||
|
||
QString speedLimitStr = (speedLimit > 1) ? QString::number(std::nearbyint(speedLimit)) : "–";
|
||
|
||
bool hasUsSpeedLimit = !frogpilot_toggles.value("speed_limit_vienna").toBool();
|
||
bool hasEuSpeedLimit = !hasUsSpeedLimit;
|
||
|
||
int euSignSize = 176;
|
||
int usSignHeight = 186;
|
||
int signMargin = 12;
|
||
|
||
if (hasUsSpeedLimit) {
|
||
speedLimitHeight = usSignHeight + signMargin;
|
||
} else if (hasEuSpeedLimit) {
|
||
speedLimitHeight = euSignSize + signMargin;
|
||
}
|
||
|
||
QRect signRect;
|
||
if (hasUsSpeedLimit) {
|
||
signRect = QRect(setSpeedRect.x() + signMargin, setSpeedRect.bottom() - speedLimitHeight, setSpeedRect.width() - 2 * signMargin, usSignHeight);
|
||
} else if (hasEuSpeedLimit) {
|
||
signRect = QRect(setSpeedRect.x() + signMargin, setSpeedRect.bottom() - speedLimitHeight, setSpeedRect.width() - 2 * signMargin, euSignSize);
|
||
}
|
||
speedLimitRect = signRect;
|
||
|
||
if (hasUsSpeedLimit) {
|
||
p.setPen(Qt::NoPen);
|
||
p.setBrush(whiteColor());
|
||
p.drawRoundedRect(signRect, 24, 24);
|
||
p.setPen(QPen(blackColor(), 6));
|
||
p.drawRoundedRect(signRect.adjusted(9, 9, -9, -9), 16, 16);
|
||
|
||
p.setOpacity(slcOverriddenSpeed == 0 ? 1.0 : 0.25);
|
||
if (slcOverriddenSpeed == 0 && frogpilot_toggles.value("show_speed_limit_offset").toBool()) {
|
||
p.setFont(InterFont(28, QFont::DemiBold));
|
||
p.drawText(signRect.adjusted(0, 22, 0, 0), Qt::AlignTop | Qt::AlignHCenter, tr("LIMIT"));
|
||
p.setFont(InterFont(70, QFont::Bold));
|
||
p.drawText(signRect.adjusted(0, 51, 0, 0), Qt::AlignTop | Qt::AlignHCenter, speedLimitStr);
|
||
p.setFont(InterFont(50, QFont::DemiBold));
|
||
p.drawText(signRect.adjusted(0, 120, 0, 0), Qt::AlignTop | Qt::AlignHCenter, speedLimitOffsetStr);
|
||
} else {
|
||
p.setFont(InterFont(28, QFont::DemiBold));
|
||
p.drawText(signRect.adjusted(0, 22, 0, 0), Qt::AlignTop | Qt::AlignHCenter, tr("SPEED"));
|
||
p.drawText(signRect.adjusted(0, 51, 0, 0), Qt::AlignTop | Qt::AlignHCenter, tr("LIMIT"));
|
||
p.setFont(InterFont(70, QFont::Bold));
|
||
p.drawText(signRect.adjusted(0, 85, 0, 0), Qt::AlignTop | Qt::AlignHCenter, speedLimitStr);
|
||
}
|
||
}
|
||
|
||
if (hasEuSpeedLimit) {
|
||
p.setPen(Qt::NoPen);
|
||
p.setBrush(whiteColor());
|
||
p.drawEllipse(signRect);
|
||
p.setPen(QPen(Qt::red, 20));
|
||
p.drawEllipse(signRect.adjusted(16, 16, -16, -16));
|
||
|
||
p.setOpacity(slcOverriddenSpeed == 0 ? 1.0 : 0.25);
|
||
p.setPen(blackColor());
|
||
if (frogpilot_toggles.value("show_speed_limit_offset").toBool()) {
|
||
p.setFont(InterFont((speedLimitStr.size() >= 3) ? 60 : 70, QFont::Bold));
|
||
p.drawText(signRect.adjusted(0, -25, 0, 0), Qt::AlignCenter, speedLimitStr);
|
||
p.setFont(InterFont(40, QFont::DemiBold));
|
||
p.drawText(signRect.adjusted(0, 100, 0, 0), Qt::AlignTop | Qt::AlignHCenter, speedLimitOffsetStr);
|
||
} else {
|
||
p.setFont(InterFont((speedLimitStr.size() >= 3) ? 60 : 70, QFont::Bold));
|
||
p.drawText(signRect, Qt::AlignCenter, speedLimitStr);
|
||
}
|
||
}
|
||
|
||
p.restore();
|
||
}
|
||
|
||
void FrogPilotAnnotatedCameraWidget::paintSpeedLimitSources(QPainter &p) {
|
||
p.save();
|
||
|
||
std::function<void(QRect&, QPixmap&, const QString&, const double)> drawSource = [&](QRect &rect, QPixmap &icon, const QString &title, double speedLimitValue) {
|
||
bool isActive = QString::fromUtf8(speedLimitSource.c_str()) == title && speedLimitValue != 0;
|
||
|
||
if (isActive) {
|
||
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));
|
||
}
|
||
|
||
QSize size(img_size / 4, img_size / 4);
|
||
QRect iconRect = QStyle::alignedRect(Qt::LeftToRight, Qt::AlignLeft | Qt::AlignVCenter, size, rect.adjusted(20, 0, 0, 0));
|
||
|
||
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, icon);
|
||
|
||
p.setPen(QPen(whiteColor(), 6));
|
||
QRect textRect(iconRect.right() + 10, rect.y(), rect.width() - iconRect.width() - 30, rect.height());
|
||
|
||
if (isActive) {
|
||
QFontMetrics fm(p.font());
|
||
int textYPosition = textRect.y() + (textRect.height() - fm.height()) / 2 + fm.ascent();
|
||
|
||
QPainterPath path;
|
||
path.addText(textRect.x(), textYPosition, p.font(), fullText);
|
||
p.strokePath(path, QPen(Qt::black, 3, Qt::SolidLine, Qt::RoundCap, Qt::RoundJoin));
|
||
p.drawText(textRect.x(), textYPosition, fullText);
|
||
} else {
|
||
p.drawText(textRect, Qt::AlignVCenter | Qt::AlignLeft, fullText);
|
||
}
|
||
};
|
||
|
||
int signMargin = 12;
|
||
|
||
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 mapboxRect(mapDataRect.x(), mapDataRect.y() + mapDataRect.height() + UI_BORDER_SIZE / 2, 450, 60);
|
||
QRect nextLimitRect(mapboxRect.x(), mapboxRect.y() + mapboxRect.height() + UI_BORDER_SIZE / 2, 450, 60);
|
||
|
||
drawSource(dashboardRect, dashboardIcon, "Dashboard", dashboardSpeedLimit * speedConversion);
|
||
drawSource(mapDataRect, mapDataIcon, "Map Data", mapSpeedLimit * speedConversion);
|
||
drawSource(mapboxRect, mapboxIcon, "Mapbox", mapboxSpeedLimit * speedConversion);
|
||
drawSource(nextLimitRect, nextMapsIcon, "Upcoming", nextSpeedLimit * 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_CEM_DISABLED];
|
||
transition = (standstillDuration - 60) / 90.0f;
|
||
} else if (standstillDuration < 300) {
|
||
startColor = bg_colors[STATUS_CEM_DISABLED];
|
||
endColor = bg_colors[STATUS_TRAFFIC_MODE_ENABLED];
|
||
transition = (standstillDuration - 150) / 150.0f;
|
||
} else {
|
||
startColor = endColor = bg_colors[STATUS_TRAFFIC_MODE_ENABLED];
|
||
}
|
||
|
||
QColor blendedColor(
|
||
startColor.red() + transition * (endColor.red() - startColor.red()),
|
||
startColor.green() + transition * (endColor.green() - startColor.green()),
|
||
startColor.blue() + transition * (endColor.blue() - startColor.blue())
|
||
);
|
||
|
||
std::function<void(const QString &, int, const QFont &, const QColor &)> drawText = [&](const QString &text, int y, const QFont &font, const QColor &color) {
|
||
p.setFont(font);
|
||
p.setPen(color);
|
||
|
||
QRect standstillRect = p.fontMetrics().boundingRect(text);
|
||
standstillRect.moveCenter({rect().center().x(), y - standstillRect.height() / 2});
|
||
p.drawText(standstillRect.x(), standstillRect.bottom(), text);
|
||
};
|
||
|
||
int minutes = standstillDuration / 60;
|
||
QString minuteStr = minutes == 1 ? tr("1 minute") : tr("%1 minutes").arg(minutes);
|
||
drawText(minuteStr, 210, InterFont(176, QFont::Bold), blendedColor);
|
||
|
||
int seconds = standstillDuration % 60;
|
||
QString secondStr = seconds == 1 ? tr("1 second") : tr("%1 seconds").arg(seconds);
|
||
drawText(secondStr, 290, InterFont(66), whiteColor());
|
||
|
||
p.restore();
|
||
}
|
||
|
||
void FrogPilotAnnotatedCameraWidget::paintStoppingPoint(QPainter &p) {
|
||
p.save();
|
||
|
||
QPointF centerPoint = (track_vertices.first() + track_vertices.last()) / 2.0f;
|
||
QPointF stopSignPosition = centerPoint - QPointF(stopSignImg.width() / 2.0f, stopSignImg.height());
|
||
p.drawPixmap(stopSignPosition, stopSignImg);
|
||
|
||
if (frogpilot_toggles.value("show_stopping_point_metrics").toBool()) {
|
||
float distance = stoppingDistance * distanceConversion;
|
||
QString distanceText = QString::number(std::nearbyint(distance)) + leadDistanceUnit;
|
||
|
||
QFont font = InterFont(45, QFont::DemiBold);
|
||
QFontMetrics fm(font);
|
||
|
||
QPointF textPosition(centerPoint.x() - fm.horizontalAdvance(distanceText) / 2.0f, centerPoint.y() - stopSignImg.height() - fm.ascent());
|
||
|
||
QPainterPath path;
|
||
path.addText(textPosition, font, distanceText);
|
||
p.strokePath(path, QPen(Qt::black, 3, Qt::SolidLine, Qt::RoundCap, Qt::RoundJoin));
|
||
|
||
p.setFont(font);
|
||
p.setPen(whiteColor());
|
||
p.drawText(textPosition, distanceText);
|
||
}
|
||
|
||
p.restore();
|
||
}
|
||
|
||
void FrogPilotAnnotatedCameraWidget::paintTurnSignals(QPainter &p) {
|
||
int frameIndex = qBound(0, animationFrameIndex, totalFrames - 1);
|
||
|
||
bool blindspotActive = blinkerLeft ? blindspotLeft : blindspotRight;
|
||
|
||
int signalXPosition = 0;
|
||
int signalYPosition = 0;
|
||
|
||
if (signalStyle == "static") {
|
||
signalXPosition = blinkerLeft ? (rect().center().x() * 0.75) - signalWidth : rect().center().x() * 1.25;
|
||
signalYPosition = signalHeight / 2;
|
||
} else {
|
||
if (signalStyle == "traditional_gif") {
|
||
signalXPosition = blinkerLeft ? width() - (frameIndex * signalMovement) + signalWidth : (frameIndex * signalMovement) - signalWidth;
|
||
} else {
|
||
signalXPosition = blinkerLeft ? width() - ((frameIndex + 1) * signalWidth) : frameIndex * signalWidth;
|
||
}
|
||
signalYPosition = height() - signalHeight - alertHeight;
|
||
}
|
||
|
||
if (blinkerLeft) {
|
||
QPixmap &imgToDraw = (blindspotActive && !blindspotImages.empty()) ? blindspotImages[0] : signalImages[frameIndex];
|
||
p.drawPixmap(signalXPosition, signalYPosition, signalWidth, signalHeight, imgToDraw);
|
||
} else {
|
||
QPixmap &imgToDraw = (blindspotActive && !blindspotImagesRight.empty()) ? blindspotImagesRight[0] : signalImagesRight[frameIndex];
|
||
p.drawPixmap(signalXPosition, signalYPosition, signalWidth, signalHeight, imgToDraw);
|
||
}
|
||
}
|
||
|
||
void FrogPilotAnnotatedCameraWidget::paintWeather(QPainter &p) {
|
||
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);
|
||
} else {
|
||
weatherIconPosition.rx() = rightHandDM ? UI_BORDER_SIZE + widget_size / 2 : width() - UI_BORDER_SIZE - btn_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 = weatherDaytime ? weatherClearDay : weatherClearNight;
|
||
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 >= 701 && weatherId <= 762) {
|
||
icon = weatherLowVisibility;
|
||
}
|
||
|
||
p.drawPixmap(weatherRect, icon->currentPixmap());
|
||
|
||
p.restore();
|
||
}
|