From f9131e4f023e8fc41a28b53bc3f2ea7a92407d19 Mon Sep 17 00:00:00 2001 From: firestarsdog <229254897+firestarsdog@users.noreply.github.com> Date: Tue, 31 Mar 2026 01:39:27 -0400 Subject: [PATCH] BigUI StarPilot Layer + AOL + Personality Buttons --- selfdrive/ui/layouts/main.py | 4 +- selfdrive/ui/onroad/starpilot/__init__.py | 0 .../ui/onroad/starpilot/personality_button.py | 63 +++++++++++++++++++ .../onroad/starpilot/starpilot_onroad_view.py | 61 ++++++++++++++++++ 4 files changed, 126 insertions(+), 2 deletions(-) create mode 100644 selfdrive/ui/onroad/starpilot/__init__.py create mode 100644 selfdrive/ui/onroad/starpilot/personality_button.py create mode 100644 selfdrive/ui/onroad/starpilot/starpilot_onroad_view.py diff --git a/selfdrive/ui/layouts/main.py b/selfdrive/ui/layouts/main.py index 535e04ab..b211342c 100644 --- a/selfdrive/ui/layouts/main.py +++ b/selfdrive/ui/layouts/main.py @@ -6,7 +6,7 @@ from openpilot.system.ui.lib.application import gui_app from openpilot.selfdrive.ui.layouts.sidebar import Sidebar, SIDEBAR_WIDTH from openpilot.selfdrive.ui.layouts.home import HomeLayout from openpilot.selfdrive.ui.layouts.settings.settings import SettingsLayout, PanelType -from openpilot.selfdrive.ui.onroad.augmented_road_view import AugmentedRoadView +from openpilot.selfdrive.ui.onroad.starpilot.starpilot_onroad_view import StarPilotOnroadView from openpilot.selfdrive.ui.ui_state import device, ui_state from openpilot.system.ui.widgets import Widget from openpilot.selfdrive.ui.layouts.onboarding import OnboardingWindow @@ -29,7 +29,7 @@ class MainLayout(Widget): self._prev_onroad = False # Initialize layouts - self._layouts = {MainState.HOME: HomeLayout(), MainState.SETTINGS: SettingsLayout(), MainState.ONROAD: AugmentedRoadView()} + self._layouts = {MainState.HOME: HomeLayout(), MainState.SETTINGS: SettingsLayout(), MainState.ONROAD: StarPilotOnroadView()} self._sidebar_rect = rl.Rectangle(0, 0, 0, 0) self._content_rect = rl.Rectangle(0, 0, 0, 0) diff --git a/selfdrive/ui/onroad/starpilot/__init__.py b/selfdrive/ui/onroad/starpilot/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/selfdrive/ui/onroad/starpilot/personality_button.py b/selfdrive/ui/onroad/starpilot/personality_button.py new file mode 100644 index 00000000..16372c21 --- /dev/null +++ b/selfdrive/ui/onroad/starpilot/personality_button.py @@ -0,0 +1,63 @@ +import pyray as rl +from cereal import log +from openpilot.common.params import Params +from openpilot.selfdrive.ui.ui_state import ui_state +from openpilot.system.ui.lib.application import gui_app +from openpilot.system.ui.widgets import Widget + +BTN_SIZE = 192 +ICON_SIZE = 144 +PERSONALITY_TO_INT = log.LongitudinalPersonality.schema.enumerants + + +class PersonalityButton(Widget): + def __init__(self): + super().__init__() + self._params_memory = Params(memory=True) + + self._personality: int = 1 # default: standard + self._traffic_mode: bool = False + + # 0=traffic, 1=aggressive, 2=standard, 3=relaxed + self._icons = [ + gui_app.starpilot_texture("stock_theme/distance_icons/traffic.png", ICON_SIZE, ICON_SIZE), + gui_app.starpilot_texture("stock_theme/distance_icons/aggressive.png", ICON_SIZE, ICON_SIZE), + gui_app.starpilot_texture("stock_theme/distance_icons/standard.png", ICON_SIZE, ICON_SIZE), + gui_app.starpilot_texture("stock_theme/distance_icons/relaxed.png", ICON_SIZE, ICON_SIZE), + ] + + self._rect = rl.Rectangle(0, 0, BTN_SIZE, BTN_SIZE) + + self._black_bg = rl.Color(0, 0, 0, 0) + self._white = rl.Color(255, 255, 255, 255) + + self.set_visible(lambda: ui_state.started and ui_state.has_longitudinal_control) + + @property + def is_interacting(self) -> bool: + return self.is_pressed + + def _update_state(self): + sm = ui_state.sm + if sm.valid.get("selfdriveState", False): + self._personality = PERSONALITY_TO_INT[sm["selfdriveState"].personality] + self._traffic_mode = ui_state.traffic_mode_enabled + + def _handle_mouse_press(self, _): + self._params_memory.put_bool("OnroadDistanceButtonPressed", True) + + def _handle_mouse_release(self, _): + self._params_memory.put_bool("OnroadDistanceButtonPressed", False) + + def _render(self, rect: rl.Rectangle): + center_x = int(self._rect.x + self._rect.width / 2) + center_y = int(self._rect.y + self._rect.height / 2) + + opacity = 180 if self.is_pressed else 255 + self._white.a = opacity + + icon_idx = 0 if self._traffic_mode else min(self._personality + 1, len(self._icons) - 1) + icon = self._icons[icon_idx] + + rl.draw_circle(center_x, center_y, self._rect.width / 2, self._black_bg) + rl.draw_texture(icon, center_x - icon.width // 2, center_y - icon.height // 2, self._white) diff --git a/selfdrive/ui/onroad/starpilot/starpilot_onroad_view.py b/selfdrive/ui/onroad/starpilot/starpilot_onroad_view.py new file mode 100644 index 00000000..12e4ea95 --- /dev/null +++ b/selfdrive/ui/onroad/starpilot/starpilot_onroad_view.py @@ -0,0 +1,61 @@ +import pyray as rl +from msgq.visionipc import VisionStreamType +from openpilot.common.params import Params +from openpilot.selfdrive.ui import UI_BORDER_SIZE +from openpilot.selfdrive.ui.onroad.augmented_road_view import AugmentedRoadView, BORDER_COLORS +from openpilot.selfdrive.ui.onroad.starpilot.personality_button import PersonalityButton, BTN_SIZE +from openpilot.selfdrive.ui.ui_state import ui_state, UIStatus + +AOL_COLOR = rl.Color(10, 186, 181, 255) + + +class StarPilotOnroadView(AugmentedRoadView): + def __init__(self, stream_type: VisionStreamType = VisionStreamType.VISION_STREAM_ROAD): + super().__init__(stream_type) + self._params = Params() + + self._personality_button = PersonalityButton() + + def _render(self, rect: rl.Rectangle): + super()._render(rect) + + if not ui_state.started: + return + + self._render_overlays() + + def _render_overlays(self): + self._position_personality_button() + self._personality_button.render() + + def _position_personality_button(self): + dm = self.driver_state_renderer + toggle_on = self._params.get_bool("OnroadDistanceButton") + + if not dm.is_visible or not toggle_on: + self._personality_button.set_visible(False) + return + + self._personality_button.set_visible( + lambda: ui_state.started and ui_state.has_longitudinal_control + ) + + y = dm.position_y - BTN_SIZE / 2 + if dm.is_rhd: + x = dm.position_x - BTN_SIZE * 2 + else: + x = dm.position_x + BTN_SIZE + + self._personality_button.set_position(x, y) + + def _draw_border(self, rect: rl.Rectangle): + rl.draw_rectangle_lines_ex(rect, UI_BORDER_SIZE, rl.BLACK) + border_rect = rl.Rectangle(rect.x + UI_BORDER_SIZE, rect.y + UI_BORDER_SIZE, + rect.width - 2 * UI_BORDER_SIZE, rect.height - 2 * UI_BORDER_SIZE) + border_color = AOL_COLOR if ui_state.always_on_lateral_active else BORDER_COLORS.get(ui_state.status, BORDER_COLORS[UIStatus.DISENGAGED]) + rl.draw_rectangle_rounded_lines_ex(border_rect, 0.12, 10, UI_BORDER_SIZE, border_color) + + def _handle_mouse_press(self, _): + if self._personality_button.is_interacting: + return + super()._handle_mouse_press(_)