From 2feddf32b2c5510168d8b453f49999e8615a17bc Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Fri, 26 Sep 2025 22:40:38 -0700 Subject: [PATCH] raylib: fix lost onroad tap events (#36211) * debug * see it's good to have abstraction * clean up * fine * wtf do you mean mypy? how can you not coerce this? --- selfdrive/ui/layouts/main.py | 2 +- selfdrive/ui/onroad/augmented_road_view.py | 16 ++++---------- selfdrive/ui/onroad/exp_button.py | 25 +++++++++------------- selfdrive/ui/onroad/hud_renderer.py | 6 +++--- system/ui/widgets/__init__.py | 5 +++++ 5 files changed, 23 insertions(+), 31 deletions(-) diff --git a/selfdrive/ui/layouts/main.py b/selfdrive/ui/layouts/main.py index 0a8fadea90..ffb45f821d 100644 --- a/selfdrive/ui/layouts/main.py +++ b/selfdrive/ui/layouts/main.py @@ -50,7 +50,7 @@ class MainLayout(Widget): on_flag=self._on_bookmark_clicked) self._layouts[MainState.HOME]._setup_widget.set_open_settings_callback(lambda: self.open_settings(PanelType.FIREHOSE)) self._layouts[MainState.SETTINGS].set_callbacks(on_close=self._set_mode_for_state) - self._layouts[MainState.ONROAD].set_callbacks(on_click=self._on_onroad_clicked) + self._layouts[MainState.ONROAD].set_click_callback(self._on_onroad_clicked) device.add_interactive_timeout_callback(self._set_mode_for_state) def _update_layout_rects(self): diff --git a/selfdrive/ui/onroad/augmented_road_view.py b/selfdrive/ui/onroad/augmented_road_view.py index f012e8b06b..0a4c45163b 100644 --- a/selfdrive/ui/onroad/augmented_road_view.py +++ b/selfdrive/ui/onroad/augmented_road_view.py @@ -1,6 +1,5 @@ import numpy as np import pyray as rl -from collections.abc import Callable from cereal import log from msgq.visionipc import VisionStreamType from openpilot.selfdrive.ui.ui_state import ui_state, UIStatus, UI_BORDER_SIZE @@ -49,12 +48,6 @@ class AugmentedRoadView(CameraView): self.alert_renderer = AlertRenderer() self.driver_state_renderer = DriverStateRenderer() - # Callbacks - self._click_callback: Callable | None = None - - def set_callbacks(self, on_click: Callable | None = None): - self._click_callback = on_click - def _render(self, rect): # Only render when system is started to avoid invalid data access if not ui_state.started: @@ -100,13 +93,12 @@ class AugmentedRoadView(CameraView): # End clipping region rl.end_scissor_mode() - # Handle click events if no HUD interaction occurred - if not self._hud_renderer.handle_mouse_event(): - if self._click_callback is not None and rl.is_mouse_button_pressed(rl.MouseButton.MOUSE_BUTTON_LEFT): - if rl.check_collision_point_rec(rl.get_mouse_position(), self._content_rect): - self._click_callback() + def _handle_mouse_press(self, _): + if not self._hud_renderer.user_interacting() and self._click_callback is not None: + self._click_callback() def _handle_mouse_release(self, _): + # We only call click callback on press if not interacting with HUD pass def _draw_border(self, rect: rl.Rectangle): diff --git a/selfdrive/ui/onroad/exp_button.py b/selfdrive/ui/onroad/exp_button.py index 27f5763077..175233c5ba 100644 --- a/selfdrive/ui/onroad/exp_button.py +++ b/selfdrive/ui/onroad/exp_button.py @@ -32,26 +32,21 @@ class ExpButton(Widget): self._experimental_mode = selfdrive_state.experimentalMode self._engageable = selfdrive_state.engageable or selfdrive_state.enabled - def handle_mouse_event(self) -> bool: - if rl.check_collision_point_rec(rl.get_mouse_position(), self._rect): - if (rl.is_mouse_button_released(rl.MouseButton.MOUSE_BUTTON_LEFT) and - self._is_toggle_allowed()): - new_mode = not self._experimental_mode - self._params.put_bool("ExperimentalMode", new_mode) - - # Hold new state temporarily - self._held_mode = new_mode - self._hold_end_time = time.monotonic() + self._hold_duration - return True - return False + def _handle_mouse_release(self, _): + super()._handle_mouse_release(_) + if self._is_toggle_allowed(): + new_mode = not self._experimental_mode + self._params.put_bool("ExperimentalMode", new_mode) + + # Hold new state temporarily + self._held_mode = new_mode + self._hold_end_time = time.monotonic() + self._hold_duration def _render(self, rect: rl.Rectangle) -> None: center_x = int(self._rect.x + self._rect.width // 2) center_y = int(self._rect.y + self._rect.height // 2) - mouse_over = rl.check_collision_point_rec(rl.get_mouse_position(), self._rect) - mouse_down = rl.is_mouse_button_down(rl.MouseButton.MOUSE_BUTTON_LEFT) and self.is_pressed - self._white_color.a = 180 if (mouse_down and mouse_over) or not self._engageable else 255 + self._white_color.a = 180 if self.is_pressed or not self._engageable else 255 texture = self._txt_exp if self._held_or_actual_mode() else self._txt_wheel rl.draw_circle(center_x, center_y, self._rect.width / 2, self._black_bg) diff --git a/selfdrive/ui/onroad/hud_renderer.py b/selfdrive/ui/onroad/hud_renderer.py index 536d993389..c813d852bc 100644 --- a/selfdrive/ui/onroad/hud_renderer.py +++ b/selfdrive/ui/onroad/hud_renderer.py @@ -69,7 +69,7 @@ class HudRenderer(Widget): self._font_bold: rl.Font = gui_app.font(FontWeight.BOLD) self._font_medium: rl.Font = gui_app.font(FontWeight.MEDIUM) - self._exp_button = ExpButton(UI_CONFIG.button_size, UI_CONFIG.wheel_icon_size) + self._exp_button: ExpButton = ExpButton(UI_CONFIG.button_size, UI_CONFIG.wheel_icon_size) def _update_state(self) -> None: """Update HUD state based on car state and controls state.""" @@ -120,8 +120,8 @@ class HudRenderer(Widget): button_y = rect.y + UI_CONFIG.border_size self._exp_button.render(rl.Rectangle(button_x, button_y, UI_CONFIG.button_size, UI_CONFIG.button_size)) - def handle_mouse_event(self) -> bool: - return bool(self._exp_button.handle_mouse_event()) + def user_interacting(self) -> bool: + return self._exp_button.is_pressed def _draw_set_speed(self, rect: rl.Rectangle) -> None: """Draw the MAX speed indicator box.""" diff --git a/system/ui/widgets/__init__.py b/system/ui/widgets/__init__.py index 6372b1813d..0157dd3ef4 100644 --- a/system/ui/widgets/__init__.py +++ b/system/ui/widgets/__init__.py @@ -96,6 +96,7 @@ class Widget(abc.ABC): # Allows touch to leave the rect and come back in focus if mouse did not release if mouse_event.left_pressed and self._touch_valid(): if rl.check_collision_point_rec(mouse_event.pos, self._rect): + self._handle_mouse_press(mouse_event.pos) self.__is_pressed[mouse_event.slot] = True self.__tracking_is_pressed[mouse_event.slot] = True @@ -131,6 +132,10 @@ class Widget(abc.ABC): def _update_layout_rects(self) -> None: """Optionally update any layout rects on Widget rect change.""" + def _handle_mouse_press(self, mouse_pos: MousePos) -> bool: + """Optionally handle mouse press events.""" + return False + def _handle_mouse_release(self, mouse_pos: MousePos) -> bool: """Optionally handle mouse release events.""" if self._click_callback: