From 6266feeed2c314e48b1fa5f735b1cc56f2a03284 Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Sat, 28 Feb 2026 00:13:34 -0800 Subject: [PATCH] Revert "ui: widgets animate out" (#37478) Revert "ui: widgets animate out (#37321)" This reverts commit 47ca2c93817e265f6784bb40695e59ab9f742790. --- selfdrive/ui/mici/layouts/main.py | 7 ++-- selfdrive/ui/mici/widgets/dialog.py | 8 +++-- system/ui/lib/application.py | 43 +++---------------------- system/ui/mici_setup.py | 2 +- system/ui/tests/test_nav_stack.py | 50 ----------------------------- system/ui/widgets/nav_widget.py | 16 +-------- 6 files changed, 14 insertions(+), 112 deletions(-) delete mode 100644 system/ui/tests/test_nav_stack.py diff --git a/selfdrive/ui/mici/layouts/main.py b/selfdrive/ui/mici/layouts/main.py index b8958993f..1a0c6bc09 100644 --- a/selfdrive/ui/mici/layouts/main.py +++ b/selfdrive/ui/mici/layouts/main.py @@ -93,14 +93,14 @@ class MiciMainLayout(Scroller): self._scroll_to(self._home_layout) if self._onroad_time_delay is not None and rl.get_time() - self._onroad_time_delay >= ONROAD_DELAY: - gui_app.request_pop_widgets_to(self) + gui_app.pop_widgets_to(self) 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: - gui_app.request_pop_widgets_to(self) + gui_app.pop_widgets_to(self) self._scroll_to(self._onroad_layout) self._prev_standstill = CS.standstill @@ -112,10 +112,9 @@ class MiciMainLayout(Scroller): if ui_state.started: # Don't pop if at standstill if not ui_state.sm["carState"].standstill: - gui_app.request_pop_widgets_to(self) + gui_app.pop_widgets_to(self) self._scroll_to(self._onroad_layout) else: - # Screen turns off on timeout offroad, so pop immediately without animation gui_app.pop_widgets_to(self) self._scroll_to(self._home_layout) diff --git a/selfdrive/ui/mici/widgets/dialog.py b/selfdrive/ui/mici/widgets/dialog.py index 1191f031b..8e978066c 100644 --- a/selfdrive/ui/mici/widgets/dialog.py +++ b/selfdrive/ui/mici/widgets/dialog.py @@ -82,8 +82,8 @@ class BigConfirmationDialogV2(BigDialogBase): def _on_confirm(self): if self._exit_on_confirm: - gui_app.request_pop_widget(self._confirm_callback) - elif self._confirm_callback: + gui_app.pop_widget() + if self._confirm_callback: self._confirm_callback() def _update_state(self): @@ -128,7 +128,9 @@ class BigInputDialog(BigDialogBase): def confirm_callback_wrapper(): text = self._keyboard.text() - gui_app.request_pop_widget(lambda: confirm_callback(text) if confirm_callback else None) + gui_app.pop_widget() + if confirm_callback: + confirm_callback(text) self._confirm_callback = confirm_callback_wrapper def _update_state(self): diff --git a/system/ui/lib/application.py b/system/ui/lib/application.py index ebfa385ea..34aed4f6a 100644 --- a/system/ui/lib/application.py +++ b/system/ui/lib/application.py @@ -384,25 +384,17 @@ class GuiApplication: self._nav_stack.append(widget) widget.show_event() - # pop_widget and pop_widgets_to are immediate (no animation). Use request_* variants for animated dismiss. - def pop_widget(self, idx: int | None = None): + def pop_widget(self): if len(self._nav_stack) < 2: cloudlog.warning("At least one widget should remain on the stack, ignoring pop!") return - if idx is None: - idx = -1 - else: - if idx < 1 or idx >= len(self._nav_stack): - return - - # re-enable widget below, and re-enable popped widget so it's clean if pushed again + # re-enable previous widget and pop current # TODO: switch to touch_valid - prev_widget = self._nav_stack[idx - 1] + prev_widget = self._nav_stack[-2] prev_widget.set_enabled(True) - widget = self._nav_stack.pop(idx) - widget.set_enabled(True) + widget = self._nav_stack.pop() widget.hide_event() def pop_widgets_to(self, widget): @@ -414,33 +406,6 @@ class GuiApplication: while len(self._nav_stack) > 0 and self._nav_stack[-1] != widget: self.pop_widget() - def request_pop_widget(self, callback: Callable | None = None): - """Request the top widget to close. NavWidgets dismiss (animate then pop); others pop immediately. Callback runs after pop.""" - if len(self._nav_stack) < 2: - cloudlog.warning("At least one widget should remain on the stack, ignoring pop!") - return - top = self._nav_stack[-1] - if hasattr(top, "dismiss"): - top.dismiss(callback) - else: - self.pop_widget() - if callback: - callback() - - def request_pop_widgets_to(self, widget): - """Request to close widgets down to the given widget. Middle widgets are removed via pop_widget logic; only the top animates down.""" - if widget not in self._nav_stack: - cloudlog.warning("Widget not in stack, cannot pop to it!") - return - - if len(self._nav_stack) < 2 or self._nav_stack[-1] == widget: - return - - # Pop second-from-top repeatedly until stack is [target, top]; each goes through re-enable + hide_event - while len(self._nav_stack) > 2 and self._nav_stack[-2] != widget: - self.pop_widget(len(self._nav_stack) - 2) - self.request_pop_widget() - def get_active_widget(self): if len(self._nav_stack) > 0: return self._nav_stack[-1] diff --git a/system/ui/mici_setup.py b/system/ui/mici_setup.py index 961cbe753..ca6354443 100755 --- a/system/ui/mici_setup.py +++ b/system/ui/mici_setup.py @@ -534,7 +534,7 @@ class Setup(Widget): def _nav_stack_tick(self): has_internet = self._network_monitor.network_connected.is_set() if has_internet and not self._prev_has_internet: - gui_app.request_pop_widgets_to(self) + gui_app.pop_widgets_to(self) self._prev_has_internet = has_internet def _update_state(self): diff --git a/system/ui/tests/test_nav_stack.py b/system/ui/tests/test_nav_stack.py deleted file mode 100644 index eae5d78c1..000000000 --- a/system/ui/tests/test_nav_stack.py +++ /dev/null @@ -1,50 +0,0 @@ -import pytest -from openpilot.system.ui.lib.application import gui_app - - -class Widget: - def __init__(self): - self.enabled, self.shown, self.hidden = True, False, False - - def set_enabled(self, e): self.enabled = e - def show_event(self): self.shown = True - def hide_event(self): self.hidden = True - - -@pytest.fixture(autouse=True) -def clean_stack(): - gui_app._nav_stack = [] - yield - gui_app._nav_stack = [] - - -def test_push(): - a, b = Widget(), Widget() - gui_app.push_widget(a) - gui_app.push_widget(b) - assert not a.enabled and not a.hidden - assert b.enabled and b.shown - - -def test_pop_re_enables(): - widgets = [Widget() for _ in range(4)] - for w in widgets: - gui_app.push_widget(w) - assert all(not w.enabled for w in widgets[:-1]) - gui_app.pop_widget() - assert widgets[-2].enabled - - -@pytest.mark.parametrize("pop_fn", [gui_app.pop_widgets_to, gui_app.request_pop_widgets_to]) -def test_pop_widgets_to(pop_fn): - widgets = [Widget() for _ in range(4)] - for w in widgets: - gui_app.push_widget(w) - - root = widgets[0] - pop_fn(root) - - assert gui_app._nav_stack == [root] - assert root.enabled and not root.hidden - for w in widgets[1:]: - assert w.enabled and w.hidden and w.shown diff --git a/system/ui/widgets/nav_widget.py b/system/ui/widgets/nav_widget.py index ad8d5d313..02afc911b 100644 --- a/system/ui/widgets/nav_widget.py +++ b/system/ui/widgets/nav_widget.py @@ -17,7 +17,6 @@ NAV_BAR_HEIGHT = 8 DISMISS_PUSH_OFFSET = 50 + NAV_BAR_MARGIN + NAV_BAR_HEIGHT # px extra to push down when dismissing DISMISS_TIME_SECONDS = 2.0 -DISMISS_ANIMATION_RC = 0.2 # time constant for non-user triggered dismiss animation class NavBar(Widget): @@ -63,7 +62,6 @@ class NavWidget(Widget, abc.ABC): self._pos_filter = BounceFilter(0.0, 0.1, 1 / gui_app.target_fps, bounce=1) self._playing_dismiss_animation = False - self._dismiss_callback: Callable | None = None self._trigger_animate_in = False self._nav_bar_show_time = 0.0 self._back_enabled: bool | Callable[[], bool] = True @@ -85,8 +83,7 @@ class NavWidget(Widget, abc.ABC): # FIXME: disabling this widget on new push_widget still causes this widget to track mouse events without mouse down super()._handle_mouse_event(mouse_event) - # Don't let touch events change filter state during dismiss animation - if not self.back_enabled or self._playing_dismiss_animation: + if not self.back_enabled: self._back_button_start_pos = None self._swiping_away = False self._can_swipe_away = True @@ -173,10 +170,6 @@ class NavWidget(Widget, abc.ABC): if self._back_callback is not None: self._back_callback() - if self._dismiss_callback is not None: - self._dismiss_callback() - self._dismiss_callback = None - self._playing_dismiss_animation = False self._back_button_start_pos = None self._swiping_away = False @@ -212,13 +205,6 @@ class NavWidget(Widget, abc.ABC): return ret - def dismiss(self, callback: Callable | None = None): - """Programmatically trigger the dismiss animation. Calls pop_widget when done, then callback.""" - if not self._playing_dismiss_animation: - self._pos_filter.update_alpha(DISMISS_ANIMATION_RC) - self._playing_dismiss_animation = True - self._dismiss_callback = callback - def show_event(self): super().show_event() # FIXME: we don't know the height of the rect at first show_event since it's before the first render :(