From a11d11c0d30d8091447faf67d6dbadebbf1a1cc9 Mon Sep 17 00:00:00 2001 From: royjr Date: Wed, 15 Jul 2026 15:49:52 -0400 Subject: [PATCH] non mici --- selfdrive/ui/layouts/main.py | 6 +- selfdrive/ui/layouts/settings/toggles.py | 16 ++++ .../ui/mici/onroad/augmented_road_view.py | 92 ++---------------- selfdrive/ui/onroad/augmented_road_view.py | 19 +++- .../ui/sunnypilot/onroad/radar_tracks.py | 94 +++++++++++++++++++ 5 files changed, 138 insertions(+), 89 deletions(-) diff --git a/selfdrive/ui/layouts/main.py b/selfdrive/ui/layouts/main.py index 1e599f4ea7..577fc9825f 100644 --- a/selfdrive/ui/layouts/main.py +++ b/selfdrive/ui/layouts/main.py @@ -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) diff --git a/selfdrive/ui/layouts/settings/toggles.py b/selfdrive/ui/layouts/settings/toggles.py index 4c83584ad5..fa1f64581b 100644 --- a/selfdrive/ui/layouts/settings/toggles.py +++ b/selfdrive/ui/layouts/settings/toggles.py @@ -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) diff --git a/selfdrive/ui/mici/onroad/augmented_road_view.py b/selfdrive/ui/mici/onroad/augmented_road_view.py index 8ba5730bc0..3ccbd2429a 100644 --- a/selfdrive/ui/mici/onroad/augmented_road_view.py +++ b/selfdrive/ui/mici/onroad/augmented_road_view.py @@ -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() diff --git a/selfdrive/ui/onroad/augmented_road_view.py b/selfdrive/ui/onroad/augmented_road_view.py index 21511412e6..9ea1a64cee 100644 --- a/selfdrive/ui/onroad/augmented_road_view.py +++ b/selfdrive/ui/onroad/augmented_road_view.py @@ -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() diff --git a/selfdrive/ui/sunnypilot/onroad/radar_tracks.py b/selfdrive/ui/sunnypilot/onroad/radar_tracks.py index 00e501496f..e1b9eeb8f0 100644 --- a/selfdrive/ui/sunnypilot/onroad/radar_tracks.py +++ b/selfdrive/ui/sunnypilot/onroad/radar_tracks.py @@ -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: