This commit is contained in:
royjr
2026-07-15 15:49:52 -04:00
parent 6ad21b7da8
commit a11d11c0d3
5 changed files with 138 additions and 89 deletions
+5 -1
View File
@@ -34,7 +34,11 @@ class MainLayout(Widget):
# Initialize layouts
self._home_layout = HomeLayout()
self._home_body_layout = BodyLayout()
self._layouts = {MainState.HOME: self._home_layout, MainState.SETTINGS: SettingsLayout(), MainState.ONROAD: AugmentedRoadView()}
self._layouts = {
MainState.HOME: self._home_layout,
MainState.SETTINGS: SettingsLayout(),
MainState.ONROAD: AugmentedRoadView(radar_tracks_settings_callback=lambda: self.open_settings(PanelType.TOGGLES)),
}
self._sidebar_rect = rl.Rectangle(0, 0, 0, 0)
self._content_rect = rl.Rectangle(0, 0, 0, 0)
+16
View File
@@ -106,7 +106,18 @@ class TogglesLayout(Widget):
icon="speed_limit.png"
)
self._radar_tracks_setting = None
self._toggles = {}
if gui_app.sunnypilot_ui():
self._radar_tracks_setting = multiple_button_item(
lambda: tr("Radar Tracks"),
"",
buttons=[lambda: tr("Off"), lambda: tr("Lead Only"), lambda: tr("Full Radar")],
button_width=250,
callback=self._set_radar_tracks,
selected_index=self._params.get("RadarTracks", return_default=True),
)
self._toggles["RadarTracks"] = self._radar_tracks_setting
self._locked_toggles = set()
for param, (title, desc, icon, needs_restart) in self._toggle_defs.items():
toggle = toggle_item(
@@ -203,6 +214,8 @@ class TogglesLayout(Widget):
# refresh toggles from params to mirror external changes
for param in self._toggle_defs:
self._toggles[param].action_item.set_state(self._params.get_bool(param))
if self._radar_tracks_setting is not None:
self._radar_tracks_setting.action_item.set_selected_button(self._params.get("RadarTracks", return_default=True))
# these toggles need restart, block while engaged
for toggle_def in self._toggle_defs:
@@ -247,3 +260,6 @@ class TogglesLayout(Widget):
def _set_longitudinal_personality(self, button_index: int):
self._params.put("LongitudinalPersonality", button_index, block=True)
def _set_radar_tracks(self, button_index: int):
self._params.put("RadarTracks", button_index, block=True)
@@ -10,7 +10,7 @@ from openpilot.selfdrive.ui.mici.onroad.hud_renderer import HudRenderer
from openpilot.selfdrive.ui.mici.onroad.model_renderer import ModelRenderer
from openpilot.selfdrive.ui.mici.onroad.confidence_ball import ConfidenceBall
from openpilot.selfdrive.ui.mici.onroad.cameraview import CameraView
from openpilot.selfdrive.ui.sunnypilot.onroad.radar_tracks import format_radar_tracks_onroad_columns
from openpilot.selfdrive.ui.sunnypilot.onroad.radar_tracks import RadarTracksStatus
from openpilot.system.ui.lib.application import FontWeight, gui_app, MousePos, MouseEvent
from openpilot.system.ui.widgets.label import UnifiedLabel
from openpilot.system.ui.widgets import Widget
@@ -138,9 +138,7 @@ class AugmentedRoadView(CameraView):
radar_tracks_settings_callback=None):
super().__init__("camerad", stream_type)
self._bookmark_callback = bookmark_callback
self._radar_tracks_settings_callback = radar_tracks_settings_callback
self._radar_tracks_settings_prompt = False
self._radar_status_rect = rl.Rectangle()
self._radar_tracks_status = RadarTracksStatus(radar_tracks_settings_callback)
self._set_placeholder_color(rl.BLACK)
self.device_camera: DeviceCameraConfig | None = None
@@ -164,22 +162,6 @@ class AugmentedRoadView(CameraView):
text_color=rl.Color(255, 255, 255, int(255 * 0.9)),
alignment=rl.GuiTextAlignment.TEXT_ALIGN_CENTER,
alignment_vertical=rl.GuiTextAlignmentVertical.TEXT_ALIGN_MIDDLE)
radar_text_args = {
"font_size": 26,
"font_weight": FontWeight.SEMI_BOLD,
"text_color": rl.Color(0, 255, 64, 255),
"alignment": rl.GuiTextAlignment.TEXT_ALIGN_RIGHT,
"alignment_vertical": rl.GuiTextAlignmentVertical.TEXT_ALIGN_TOP,
"wrap_text": False,
}
self._radar_ranges_label = UnifiedLabel("", **radar_text_args)
self._radar_counts_label = UnifiedLabel("none", **radar_text_args)
self._radar_status = ("", "none")
self._radar_layout_key: tuple[str, str, int] | None = None
self._radar_range_width = 0
self._radar_count_width = 36
self._radar_status_width = 52
self._radar_status_height = 42
self._fade_texture = gui_app.texture("icons_mici/onroad/onroad_fade.png")
@@ -191,16 +173,9 @@ class AugmentedRoadView(CameraView):
super()._update_state()
if ui_state.sm.updated["liveTracks"]:
live_tracks = ui_state.sm["liveTracks"]
self._radar_tracks_settings_prompt = bool(live_tracks.radarTracksAvailable and ui_state.radar_tracks != 2)
if self._radar_tracks_settings_prompt:
status = ("", "radar detected\ntap to enable")
else:
status = format_radar_tracks_onroad_columns(live_tracks) if ui_state.sm.valid["liveTracks"] else ("", "none")
self._set_radar_status(status)
self._radar_tracks_status.update(ui_state.sm["liveTracks"], ui_state.sm.valid["liveTracks"], ui_state.radar_tracks)
elif not ui_state.sm.alive["liveTracks"]:
self._radar_tracks_settings_prompt = False
self._set_radar_status(("", "none"))
self._radar_tracks_status.reset()
# update offroad label
if ui_state.panda_type == log.PandaState.PandaType.unknown:
@@ -210,41 +185,8 @@ class AugmentedRoadView(CameraView):
else:
self._offroad_label.set_text("start the car to\nuse sunnypilot")
def _set_radar_status(self, status: tuple[str, str]) -> None:
if status == self._radar_status:
return
self._radar_status = status
self._radar_ranges_label.set_text(status[0])
self._radar_counts_label.set_text(status[1])
self._radar_layout_key = None
def _update_radar_status_layout(self) -> None:
radar_horizontal_padding = 8
radar_column_gap = 8
radar_max_inner_width = int(self._content_rect.width - 40)
layout_key = (*self._radar_status, radar_max_inner_width)
if layout_key == self._radar_layout_key:
return
radar_range_max_width = radar_max_inner_width - 36 - radar_column_gap
self._radar_ranges_label.get_content_height(radar_range_max_width)
self._radar_counts_label.get_content_height(radar_max_inner_width)
self._radar_range_width = int(np.ceil(self._radar_ranges_label.text_width))
self._radar_count_width = max(36, int(np.ceil(self._radar_counts_label.text_width)))
radar_status_inner_width = self._radar_range_width + self._radar_count_width + (radar_column_gap if self._radar_range_width else 0)
self._radar_status_width = radar_status_inner_width + radar_horizontal_padding * 2
self._radar_status_height = max(
42,
self._radar_ranges_label.get_content_height(max(self._radar_range_width, 1)) + 10,
self._radar_counts_label.get_content_height(self._radar_count_width) + 10,
)
self._radar_layout_key = layout_key
def _handle_mouse_release(self, mouse_pos: MousePos):
if (self._radar_tracks_settings_callback is not None and
rl.check_collision_point_rec(mouse_pos, self._radar_status_rect)):
self._radar_tracks_settings_callback()
if self._radar_tracks_status.handle_mouse(mouse_pos):
return
# Don't trigger click callback if bookmark was triggered
@@ -289,29 +231,7 @@ class AugmentedRoadView(CameraView):
# Fade out bottom of overlays for looks
rl.draw_texture_ex(self._fade_texture, rl.Vector2(self._content_rect.x, self._content_rect.y), 0.0, 1.0, rl.WHITE)
radar_horizontal_padding = 8
radar_column_gap = 8
self._update_radar_status_layout()
self._radar_status_rect = rl.Rectangle(
self._content_rect.x + self._content_rect.width - self._radar_status_width - 12,
self._content_rect.y + 8,
self._radar_status_width,
self._radar_status_height,
)
radar_status_rect = self._radar_status_rect
rl.draw_rectangle_rounded(radar_status_rect, 0.5, 8, rl.Color(0, 0, 0, 170))
self._radar_ranges_label.render(rl.Rectangle(
radar_status_rect.x + radar_horizontal_padding,
radar_status_rect.y + 5,
self._radar_range_width,
radar_status_rect.height - 10,
))
self._radar_counts_label.render(rl.Rectangle(
radar_status_rect.x + radar_horizontal_padding + self._radar_range_width + (radar_column_gap if self._radar_range_width else 0),
radar_status_rect.y + 5,
self._radar_count_width,
radar_status_rect.height - 10,
))
self._radar_tracks_status.render(self._content_rect)
alert_to_render, not_animating_out = self._alert_renderer.will_render()
+17 -2
View File
@@ -18,6 +18,7 @@ if gui_app.sunnypilot_ui():
from openpilot.selfdrive.ui.sunnypilot.onroad.augmented_road_view import BORDER_COLORS_SP, AugmentedRoadViewSP
from openpilot.selfdrive.ui.sunnypilot.onroad.driver_state import DriverStateRendererSP as DriverStateRenderer
from openpilot.selfdrive.ui.sunnypilot.onroad.hud_renderer import HudRendererSP as HudRenderer
from openpilot.selfdrive.ui.sunnypilot.onroad.radar_tracks import RadarTracksStatus
from openpilot.selfdrive.ui.sunnypilot.ui_state import OnroadTimerStatus
OpState = log.SelfdriveState.OpenpilotState
@@ -39,7 +40,7 @@ INF_POINT = np.array([1000.0, 0.0, 0.0])
class AugmentedRoadView(CameraView, AugmentedRoadViewSP):
def __init__(self, stream_type: VisionStreamType = VisionStreamType.VISION_STREAM_ROAD):
def __init__(self, stream_type: VisionStreamType = VisionStreamType.VISION_STREAM_ROAD, radar_tracks_settings_callback=None):
CameraView.__init__(self, "camerad", stream_type)
AugmentedRoadViewSP.__init__(self)
self._set_placeholder_color(BORDER_COLORS[UIStatus.DISENGAGED])
@@ -51,12 +52,22 @@ class AugmentedRoadView(CameraView, AugmentedRoadViewSP):
self._matrix_cache_key = (0, 0.0, 0.0, stream_type)
self._cached_matrix: np.ndarray | None = None
self._content_rect = rl.Rectangle()
self._radar_tracks_status = RadarTracksStatus(radar_tracks_settings_callback, right_margin=240) if gui_app.sunnypilot_ui() else None
self.model_renderer = ModelRenderer()
self._hud_renderer = HudRenderer()
self.alert_renderer = AlertRenderer()
self.driver_state_renderer = DriverStateRenderer()
def _update_state(self):
super()._update_state()
if self._radar_tracks_status is None:
return
if ui_state.sm.updated["liveTracks"]:
self._radar_tracks_status.update(ui_state.sm["liveTracks"], ui_state.sm.valid["liveTracks"], ui_state.radar_tracks)
elif not ui_state.sm.alive["liveTracks"]:
self._radar_tracks_status.reset()
def _render(self, rect):
# Only render when system is started to avoid invalid data access
if not ui_state.started:
@@ -90,6 +101,8 @@ class AugmentedRoadView(CameraView, AugmentedRoadViewSP):
# Draw all UI overlays
self.model_renderer.render(self._content_rect)
AugmentedRoadViewSP.update_fade_out_bottom_overlay(self, self._content_rect)
if self._radar_tracks_status is not None:
self._radar_tracks_status.render(self._content_rect)
self._hud_renderer.render(self._content_rect)
self.alert_renderer.render(self._content_rect)
self.driver_state_renderer.render(self._content_rect)
@@ -103,7 +116,9 @@ class AugmentedRoadView(CameraView, AugmentedRoadViewSP):
# Draw colored border based on driving state
self._draw_border(rect)
def _handle_mouse_press(self, _):
def _handle_mouse_press(self, mouse_pos):
if self._radar_tracks_status is not None and self._radar_tracks_status.handle_mouse(mouse_pos):
return
if not self._hud_renderer.user_interacting() and self._click_callback is not None:
self._click_callback()
@@ -6,6 +6,8 @@ See the LICENSE.md file in the root directory for more details.
"""
import math
import pyray as rl
from openpilot.system.ui.lib.application import FontWeight
from openpilot.system.ui.widgets.label import UnifiedLabel
RELATIVE_SPEED_MOVING_THRESHOLD = 0.5 # m/s relative speed deadband
STATIONARY_SPEED_THRESHOLD = 1.0 # m/s estimated ground speed
@@ -33,6 +35,98 @@ def format_radar_tracks_onroad_columns(live_tracks) -> tuple[str, str]:
return range_text, count_text
class RadarTracksStatus:
HORIZONTAL_PADDING = 8
COLUMN_GAP = 8
def __init__(self, settings_callback=None, right_margin: int = 12):
self._settings_callback = settings_callback
self._right_margin = right_margin
self._rect = rl.Rectangle()
text_args = {
"font_size": 26,
"font_weight": FontWeight.SEMI_BOLD,
"text_color": rl.Color(0, 255, 64, 255),
"alignment": rl.GuiTextAlignment.TEXT_ALIGN_RIGHT,
"alignment_vertical": rl.GuiTextAlignmentVertical.TEXT_ALIGN_TOP,
"wrap_text": False,
}
self._ranges_label = UnifiedLabel("", **text_args)
self._counts_label = UnifiedLabel("none", **text_args)
self._status = ("", "none")
self._layout_key: tuple[str, str, int] | None = None
self._range_width = 0
self._count_width = 36
self._width = 52
self._height = 42
def update(self, live_tracks, valid: bool, radar_mode: int) -> None:
if live_tracks.radarTracksAvailable and radar_mode != 2:
status = ("", "radar detected\ntap to enable")
else:
status = format_radar_tracks_onroad_columns(live_tracks) if valid else ("", "none")
self._set_status(status)
def reset(self) -> None:
self._set_status(("", "none"))
def handle_mouse(self, mouse_pos) -> bool:
if self._settings_callback is None or not rl.check_collision_point_rec(mouse_pos, self._rect):
return False
self._settings_callback()
return True
def render(self, content_rect: rl.Rectangle) -> None:
self._update_layout(int(content_rect.width - 40))
self._rect = rl.Rectangle(
content_rect.x + content_rect.width - self._width - self._right_margin,
content_rect.y + 8,
self._width,
self._height,
)
rl.draw_rectangle_rounded(self._rect, 0.5, 8, rl.Color(0, 0, 0, 170))
self._ranges_label.render(rl.Rectangle(
self._rect.x + self.HORIZONTAL_PADDING,
self._rect.y + 5,
self._range_width,
self._rect.height - 10,
))
self._counts_label.render(rl.Rectangle(
self._rect.x + self.HORIZONTAL_PADDING + self._range_width + (self.COLUMN_GAP if self._range_width else 0),
self._rect.y + 5,
self._count_width,
self._rect.height - 10,
))
def _set_status(self, status: tuple[str, str]) -> None:
if status == self._status:
return
self._status = status
self._ranges_label.set_text(status[0])
self._counts_label.set_text(status[1])
self._layout_key = None
def _update_layout(self, max_inner_width: int) -> None:
layout_key = (*self._status, max_inner_width)
if layout_key == self._layout_key:
return
self._ranges_label.get_content_height(max_inner_width - 36 - self.COLUMN_GAP)
self._counts_label.get_content_height(max_inner_width)
self._range_width = math.ceil(self._ranges_label.text_width)
self._count_width = max(36, math.ceil(self._counts_label.text_width))
inner_width = self._range_width + self._count_width + (self.COLUMN_GAP if self._range_width else 0)
self._width = inner_width + self.HORIZONTAL_PADDING * 2
self._height = max(
42,
self._ranges_label.get_content_height(max(self._range_width, 1)) + 10,
self._counts_label.get_content_height(self._count_width) + 10,
)
self._layout_key = layout_key
class RadarTracks:
def draw_radar_tracks(self, live_tracks, map_to_screen, path_offset_z, track_size=6, screen_offset=(0, 0), v_ego=0.0):
for track in live_tracks.points: