NavWidget: clean up scroller access (#37480)

* clean up

* more

* great clean ups

* better name

* remove useless _can_swipe_away

* reorder

* rename

* state machine is nice but might be too much

* Revert "state machine is nice but might be too much"

This reverts commit f8952969243a2eac3ed5f84793ba7b0c0cdf24bf.

* got a better name out of it though

* clean up

* clean up

* rm!

* rm

* and this

* and

* clean up
This commit is contained in:
Shane Smiskol
2026-02-28 03:26:18 -08:00
committed by GitHub
parent 940c5b3b3f
commit d016071df3
4 changed files with 48 additions and 37 deletions
+2 -8
View File
@@ -7,8 +7,7 @@ from collections.abc import Callable
from openpilot.common.basedir import BASEDIR
from openpilot.common.params import Params
from openpilot.common.time_helpers import system_time_valid
from openpilot.system.ui.widgets.scroller import NavScroller
from openpilot.system.ui.lib.scroll_panel2 import GuiScrollPanel2
from openpilot.system.ui.widgets.scroller import NavRawScrollPanel, NavScroller
from openpilot.selfdrive.ui.mici.widgets.button import BigButton, BigCircleButton
from openpilot.selfdrive.ui.mici.widgets.dialog import BigDialog, BigConfirmationDialogV2
from openpilot.selfdrive.ui.mici.widgets.pairing_dialog import PairingDialog
@@ -17,21 +16,16 @@ from openpilot.selfdrive.ui.mici.layouts.onboarding import TrainingGuide
from openpilot.system.ui.lib.application import gui_app, FontWeight, MousePos
from openpilot.system.ui.lib.multilang import tr
from openpilot.system.ui.widgets import Widget
from openpilot.system.ui.widgets.nav_widget import NavWidget
from openpilot.selfdrive.ui.ui_state import ui_state
from openpilot.system.ui.widgets.label import MiciLabel
from openpilot.system.ui.widgets.html_render import HtmlModal, HtmlRenderer
from openpilot.system.athena.registration import UNREGISTERED_DONGLE_ID
class MiciFccModal(NavWidget):
BACK_TOUCH_AREA_PERCENTAGE = 0.1
class MiciFccModal(NavRawScrollPanel):
def __init__(self, file_path: str | None = None, text: str | None = None):
super().__init__()
self._content = HtmlRenderer(file_path=file_path, text=text)
self._scroll_panel = GuiScrollPanel2(horizontal=False)
self._scroll_panel.set_enabled(lambda: self.enabled and not self._dragging_down)
self._fcc_logo = gui_app.texture("icons_mici/settings/device/fcc_logo.png", 76, 64)
def _render(self, rect: rl.Rectangle):
@@ -14,7 +14,7 @@ from openpilot.system.ui.lib.wrap_text import wrap_text
from openpilot.system.ui.lib.scroll_panel2 import GuiScrollPanel2
from openpilot.system.ui.lib.multilang import tr, trn, tr_noop
from openpilot.system.ui.widgets import Widget
from openpilot.system.ui.widgets.nav_widget import NavWidget
from openpilot.system.ui.widgets.scroller import NavRawScrollPanel
TITLE = tr_noop("Firehose Mode")
DESCRIPTION = tr_noop(
@@ -218,9 +218,5 @@ class FirehoseLayoutBase(Widget):
time.sleep(self.UPDATE_INTERVAL)
class FirehoseLayout(FirehoseLayoutBase, NavWidget):
BACK_TOUCH_AREA_PERCENTAGE = 0.1
def __init__(self):
super().__init__()
self._scroll_panel.set_enabled(lambda: self.enabled and not self._dragging_down)
class FirehoseLayout(NavRawScrollPanel, FirehoseLayoutBase):
pass
+20 -22
View File
@@ -14,11 +14,12 @@ NAV_BAR_MARGIN = 6
NAV_BAR_WIDTH = 205
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_PUSH_OFFSET = NAV_BAR_MARGIN + NAV_BAR_HEIGHT + 50 # px extra to push down when dismissing
class NavBar(Widget):
FADE_AFTER_SECONDS = 2.0
def __init__(self):
super().__init__()
self.set_rect(rl.Rectangle(0, 0, NAV_BAR_WIDTH, NAV_BAR_HEIGHT))
@@ -37,7 +38,7 @@ class NavBar(Widget):
self._fade_time = rl.get_time()
def _render(self, _):
if rl.get_time() - self._fade_time > DISMISS_TIME_SECONDS:
if rl.get_time() - self._fade_time > self.FADE_AFTER_SECONDS:
self._alpha = 0.0
alpha = self._alpha_filter.update(self._alpha)
@@ -54,42 +55,37 @@ class NavWidget(Widget, abc.ABC):
def __init__(self):
super().__init__()
# State
self._drag_start_pos: MousePos | None = None # cleared after certain amount of horizontal movement
self._dragging_down = False # swiped down enough to trigger dismissing on release
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)
# TODO: move this state into NavBar
self._nav_bar = NavBar()
self._nav_bar_show_time = 0.0
self._nav_bar_y_filter = FirstOrderFilter(0.0, 0.1, 1 / gui_app.target_fps)
def _back_enabled(self) -> bool:
# Children can override this to block swipe away, like when not at
# the top of a vertical scroll panel to prevent erroneous swipes
return True
def _handle_mouse_event(self, mouse_event: MouseEvent) -> None:
# 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)
if mouse_event.left_pressed:
# user is able to swipe away if starting near top of screen, or anywhere if scroller is at top
# user is able to swipe away if starting near top of screen
self._y_pos_filter.update_alpha(0.04)
in_dismiss_area = mouse_event.pos.y < self._rect.height * self.BACK_TOUCH_AREA_PERCENTAGE
# TODO: remove vertical scrolling and then this hacky logic to check if scroller is at top
scroller_at_top = False
vertical_scroller = False
# TODO: -20? snapping in WiFi dialog can make offset not be positive at the top
if hasattr(self, '_scroller'):
scroller_at_top = self._scroller.scroll_panel.get_offset() >= -20 and not self._scroller._horizontal
vertical_scroller = not self._scroller._horizontal
elif hasattr(self, '_scroll_panel'):
scroller_at_top = self._scroll_panel.get_offset() >= -20 and not self._scroll_panel._horizontal
vertical_scroller = not self._scroll_panel._horizontal
# Vertical scrollers need to be at the top to swipe away to prevent erroneous swipes
if (not vertical_scroller and in_dismiss_area) or scroller_at_top:
if in_dismiss_area and self._back_enabled():
self._drag_start_pos = mouse_event.pos
elif mouse_event.left_down:
if self._drag_start_pos is not None:
# block swiping away if too much horizontal or upward movement
# block (lock-in) threshold is higher than start dismissing
horizontal_movement = abs(mouse_event.pos.x - self._drag_start_pos.x) > BLOCK_SWIPE_AWAY_THRESHOLD
upward_movement = mouse_event.pos.y - self._drag_start_pos.y < -BLOCK_SWIPE_AWAY_THRESHOLD
@@ -102,7 +98,9 @@ class NavWidget(Widget, abc.ABC):
self._drag_start_pos = None
elif mouse_event.left_released:
# reset rc for either slide up or down animation
self._y_pos_filter.update_alpha(0.1)
# if far enough, trigger back navigation callback
if self._drag_start_pos is not None:
if mouse_event.pos.y - self._drag_start_pos.y > SWIPE_AWAY_THRESHOLD:
@@ -116,10 +114,13 @@ class NavWidget(Widget, abc.ABC):
new_y = 0.0
if self._dragging_down:
self._nav_bar.set_alpha(1.0)
# FIXME: disabling this widget on new push_widget still causes this widget to track mouse events without mouse down
if not self.enabled:
self._drag_start_pos = None
# TODO: why is this not in handle_mouse_event? have to hack above
if self._drag_start_pos is not None:
last_mouse_event = gui_app.last_mouse_event
# push entire widget as user drags it away
@@ -127,9 +128,6 @@ class NavWidget(Widget, abc.ABC):
if new_y < SWIPE_AWAY_THRESHOLD:
new_y /= 2 # resistance until mouse release would dismiss widget
if self._dragging_down:
self._nav_bar.set_alpha(1.0)
if self._playing_dismiss_animation:
new_y = self._rect.height + DISMISS_PUSH_OFFSET
+23
View File
@@ -444,3 +444,26 @@ class NavScroller(NavWidget, Scroller):
super().__init__(**kwargs)
# pass down enabled to child widget for nav stack + disable while swiping away NavWidget
self._scroller.set_enabled(lambda: self.enabled and not self._dragging_down)
def _back_enabled(self) -> bool:
# Vertical scrollers need to be at the top to swipe away to prevent erroneous swipes
# TODO: only used for offroad alerts, remove when horizontal
return self._scroller._horizontal or self._scroller.scroll_panel.get_offset() >= -20 # some tolerance
# TODO: only used for a few vertical scrollers, remove when horizontal
class NavRawScrollPanel(NavWidget):
# can swipe anywhere, only when at top
BACK_TOUCH_AREA_PERCENTAGE = 1.0
def __init__(self):
super().__init__()
self._scroll_panel = GuiScrollPanel2(horizontal=False)
self._scroll_panel.set_enabled(lambda: self.enabled and not self._dragging_down)
def show_event(self):
super().show_event()
self._scroll_panel.set_offset(0)
def _back_enabled(self) -> bool:
return self._scroll_panel.get_offset() >= -20