Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions selfdrive/ui/mici/layouts/home.py
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,7 @@ def __init__(self):
self._version_commit_label = MiciLabel("", font_size=36, color=rl.GRAY, font_weight=FontWeight.ROMAN)

def show_event(self):
super().show_event()
self._version_text = self._get_version_text()
self._update_params()

Expand Down
43 changes: 38 additions & 5 deletions system/ui/widgets/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import abc
import pyray as rl
from enum import IntEnum
from typing import TypeVar
from collections.abc import Callable
from openpilot.system.ui.lib.application import gui_app, MousePos, MAX_TOUCH_SLOTS, MouseEvent

Expand All @@ -13,6 +14,10 @@ class Device:
awake = True
device = Device()

W = TypeVar('W', bound='Widget')

DEBUG = True


class DialogResult(IntEnum):
CANCEL = 0
Expand All @@ -24,11 +29,14 @@ class Widget(abc.ABC):
def __init__(self):
self._rect: rl.Rectangle = rl.Rectangle(0, 0, 0, 0)
self._parent_rect: rl.Rectangle | None = None
self._children: list[Widget] = []

self._enabled: bool | Callable[[], bool] = True
self._is_visible: bool | Callable[[], bool] = True

self.__is_pressed = [False] * MAX_TOUCH_SLOTS
# if current mouse/touch down started within the widget's rectangle
self.__tracking_is_pressed = [False] * MAX_TOUCH_SLOTS
self._enabled: bool | Callable[[], bool] = True
self._is_visible: bool | Callable[[], bool] = True
self._touch_valid_callback: Callable[[], bool] | None = None
self._click_delay: float | None = None # seconds to hold is_pressed after release
self._click_release_time: float | None = None
Expand Down Expand Up @@ -197,12 +205,37 @@ def _handle_mouse_event(self, mouse_event: MouseEvent) -> None:
"""Optionally handle mouse events. This is called before rendering."""
# Default implementation does nothing, can be overridden by subclasses

def _child(self, widget: W) -> W:
"""
Register a widget as a child. Lifecycle events (show/hide) propagate to registered children.
- If the widget is pushed onto the nav stack, do NOT register it (gui_app manages its lifecycle).
- If the widget is rendered inline in _render(), register it.
"""
assert widget not in self._children, f"{type(widget).__name__} already a child of {type(self).__name__}"
self._children.append(widget)
return widget

_show_hide_depth = 0

def show_event(self):
"""Optionally handle show event. Parent must manually call this"""
# TODO: iterate through all child objects, check for subclassing from Widget/Layout (Scroller)
"""Called when widget becomes visible. Propagates to registered children."""
if DEBUG:
print(f"{' ' * Widget._show_hide_depth}show_event: {type(self).__name__}")
Widget._show_hide_depth += 1
for child in self._children:
child.show_event()
if DEBUG:
Widget._show_hide_depth -= 1

def hide_event(self):
"""Optionally handle hide event. Parent must manually call this"""
"""Called when widget is hidden. Propagates to registered children."""
if DEBUG:
print(f"{' ' * Widget._show_hide_depth}hide_event: {type(self).__name__}")
Widget._show_hide_depth += 1
for child in self._children:
child.hide_event()
if DEBUG:
Widget._show_hide_depth -= 1

def dismiss(self, callback: Callable[[], None] | None = None):
"""Immediately dismiss the widget, firing the callback after."""
Expand Down
3 changes: 1 addition & 2 deletions system/ui/widgets/nav_widget.py
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@ def __init__(self):
self._shown_callback: Callable[[], None] | None = None # transient callback fired after show animation completes

# TODO: move this state into NavBar
self._nav_bar = NavBar()
self._nav_bar = self._child(NavBar())
self._nav_bar_show_time = 0.0
self._nav_bar_y_filter = FirstOrderFilter(0.0, 0.1, 1 / gui_app.target_fps)

Expand Down Expand Up @@ -214,7 +214,6 @@ def dismiss(self, callback: Callable[[], None] | None = None):

def show_event(self):
super().show_event()
self._nav_bar.show_event()

# Reset state
self._drag_start_pos = None
Expand Down
10 changes: 1 addition & 9 deletions system/ui/widgets/scroller.py
Original file line number Diff line number Diff line change
Expand Up @@ -422,18 +422,10 @@ class Scroller(Widget):
"""Wrapper for _Scroller so that children do not need to call events or pass down enabled for nav stack."""
def __init__(self, **kwargs):
super().__init__()
self._scroller = _Scroller([], **kwargs)
self._scroller = self._child(_Scroller([], **kwargs))
# pass down enabled to child widget for nav stack
self._scroller.set_enabled(lambda: self.enabled)

def show_event(self):
super().show_event()
self._scroller.show_event()

def hide_event(self):
super().hide_event()
self._scroller.hide_event()

def _render(self, _):
self._scroller.render(self._rect)

Expand Down
Loading