From 6cbef7bc13d393ee71c6aa06f5506deef67b1925 Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Sat, 28 Feb 2026 08:00:07 -0800 Subject: [PATCH] ui: widgets animate out v2 (#37483) * i like this better * clean up * debug * fix able to click navwidgets that are closing (tested at rc 10) * add dismiss guards * fix keyboard so it unselects * pairing: use dismiss * main todo * rm pop_widgets_to! * reset dismiss state on show event * debug pop animation bugs * Revert "debug pop animation bugs" This reverts commit 9239f2e12cf79b1f75d15d39262fdd15ff5a5200. * revert * cmt * type * clean up * now do the todo * treat using widgets, not idxs, as a separate clean up for later * actually if not navwidget this is buggy * fix * short * simpler --- selfdrive/ui/mici/layouts/main.py | 12 ++++------ system/ui/lib/application.py | 38 +++++++++++++++++++++++-------- 2 files changed, 34 insertions(+), 16 deletions(-) diff --git a/selfdrive/ui/mici/layouts/main.py b/selfdrive/ui/mici/layouts/main.py index bd9167e82..2c3fea0d3 100644 --- a/selfdrive/ui/mici/layouts/main.py +++ b/selfdrive/ui/mici/layouts/main.py @@ -94,15 +94,13 @@ class MiciMainLayout(Scroller): # 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: - gui_app.pop_widgets_to(self) - self._scroll_to(self._onroad_layout) + 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: - gui_app.pop_widgets_to(self) - self._scroll_to(self._onroad_layout) + gui_app.pop_widgets_to(self, lambda: self._scroll_to(self._onroad_layout)) self._prev_standstill = CS.standstill def _on_interactive_timeout(self): @@ -113,10 +111,10 @@ class MiciMainLayout(Scroller): if ui_state.started: # Don't pop if at standstill if not ui_state.sm["carState"].standstill: - gui_app.pop_widgets_to(self) - self._scroll_to(self._onroad_layout) + gui_app.pop_widgets_to(self, lambda: self._scroll_to(self._onroad_layout)) else: - gui_app.pop_widgets_to(self) + # Screen turns off on timeout offroad, so pop immediately without animation + gui_app.pop_widgets_to(self, instant=True) self._scroll_to(self._home_layout) def _on_bookmark_clicked(self): diff --git a/system/ui/lib/application.py b/system/ui/lib/application.py index 34aed4f6a..98e05e112 100644 --- a/system/ui/lib/application.py +++ b/system/ui/lib/application.py @@ -383,27 +383,47 @@ class GuiApplication: self._nav_stack.append(widget) widget.show_event() + widget.set_enabled(True) - def pop_widget(self): + def pop_widget(self, idx: int | None = None): + # Pops widget instantly without animation if len(self._nav_stack) < 2: cloudlog.warning("At least one widget should remain on the stack, ignoring pop!") return - # re-enable previous widget and pop current - # TODO: switch to touch_valid - prev_widget = self._nav_stack[-2] - prev_widget.set_enabled(True) + idx_to_pop = len(self._nav_stack) - 1 if idx is None else idx + if idx_to_pop <= 0 or idx_to_pop >= len(self._nav_stack): + cloudlog.warning(f"Invalid index {idx_to_pop} to pop, ignoring!") + return - widget = self._nav_stack.pop() + # only re-enable previous widget if popping top widget + if idx_to_pop == len(self._nav_stack) - 1: + prev_widget = self._nav_stack[idx_to_pop - 1] + prev_widget.set_enabled(True) + + widget = self._nav_stack.pop(idx_to_pop) widget.hide_event() - def pop_widgets_to(self, widget): + def pop_widgets_to(self, widget: object, callback: Callable[[], None] | None = None, instant: bool = False): + # Pops middle widgets instantly without animation then dismisses top, animated out if NavWidget if widget not in self._nav_stack: cloudlog.warning("Widget not in stack, cannot pop to it!") return - # pops all widgets after specified widget - while len(self._nav_stack) > 0 and self._nav_stack[-1] != widget: + # Nothing to pop, ensure we still run callback + top_widget = self._nav_stack[-1] + if top_widget == widget: + if callback: + callback() + return + + # instantly pop widgets in between, then dismiss top widget for animation + while len(self._nav_stack) > 1 and self._nav_stack[-2] != widget: + self.pop_widget(len(self._nav_stack) - 2) + + if not instant: + top_widget.dismiss(callback) + else: self.pop_widget() def get_active_widget(self):