from enum import IntEnum
from openpilot.common.conversions import Conversions as CV
from openpilot.common.params import Params
from openpilot.selfdrive.ui.ui_state import ui_state
from openpilot.system.ui.widgets import Widget
from openpilot.system.ui.widgets.list_view import ListItem, ToggleAction
from openpilot.system.ui.widgets.scroller_tici import Scroller
from openpilot.frogpilot.common.frogpilot_variables import update_frogpilot_toggles
from openpilot.frogpilot.system.ui.widgets.frogpilot_controls import (
FrogPilotButtonsControl,
FrogPilotButtonToggleControl,
FrogPilotManageControl,
FrogPilotParamValueControl,
)
ADVANCED_CUSTOM_ONROAD_UI_KEYS = {
"HideAlerts",
"HideLeadMarker",
"HideMaxSpeed",
"HideSpeed",
"HideSpeedLimit",
"WheelSpeed",
}
CUSTOM_ONROAD_UI_KEYS = {
"AccelerationPath",
"AdjacentPath",
"BlindSpotPath",
"Compass",
"OnroadDistanceButton",
"PedalsOnUI",
"RotatingWheel",
}
MODEL_UI_KEYS = {
"DynamicPathWidth",
"LaneLinesWidth",
"PathEdgeWidth",
"PathWidth",
"RoadEdgesWidth",
}
NAVIGATION_UI_KEYS = {
"RoadNameUI",
"ShowSpeedLimits",
"SLCMapboxFiller",
"UseVienna",
}
QUALITY_OF_LIFE_KEYS = {
"CameraView",
"DriverCamera",
"StoppedTimer",
}
INCH_TO_CM = 2.54
CM_TO_INCH = 1.0 / INCH_TO_CM
FOOT_TO_METER = CV.FOOT_TO_METER
METER_TO_FOOT = CV.METER_TO_FOOT
class SubPanel(IntEnum):
MAIN = 0
ADVANCED_CUSTOM_UI = 1
CUSTOM_UI = 2
MODEL_UI = 3
NAVIGATION_UI = 4
QUALITY_OF_LIFE = 5
def build_imperial_small_distance_labels():
"""Build labels for inches (0-24)."""
labels = {}
for i in range(25):
if i == 0:
labels[i] = "Off"
elif i == 1:
labels[i] = "1 inch"
else:
labels[i] = f"{i} inches"
return labels
def build_metric_small_distance_labels():
"""Build labels for centimeters (0-60)."""
labels = {}
for i in range(61):
if i == 0:
labels[i] = "Off"
elif i == 1:
labels[i] = "1 centimeter"
else:
labels[i] = f"{i} centimeters"
return labels
def build_imperial_distance_labels():
"""Build labels for feet (0-10)."""
labels = {}
for i in range(11):
if i == 0:
labels[i] = "Off"
elif i == 1:
labels[i] = "1 foot"
else:
labels[i] = f"{i} feet"
return labels
def build_metric_distance_labels():
"""Build labels for meters (0.0-3.0 in 0.1 steps)."""
labels = {}
for i in range(31):
val = i / 10.0
if val == 0.0:
labels[val] = "Off"
elif val == 1.0:
labels[val] = "1 meter"
else:
labels[val] = f"{val:.1f} meters"
return labels
def build_path_edge_labels():
"""Build labels for path edge width (0-100%)."""
labels = {}
for i in range(101):
if i == 0:
labels[i] = "Off"
else:
labels[i] = f"{i}%"
return labels
class FrogPilotVisualsPanel(Widget):
def __init__(self):
super().__init__()
self._current_panel = SubPanel.MAIN
self._is_metric = False
self._params = Params()
self._toggles = {}
self._tuning_level = 0
# Car capabilities
self._has_bsm = False
self._has_openpilot_longitudinal = False
# Build all panels
self._build_main_panel()
self._build_advanced_custom_ui_panel()
self._build_custom_ui_panel()
self._build_model_ui_panel()
self._build_navigation_ui_panel()
self._build_quality_of_life_panel()
ui_state.add_offroad_transition_callback(self._on_offroad_transition)
def _on_offroad_transition(self):
previous_metric = self._is_metric
self._is_metric = self._params.get_bool("IsMetric")
if self._is_metric != previous_metric:
self._convert_metric_values(previous_metric)
self._update_metric()
self._load_car_capabilities()
self._update_toggles()
def _simple_toggle(self, param: str, state: bool):
self._params.put_bool(param, state)
update_frogpilot_toggles()
# ==================== MAIN PANEL ====================
def _build_main_panel(self):
self._advanced_custom_ui_control = FrogPilotManageControl(
"AdvancedCustomUI",
"Advanced UI Controls",
"Advanced visual changes to fine-tune how the driving screen looks.",
"../../frogpilot/assets/toggle_icons/icon_advanced_device.png",
)
self._advanced_custom_ui_control.set_manage_callback(self._open_advanced_custom_ui)
self._custom_ui_control = FrogPilotManageControl(
"CustomUI",
"Driving Screen Widgets",
"Custom FrogPilot widgets for the driving screen.",
"../assets/icons/calibration.png",
)
self._custom_ui_control.set_manage_callback(self._open_custom_ui)
self._model_ui_control = FrogPilotManageControl(
"ModelUI",
"Model UI",
"Model visualizations for the driving path, lane lines, path edges, and road edges.",
"../../frogpilot/assets/toggle_icons/icon_road.png",
)
self._model_ui_control.set_manage_callback(self._open_model_ui)
self._navigation_ui_control = FrogPilotManageControl(
"NavigationUI",
"Navigation Widgets",
"Speed limits, and other navigation widgets.",
"../../frogpilot/assets/toggle_icons/icon_map.png",
)
self._navigation_ui_control.set_manage_callback(self._open_navigation_ui)
self._qol_visuals_control = FrogPilotManageControl(
"QOLVisuals",
"Quality of Life",
"Miscellaneous visual changes to fine-tune how the driving screen looks.",
"../../frogpilot/assets/toggle_icons/icon_quality_of_life.png",
)
self._qol_visuals_control.set_manage_callback(self._open_quality_of_life)
main_items = [
self._advanced_custom_ui_control,
self._custom_ui_control,
self._model_ui_control,
self._navigation_ui_control,
self._qol_visuals_control,
]
self._toggles["AdvancedCustomUI"] = self._advanced_custom_ui_control
self._toggles["CustomUI"] = self._custom_ui_control
self._toggles["ModelUI"] = self._model_ui_control
self._toggles["NavigationUI"] = self._navigation_ui_control
self._toggles["QOLVisuals"] = self._qol_visuals_control
self._main_scroller = Scroller(main_items, line_separator=True, spacing=0)
# ==================== ADVANCED CUSTOM UI PANEL ====================
def _build_advanced_custom_ui_panel(self):
self._hide_speed_item = ListItem(
title="Hide Current Speed",
description="Hide the current speed from the driving screen.",
action_item=ToggleAction(
initial_state=self._params.get_bool("HideSpeed"),
callback=lambda state: self._simple_toggle("HideSpeed", state),
),
)
self._hide_lead_marker_item = ListItem(
title="Hide Lead Marker",
description="Hide the lead-vehicle marker from the driving screen.",
action_item=ToggleAction(
initial_state=self._params.get_bool("HideLeadMarker"),
callback=lambda state: self._simple_toggle("HideLeadMarker", state),
),
)
self._hide_max_speed_item = ListItem(
title="Hide Max Speed",
description="Hide the max speed from the driving screen.",
action_item=ToggleAction(
initial_state=self._params.get_bool("HideMaxSpeed"),
callback=lambda state: self._simple_toggle("HideMaxSpeed", state),
),
)
self._hide_alerts_item = ListItem(
title="Hide Non-Critical Alerts",
description="Hide non-critical alerts from the driving screen.",
action_item=ToggleAction(
initial_state=self._params.get_bool("HideAlerts"),
callback=lambda state: self._simple_toggle("HideAlerts", state),
),
)
self._hide_speed_limit_item = ListItem(
title="Hide Speed Limits",
description="Hide posted speed limits from the driving screen.",
action_item=ToggleAction(
initial_state=self._params.get_bool("HideSpeedLimit"),
callback=lambda state: self._simple_toggle("HideSpeedLimit", state),
),
)
self._wheel_speed_item = ListItem(
title="Use Wheel Speed",
description="Use the vehicle's wheel speed instead of the cluster speed. This is purely a visual change and doesn't impact how openpilot drives!",
action_item=ToggleAction(
initial_state=self._params.get_bool("WheelSpeed"),
callback=lambda state: self._simple_toggle("WheelSpeed", state),
),
)
advanced_items = [
self._hide_speed_item,
self._hide_lead_marker_item,
self._hide_max_speed_item,
self._hide_alerts_item,
self._hide_speed_limit_item,
self._wheel_speed_item,
]
self._toggles["HideSpeed"] = self._hide_speed_item
self._toggles["HideLeadMarker"] = self._hide_lead_marker_item
self._toggles["HideMaxSpeed"] = self._hide_max_speed_item
self._toggles["HideAlerts"] = self._hide_alerts_item
self._toggles["HideSpeedLimit"] = self._hide_speed_limit_item
self._toggles["WheelSpeed"] = self._wheel_speed_item
self._advanced_custom_ui_scroller = Scroller(advanced_items, line_separator=True, spacing=0)
# ==================== CUSTOM UI PANEL ====================
def _build_custom_ui_panel(self):
self._acceleration_path_item = ListItem(
title="Acceleration Path",
description="Color the driving path by planned acceleration and braking.",
action_item=ToggleAction(
initial_state=self._params.get_bool("AccelerationPath"),
callback=lambda state: self._simple_toggle("AccelerationPath", state),
),
)
self._adjacent_path_item = ListItem(
title="Adjacent Lanes",
description="Show the driving paths for the left and right lanes.",
action_item=ToggleAction(
initial_state=self._params.get_bool("AdjacentPath"),
callback=lambda state: self._simple_toggle("AdjacentPath", state),
),
)
self._blind_spot_path_item = ListItem(
title="Blind Spot Path",
description="Show a red path when a vehicle is in that lane's blind spot.",
action_item=ToggleAction(
initial_state=self._params.get_bool("BlindSpotPath"),
callback=lambda state: self._simple_toggle("BlindSpotPath", state),
),
)
self._compass_item = ListItem(
title="Compass",
description="Show the current driving direction with a simple on-screen compass.",
action_item=ToggleAction(
initial_state=self._params.get_bool("Compass"),
callback=lambda state: self._simple_toggle("Compass", state),
),
)
self._onroad_distance_button_item = ListItem(
title="Driving Personality Button",
description="Control and view the current driving personality via a driving screen widget.",
action_item=ToggleAction(
initial_state=self._params.get_bool("OnroadDistanceButton"),
callback=lambda state: self._simple_toggle("OnroadDistanceButton", state),
),
)
# PedalsOnUI with Dynamic/Static mutually exclusive options
self._pedals_on_ui_control = FrogPilotButtonToggleControl(
"PedalsOnUI",
"Gas / Brake Pedal Indicators",
"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",
"",
button_params=["DynamicPedalsOnUI", "StaticPedalsOnUI"],
button_texts=["Dynamic", "Static"],
)
self._pedals_on_ui_control.set_button_callback(self._on_pedals_button_click)
self._rotating_wheel_item = ListItem(
title="Rotating Steering Wheel",
description="Rotate the driving screen wheel with the physical steering wheel.",
action_item=ToggleAction(
initial_state=self._params.get_bool("RotatingWheel"),
callback=lambda state: self._simple_toggle("RotatingWheel", state),
),
)
custom_items = [
self._acceleration_path_item,
self._adjacent_path_item,
self._blind_spot_path_item,
self._compass_item,
self._onroad_distance_button_item,
self._pedals_on_ui_control,
self._rotating_wheel_item,
]
self._toggles["AccelerationPath"] = self._acceleration_path_item
self._toggles["AdjacentPath"] = self._adjacent_path_item
self._toggles["BlindSpotPath"] = self._blind_spot_path_item
self._toggles["Compass"] = self._compass_item
self._toggles["OnroadDistanceButton"] = self._onroad_distance_button_item
self._toggles["PedalsOnUI"] = self._pedals_on_ui_control
self._toggles["RotatingWheel"] = self._rotating_wheel_item
self._custom_ui_scroller = Scroller(custom_items, line_separator=True, spacing=0)
def _on_pedals_button_click(self, button_id: int):
"""Handle mutually exclusive Dynamic/Static pedals options."""
if button_id == 0:
# Dynamic clicked - disable Static
self._params.put_bool("StaticPedalsOnUI", False)
elif button_id == 1:
# Static clicked - disable Dynamic
self._params.put_bool("DynamicPedalsOnUI", False)
update_frogpilot_toggles()
# ==================== MODEL UI PANEL ====================
def _build_model_ui_panel(self):
self._dynamic_path_width_item = ListItem(
title="Dynamic Path Width",
description="Change the path width based on engagement.
Fully Engaged: 100%
Always On Lateral: 75%
Disengaged: 50%",
action_item=ToggleAction(
initial_state=self._params.get_bool("DynamicPathWidth"),
callback=lambda state: self._simple_toggle("DynamicPathWidth", state),
),
)
# Lane Lines Width - 0-24 inches or 0-60 cm
self._lane_lines_width_control = FrogPilotParamValueControl(
"LaneLinesWidth",
"Lane Lines Width",
"Set the lane-line thickness.
Default matches the MUTCD lane-line width standard of 4 inches.",
"",
min_value=0,
max_value=24,
unit=" inches",
labels=build_imperial_small_distance_labels(),
)
# Path Edge Width - 0-100%
self._path_edge_width_control = FrogPilotParamValueControl(
"PathEdgeWidth",
"Path Edges Width",
"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",
"",
min_value=0,
max_value=100,
unit="",
labels=build_path_edge_labels(),
)
# Path Width - 0-10 feet or 0-3 meters (0.1 step)
self._path_width_control = FrogPilotParamValueControl(
"PathWidth",
"Path Width",
"Set the driving-path width.
Default (6.1 feet) matches the width of a 2019 Lexus ES 350.",
"",
min_value=0,
max_value=10,
unit=" feet",
labels=build_imperial_distance_labels(),
step=0.1,
)
# Road Edges Width - 0-24 inches or 0-60 cm
self._road_edges_width_control = FrogPilotParamValueControl(
"RoadEdgesWidth",
"Road Edges Width",
"Set the road-edge thickness.
Default matches half of the MUTCD lane-line width standard of 4 inches.",
"",
min_value=0,
max_value=24,
unit=" inches",
labels=build_imperial_small_distance_labels(),
)
model_items = [
self._dynamic_path_width_item,
self._lane_lines_width_control,
self._path_edge_width_control,
self._path_width_control,
self._road_edges_width_control,
]
self._toggles["DynamicPathWidth"] = self._dynamic_path_width_item
self._toggles["LaneLinesWidth"] = self._lane_lines_width_control
self._toggles["PathEdgeWidth"] = self._path_edge_width_control
self._toggles["PathWidth"] = self._path_width_control
self._toggles["RoadEdgesWidth"] = self._road_edges_width_control
self._model_ui_scroller = Scroller(model_items, line_separator=True, spacing=0)
# ==================== NAVIGATION UI PANEL ====================
def _build_navigation_ui_panel(self):
self._road_name_ui_item = ListItem(
title="Road Name",
description="Display the road name at the bottom of the driving screen using data from \"OpenStreetMap (OSM)\".",
action_item=ToggleAction(
initial_state=self._params.get_bool("RoadNameUI"),
callback=lambda state: self._simple_toggle("RoadNameUI", state),
),
)
self._show_speed_limits_item = ListItem(
title="Show Speed Limits",
description="Show speed limits in the top-left corner of the driving screen. Uses data from the car's dashboard (if supported) and \"OpenStreetMap (OSM)\".",
action_item=ToggleAction(
initial_state=self._params.get_bool("ShowSpeedLimits"),
callback=lambda state: self._on_show_speed_limits_toggle(state),
),
)
self._slc_mapbox_filler_item = ListItem(
title="Show Speed Limits from Mapbox",
description="Use Mapbox speed-limit data when no other source is available.",
action_item=ToggleAction(
initial_state=self._params.get_bool("SLCMapboxFiller"),
callback=lambda state: self._simple_toggle("SLCMapboxFiller", state),
),
)
self._use_vienna_item = ListItem(
title="Use Vienna-Style Speed Signs",
description="Show Vienna-style (EU) speed-limit signs instead of MUTCD (US).",
action_item=ToggleAction(
initial_state=self._params.get_bool("UseVienna"),
callback=lambda state: self._simple_toggle("UseVienna", state),
),
)
navigation_items = [
self._road_name_ui_item,
self._show_speed_limits_item,
self._slc_mapbox_filler_item,
self._use_vienna_item,
]
self._toggles["RoadNameUI"] = self._road_name_ui_item
self._toggles["ShowSpeedLimits"] = self._show_speed_limits_item
self._toggles["SLCMapboxFiller"] = self._slc_mapbox_filler_item
self._toggles["UseVienna"] = self._use_vienna_item
self._navigation_ui_scroller = Scroller(navigation_items, line_separator=True, spacing=0)
def _on_show_speed_limits_toggle(self, state: bool):
"""Handle ShowSpeedLimits toggle and update dependent visibility."""
self._params.put_bool("ShowSpeedLimits", state)
update_frogpilot_toggles()
self._update_toggles()
# ==================== QUALITY OF LIFE PANEL ====================
def _build_quality_of_life_panel(self):
# Camera View - 4 options: Auto, Driver, Standard, Wide
self._camera_view_control = FrogPilotButtonsControl(
"CameraView",
"Camera View",
"Select the active camera view. This is purely a visual change and doesn't impact how openpilot drives!",
"",
button_texts=["AUTO", "DRIVER", "STANDARD", "WIDE"],
checkable=True,
exclusive=True,
)
self._camera_view_control.set_click_callback(self._on_camera_view_click)
self._update_camera_view_selection()
self._driver_camera_item = ListItem(
title="Show Driver Camera When In Reverse",
description="Show the driver camera feed when the vehicle is in reverse.",
action_item=ToggleAction(
initial_state=self._params.get_bool("DriverCamera"),
callback=lambda state: self._simple_toggle("DriverCamera", state),
),
)
self._stopped_timer_item = ListItem(
title="Stopped Timer",
description="Show a timer when stopped in place of the current speed to indicate how long the vehicle has been stopped.",
action_item=ToggleAction(
initial_state=self._params.get_bool("StoppedTimer"),
callback=lambda state: self._simple_toggle("StoppedTimer", state),
),
)
qol_items = [
self._camera_view_control,
self._driver_camera_item,
self._stopped_timer_item,
]
self._toggles["CameraView"] = self._camera_view_control
self._toggles["DriverCamera"] = self._driver_camera_item
self._toggles["StoppedTimer"] = self._stopped_timer_item
self._qol_scroller = Scroller(qol_items, line_separator=True, spacing=0)
def _on_camera_view_click(self, button_id: int):
"""Handle camera view selection (0=Auto, 1=Driver, 2=Standard, 3=Wide)."""
self._params.put_int("CameraView", button_id)
self._update_camera_view_selection()
update_frogpilot_toggles()
def _update_camera_view_selection(self):
"""Update the camera view button selection state."""
current = self._params.get_int("CameraView") or 0
if hasattr(self._camera_view_control, "set_checked_button"):
self._camera_view_control.set_checked_button(current)
# ==================== PANEL NAVIGATION ====================
def _open_advanced_custom_ui(self):
self._current_panel = SubPanel.ADVANCED_CUSTOM_UI
def _open_custom_ui(self):
self._current_panel = SubPanel.CUSTOM_UI
def _open_model_ui(self):
self._current_panel = SubPanel.MODEL_UI
def _open_navigation_ui(self):
self._current_panel = SubPanel.NAVIGATION_UI
def _open_quality_of_life(self):
self._current_panel = SubPanel.QUALITY_OF_LIFE
def _close_sub_panel(self):
self._current_panel = SubPanel.MAIN
# ==================== METRIC CONVERSION ====================
def _convert_metric_values(self, was_metric: bool):
"""Convert stored values when metric setting changes."""
if was_metric:
# Converting from metric to imperial
small_conversion = CM_TO_INCH
distance_conversion = METER_TO_FOOT
else:
# Converting from imperial to metric
small_conversion = INCH_TO_CM
distance_conversion = FOOT_TO_METER
# Convert lane lines width (inches <-> cm)
lane_lines_width = self._params.get_int("LaneLinesWidth") or 0
self._params.put_int("LaneLinesWidth", int(lane_lines_width * small_conversion))
# Convert road edges width (inches <-> cm)
road_edges_width = self._params.get_int("RoadEdgesWidth") or 0
self._params.put_int("RoadEdgesWidth", int(road_edges_width * small_conversion))
# Convert path width (feet <-> meters)
path_width = self._params.get_float("PathWidth") or 0.0
self._params.put_float("PathWidth", path_width * distance_conversion)
def _update_metric(self):
"""Update control labels and ranges based on metric setting."""
if self._is_metric:
# Metric: cm for small distances, meters for path width
self._lane_lines_width_control.set_description(
"Set the lane-line thickness.
Default matches the MUTCD lane-line width standard of 10 centimeters."
)
self._path_width_control.set_description(
"Set the driving-path width.
Default (1.9 meters) matches the width of a 2019 Lexus ES 350."
)
self._road_edges_width_control.set_description(
"Set the road-edge thickness.
Default matches half of the MUTCD lane-line width standard of 10 centimeters."
)
if hasattr(self._lane_lines_width_control, "update_control"):
self._lane_lines_width_control.update_control(0, 60, build_metric_small_distance_labels())
if hasattr(self._road_edges_width_control, "update_control"):
self._road_edges_width_control.update_control(0, 60, build_metric_small_distance_labels())
if hasattr(self._path_width_control, "update_control"):
self._path_width_control.update_control(0, 3, build_metric_distance_labels())
else:
# Imperial: inches for small distances, feet for path width
self._lane_lines_width_control.set_description(
"Set the lane-line thickness.
Default matches the MUTCD lane-line width standard of 4 inches."
)
self._path_width_control.set_description(
"Set the driving-path width.
Default (6.1 feet) matches the width of a 2019 Lexus ES 350."
)
self._road_edges_width_control.set_description(
"Set the road-edge thickness.
Default matches half of the MUTCD lane-line width standard of 4 inches."
)
if hasattr(self._lane_lines_width_control, "update_control"):
self._lane_lines_width_control.update_control(0, 24, build_imperial_small_distance_labels())
if hasattr(self._road_edges_width_control, "update_control"):
self._road_edges_width_control.update_control(0, 24, build_imperial_small_distance_labels())
if hasattr(self._path_width_control, "update_control"):
self._path_width_control.update_control(0, 10, build_imperial_distance_labels())
# ==================== VISIBILITY UPDATES ====================
def _load_car_capabilities(self):
"""Load car capabilities from frogpilot variables."""
try:
from openpilot.frogpilot.common.frogpilot_variables import get_frogpilot_toggles
toggles = get_frogpilot_toggles()
self._has_bsm = getattr(toggles, "has_bsm", False)
self._has_openpilot_longitudinal = getattr(toggles, "has_openpilot_longitudinal", False)
except Exception:
self._has_bsm = False
self._has_openpilot_longitudinal = False
def _update_toggles(self):
"""Update toggle visibility based on tuning level and car capabilities."""
self._tuning_level = self._params.get_int("TuningLevel") or 0
# Load toggle levels
try:
import json
toggle_levels_str = self._params.get("FrogPilotTogglesLevels", encoding="utf-8") or "{}"
toggle_levels = json.loads(toggle_levels_str)
except Exception:
toggle_levels = {}
# First, hide all parent toggles
for key in ["AdvancedCustomUI", "CustomUI", "ModelUI", "NavigationUI", "QOLVisuals"]:
if key in self._toggles and hasattr(self._toggles[key], "set_visible"):
self._toggles[key].set_visible(False)
# Check which child toggles are visible and show their parents accordingly
slc_enabled = self._params.get_bool("SpeedLimitController")
show_speed_limits = self._params.get_bool("ShowSpeedLimits")
mapbox_key = self._params.get("MapboxSecretKey", encoding="utf-8") or ""
# Advanced Custom UI children
advanced_visible = False
for key in ADVANCED_CUSTOM_ONROAD_UI_KEYS:
if key not in self._toggles:
continue
toggle_level = toggle_levels.get(key, 0)
visible = self._tuning_level >= toggle_level
# Special visibility conditions
if key == "HideLeadMarker":
visible = visible and self._has_openpilot_longitudinal
elif key == "HideSpeedLimit":
visible = visible and self._has_openpilot_longitudinal and slc_enabled
if hasattr(self._toggles[key], "set_visible"):
self._toggles[key].set_visible(visible)
if visible:
advanced_visible = True
if advanced_visible and hasattr(self._toggles["AdvancedCustomUI"], "set_visible"):
self._toggles["AdvancedCustomUI"].set_visible(True)
# Custom UI children
custom_visible = False
for key in CUSTOM_ONROAD_UI_KEYS:
if key not in self._toggles:
continue
toggle_level = toggle_levels.get(key, 0)
visible = self._tuning_level >= toggle_level
# Special visibility conditions
if key == "AccelerationPath":
visible = visible and self._has_openpilot_longitudinal
elif key == "BlindSpotPath":
visible = visible and self._has_bsm
elif key == "OnroadDistanceButton":
visible = visible and self._has_openpilot_longitudinal
elif key == "PedalsOnUI":
visible = visible and self._has_openpilot_longitudinal
if hasattr(self._toggles[key], "set_visible"):
self._toggles[key].set_visible(visible)
if visible:
custom_visible = True
if custom_visible and hasattr(self._toggles["CustomUI"], "set_visible"):
self._toggles["CustomUI"].set_visible(True)
# Model UI children
model_visible = False
for key in MODEL_UI_KEYS:
if key not in self._toggles:
continue
toggle_level = toggle_levels.get(key, 0)
visible = self._tuning_level >= toggle_level
if hasattr(self._toggles[key], "set_visible"):
self._toggles[key].set_visible(visible)
if visible:
model_visible = True
if model_visible and hasattr(self._toggles["ModelUI"], "set_visible"):
self._toggles["ModelUI"].set_visible(True)
# Navigation UI children
nav_visible = False
for key in NAVIGATION_UI_KEYS:
if key not in self._toggles:
continue
toggle_level = toggle_levels.get(key, 0)
visible = self._tuning_level >= toggle_level
# Special visibility conditions
if key == "ShowSpeedLimits":
# ShowSpeedLimits visible when SpeedLimitController is OFF or no longitudinal
visible = visible and (not slc_enabled or not self._has_openpilot_longitudinal)
elif key == "SLCMapboxFiller":
# Visible if ShowSpeedLimits enabled, SLC off (or no longitudinal), and Mapbox key present
visible = visible and show_speed_limits
visible = visible and (not slc_enabled or not self._has_openpilot_longitudinal)
visible = visible and bool(mapbox_key)
elif key == "UseVienna":
# Visible if either ShowSpeedLimits or SpeedLimitController is enabled
visible = visible and (show_speed_limits or slc_enabled)
if hasattr(self._toggles[key], "set_visible"):
self._toggles[key].set_visible(visible)
if visible:
nav_visible = True
if nav_visible and hasattr(self._toggles["NavigationUI"], "set_visible"):
self._toggles["NavigationUI"].set_visible(True)
# Quality of Life children
qol_visible = False
for key in QUALITY_OF_LIFE_KEYS:
if key not in self._toggles:
continue
toggle_level = toggle_levels.get(key, 0)
visible = self._tuning_level >= toggle_level
if hasattr(self._toggles[key], "set_visible"):
self._toggles[key].set_visible(visible)
if visible:
qol_visible = True
if qol_visible and hasattr(self._toggles["QOLVisuals"], "set_visible"):
self._toggles["QOLVisuals"].set_visible(True)
# ==================== LIFECYCLE ====================
def show_event(self):
super().show_event()
self._main_scroller.show_event()
self._is_metric = self._params.get_bool("IsMetric")
self._update_metric()
self._load_car_capabilities()
self._update_toggles()
self._update_camera_view_selection()
def hide_event(self):
super().hide_event()
self._current_panel = SubPanel.MAIN
def _render(self, rect):
if self._current_panel == SubPanel.ADVANCED_CUSTOM_UI:
self._advanced_custom_ui_scroller.render(rect)
elif self._current_panel == SubPanel.CUSTOM_UI:
self._custom_ui_scroller.render(rect)
elif self._current_panel == SubPanel.MODEL_UI:
self._model_ui_scroller.render(rect)
elif self._current_panel == SubPanel.NAVIGATION_UI:
self._navigation_ui_scroller.render(rect)
elif self._current_panel == SubPanel.QUALITY_OF_LIFE:
self._qol_scroller.render(rect)
else:
self._main_scroller.render(rect)