From d2ab08eaf20dc742f8a31e6ddab9f2e616ab40e8 Mon Sep 17 00:00:00 2001 From: Jason Wen Date: Fri, 1 May 2026 00:15:14 -0400 Subject: [PATCH] some changes --- selfdrive/ui/mici/layouts/main.py | 10 ++---- selfdrive/ui/sunnypilot/mici/layouts/main.py | 13 +++++++ .../ui/sunnypilot/mici/layouts/onroad.py | 31 ++++++++++------- .../mici/layouts/onroad_info_panel.py | 7 ++-- .../mici/onroad/augmented_road_view.py | 12 +++---- .../mici/widgets/scroll_panel_sp.py | 34 +++++++++++++++++++ .../ui/sunnypilot/mici/widgets/scroller_sp.py | 16 +++++++++ selfdrive/ui/ui.py | 3 ++ system/ui/lib/scroll_panel2.py | 22 +++--------- 9 files changed, 100 insertions(+), 48 deletions(-) create mode 100644 selfdrive/ui/sunnypilot/mici/layouts/main.py create mode 100644 selfdrive/ui/sunnypilot/mici/widgets/scroll_panel_sp.py create mode 100644 selfdrive/ui/sunnypilot/mici/widgets/scroller_sp.py diff --git a/selfdrive/ui/mici/layouts/main.py b/selfdrive/ui/mici/layouts/main.py index 65d6afc80e..179014b341 100644 --- a/selfdrive/ui/mici/layouts/main.py +++ b/selfdrive/ui/mici/layouts/main.py @@ -69,11 +69,6 @@ class MiciMainLayout(Scroller): self._onroad_layout.set_click_callback(lambda: self._scroll_to(self._home_layout)) device.add_interactive_timeout_callback(self._on_interactive_timeout) - def _is_on_side_panel(self) -> bool: # TODO: remove sunny not going like it ? - onroad_x = self._onroad_layout.rect.x - current_scroll = self._scroller.scroll_panel.get_offset() - return abs(current_scroll - onroad_x) > self._rect.width / 2 - def _scroll_to(self, layout: Widget): layout_x = int(layout.rect.x) self._scroller.scroll_to(layout_x, smooth=True) @@ -105,21 +100,20 @@ class MiciMainLayout(Scroller): # onroad: after delay, pop nav stack and scroll to onroad # offroad: immediately scroll to home, but don't pop nav stack (can stay in settings) if ui_state.started: - self._scroll_to(self._onroad_layout) self._onroad_time_delay = rl.get_time() else: self._scroll_to(self._home_layout) # FIXME: these two pops can interrupt user interacting in the settings if self._onroad_time_delay is not None and rl.get_time() - self._onroad_time_delay >= ONROAD_DELAY: - if not self._is_on_side_panel(): + if not gui_app.sunnypilot_ui() or self._should_auto_scroll_to_onroad(): gui_app.pop_widgets_to(self, lambda: self._scroll_to(self._onroad_layout)) self._onroad_time_delay = None # When car leaves standstill, pop nav stack and scroll to onroad CS = ui_state.sm["carState"] if not CS.standstill and self._prev_standstill: - if not self._is_on_side_panel(): + if not gui_app.sunnypilot_ui() or self._should_auto_scroll_to_onroad(): gui_app.pop_widgets_to(self, lambda: self._scroll_to(self._onroad_layout)) self._prev_standstill = CS.standstill diff --git a/selfdrive/ui/sunnypilot/mici/layouts/main.py b/selfdrive/ui/sunnypilot/mici/layouts/main.py new file mode 100644 index 0000000000..b0401e8119 --- /dev/null +++ b/selfdrive/ui/sunnypilot/mici/layouts/main.py @@ -0,0 +1,13 @@ +""" +Copyright (c) 2021-, Haibin Wen, sunnypilot, and a number of other contributors. + +This file is part of sunnypilot and is licensed under the MIT License. +See the LICENSE.md file in the root directory for more details. +""" + +from openpilot.selfdrive.ui.mici.layouts.main import MiciMainLayout + + +class MiciMainLayoutSP(MiciMainLayout): + def _should_auto_scroll_to_onroad(self) -> bool: + return not self._onroad_layout.is_on_info_panel() diff --git a/selfdrive/ui/sunnypilot/mici/layouts/onroad.py b/selfdrive/ui/sunnypilot/mici/layouts/onroad.py index 5f48465497..f09b6a8693 100644 --- a/selfdrive/ui/sunnypilot/mici/layouts/onroad.py +++ b/selfdrive/ui/sunnypilot/mici/layouts/onroad.py @@ -5,19 +5,20 @@ This file is part of sunnypilot and is licensed under the MIT License. See the LICENSE.md file in the root directory for more details. """ import pyray as rl -from openpilot.system.ui.widgets.scroller import Scroller from openpilot.system.ui.lib.application import gui_app -from openpilot.selfdrive.ui.mici.onroad.augmented_road_view import AugmentedRoadView +from openpilot.selfdrive.ui.sunnypilot.mici.widgets.scroller_sp import ScrollerSP +from openpilot.selfdrive.ui.sunnypilot.mici.onroad.augmented_road_view import AugmentedRoadViewSP +from openpilot.selfdrive.ui.sunnypilot.mici.layouts.onroad_info_panel import OnroadInfoPanel -if gui_app.sunnypilot_ui(): - from openpilot.selfdrive.ui.sunnypilot.mici.onroad.augmented_road_view import AugmentedRoadViewSP as AugmentedRoadView - from openpilot.selfdrive.ui.sunnypilot.mici.layouts.onroad_info_panel import OnroadInfoPanel +CONFIDENCE_BALL_VISIBLE_RATIO = 0.4 +HORIZONTAL_SETTLE_PX = 5 +HORIZONTAL_RESET_RATIO = 0.5 -class OnroadViewContainerSP(Scroller): +class OnroadViewContainerSP(ScrollerSP): def __init__(self, bookmark_callback=None): super().__init__(horizontal=False, snap_items=True, spacing=0, pad=0, scroll_indicator=False, edge_shadows=False) - self.road_view = AugmentedRoadView(bookmark_callback=bookmark_callback) + self.road_view = AugmentedRoadViewSP(bookmark_callback=bookmark_callback) self.onroad_info_panel = OnroadInfoPanel(bookmark_callback=bookmark_callback) self._scroller.add_widgets([ @@ -25,6 +26,7 @@ class OnroadViewContainerSP(Scroller): self.onroad_info_panel, ]) self._scroller.set_reset_scroll_at_show(False) + self._scroller.set_scrolling_enabled(lambda: abs(self.rect.x) < HORIZONTAL_SETTLE_PX) def set_rect(self, rect: rl.Rectangle): super().set_rect(rect) @@ -32,21 +34,24 @@ class OnroadViewContainerSP(Scroller): self.onroad_info_panel.set_rect(rect) return self - def is_swiping_left(self): + def is_swiping_left(self) -> bool: return self.road_view.is_swiping_left() or self.onroad_info_panel.is_swiping_left() - def set_click_callback(self, callback): + def set_click_callback(self, callback) -> None: self.road_view.set_click_callback(callback) self.onroad_info_panel.set_click_callback(callback) + def is_on_info_panel(self) -> bool: + """True when scrolled past halfway toward onroad_info_panel (used by main layout + to skip auto-pop-back-to-camera while user is reading the info panel).""" + return abs(self._scroller.scroll_panel.get_offset()) > self._rect.height / 2 + def _render(self, rect: rl.Rectangle): - if abs(self.rect.x) > gui_app.width / 2: + if abs(self.rect.x) > gui_app.width * HORIZONTAL_RESET_RATIO: self._scroller.scroll_panel.set_offset(0) - self._scroller.set_scrolling_enabled(lambda: abs(self.rect.x) < 5) - vertical_offset = self._scroller.scroll_panel.get_offset() - show_ball = abs(vertical_offset) < rect.height * 0.4 + show_ball = abs(vertical_offset) < rect.height * CONFIDENCE_BALL_VISIBLE_RATIO self.road_view.set_show_confidence_ball(show_ball) super()._render(rect) diff --git a/selfdrive/ui/sunnypilot/mici/layouts/onroad_info_panel.py b/selfdrive/ui/sunnypilot/mici/layouts/onroad_info_panel.py index f755c6f703..e51b256868 100644 --- a/selfdrive/ui/sunnypilot/mici/layouts/onroad_info_panel.py +++ b/selfdrive/ui/sunnypilot/mici/layouts/onroad_info_panel.py @@ -32,6 +32,7 @@ class OnroadInfoPanelColors: dark_grey: rl.Color = rl.Color(100, 100, 100, 255) bg_dark: rl.Color = rl.Color(0, 0, 0, 255) card_bg: rl.Color = rl.Color(50, 50, 50, 200) + badge_bg: rl.Color = rl.Color(60, 60, 60, 255) COLORS = OnroadInfoPanelColors() @@ -121,7 +122,7 @@ class OnroadInfoPanel(Widget): rl.draw_text_ex(self._font_semi_bold, unit, rl.Vector2(left_x, mid_y - 95), 38, 0, COLORS.grey) rl.draw_text_ex(self._font_bold, speed_val, rl.Vector2(left_x, mid_y - 60), 110, 0, speed_color) - sign_width = 135 if ui_state.is_metric else 135 + sign_width = 135 sign_height = 135 if ui_state.is_metric else 175 has_next = self.next_speed_limit > 0 and self.next_speed_limit != self.speed_limit @@ -172,13 +173,13 @@ class OnroadInfoPanel(Widget): badge_cx = badge_x + badge_r badge_cy = badge_y + badge_r rl.draw_circle(int(badge_cx), int(badge_cy), badge_r + 2, COLORS.dark_grey) - rl.draw_circle(int(badge_cx), int(badge_cy), badge_r, rl.Color(60, 60, 60, 255)) + rl.draw_circle(int(badge_cx), int(badge_cy), badge_r, COLORS.badge_bg) self._draw_text_centered(self._font_bold, offset_val, 24, rl.Vector2(badge_cx, badge_cy), COLORS.white) else: mutcd_badge_x = sign_x + sign_width - badge_sz * 0.65 mutcd_badge_y = sign_y - badge_sz * 0.50 badge_rect = rl.Rectangle(mutcd_badge_x, mutcd_badge_y, badge_sz, badge_sz) - rl.draw_rectangle_rounded(badge_rect, 0.25, 10, rl.Color(60, 60, 60, 255)) + rl.draw_rectangle_rounded(badge_rect, 0.25, 10, COLORS.badge_bg) rl.draw_rectangle_rounded_lines_ex(badge_rect, 0.25, 10, 2, COLORS.dark_grey) self._draw_text_centered(self._font_bold, offset_val, 24, rl.Vector2(mutcd_badge_x + badge_sz / 2, mutcd_badge_y + badge_sz / 2), COLORS.white) diff --git a/selfdrive/ui/sunnypilot/mici/onroad/augmented_road_view.py b/selfdrive/ui/sunnypilot/mici/onroad/augmented_road_view.py index 21b9124534..acd4a162d0 100644 --- a/selfdrive/ui/sunnypilot/mici/onroad/augmented_road_view.py +++ b/selfdrive/ui/sunnypilot/mici/onroad/augmented_road_view.py @@ -9,12 +9,17 @@ import pyray as rl from openpilot.selfdrive.ui.mici.onroad.augmented_road_view import AugmentedRoadView +class _SuppressedConfidenceBall: + def render(self, *_): + pass + + class AugmentedRoadViewSP(AugmentedRoadView): def __init__(self, **kwargs): super().__init__(**kwargs) self._show_confidence_ball: bool = True self._real_confidence_ball = self._confidence_ball - self._confidence_ball = _ConfidenceBall() + self._confidence_ball = _SuppressedConfidenceBall() def set_show_confidence_ball(self, show: bool) -> None: self._show_confidence_ball = show @@ -23,8 +28,3 @@ class AugmentedRoadViewSP(AugmentedRoadView): super()._render(rect) if self._show_confidence_ball: self._real_confidence_ball.render(self.rect) - - -class _ConfidenceBall: - def render(self, *_): - pass diff --git a/selfdrive/ui/sunnypilot/mici/widgets/scroll_panel_sp.py b/selfdrive/ui/sunnypilot/mici/widgets/scroll_panel_sp.py new file mode 100644 index 0000000000..1c97f59e84 --- /dev/null +++ b/selfdrive/ui/sunnypilot/mici/widgets/scroll_panel_sp.py @@ -0,0 +1,34 @@ +""" +Copyright (c) 2021-, Haibin Wen, sunnypilot, and a number of other contributors. + +This file is part of sunnypilot and is licensed under the MIT License. +See the LICENSE.md file in the root directory for more details. +""" + +import pyray as rl +from openpilot.system.ui.lib.application import MouseEvent +from openpilot.system.ui.lib.scroll_panel2 import GuiScrollPanel2, ScrollState + + +class GuiScrollPanel2SP(GuiScrollPanel2): + """Reject orthogonal-dominant drags so nested scrollers (outer horizontal + + inner vertical) don't both engage on a slightly diagonal swipe. + + Implemented as a post-super state rollback rather than reimplementing the + PRESSED state machine — keeps stock behaviour authoritative.""" + + def _handle_mouse_event(self, mouse_event: MouseEvent, bounds: rl.Rectangle, bounds_size: float, + content_size: float) -> None: + pre_state = self._state + super()._handle_mouse_event(mouse_event, bounds, bounds_size, content_size) + + if self._state == ScrollState.MANUAL_SCROLL and pre_state == ScrollState.PRESSED and \ + self._initial_click_event is not None: + diff_x = abs(mouse_event.pos.x - self._initial_click_event.pos.x) + diff_y = abs(mouse_event.pos.y - self._initial_click_event.pos.y) + along = diff_x if self._horizontal else diff_y + anti = diff_y if self._horizontal else diff_x + if anti > along: + self._state = ScrollState.STEADY + self._velocity = 0.0 + self._velocity_buffer.clear() diff --git a/selfdrive/ui/sunnypilot/mici/widgets/scroller_sp.py b/selfdrive/ui/sunnypilot/mici/widgets/scroller_sp.py new file mode 100644 index 0000000000..3a678162e0 --- /dev/null +++ b/selfdrive/ui/sunnypilot/mici/widgets/scroller_sp.py @@ -0,0 +1,16 @@ +""" +Copyright (c) 2021-, Haibin Wen, sunnypilot, and a number of other contributors. + +This file is part of sunnypilot and is licensed under the MIT License. +See the LICENSE.md file in the root directory for more details. +""" + +from openpilot.system.ui.widgets.scroller import Scroller +from openpilot.selfdrive.ui.sunnypilot.mici.widgets.scroll_panel_sp import GuiScrollPanel2SP + + +class ScrollerSP(Scroller): + def __init__(self, **kwargs): + super().__init__(**kwargs) + inner = self._scroller + inner.scroll_panel = GuiScrollPanel2SP(inner._horizontal, handle_out_of_bounds=not inner._snap_items) diff --git a/selfdrive/ui/ui.py b/selfdrive/ui/ui.py index e3cac2618e..374bee1a5b 100755 --- a/selfdrive/ui/ui.py +++ b/selfdrive/ui/ui.py @@ -8,6 +8,9 @@ from openpilot.selfdrive.ui.layouts.main import MainLayout from openpilot.selfdrive.ui.mici.layouts.main import MiciMainLayout from openpilot.selfdrive.ui.ui_state import ui_state +if gui_app.sunnypilot_ui(): + from openpilot.selfdrive.ui.sunnypilot.mici.layouts.main import MiciMainLayoutSP as MiciMainLayout + BIG_UI = gui_app.big_ui() diff --git a/system/ui/lib/scroll_panel2.py b/system/ui/lib/scroll_panel2.py index d56fd6b9dd..18fd8a9a67 100644 --- a/system/ui/lib/scroll_panel2.py +++ b/system/ui/lib/scroll_panel2.py @@ -147,16 +147,8 @@ class GuiScrollPanel2: self._initial_click_event = mouse_event elif self._state == ScrollState.PRESSED: - initial_click_pos = self._initial_click_event.pos - diff_x = abs(mouse_event.pos.x - initial_click_pos.x) - diff_y = abs(mouse_event.pos.y - initial_click_pos.y) - - diff = diff_x if self._horizontal else diff_y - anti_diff = diff_y if self._horizontal else diff_x - - if diff > MIN_DRAG_PIXELS or anti_diff > MIN_DRAG_PIXELS: - print(f"[{'HORIZ' if self._horizontal else 'VERT '}] SWIPE detected. diff={diff:.1f}, anti={anti_diff:.1f}") - + initial_click_pos = self._get_mouse_pos(cast(MouseEvent, self._initial_click_event)) + diff = abs(mouse_pos - initial_click_pos) if mouse_event.left_released: # Special handling for down and up clicks across two frames # TODO: not sure what that means or if it's accurate anymore @@ -165,15 +157,9 @@ class GuiScrollPanel2: elif diff <= MIN_DRAG_PIXELS: self._state = ScrollState.STEADY else: - if diff > anti_diff: - self._state = ScrollState.MANUAL_SCROLL - else: - self._state = ScrollState.STEADY - elif diff > MIN_DRAG_PIXELS and diff > anti_diff: + self._state = ScrollState.MANUAL_SCROLL + elif diff > MIN_DRAG_PIXELS: self._state = ScrollState.MANUAL_SCROLL - elif anti_diff > MIN_DRAG_PIXELS and anti_diff > diff: - # Orthogonal swipe detected, abort handling - self._state = ScrollState.STEADY elif self._state == ScrollState.MANUAL_SCROLL: if mouse_event.left_released: