import pyray as rl from enum import IntEnum import cereal.messaging as messaging from openpilot.selfdrive.ui.mici.layouts.home import MiciHomeLayout from openpilot.selfdrive.ui.mici.layouts.settings.settings import SettingsLayout from openpilot.selfdrive.ui.mici.layouts.offroad_alerts import MiciOffroadAlerts from openpilot.selfdrive.ui.mici.onroad.augmented_road_view import AugmentedRoadView from openpilot.selfdrive.ui.ui_state import device, ui_state from openpilot.selfdrive.ui.mici.layouts.onboarding import OnboardingWindow from openpilot.system.ui.widgets import Widget from openpilot.system.ui.widgets.scroller import Scroller from openpilot.system.ui.lib.application import gui_app ONROAD_DELAY = 2.5 # seconds class MainState(IntEnum): MAIN = 0 SETTINGS = 1 class MiciMainLayout(Widget): def __init__(self): super().__init__() self._pm = messaging.PubMaster(['bookmarkButton']) self._current_mode: MainState | None = None self._prev_onroad = False self._prev_standstill = False self._onroad_time_delay: float | None = None self._setup = False # Initialize widgets self._home_layout = MiciHomeLayout() self._alerts_layout = MiciOffroadAlerts() self._settings_layout = SettingsLayout() self._onroad_layout = AugmentedRoadView(bookmark_callback=self._on_bookmark_clicked) # Initialize widget rects for widget in (self._home_layout, self._settings_layout, self._alerts_layout, self._onroad_layout): # TODO: set parent rect and use it if never passed rect from render (like in Scroller) widget.set_rect(rl.Rectangle(0, 0, gui_app.width, gui_app.height)) self._scroller = Scroller([ self._alerts_layout, self._home_layout, self._onroad_layout, ], spacing=0, pad_start=0, pad_end=0) self._scroller.set_reset_scroll_at_show(False) # Disable scrolling when onroad is interacting with bookmark self._scroller.set_scrolling_enabled(lambda: not self._onroad_layout.is_swiping_left()) self._layouts = { MainState.MAIN: self._scroller, MainState.SETTINGS: self._settings_layout, } # Set callbacks self._setup_callbacks() # Start onboarding if terms or training not completed self._onboarding_window = OnboardingWindow() if not self._onboarding_window.completed: gui_app.set_modal_overlay(self._onboarding_window) def _setup_callbacks(self): self._home_layout.set_callbacks(on_settings=self._on_settings_clicked) self._settings_layout.set_callbacks(on_close=self._on_settings_closed) self._onroad_layout.set_click_callback(lambda: self._scroll_to(self._home_layout)) device.add_interactive_timeout_callback(self._set_mode_for_started) def _scroll_to(self, layout: Widget): layout_x = int(layout.rect.x) self._scroller.scroll_to(layout_x, smooth=True) def _render(self, _): # Initial show event if self._current_mode is None: self._set_mode(MainState.MAIN) if not self._setup: if self._alerts_layout.active_alerts() > 0: self._scroller.scroll_to(self._alerts_layout.rect.x) else: self._scroller.scroll_to(self._rect.width) self._setup = True # Render if self._current_mode == MainState.MAIN: self._scroller.render(self._rect) elif self._current_mode == MainState.SETTINGS: self._settings_layout.render(self._rect) self._handle_transitions() def _set_mode(self, mode: MainState): if mode != self._current_mode: if self._current_mode is not None: self._layouts[self._current_mode].hide_event() self._layouts[mode].show_event() self._current_mode = mode def _handle_transitions(self): if ui_state.started != self._prev_onroad: self._prev_onroad = ui_state.started if ui_state.started: self._onroad_time_delay = rl.get_time() else: self._set_mode_for_started(True) # delay so we show home for a bit after starting if self._onroad_time_delay is not None and rl.get_time() - self._onroad_time_delay >= ONROAD_DELAY: self._set_mode_for_started(True) self._onroad_time_delay = None CS = ui_state.sm["carState"] if not CS.standstill and self._prev_standstill: self._set_mode(MainState.MAIN) self._scroll_to(self._onroad_layout) self._prev_standstill = CS.standstill def _set_mode_for_started(self, onroad_transition: bool = False): if ui_state.started: CS = ui_state.sm["carState"] # Only go onroad if car starts or is not at a standstill if not CS.standstill or onroad_transition: self._set_mode(MainState.MAIN) self._scroll_to(self._onroad_layout) else: # Stay in settings if car turns off while in settings if not onroad_transition or self._current_mode != MainState.SETTINGS: self._set_mode(MainState.MAIN) self._scroll_to(self._home_layout) def _on_settings_clicked(self): self._set_mode(MainState.SETTINGS) def _on_settings_closed(self): self._set_mode(MainState.MAIN) def _on_bookmark_clicked(self): user_bookmark = messaging.new_message('bookmarkButton') user_bookmark.valid = True self._pm.send('bookmarkButton', user_bookmark)