#include "starpilot/ui/qt/offroad/visual_settings.h" StarPilotVisualsPanel::StarPilotVisualsPanel(StarPilotSettingsWindow *parent, bool forceOpen) : StarPilotListWidget(parent), parent(parent) { forceOpenDescriptions = forceOpen; QStackedLayout *visualsLayout = new QStackedLayout(); addItem(visualsLayout); StarPilotListWidget *visualsList = new StarPilotListWidget(this); ScrollView *visualsPanel = new ScrollView(visualsList, this); visualsLayout->addWidget(visualsPanel); StarPilotListWidget *advancedCustomList = new StarPilotListWidget(this); StarPilotListWidget *customUIList = new StarPilotListWidget(this); StarPilotListWidget *modelUIList = new StarPilotListWidget(this); StarPilotListWidget *navigationUIList = new StarPilotListWidget(this); StarPilotListWidget *qualityOfLifeList = new StarPilotListWidget(this); ScrollView *advancedCustomPanel = new ScrollView(advancedCustomList, this); ScrollView *customUIPanel = new ScrollView(customUIList, this); ScrollView *modelUIPanel = new ScrollView(modelUIList, this); ScrollView *navigationUIPanel = new ScrollView(navigationUIList, this); ScrollView *qualityOfLifePanel = new ScrollView(qualityOfLifeList, this); visualsLayout->addWidget(advancedCustomPanel); visualsLayout->addWidget(customUIPanel); visualsLayout->addWidget(modelUIPanel); visualsLayout->addWidget(navigationUIPanel); visualsLayout->addWidget(qualityOfLifePanel); const std::vector> visualToggles { {"AdvancedCustomUI", tr("Advanced UI Controls"), tr("Advanced visual changes to fine-tune how the driving screen looks."), "../../starpilot/assets/toggle_icons/icon_advanced_device.png"}, {"HideSpeed", tr("Hide Current Speed"), tr("Hide the current speed from the driving screen."), ""}, {"HideLeadMarker", tr("Hide Lead Marker"), tr("Hide the lead-vehicle marker from the driving screen."), ""}, {"HideMaxSpeed", tr("Hide Max Speed"), tr("Hide the max speed from the driving screen."), ""}, {"HideAlerts", tr("Hide Non-Critical Alerts"), tr("Hide non-critical alerts from the driving screen."), ""}, {"HideChangingLanesBanner", tr("Hide Changing Lanes Banner"), tr("Hide the 'Changing Lanes' banner from the driving screen."), ""}, {"HideDistanceProfileBanner", tr("Hide Distance Profile Banner"), tr("Hide the driving personality banner when changing distance profiles."), ""}, {"HideDMIcon", tr("Hide Driver Monitoring Icon"), tr("Hide the driver monitoring icon from the driving screen."), ""}, {"HideTurningBanner", tr("Hide Turning Banner"), tr("Hide the 'Turning Left/Right' banner from the driving screen."), ""}, {"HideSpeedLimit", tr("Hide Speed Limits"), tr("Hide posted speed limits from the driving screen."), ""}, {"HideSteeringWheel", tr("Hide Steering Wheel"), tr("Hide the steering-wheel button from the top-right of the driving screen."), ""}, {"WheelSpeed", tr("Use Wheel Speed"), tr("Use the vehicle's wheel speed instead of the cluster speed. This is purely a visual change and doesn't impact how openpilot drives!"), ""}, {"CustomUI", tr("Driving Screen Widgets"), tr("Custom StarPilot widgets for the driving screen."), "../assets/icons/calibration.png"}, {"AccelerationPath", tr("Acceleration Path"), tr("Color the driving path by planned acceleration and braking."), ""}, {"AdjacentPath", tr("Adjacent Lanes"), tr("Show the driving paths for the left and right lanes."), ""}, {"BlindSpotPath", tr("Blind Spot Path"), tr("Show a red path when a vehicle is in that lane's blind spot."), ""}, {"Compass", tr("Compass"), tr("Show the current driving direction with a simple on-screen compass."), ""}, {"OnroadDistanceButton", tr("Driving Personality Button"), tr("Control and view the current driving personality via a driving screen widget."), ""}, {"PedalsOnUI", tr("Gas / Brake Pedal Indicators"), tr("On-screen gas and brake indicators.

Dynamic: Opacity changes according to how much openpilot is accelerating or braking
Static: Full when active, dim when not"), ""}, {"RotatingWheel", tr("Rotating Steering Wheel"), tr("Rotate the driving screen wheel with the physical steering wheel."), ""}, {"ModelUI", tr("Model UI"), tr("Model visualizations for the driving path, lane lines, path edges, and road edges."), "../../starpilot/assets/toggle_icons/icon_road.png"}, {"DynamicPathWidth", tr("Dynamic Path Width"), tr("Change the path width based on engagement.

Fully Engaged: 100%
Always On Lateral: 75%
Disengaged: 50%"), ""}, {"LaneLinesWidth", tr("Lane Lines Width"), tr("Set the lane-line thickness.

Default matches the MUTCD lane-line width standard of 4 inches."), ""}, {"PathEdgeWidth", tr("Path Edges Width"), tr("Set the driving-path edge width that represents different driving modes and statuses.

Default is 20% of the total path width.

Color Guide:

- Light Blue: Always On Lateral
- Green: Default
- Orange: Experimental Mode
- Red: Traffic Mode
- Yellow: Conditional Experimental Mode overridden"), ""}, {"PathWidth", tr("Path Width"), tr("Set the driving-path width.

Default (6.1 feet) matches the width of a 2019 Lexus ES 350."), ""}, {"RoadEdgesWidth", tr("Road Edges Width"), tr("Set the road-edge thickness.

Default matches half of the MUTCD lane-line width standard of 4 inches."), ""}, {"NavigationUI", tr("Navigation Widgets"), tr("Speed limits, and other navigation widgets."), "../../starpilot/assets/toggle_icons/icon_map.png"}, {"ClearNavOnOffroad", tr("Clear Route When Offroad"), tr("Clear the active navigation destination when the device goes offroad."), ""}, {"RoadNameUI", tr("Road Name"), tr("Display the road name at the bottom of the driving screen using data from \"OpenStreetMap (OSM)\"."), ""}, {"ShowSpeedLimits", tr("Show Speed Limits"), tr("Show speed limits in the top-left corner of the driving screen. Uses data from the car's dashboard (if supported) and \"OpenStreetMap (OSM)\"."), ""}, {"SLCMapboxFiller", tr("Show Speed Limits from Mapbox"), tr("Use Mapbox speed-limit data when no other source is available."), ""}, {"UseVienna", tr("Use Vienna-Style Speed Signs"), tr("Show Vienna-style (EU) speed-limit signs instead of MUTCD (US)."), ""}, {"QOLVisuals", tr("Quality of Life"), tr("Miscellaneous visual changes to fine-tune how the driving screen looks."), "../../starpilot/assets/toggle_icons/icon_quality_of_life.png"}, {"CameraView", tr("Camera View"), tr("Select the active camera view. This is purely a visual change and doesn't impact how openpilot drives!"), ""}, {"DriverCamera", tr("Show Driver Camera When In Reverse"), tr("Show the driver camera feed when the vehicle is in reverse."), ""}, {"StoppedTimer", tr("Stopped Timer"), tr("Show a timer when stopped in place of the current speed to indicate how long the vehicle has been stopped."), ""}, {"DisableWideRoad", tr("Disable Wide Road Camera"), QString("%1

%2").arg(tr("Only enable this if the wide camera is broken or for development!")).arg(tr("Disabling the wide camera may degrade driving performance and cause instability.

Requires a reboot to take effect.")), "../../starpilot/assets/toggle_icons/icon_advanced_device.png"} }; for (const auto &[param, title, desc, icon] : visualToggles) { AbstractControl *visualToggle; if (param == "AdvancedCustomUI") { StarPilotManageControl *advancedCustomUIToggle = new StarPilotManageControl(param, title, desc, icon); QObject::connect(advancedCustomUIToggle, &StarPilotManageControl::manageButtonClicked, [visualsLayout, advancedCustomPanel]() { visualsLayout->setCurrentWidget(advancedCustomPanel); }); visualToggle = advancedCustomUIToggle; } else if (param == "CustomUI") { StarPilotManageControl *customUIToggle = new StarPilotManageControl(param, title, desc, icon); QObject::connect(customUIToggle, &StarPilotManageControl::manageButtonClicked, [visualsLayout, customUIPanel]() { visualsLayout->setCurrentWidget(customUIPanel); }); visualToggle = customUIToggle; } else if (param == "PedalsOnUI") { std::vector pedalsToggles{"DynamicPedalsOnUI", "StaticPedalsOnUI"}; std::vector pedalsToggleNames{tr("Dynamic"), tr("Static")}; StarPilotButtonToggleControl *pedalsToggle = new StarPilotButtonToggleControl(param, title, desc, icon, pedalsToggles, pedalsToggleNames, true); QObject::connect(pedalsToggle, &StarPilotButtonToggleControl::buttonClicked, [this](int id) { if (id == 0) { params.putBool("StaticPedalsOnUI", false); } else if (id == 1) { params.putBool("DynamicPedalsOnUI", false); } }); visualToggle = pedalsToggle; } else if (param == "ModelUI") { StarPilotManageControl *modelUIToggle = new StarPilotManageControl(param, title, desc, icon); QObject::connect(modelUIToggle, &StarPilotManageControl::manageButtonClicked, [visualsLayout, modelUIPanel]() { visualsLayout->setCurrentWidget(modelUIPanel); }); visualToggle = modelUIToggle; } else if (param == "LaneLinesWidth" || param == "RoadEdgesWidth") { visualToggle = new StarPilotParamValueControl(param, title, desc, icon, 0, 24, tr(" inches")); } else if (param == "PathEdgeWidth") { std::map pathEdgeLabels; for (int i = 0; i <= 100; ++i) { pathEdgeLabels[i] = i == 0 ? tr("Off") : QString::number(i) + "%"; } visualToggle = new StarPilotParamValueControl(param, title, desc, icon, 0, 100, QString(), pathEdgeLabels); } else if (param == "PathWidth") { visualToggle = new StarPilotParamValueControl(param, title, desc, icon, 0, 10, tr(" feet"), std::map(), 0.1); } else if (param == "NavigationUI") { StarPilotManageControl *navigationUIToggle = new StarPilotManageControl(param, title, desc, icon); QObject::connect(navigationUIToggle, &StarPilotManageControl::manageButtonClicked, [visualsLayout, navigationUIPanel]() { visualsLayout->setCurrentWidget(navigationUIPanel); }); visualToggle = navigationUIToggle; } else if (param == "QOLVisuals") { StarPilotManageControl *qolToggle = new StarPilotManageControl(param, title, desc, icon); QObject::connect(qolToggle, &StarPilotManageControl::manageButtonClicked, [visualsLayout, qualityOfLifePanel]() { visualsLayout->setCurrentWidget(qualityOfLifePanel); }); visualToggle = qolToggle; } else if (param == "CameraView") { std::vector cameraOptions{tr("Auto"), tr("Driver"), tr("Standard"), tr("Wide"), tr("None")}; ButtonParamControl *cameraSelection = new ButtonParamControl(param, title, desc, icon, cameraOptions); visualToggle = cameraSelection; } else { visualToggle = new ParamControl(param, title, desc, icon); } toggles[param] = visualToggle; if (advancedCustomOnroadUIKeys.contains(param)) { advancedCustomList->addItem(visualToggle); } else if (customOnroadUIKeys.contains(param)) { customUIList->addItem(visualToggle); } else if (modelUIKeys.contains(param)) { modelUIList->addItem(visualToggle); } else if (navigationUIKeys.contains(param)) { navigationUIList->addItem(visualToggle); } else if (qualityOfLifeKeys.contains(param)) { qualityOfLifeList->addItem(visualToggle); } else { visualsList->addItem(visualToggle); parentKeys.insert(param); } if (StarPilotManageControl *frogPilotManageToggle = qobject_cast(visualToggle)) { QObject::connect(frogPilotManageToggle, &StarPilotManageControl::manageButtonClicked, [this]() { emit openSubPanel(); openDescriptions(forceOpenDescriptions, toggles); }); } QObject::connect(visualToggle, &AbstractControl::hideDescriptionEvent, [this]() { update(); }); QObject::connect(visualToggle, &AbstractControl::showDescriptionEvent, [this]() { update(); }); if (ToggleControl *toggle = qobject_cast(visualToggle)) { QObject::connect(toggle, &ToggleControl::toggleFlipped, this, []() { updateStarPilotToggles(); }); } } QSet forceUpdateKeys = {"HideLeadMarker", "ShowSpeedLimits"}; for (const QString &key : forceUpdateKeys) { QObject::connect(static_cast(toggles[key]), &ToggleControl::toggleFlipped, this, &StarPilotVisualsPanel::updateToggles); } static_cast(toggles["DisableWideRoad"])->setConfirmation(true, false); QObject::connect(static_cast(toggles["DisableWideRoad"]), &ToggleControl::toggleFlipped, [this](bool state) { if (StarPilotConfirmationDialog::toggleReboot(this)) { Hardware::reboot(); } }); openDescriptions(forceOpenDescriptions, toggles); QObject::connect(parent, &StarPilotSettingsWindow::closeSubPanel, [visualsLayout, visualsPanel, this] { openDescriptions(forceOpenDescriptions, toggles); visualsLayout->setCurrentWidget(visualsPanel); }); QObject::connect(parent, &StarPilotSettingsWindow::closeSubSubPanel, [this]() { openDescriptions(forceOpenDescriptions, toggles); }); QObject::connect(parent, &StarPilotSettingsWindow::updateMetric, this, &StarPilotVisualsPanel::updateMetric); } void StarPilotVisualsPanel::showEvent(QShowEvent *event) { updateToggles(); } void StarPilotVisualsPanel::updateMetric(bool metric, bool bootRun) { static bool previousMetric; if (metric != previousMetric && !bootRun) { double distanceConversion = metric ? FOOT_TO_METER : METER_TO_FOOT; double smallDistanceConversion = metric ? INCH_TO_CM : CM_TO_INCH; params.putIntNonBlocking("LaneLinesWidth", params.getInt("LaneLinesWidth") * smallDistanceConversion); params.putIntNonBlocking("RoadEdgesWidth", params.getInt("RoadEdgesWidth") * smallDistanceConversion); params.putFloatNonBlocking("PathWidth", params.getFloat("PathWidth") * distanceConversion); } previousMetric = metric; static std::map imperialDistanceLabels; static std::map imperialSmallDistanceLabels; static std::map metricDistanceLabels; static std::map metricSmallDistanceLabels; static bool labelsInitialized = false; if (!labelsInitialized) { for (int i = 0; i <= 10; ++i) { imperialDistanceLabels[i] = i == 0 ? tr("Off") : i == 1 ? QString::number(i) + tr(" foot") : QString::number(i) + tr(" feet"); } for (int i = 0; i <= 24; ++i) { imperialSmallDistanceLabels[i] = i == 0 ? tr("Off") : i == 1 ? QString::number(i) + tr(" inch") : QString::number(i) + tr(" inches"); } for (float i = 0.0f; i <= 3.0f; i += 0.1f) { metricDistanceLabels[i] = i == 0.0f ? tr("Off") : i == 1.0 ? QString::number(i) + tr(" meter") : QString::number(i, 'f', 1) + tr(" meters"); } for (int i = 0; i <= 60; ++i) { metricSmallDistanceLabels[i] = i == 0 ? tr("Off") : i == 1 ? QString::number(i) + tr(" centimeter") : QString::number(i) + tr(" centimeters"); } labelsInitialized = true; } StarPilotParamValueControl *laneLinesWidthToggle = static_cast(toggles["LaneLinesWidth"]); StarPilotParamValueControl *pathWidthToggle = static_cast(toggles["PathWidth"]); StarPilotParamValueControl *roadEdgesWidthToggle = static_cast(toggles["RoadEdgesWidth"]); if (metric) { laneLinesWidthToggle->setDescription(tr("Set the lane-line thickness.

Default matches the MUTCD lane-line width standard of 10 centimeters.")); pathWidthToggle->setDescription(tr("Set the driving-path width.

Default (1.9 meters) matches the width of a 2019 Lexus ES 350.")); roadEdgesWidthToggle->setDescription(tr("Set the road-edge thickness.

Default matches half of the MUTCD lane-line width standard of 10 centimeters.")); laneLinesWidthToggle->updateControl(0, 60, metricSmallDistanceLabels); roadEdgesWidthToggle->updateControl(0, 60, metricSmallDistanceLabels); pathWidthToggle->updateControl(0, 3, metricDistanceLabels); } else { laneLinesWidthToggle->setDescription(tr("Set the lane-line thickness.

Default matches the MUTCD lane-line width standard of 4 inches.")); pathWidthToggle->setDescription(tr("Set the driving-path width.

Default (6.1 feet) matches the width of a 2019 Lexus ES 350.")); roadEdgesWidthToggle->setDescription(tr("Set the road-edge thickness.

Default matches half of the MUTCD lane-line width standard of 4 inches.")); laneLinesWidthToggle->updateControl(0, 24, imperialSmallDistanceLabels); roadEdgesWidthToggle->updateControl(0, 24, imperialSmallDistanceLabels); pathWidthToggle->updateControl(0, 10, imperialDistanceLabels); } } void StarPilotVisualsPanel::updateToggles() { const bool showAllToggles = parent->showAllTogglesEnabled(); for (auto &[key, toggle] : toggles) { if (parentKeys.contains(key)) { toggle->setVisible(showAllToggles); } } for (auto &[key, toggle] : toggles) { if (parentKeys.contains(key)) { continue; } bool setVisible = showAllToggles || parent->tuningLevel >= parent->starpilotToggleLevels[key].toDouble(); if (!showAllToggles) { if (key == "AccelerationPath") { setVisible &= parent->hasOpenpilotLongitudinal; } else if (key == "BlindSpotPath") { setVisible &= parent->hasBSM; } else if (key == "HideLeadMarker") { setVisible &= parent->hasOpenpilotLongitudinal; } else if (key == "HideSpeedLimit") { setVisible &= parent->hasOpenpilotLongitudinal && params.getBool("SpeedLimitController"); } else if (key == "OnroadDistanceButton") { setVisible &= parent->hasOpenpilotLongitudinal; } else if (key == "PedalsOnUI") { setVisible &= parent->hasOpenpilotLongitudinal; } else if (key == "ShowSpeedLimits") { setVisible &= !params.getBool("SpeedLimitController") || !parent->hasOpenpilotLongitudinal; } else if (key == "SLCMapboxFiller") { setVisible &= params.getBool("ShowSpeedLimits"); setVisible &= !params.getBool("SpeedLimitController") || !parent->hasOpenpilotLongitudinal; setVisible &= !params.get("MapboxSecretKey").empty(); } else if (key == "UseVienna") { setVisible &= params.getBool("ShowSpeedLimits") || params.getBool("SpeedLimitController"); } } toggle->setVisible(setVisible); if (setVisible) { if (advancedCustomOnroadUIKeys.contains(key)) { toggles["AdvancedCustomUI"]->setVisible(true); } else if (customOnroadUIKeys.contains(key)) { toggles["CustomUI"]->setVisible(true); } else if (modelUIKeys.contains(key)) { toggles["ModelUI"]->setVisible(true); } else if (navigationUIKeys.contains(key)) { toggles["NavigationUI"]->setVisible(true); } else if (qualityOfLifeKeys.contains(key)) { toggles["QOLVisuals"]->setVisible(true); } } } openDescriptions(forceOpenDescriptions, toggles); update(); }