diff --git a/selfdrive/ui/mici/widgets/dialog.py b/selfdrive/ui/mici/widgets/dialog.py index 6cfb6455b..619c1ca28 100644 --- a/selfdrive/ui/mici/widgets/dialog.py +++ b/selfdrive/ui/mici/widgets/dialog.py @@ -81,8 +81,8 @@ class BigConfirmationDialogV2(BigDialogBase): def _on_confirm(self): if self._exit_on_confirm: - gui_app.pop_widget() - if self._confirm_callback: + self.dismiss(self._confirm_callback) + elif self._confirm_callback: self._confirm_callback() def _update_state(self): @@ -127,9 +127,7 @@ class BigInputDialog(BigDialogBase): def confirm_callback_wrapper(): text = self._keyboard.text() - gui_app.pop_widget() - if confirm_callback: - confirm_callback(text) + self.dismiss((lambda: confirm_callback(text)) if confirm_callback else None) self._confirm_callback = confirm_callback_wrapper def _update_state(self): diff --git a/selfdrive/ui/mici/widgets/pairing_dialog.py b/selfdrive/ui/mici/widgets/pairing_dialog.py index 7476a3b65..991cb05a8 100644 --- a/selfdrive/ui/mici/widgets/pairing_dialog.py +++ b/selfdrive/ui/mici/widgets/pairing_dialog.py @@ -68,8 +68,8 @@ class PairingDialog(NavWidget): def _update_state(self): super()._update_state() - if ui_state.prime_state.is_paired(): - self._playing_dismiss_animation = True + if ui_state.prime_state.is_paired() and not self.is_dismissing: + self.dismiss() def _render(self, rect: rl.Rectangle): self._check_qr_refresh() diff --git a/system/ui/widgets/__init__.py b/system/ui/widgets/__init__.py index cc8d72959..1e2f78381 100644 --- a/system/ui/widgets/__init__.py +++ b/system/ui/widgets/__init__.py @@ -195,3 +195,9 @@ class Widget(abc.ABC): def hide_event(self): """Optionally handle hide event. Parent must manually call this""" + + def dismiss(self, callback: Callable[[], None] | None = None): + """Immediately dismiss the widget, firing the callback after.""" + gui_app.pop_widget() + if callback: + callback() diff --git a/system/ui/widgets/nav_widget.py b/system/ui/widgets/nav_widget.py index 3ac4c903b..fe17f12a8 100644 --- a/system/ui/widgets/nav_widget.py +++ b/system/ui/widgets/nav_widget.py @@ -2,6 +2,7 @@ from __future__ import annotations import abc import pyray as rl +from collections.abc import Callable from openpilot.system.ui.widgets import Widget from openpilot.common.filter_simple import BounceFilter, FirstOrderFilter from openpilot.system.ui.lib.application import gui_app, MousePos, MouseEvent @@ -15,6 +16,7 @@ NAV_BAR_WIDTH = 205 NAV_BAR_HEIGHT = 8 DISMISS_PUSH_OFFSET = NAV_BAR_MARGIN + NAV_BAR_HEIGHT + 50 # px extra to push down when dismissing +DISMISS_ANIMATION_RC = 0.2 # slightly slower for non-user triggered dismiss animation class NavBar(Widget): @@ -61,6 +63,8 @@ class NavWidget(Widget, abc.ABC): self._playing_dismiss_animation = False # released and animating away self._y_pos_filter = BounceFilter(0.0, 0.1, 1 / gui_app.target_fps, bounce=1) + self._dismiss_callback: Callable[[], None] | None = None + # TODO: move this state into NavBar self._nav_bar = NavBar() self._nav_bar_show_time = 0.0 @@ -141,6 +145,9 @@ class NavWidget(Widget, abc.ABC): if new_y > self._rect.height + DISMISS_PUSH_OFFSET - 10: gui_app.pop_widget() + if self._dismiss_callback is not None: + self._dismiss_callback() + self._dismiss_callback = None self._playing_dismiss_animation = False self._drag_start_pos = None @@ -180,6 +187,13 @@ class NavWidget(Widget, abc.ABC): def is_dismissing(self) -> bool: return self._dragging_down or self._playing_dismiss_animation + def dismiss(self, callback: Callable[[], None] | None = None): + """Programmatically trigger the dismiss animation. Calls pop_widget when done, then callback.""" + if not self._playing_dismiss_animation: + self._playing_dismiss_animation = True + self._y_pos_filter.update_alpha(DISMISS_ANIMATION_RC) + self._dismiss_callback = callback + def show_event(self): super().show_event() self._nav_bar.show_event() @@ -188,6 +202,7 @@ class NavWidget(Widget, abc.ABC): self._drag_start_pos = None self._dragging_down = False self._playing_dismiss_animation = False + self._dismiss_callback = None # Start NavWidget off-screen, no matter how tall it is self._y_pos_filter.update_alpha(0.1) self._y_pos_filter.x = gui_app.height