From 0711160b1c1c88f939401b7ae6f5664b834d9c52 Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Fri, 26 Sep 2025 18:59:15 -0700 Subject: [PATCH] raylib: dismiss dialog on pair (#36205) * show for unknown * use Button to make clicking work * close on pair * close on pair * make widget! * dynamic pairing btn * whyyy * clean up * can do this * this button is also hard to tap --- selfdrive/ui/layouts/settings/device.py | 5 ++++- selfdrive/ui/lib/prime_state.py | 20 ++++++++++++-------- selfdrive/ui/ui.py | 9 ++++----- selfdrive/ui/widgets/pairing_dialog.py | 11 +++++++++-- selfdrive/ui/widgets/setup.py | 15 +++++++-------- system/ui/lib/application.py | 3 ++- system/ui/widgets/scroller.py | 11 ++++++++--- 7 files changed, 46 insertions(+), 28 deletions(-) diff --git a/selfdrive/ui/layouts/settings/device.py b/selfdrive/ui/layouts/settings/device.py index df8bba030c..14847df102 100644 --- a/selfdrive/ui/layouts/settings/device.py +++ b/selfdrive/ui/layouts/settings/device.py @@ -44,10 +44,13 @@ class DeviceLayout(Widget): dongle_id = self._params.get("DongleId") or "N/A" serial = self._params.get("HardwareSerial") or "N/A" + self._pair_device_btn = button_item("Pair Device", "PAIR", DESCRIPTIONS['pair_device'], callback=self._pair_device) + self._pair_device_btn.set_visible(lambda: not ui_state.prime_state.is_paired()) + items = [ text_item("Dongle ID", dongle_id), text_item("Serial", serial), - button_item("Pair Device", "PAIR", DESCRIPTIONS['pair_device'], callback=self._pair_device), + self._pair_device_btn, button_item("Driver Camera", "PREVIEW", DESCRIPTIONS['driver_camera'], callback=self._show_driver_camera, enabled=ui_state.is_offroad), button_item("Reset Calibration", "RESET", DESCRIPTIONS['reset_calibration'], callback=self._reset_calibration_prompt), regulatory_btn := button_item("Regulatory", "VIEW", callback=self._on_regulatory), diff --git a/selfdrive/ui/lib/prime_state.py b/selfdrive/ui/lib/prime_state.py index be2132c1b7..30ad0f763a 100644 --- a/selfdrive/ui/lib/prime_state.py +++ b/selfdrive/ui/lib/prime_state.py @@ -11,14 +11,14 @@ from openpilot.selfdrive.ui.lib.api_helpers import get_token class PrimeType(IntEnum): - UNKNOWN = -2, - UNPAIRED = -1, - NONE = 0, - MAGENTA = 1, - LITE = 2, - BLUE = 3, - MAGENTA_NEW = 4, - PURPLE = 5, + UNKNOWN = -2 + UNPAIRED = -1 + NONE = 0 + MAGENTA = 1 + LITE = 2 + BLUE = 3 + MAGENTA_NEW = 4 + PURPLE = 5 class PrimeState: @@ -96,5 +96,9 @@ class PrimeState: with self._lock: return bool(self.prime_type > PrimeType.NONE) + def is_paired(self) -> bool: + with self._lock: + return self.prime_type > PrimeType.UNPAIRED + def __del__(self): self.stop() diff --git a/selfdrive/ui/ui.py b/selfdrive/ui/ui.py index bb2b431e03..0230e16a4f 100755 --- a/selfdrive/ui/ui.py +++ b/selfdrive/ui/ui.py @@ -10,15 +10,14 @@ def main(): gui_app.init_window("UI") main_layout = MainLayout() main_layout.set_rect(rl.Rectangle(0, 0, gui_app.width, gui_app.height)) - for _ in gui_app.render(): + for showing_dialog in gui_app.render(): ui_state.update() - # TODO handle brigntness and awake state here - - main_layout.render() - kick_watchdog() + if not showing_dialog: + main_layout.render() + if __name__ == "__main__": main() diff --git a/selfdrive/ui/widgets/pairing_dialog.py b/selfdrive/ui/widgets/pairing_dialog.py index 79d05eaf42..55d53125d8 100644 --- a/selfdrive/ui/widgets/pairing_dialog.py +++ b/selfdrive/ui/widgets/pairing_dialog.py @@ -6,17 +6,20 @@ import time from openpilot.common.api import Api from openpilot.common.swaglog import cloudlog from openpilot.common.params import Params +from openpilot.system.ui.widgets import Widget from openpilot.system.ui.lib.application import FontWeight, gui_app from openpilot.system.ui.lib.wrap_text import wrap_text from openpilot.system.ui.lib.text_measure import measure_text_cached +from openpilot.selfdrive.ui.ui_state import ui_state -class PairingDialog: +class PairingDialog(Widget): """Dialog for device pairing with QR code.""" QR_REFRESH_INTERVAL = 300 # 5 minutes in seconds def __init__(self): + super().__init__() self.params = Params() self.qr_texture: rl.Texture | None = None self.last_qr_generation = 0 @@ -60,7 +63,11 @@ class PairingDialog: self._generate_qr_code() self.last_qr_generation = current_time - def render(self, rect: rl.Rectangle) -> int: + def _update_state(self): + if ui_state.prime_state.is_paired(): + gui_app.set_modal_overlay(None) + + def _render(self, rect: rl.Rectangle) -> int: rl.clear_background(rl.Color(224, 224, 224, 255)) self._check_qr_refresh() diff --git a/selfdrive/ui/widgets/setup.py b/selfdrive/ui/widgets/setup.py index 35a7c4101c..bf6d113f62 100644 --- a/selfdrive/ui/widgets/setup.py +++ b/selfdrive/ui/widgets/setup.py @@ -1,11 +1,10 @@ import pyray as rl -from openpilot.selfdrive.ui.lib.prime_state import PrimeType from openpilot.selfdrive.ui.ui_state import ui_state from openpilot.selfdrive.ui.widgets.pairing_dialog import PairingDialog from openpilot.system.ui.lib.application import gui_app, FontWeight from openpilot.system.ui.lib.wrap_text import wrap_text from openpilot.system.ui.widgets import Widget -from openpilot.system.ui.widgets.button import gui_button, ButtonStyle +from openpilot.system.ui.widgets.button import Button, ButtonStyle class SetupWidget(Widget): @@ -13,12 +12,15 @@ class SetupWidget(Widget): super().__init__() self._open_settings_callback = None self._pairing_dialog: PairingDialog | None = None + self._pair_device_btn = Button("Pair device", self._show_pairing, button_style=ButtonStyle.PRIMARY) + self._open_settings_btn = Button("Open", lambda: self._open_settings_callback() if self._open_settings_callback else None, + button_style=ButtonStyle.PRIMARY) def set_open_settings_callback(self, callback): self._open_settings_callback = callback def _render(self, rect: rl.Rectangle): - if ui_state.prime_state.get_type() == PrimeType.UNPAIRED: + if not ui_state.prime_state.is_paired(): self._render_registration(rect) else: self._render_firehose_prompt(rect) @@ -46,8 +48,7 @@ class SetupWidget(Widget): y += 50 button_rect = rl.Rectangle(x, y + 50, w, 128) - if gui_button(button_rect, "Pair device", button_style=ButtonStyle.PRIMARY): - self._show_pairing() + self._pair_device_btn.render(button_rect) def _render_firehose_prompt(self, rect: rl.Rectangle): """Render firehose prompt widget.""" @@ -80,9 +81,7 @@ class SetupWidget(Widget): # Open button button_height = 48 + 64 # font size + padding button_rect = rl.Rectangle(x, y, w, button_height) - if gui_button(button_rect, "Open", button_style=ButtonStyle.PRIMARY): - if self._open_settings_callback: - self._open_settings_callback() + self._open_settings_btn.render(button_rect) def _show_pairing(self): if not self._pairing_dialog: diff --git a/system/ui/lib/application.py b/system/ui/lib/application.py index 5b35f7ac9d..911891f10b 100644 --- a/system/ui/lib/application.py +++ b/system/ui/lib/application.py @@ -274,8 +274,9 @@ class GuiApplication: self._modal_overlay = ModalOverlay() if original_modal.callback is not None: original_modal.callback(result) + yield True else: - yield + yield False if self._render_texture: rl.end_texture_mode() diff --git a/system/ui/widgets/scroller.py b/system/ui/widgets/scroller.py index f7304bf6a6..757500a71c 100644 --- a/system/ui/widgets/scroller.py +++ b/system/ui/widgets/scroller.py @@ -27,7 +27,7 @@ class Scroller(Widget): super().__init__() self._items: list[Widget] = [] self._spacing = spacing - self._line_separator = line_separator + self._line_separator = LineSeparator() if line_separator else None self._pad_end = pad_end self.scroll_panel = GuiScrollPanel() @@ -36,14 +36,19 @@ class Scroller(Widget): self.add_widget(item) def add_widget(self, item: Widget) -> None: - if self._line_separator and len(self._items) > 0: - self._items.append(LineSeparator()) self._items.append(item) item.set_touch_valid_callback(self.scroll_panel.is_touch_valid) def _render(self, _): # TODO: don't draw items that are not in the viewport visible_items = [item for item in self._items if item.is_visible] + + # Add line separator between items + if self._line_separator is not None: + l = len(visible_items) + for i in range(1, len(visible_items)): + visible_items.insert(l - i, self._line_separator) + content_height = sum(item.rect.height for item in visible_items) + self._spacing * (len(visible_items)) if not self._pad_end: content_height -= self._spacing