mirror of
https://github.com/firestar5683/StarPilot.git
synced 2026-08-05 16:26:06 +08:00
ui: navigation stack (#37094)
* initial * start to support nav stack in settings panels + fix some navwidget bugs * add deprecation warning and move more to new nav stack * fix overriding NavWidget enabled and do developer panel * fix interactive timeout and do main * more device, not done yet * minor network fixes * dcam dialog * start onboarding * fix onboarding * do mici setup * remove now useless CUSTOM_SOFTWARE * support big ui with old modal overlay * reset can be old modal overlay, but updater needs new since it uses wifiui * flip name truthiness to inspire excitement * all *should* work, but will do pass later * clean up main * clean up settiings * clean up dialog and developer * cleanup mici setup some * rm one more * fix keyboard * revert * might as well but clarify * fix networkinfopage buttons * lint * nice clean up from cursor * animate background fade with position * fix device overlays * cursor fix pt1 cursor fix pt2 * rm print * capital * temp fix from cursor for onboarding not freeing space after reviewing training guide * fix home screen scroller snap not resetting * stash * nice gradient on top * 40 * 20 * no gradient * return unused returns and always show regulatory btn * nice! * clean up * new_modal is always true! * more clean up * clean up * big only renders top 1 * fixup setup and updater * stash * Revert "stash" This reverts commit 3cfb226ccb51869ed1f7d630b5fdd6725ad094d5. * fix mici keys coming in from top * clean up * fix mici dialogs like tici, pop first incase call back pushes * clever way but not not * Revert "clever way but not not" This reverts commit f69d106df61262f049df20cc1a9064ca1e6feeb7. * more setup * mici keyboard: fix not disabling below * cmt * fix wifi callbacks not running in rare case * clean up network * clean up network * clean up dialog * pairing * rm * todo * fix replay * they push themselkves! * clean up ui_state * clean up application * clean up * stash * Revert "stash" This reverts commit 07d3f5f26c99ef891086b6fe03095d53a62b8631. * typing * lint
This commit is contained in:
@@ -1,5 +1,4 @@
|
||||
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
|
||||
@@ -15,18 +14,12 @@ 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
|
||||
@@ -49,38 +42,31 @@ class MiciMainLayout(Widget):
|
||||
self._onroad_layout,
|
||||
], spacing=0, pad_start=0, pad_end=0, scroll_indicator=False, edge_shadows=False)
|
||||
self._scroller.set_reset_scroll_at_show(False)
|
||||
self._scroller.set_enabled(lambda: self.enabled) # for nav stack
|
||||
|
||||
# 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
|
||||
gui_app.push_widget(self)
|
||||
|
||||
# Start onboarding if terms or training not completed, make sure to push after self
|
||||
self._onboarding_window = OnboardingWindow()
|
||||
if not self._onboarding_window.completed:
|
||||
gui_app.set_modal_overlay(self._onboarding_window)
|
||||
gui_app.push_widget(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._home_layout.set_callbacks(on_settings=lambda: gui_app.push_widget(self._settings_layout))
|
||||
self._onroad_layout.set_click_callback(lambda: self._scroll_to(self._home_layout))
|
||||
device.add_interactive_timeout_callback(self._set_mode_for_started)
|
||||
device.add_interactive_timeout_callback(self._on_interactive_timeout)
|
||||
|
||||
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)
|
||||
@@ -89,59 +75,50 @@ class MiciMainLayout(Widget):
|
||||
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._scroller.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):
|
||||
# Don't pop if onboarding
|
||||
if gui_app.get_active_widget() == self._onboarding_window:
|
||||
return
|
||||
|
||||
if ui_state.started != self._prev_onroad:
|
||||
self._prev_onroad = ui_state.started
|
||||
|
||||
# onroad: after delay, pop nav stack and scroll to onroad
|
||||
# offroad: immediately scroll to home, but don't pop nav stack (can stay in settings)
|
||||
if ui_state.started:
|
||||
self._onroad_time_delay = rl.get_time()
|
||||
else:
|
||||
self._set_mode_for_started(True)
|
||||
self._scroll_to(self._home_layout)
|
||||
|
||||
# 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)
|
||||
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:
|
||||
self._set_mode(MainState.MAIN)
|
||||
gui_app.pop_widgets_to(self)
|
||||
self._scroll_to(self._onroad_layout)
|
||||
self._prev_standstill = CS.standstill
|
||||
|
||||
def _set_mode_for_started(self, onroad_transition: bool = False):
|
||||
def _on_interactive_timeout(self):
|
||||
# Don't pop if onboarding
|
||||
if gui_app.get_active_widget() == self._onboarding_window:
|
||||
return
|
||||
|
||||
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)
|
||||
# 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)
|
||||
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)
|
||||
gui_app.pop_widgets_to(self)
|
||||
self._scroll_to(self._home_layout)
|
||||
|
||||
def _on_bookmark_clicked(self):
|
||||
user_bookmark = messaging.new_message('bookmarkButton')
|
||||
|
||||
@@ -7,7 +7,7 @@ import pyray as rl
|
||||
from openpilot.common.filter_simple import FirstOrderFilter
|
||||
from openpilot.system.hardware import HARDWARE
|
||||
from openpilot.system.ui.lib.application import FontWeight, gui_app
|
||||
from openpilot.system.ui.widgets import Widget
|
||||
from openpilot.system.ui.widgets import Widget, NavWidget
|
||||
from openpilot.system.ui.widgets.button import SmallButton, SmallCircleIconButton
|
||||
from openpilot.system.ui.widgets.label import UnifiedLabel
|
||||
from openpilot.system.ui.widgets.slider import SmallSlider
|
||||
@@ -42,7 +42,7 @@ class DriverCameraSetupDialog(DriverCameraDialog):
|
||||
gui_label(rect, tr("camera starting"), font_size=64, font_weight=FontWeight.BOLD,
|
||||
alignment=rl.GuiTextAlignment.TEXT_ALIGN_CENTER)
|
||||
rl.end_scissor_mode()
|
||||
return -1
|
||||
return
|
||||
|
||||
# Position dmoji on opposite side from driver
|
||||
is_rhd = self.driver_state_renderer.is_rhd
|
||||
@@ -55,7 +55,6 @@ class DriverCameraSetupDialog(DriverCameraDialog):
|
||||
self._draw_face_detection(rect)
|
||||
|
||||
rl.end_scissor_mode()
|
||||
return -1
|
||||
|
||||
|
||||
class TrainingGuidePreDMTutorial(SetupTermsPage):
|
||||
@@ -367,9 +366,9 @@ class TrainingGuide(Widget):
|
||||
self._completed_callback()
|
||||
|
||||
def _render(self, _):
|
||||
rl.draw_rectangle_rec(self._rect, rl.BLACK)
|
||||
if self._step < len(self._steps):
|
||||
self._steps[self._step].render(self._rect)
|
||||
return -1
|
||||
|
||||
|
||||
class DeclinePage(Widget):
|
||||
@@ -438,9 +437,11 @@ class TermsPage(SetupTermsPage):
|
||||
))
|
||||
|
||||
|
||||
class OnboardingWindow(Widget):
|
||||
class OnboardingWindow(NavWidget):
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
self.set_back_enabled(False)
|
||||
|
||||
self._accepted_terms: bool = ui_state.params.get("HasAcceptedTerms") == terms_version
|
||||
self._training_done: bool = ui_state.params.get("CompletedTrainingVersion") == training_version
|
||||
|
||||
@@ -473,7 +474,7 @@ class OnboardingWindow(Widget):
|
||||
|
||||
def close(self):
|
||||
ui_state.params.put_bool("IsDriverViewEnabled", False)
|
||||
gui_app.set_modal_overlay(None)
|
||||
gui_app.pop_widget()
|
||||
|
||||
def _on_terms_accepted(self):
|
||||
ui_state.params.put("HasAcceptedTerms", terms_version)
|
||||
@@ -490,4 +491,3 @@ class OnboardingWindow(Widget):
|
||||
self._training_guide.render(self._rect)
|
||||
elif self._state == OnboardingState.DECLINE:
|
||||
self._decline_page.render(self._rect)
|
||||
return -1
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import pyray as rl
|
||||
from collections.abc import Callable
|
||||
|
||||
from openpilot.common.time_helpers import system_time_valid
|
||||
from openpilot.system.ui.widgets.scroller import Scroller
|
||||
@@ -13,9 +12,9 @@ from openpilot.selfdrive.ui.widgets.ssh_key import SshKeyAction
|
||||
|
||||
|
||||
class DeveloperLayoutMici(NavWidget):
|
||||
def __init__(self, back_callback: Callable):
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
self.set_back_callback(back_callback)
|
||||
self.set_back_callback(gui_app.pop_widget)
|
||||
|
||||
def github_username_callback(username: str):
|
||||
if username:
|
||||
@@ -25,16 +24,16 @@ class DeveloperLayoutMici(NavWidget):
|
||||
self._ssh_keys_btn.set_value(username)
|
||||
else:
|
||||
dlg = BigDialog("", ssh_keys._error_message)
|
||||
gui_app.set_modal_overlay(dlg)
|
||||
gui_app.push_widget(dlg)
|
||||
|
||||
def ssh_keys_callback():
|
||||
github_username = ui_state.params.get("GithubUsername") or ""
|
||||
dlg = BigInputDialog("enter GitHub username...", github_username, confirm_callback=github_username_callback)
|
||||
if not system_time_valid():
|
||||
dlg = BigDialog("Please connect to Wi-Fi to fetch your key", "")
|
||||
gui_app.set_modal_overlay(dlg)
|
||||
gui_app.push_widget(dlg)
|
||||
return
|
||||
gui_app.set_modal_overlay(dlg)
|
||||
gui_app.push_widget(dlg)
|
||||
|
||||
txt_ssh = gui_app.texture("icons_mici/settings/developer/ssh.png", 56, 64)
|
||||
github_username = ui_state.params.get("GithubUsername") or ""
|
||||
|
||||
@@ -28,7 +28,7 @@ class MiciFccModal(NavWidget):
|
||||
|
||||
def __init__(self, file_path: str | None = None, text: str | None = None):
|
||||
super().__init__()
|
||||
self.set_back_callback(lambda: gui_app.set_modal_overlay(None))
|
||||
self.set_back_callback(gui_app.pop_widget)
|
||||
self._content = HtmlRenderer(file_path=file_path, text=text)
|
||||
self._scroll_panel = GuiScrollPanel2(horizontal=False)
|
||||
self._fcc_logo = gui_app.texture("icons_mici/settings/device/fcc_logo.png", 76, 64)
|
||||
@@ -47,8 +47,6 @@ class MiciFccModal(NavWidget):
|
||||
|
||||
rl.draw_texture_ex(self._fcc_logo, fcc_pos, 0.0, 1.0, rl.WHITE)
|
||||
|
||||
return -1
|
||||
|
||||
|
||||
def _engaged_confirmation_callback(callback: Callable, action_text: str):
|
||||
if not ui_state.engaged:
|
||||
@@ -74,10 +72,10 @@ def _engaged_confirmation_callback(callback: Callable, action_text: str):
|
||||
dlg: BigConfirmationDialogV2 | BigDialog = BigConfirmationDialogV2(f"slide to\n{action_text.lower()}", icon, red=red,
|
||||
exit_on_confirm=action_text == "reset",
|
||||
confirm_callback=confirm_callback)
|
||||
gui_app.set_modal_overlay(dlg)
|
||||
gui_app.push_widget(dlg)
|
||||
else:
|
||||
dlg = BigDialog(f"Disengage to {action_text}", "")
|
||||
gui_app.set_modal_overlay(dlg)
|
||||
gui_app.push_widget(dlg)
|
||||
|
||||
|
||||
class DeviceInfoLayoutMici(Widget):
|
||||
@@ -147,7 +145,7 @@ class PairBigButton(BigButton):
|
||||
dlg = BigDialog(tr("Device must be registered with the comma.ai backend to pair"), "")
|
||||
else:
|
||||
dlg = PairingDialog()
|
||||
gui_app.set_modal_overlay(dlg)
|
||||
gui_app.push_widget(dlg)
|
||||
|
||||
|
||||
UPDATER_TIMEOUT = 10.0 # seconds to wait for updater to respond
|
||||
@@ -173,7 +171,7 @@ class UpdateOpenpilotBigButton(BigButton):
|
||||
def _handle_mouse_release(self, mouse_pos: MousePos):
|
||||
if not system_time_valid():
|
||||
dlg = BigDialog(tr("Please connect to Wi-Fi to update"), "")
|
||||
gui_app.set_modal_overlay(dlg)
|
||||
gui_app.push_widget(dlg)
|
||||
return
|
||||
|
||||
self.set_enabled(False)
|
||||
@@ -268,12 +266,10 @@ class UpdateOpenpilotBigButton(BigButton):
|
||||
|
||||
|
||||
class DeviceLayoutMici(NavWidget):
|
||||
def __init__(self, back_callback: Callable):
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
|
||||
self._fcc_dialog: HtmlModal | None = None
|
||||
self._driver_camera: DriverCameraDialog | None = None
|
||||
self._training_guide: TrainingGuide | None = None
|
||||
|
||||
def power_off_callback():
|
||||
ui_state.params.put_bool("DoShutdown", True)
|
||||
@@ -309,11 +305,11 @@ class DeviceLayoutMici(NavWidget):
|
||||
regulatory_btn.set_click_callback(self._on_regulatory)
|
||||
|
||||
driver_cam_btn = BigButton("driver\ncamera preview", "", "icons_mici/settings/device/cameras.png")
|
||||
driver_cam_btn.set_click_callback(self._show_driver_camera)
|
||||
driver_cam_btn.set_click_callback(lambda: gui_app.push_widget(DriverCameraDialog()))
|
||||
driver_cam_btn.set_enabled(lambda: ui_state.is_offroad())
|
||||
|
||||
review_training_guide_btn = BigButton("review\ntraining guide", "", "icons_mici/settings/device/info.png")
|
||||
review_training_guide_btn.set_click_callback(self._on_review_training_guide)
|
||||
review_training_guide_btn.set_click_callback(lambda: gui_app.push_widget(TrainingGuide(completed_callback=gui_app.pop_widget)))
|
||||
review_training_guide_btn.set_enabled(lambda: ui_state.is_offroad())
|
||||
|
||||
self._scroller = Scroller([
|
||||
@@ -330,7 +326,8 @@ class DeviceLayoutMici(NavWidget):
|
||||
], snap_items=False)
|
||||
|
||||
# Set up back navigation
|
||||
self.set_back_callback(back_callback)
|
||||
# TODO: can this somehow be generic in widgets/__init__.py or application.py?
|
||||
self.set_back_callback(gui_app.pop_widget)
|
||||
|
||||
# Hide power off button when onroad
|
||||
ui_state.add_offroad_transition_callback(self._offroad_transition)
|
||||
@@ -338,24 +335,11 @@ class DeviceLayoutMici(NavWidget):
|
||||
def _on_regulatory(self):
|
||||
if not self._fcc_dialog:
|
||||
self._fcc_dialog = MiciFccModal(os.path.join(BASEDIR, "selfdrive/assets/offroad/mici_fcc.html"))
|
||||
gui_app.set_modal_overlay(self._fcc_dialog)
|
||||
gui_app.push_widget(self._fcc_dialog)
|
||||
|
||||
def _offroad_transition(self):
|
||||
self._power_off_btn.set_visible(ui_state.is_offroad())
|
||||
|
||||
def _show_driver_camera(self):
|
||||
if not self._driver_camera:
|
||||
self._driver_camera = DriverCameraDialog()
|
||||
gui_app.set_modal_overlay(self._driver_camera, callback=lambda result: setattr(self, '_driver_camera', None))
|
||||
|
||||
def _on_review_training_guide(self):
|
||||
if not self._training_guide:
|
||||
def completed_callback():
|
||||
gui_app.set_modal_overlay(None)
|
||||
|
||||
self._training_guide = TrainingGuide(completed_callback=completed_callback)
|
||||
gui_app.set_modal_overlay(self._training_guide, callback=lambda result: setattr(self, '_training_guide', None))
|
||||
|
||||
def show_event(self):
|
||||
super().show_event()
|
||||
self._scroller.show_event()
|
||||
|
||||
@@ -132,9 +132,6 @@ class FirehoseLayoutBase(Widget):
|
||||
y = self._draw_wrapped_text(x, y, w, tr(answer), gui_app.font(FontWeight.ROMAN), 32, self.LIGHT_GRAY)
|
||||
y += 20
|
||||
|
||||
# return value not used by NavWidget
|
||||
return -1
|
||||
|
||||
def _draw_wrapped_text(self, x, y, width, text, font, font_size, color):
|
||||
wrapped = wrap_text(font, text, font_size, width)
|
||||
for line in wrapped:
|
||||
@@ -223,6 +220,6 @@ class FirehoseLayoutBase(Widget):
|
||||
class FirehoseLayout(FirehoseLayoutBase, NavWidget):
|
||||
BACK_TOUCH_AREA_PERCENTAGE = 0.1
|
||||
|
||||
def __init__(self, back_callback):
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
self.set_back_callback(back_callback)
|
||||
self.set_back_callback(gui_app.pop_widget)
|
||||
|
||||
@@ -1,6 +1,4 @@
|
||||
import pyray as rl
|
||||
from enum import IntEnum
|
||||
from collections.abc import Callable
|
||||
|
||||
from openpilot.system.ui.widgets.scroller import Scroller
|
||||
from openpilot.selfdrive.ui.mici.layouts.settings.network.wifi_ui import WifiUIMici, WifiIcon
|
||||
@@ -13,21 +11,13 @@ from openpilot.system.ui.widgets import NavWidget
|
||||
from openpilot.system.ui.lib.wifi_manager import WifiManager, Network, MeteredType, ConnectStatus, normalize_ssid
|
||||
|
||||
|
||||
class NetworkPanelType(IntEnum):
|
||||
NONE = 0
|
||||
WIFI = 1
|
||||
|
||||
|
||||
class NetworkLayoutMici(NavWidget):
|
||||
def __init__(self, back_callback: Callable):
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
|
||||
self._current_panel = NetworkPanelType.WIFI
|
||||
self.set_back_enabled(lambda: self._current_panel == NetworkPanelType.NONE)
|
||||
|
||||
self._wifi_manager = WifiManager()
|
||||
self._wifi_manager.set_active(False)
|
||||
self._wifi_ui = WifiUIMici(self._wifi_manager, back_callback=lambda: self._switch_to_panel(NetworkPanelType.NONE))
|
||||
self._wifi_ui = WifiUIMici(self._wifi_manager)
|
||||
|
||||
self._wifi_manager.add_callbacks(
|
||||
networks_updated=self._on_network_updated,
|
||||
@@ -52,7 +42,7 @@ class NetworkLayoutMici(NavWidget):
|
||||
tethering_password = self._wifi_manager.tethering_password
|
||||
dlg = BigInputDialog("enter password...", tethering_password, minimum_length=8,
|
||||
confirm_callback=tethering_password_callback)
|
||||
gui_app.set_modal_overlay(dlg)
|
||||
gui_app.push_widget(dlg)
|
||||
|
||||
txt_tethering = gui_app.texture("icons_mici/settings/network/tethering.png", 64, 54)
|
||||
self._tethering_password_btn = BigButton("tethering password", "", txt_tethering)
|
||||
@@ -79,7 +69,7 @@ class NetworkLayoutMici(NavWidget):
|
||||
self._wifi_full_txt = gui_app.texture("icons_mici/settings/network/wifi_strength_full.png", 64, 47)
|
||||
|
||||
self._wifi_button = BigButton("wi-fi", "not connected", self._wifi_slash_txt, scroll=True)
|
||||
self._wifi_button.set_click_callback(lambda: self._switch_to_panel(NetworkPanelType.WIFI))
|
||||
self._wifi_button.set_click_callback(lambda: gui_app.push_widget(self._wifi_ui))
|
||||
|
||||
# ******** Advanced settings ********
|
||||
# ******** Roaming toggle ********
|
||||
@@ -111,7 +101,7 @@ class NetworkLayoutMici(NavWidget):
|
||||
self._wifi_manager.update_gsm_settings(roaming_enabled, ui_state.params.get("GsmApn") or "", metered)
|
||||
|
||||
# Set up back navigation
|
||||
self.set_back_callback(back_callback)
|
||||
self.set_back_callback(gui_app.pop_widget)
|
||||
|
||||
def _update_state(self):
|
||||
super()._update_state()
|
||||
@@ -146,14 +136,18 @@ class NetworkLayoutMici(NavWidget):
|
||||
|
||||
def show_event(self):
|
||||
super().show_event()
|
||||
self._current_panel = NetworkPanelType.NONE
|
||||
self._wifi_manager.set_active(True)
|
||||
self._scroller.show_event()
|
||||
|
||||
# Process wifi callbacks while at any point in the nav stack
|
||||
gui_app.set_nav_stack_tick(self._wifi_manager.process_callbacks)
|
||||
|
||||
def hide_event(self):
|
||||
super().hide_event()
|
||||
self._wifi_manager.set_active(False)
|
||||
|
||||
gui_app.set_nav_stack_tick(None)
|
||||
|
||||
def _toggle_roaming(self, checked: bool):
|
||||
self._wifi_manager.update_gsm_settings(checked, ui_state.params.get("GsmApn") or "", ui_state.params.get_bool("GsmMetered"))
|
||||
|
||||
@@ -169,7 +163,7 @@ class NetworkLayoutMici(NavWidget):
|
||||
|
||||
current_apn = ui_state.params.get("GsmApn") or ""
|
||||
dlg = BigInputDialog("enter APN...", current_apn, minimum_length=0, confirm_callback=update_apn)
|
||||
gui_app.set_modal_overlay(dlg)
|
||||
gui_app.push_widget(dlg)
|
||||
|
||||
def _toggle_cellular_metered(self, checked: bool):
|
||||
self._wifi_manager.update_gsm_settings(ui_state.params.get_bool("GsmRoaming"), ui_state.params.get("GsmApn") or "", checked)
|
||||
@@ -191,17 +185,5 @@ class NetworkLayoutMici(NavWidget):
|
||||
MeteredType.NO: 'unmetered'
|
||||
}.get(self._wifi_manager.current_network_metered, 'default'))
|
||||
|
||||
def _switch_to_panel(self, panel_type: NetworkPanelType):
|
||||
if panel_type == NetworkPanelType.WIFI:
|
||||
self._wifi_ui.show_event()
|
||||
elif self._current_panel == NetworkPanelType.WIFI:
|
||||
self._wifi_ui.hide_event()
|
||||
self._current_panel = panel_type
|
||||
|
||||
def _render(self, rect: rl.Rectangle):
|
||||
self._wifi_manager.process_callbacks()
|
||||
|
||||
if self._current_panel == NetworkPanelType.WIFI:
|
||||
self._wifi_ui.render(rect)
|
||||
else:
|
||||
self._scroller.render(rect)
|
||||
self._scroller.render(rect)
|
||||
|
||||
@@ -186,10 +186,9 @@ class ConnectButton(Widget):
|
||||
class ForgetButton(Widget):
|
||||
HORIZONTAL_MARGIN = 8
|
||||
|
||||
def __init__(self, forget_network: Callable, open_network_manage_page):
|
||||
def __init__(self, forget_network: Callable):
|
||||
super().__init__()
|
||||
self._forget_network = forget_network
|
||||
self._open_network_manage_page = open_network_manage_page
|
||||
|
||||
self._bg_txt = gui_app.texture("icons_mici/settings/network/new/forget_button.png", 100, 100)
|
||||
self._bg_pressed_txt = gui_app.texture("icons_mici/settings/network/new/forget_button_pressed.png", 100, 100)
|
||||
@@ -200,7 +199,7 @@ class ForgetButton(Widget):
|
||||
super()._handle_mouse_release(mouse_pos)
|
||||
dlg = BigConfirmationDialogV2("slide to forget", "icons_mici/settings/network/new/trash.png", red=True,
|
||||
confirm_callback=self._forget_network)
|
||||
gui_app.set_modal_overlay(dlg, callback=self._open_network_manage_page)
|
||||
gui_app.push_widget(dlg)
|
||||
|
||||
def _render(self, _):
|
||||
bg_txt = self._bg_pressed_txt if self.is_pressed else self._bg_txt
|
||||
@@ -212,7 +211,7 @@ class ForgetButton(Widget):
|
||||
|
||||
|
||||
class NetworkInfoPage(NavWidget):
|
||||
def __init__(self, wifi_manager, connect_callback: Callable, forget_callback: Callable, open_network_manage_page: Callable,
|
||||
def __init__(self, wifi_manager, connect_callback: Callable, forget_callback: Callable,
|
||||
connecting_callback: Callable[[], str | None], connected_callback: Callable[[], str | None]):
|
||||
super().__init__()
|
||||
self._wifi_manager = wifi_manager
|
||||
@@ -220,8 +219,8 @@ class NetworkInfoPage(NavWidget):
|
||||
self.set_rect(rl.Rectangle(0, 0, gui_app.width, gui_app.height))
|
||||
|
||||
self._wifi_icon = WifiIcon()
|
||||
self._forget_btn = ForgetButton(lambda: forget_callback(self._network.ssid) if self._network is not None else None,
|
||||
open_network_manage_page)
|
||||
self._forget_btn = ForgetButton(lambda: forget_callback(self._network.ssid) if self._network is not None else None)
|
||||
self._forget_btn.set_enabled(lambda: self.enabled) # for stack
|
||||
self._connect_btn = ConnectButton()
|
||||
self._connect_btn.set_click_callback(lambda: connect_callback(self._network.ssid) if self._network is not None else None)
|
||||
|
||||
@@ -230,7 +229,7 @@ class NetworkInfoPage(NavWidget):
|
||||
self._subtitle = UnifiedLabel("", 36, FontWeight.ROMAN, rl.Color(255, 255, 255, int(255 * 0.9 * 0.65)),
|
||||
alignment_vertical=rl.GuiTextAlignmentVertical.TEXT_ALIGN_MIDDLE)
|
||||
|
||||
self.set_back_callback(lambda: gui_app.set_modal_overlay(None))
|
||||
self.set_back_callback(gui_app.pop_widget)
|
||||
|
||||
# State
|
||||
self._network: Network | None = None
|
||||
@@ -249,11 +248,13 @@ class NetworkInfoPage(NavWidget):
|
||||
break
|
||||
else:
|
||||
# network disappeared, close page
|
||||
gui_app.set_modal_overlay(None)
|
||||
# TODO: pop_widgets_to, to close potentially open keyboard too
|
||||
if gui_app.get_active_widget() == self:
|
||||
gui_app.pop_widget()
|
||||
|
||||
def _update_state(self):
|
||||
super()._update_state()
|
||||
# Modal overlays stop main UI rendering, so we need to call here
|
||||
# TODO: remove? only left for potential compatibility with setup/updater
|
||||
self._wifi_manager.process_callbacks()
|
||||
|
||||
if self._network is None:
|
||||
@@ -271,7 +272,7 @@ class NetworkInfoPage(NavWidget):
|
||||
self._connect_btn.set_enabled(False)
|
||||
else: # saved or unknown
|
||||
self._connect_btn.set_label("connect")
|
||||
self._connect_btn.set_enabled(True)
|
||||
self._connect_btn.set_enabled(self.enabled)
|
||||
|
||||
self._title.set_text(normalize_ssid(self._network.ssid))
|
||||
if self._network.security_type == SecurityType.OPEN:
|
||||
@@ -336,17 +337,15 @@ class NetworkInfoPage(NavWidget):
|
||||
self._forget_btn.rect.height,
|
||||
))
|
||||
|
||||
return -1
|
||||
|
||||
|
||||
class WifiUIMici(BigMultiOptionDialog):
|
||||
def __init__(self, wifi_manager: WifiManager, back_callback: Callable):
|
||||
def __init__(self, wifi_manager: WifiManager):
|
||||
super().__init__([], None)
|
||||
|
||||
# Set up back navigation
|
||||
self.set_back_callback(back_callback)
|
||||
self.set_back_callback(gui_app.pop_widget)
|
||||
|
||||
self._network_info_page = NetworkInfoPage(wifi_manager, self._connect_to_network, wifi_manager.forget_connection, self._open_network_manage_page,
|
||||
self._network_info_page = NetworkInfoPage(wifi_manager, self._connect_to_network, wifi_manager.forget_connection,
|
||||
lambda: wifi_manager.connecting_to_ssid, lambda: wifi_manager.connected_ssid)
|
||||
|
||||
self._loading_animation = LoadingAnimation()
|
||||
@@ -370,11 +369,6 @@ class WifiUIMici(BigMultiOptionDialog):
|
||||
super().hide_event()
|
||||
self._scroller.hide_event()
|
||||
|
||||
def _open_network_manage_page(self, result=None):
|
||||
if self._network_info_page._network is not None and self._network_info_page._network.ssid in self._networks:
|
||||
self._network_info_page.update_networks(self._networks)
|
||||
gui_app.set_modal_overlay(self._network_info_page)
|
||||
|
||||
def _on_network_updated(self, networks: list[Network]):
|
||||
self._networks = {network.ssid: network for network in networks}
|
||||
self._update_buttons()
|
||||
@@ -413,7 +407,7 @@ class WifiUIMici(BigMultiOptionDialog):
|
||||
|
||||
if option in self._networks:
|
||||
self._network_info_page.set_current_network(self._networks[option])
|
||||
self._open_network_manage_page()
|
||||
gui_app.push_widget(self._network_info_page)
|
||||
|
||||
def _connect_to_network(self, ssid: str):
|
||||
network = self._networks.get(ssid)
|
||||
@@ -434,14 +428,7 @@ class WifiUIMici(BigMultiOptionDialog):
|
||||
hint = "wrong password..." if incorrect_password else "enter password..."
|
||||
dlg = BigInputDialog(hint, "", minimum_length=8,
|
||||
confirm_callback=lambda _password: self._connect_with_password(ssid, _password))
|
||||
|
||||
def on_close(result=None):
|
||||
gui_app.set_modal_overlay_tick(None)
|
||||
self._open_network_manage_page(result)
|
||||
|
||||
# Process wifi callbacks while the keyboard is shown so forgotten clears connecting state
|
||||
gui_app.set_modal_overlay_tick(self._wifi_manager.process_callbacks)
|
||||
gui_app.set_modal_overlay(dlg, on_close)
|
||||
gui_app.push_widget(dlg)
|
||||
|
||||
def _render(self, _):
|
||||
super()._render(_)
|
||||
|
||||
@@ -1,7 +1,4 @@
|
||||
import pyray as rl
|
||||
from dataclasses import dataclass
|
||||
from enum import IntEnum
|
||||
from collections.abc import Callable
|
||||
|
||||
from openpilot.common.params import Params
|
||||
from openpilot.system.ui.widgets.scroller import Scroller
|
||||
@@ -12,22 +9,7 @@ from openpilot.selfdrive.ui.mici.layouts.settings.device import DeviceLayoutMici
|
||||
from openpilot.selfdrive.ui.mici.layouts.settings.developer import DeveloperLayoutMici
|
||||
from openpilot.selfdrive.ui.mici.layouts.settings.firehose import FirehoseLayout
|
||||
from openpilot.system.ui.lib.application import gui_app, FontWeight
|
||||
from openpilot.system.ui.widgets import Widget, NavWidget
|
||||
|
||||
|
||||
class PanelType(IntEnum):
|
||||
TOGGLES = 0
|
||||
NETWORK = 1
|
||||
DEVICE = 2
|
||||
DEVELOPER = 3
|
||||
USER_MANUAL = 4
|
||||
FIREHOSE = 5
|
||||
|
||||
|
||||
@dataclass
|
||||
class PanelInfo:
|
||||
name: str
|
||||
instance: Widget
|
||||
from openpilot.system.ui.widgets import NavWidget
|
||||
|
||||
|
||||
class SettingsBigButton(BigButton):
|
||||
@@ -39,19 +21,26 @@ class SettingsLayout(NavWidget):
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
self._params = Params()
|
||||
self._current_panel = None # PanelType.DEVICE
|
||||
|
||||
toggles_panel = TogglesLayoutMici()
|
||||
toggles_btn = SettingsBigButton("toggles", "", "icons_mici/settings.png")
|
||||
toggles_btn.set_click_callback(lambda: self._set_current_panel(PanelType.TOGGLES))
|
||||
network_btn = SettingsBigButton("network", "", "icons_mici/settings/network/wifi_strength_full.png", icon_size=(76, 56))
|
||||
network_btn.set_click_callback(lambda: self._set_current_panel(PanelType.NETWORK))
|
||||
device_btn = SettingsBigButton("device", "", "icons_mici/settings/device_icon.png", icon_size=(74, 60))
|
||||
device_btn.set_click_callback(lambda: self._set_current_panel(PanelType.DEVICE))
|
||||
developer_btn = SettingsBigButton("developer", "", "icons_mici/settings/developer_icon.png", icon_size=(64, 60))
|
||||
developer_btn.set_click_callback(lambda: self._set_current_panel(PanelType.DEVELOPER))
|
||||
toggles_btn.set_click_callback(lambda: gui_app.push_widget(toggles_panel))
|
||||
|
||||
network_panel = NetworkLayoutMici()
|
||||
network_btn = SettingsBigButton("network", "", "icons_mici/settings/network/wifi_strength_full.png", icon_size=(76, 56))
|
||||
network_btn.set_click_callback(lambda: gui_app.push_widget(network_panel))
|
||||
|
||||
device_panel = DeviceLayoutMici()
|
||||
device_btn = SettingsBigButton("device", "", "icons_mici/settings/device_icon.png", icon_size=(74, 60))
|
||||
device_btn.set_click_callback(lambda: gui_app.push_widget(device_panel))
|
||||
|
||||
developer_panel = DeveloperLayoutMici()
|
||||
developer_btn = SettingsBigButton("developer", "", "icons_mici/settings/developer_icon.png", icon_size=(64, 60))
|
||||
developer_btn.set_click_callback(lambda: gui_app.push_widget(developer_panel))
|
||||
|
||||
firehose_panel = FirehoseLayout()
|
||||
firehose_btn = SettingsBigButton("firehose", "", "icons_mici/settings/firehose.png", icon_size=(52, 62))
|
||||
firehose_btn.set_click_callback(lambda: self._set_current_panel(PanelType.FIREHOSE))
|
||||
firehose_btn.set_click_callback(lambda: gui_app.push_widget(firehose_panel))
|
||||
|
||||
self._scroller = Scroller([
|
||||
toggles_btn,
|
||||
@@ -64,55 +53,17 @@ class SettingsLayout(NavWidget):
|
||||
], snap_items=False)
|
||||
|
||||
# Set up back navigation
|
||||
self.set_back_callback(self.close_settings)
|
||||
self.set_back_enabled(lambda: self._current_panel is None)
|
||||
|
||||
self._panels = {
|
||||
PanelType.TOGGLES: PanelInfo("Toggles", TogglesLayoutMici(back_callback=lambda: self._set_current_panel(None))),
|
||||
PanelType.NETWORK: PanelInfo("Network", NetworkLayoutMici(back_callback=lambda: self._set_current_panel(None))),
|
||||
PanelType.DEVICE: PanelInfo("Device", DeviceLayoutMici(back_callback=lambda: self._set_current_panel(None))),
|
||||
PanelType.DEVELOPER: PanelInfo("Developer", DeveloperLayoutMici(back_callback=lambda: self._set_current_panel(None))),
|
||||
PanelType.FIREHOSE: PanelInfo("Firehose", FirehoseLayout(back_callback=lambda: self._set_current_panel(None))),
|
||||
}
|
||||
self.set_back_callback(gui_app.pop_widget)
|
||||
|
||||
self._font_medium = gui_app.font(FontWeight.MEDIUM)
|
||||
|
||||
# Callbacks
|
||||
self._close_callback: Callable | None = None
|
||||
|
||||
def show_event(self):
|
||||
super().show_event()
|
||||
self._set_current_panel(None)
|
||||
self._scroller.show_event()
|
||||
if self._current_panel is not None:
|
||||
self._panels[self._current_panel].instance.show_event()
|
||||
|
||||
def hide_event(self):
|
||||
super().hide_event()
|
||||
if self._current_panel is not None:
|
||||
self._panels[self._current_panel].instance.hide_event()
|
||||
|
||||
def set_callbacks(self, on_close: Callable):
|
||||
self._close_callback = on_close
|
||||
self._scroller.hide_event()
|
||||
|
||||
def _render(self, rect: rl.Rectangle):
|
||||
if self._current_panel is not None:
|
||||
self._draw_current_panel()
|
||||
else:
|
||||
self._scroller.render(rect)
|
||||
|
||||
def _draw_current_panel(self):
|
||||
panel = self._panels[self._current_panel]
|
||||
panel.instance.render(self._rect)
|
||||
|
||||
def _set_current_panel(self, panel_type: PanelType | None):
|
||||
if panel_type != self._current_panel:
|
||||
if self._current_panel is not None:
|
||||
self._panels[self._current_panel].instance.hide_event()
|
||||
self._current_panel = panel_type
|
||||
if self._current_panel is not None:
|
||||
self._panels[self._current_panel].instance.show_event()
|
||||
|
||||
def close_settings(self):
|
||||
if self._close_callback:
|
||||
self._close_callback()
|
||||
self._scroller.render(rect)
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import pyray as rl
|
||||
from collections.abc import Callable
|
||||
from cereal import log
|
||||
|
||||
from openpilot.system.ui.widgets.scroller import Scroller
|
||||
@@ -13,9 +12,9 @@ PERSONALITY_TO_INT = log.LongitudinalPersonality.schema.enumerants
|
||||
|
||||
|
||||
class TogglesLayoutMici(NavWidget):
|
||||
def __init__(self, back_callback: Callable):
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
self.set_back_callback(back_callback)
|
||||
self.set_back_callback(gui_app.pop_widget)
|
||||
|
||||
self._personality_toggle = BigMultiParamToggle("driving personality", "LongitudinalPersonality", ["aggressive", "standard", "relaxed"])
|
||||
self._experimental_btn = BigParamControl("experimental mode", "ExperimentalMode")
|
||||
|
||||
@@ -363,7 +363,7 @@ class AugmentedRoadView(CameraView):
|
||||
|
||||
if __name__ == "__main__":
|
||||
gui_app.init_window("OnRoad Camera View")
|
||||
road_camera_view = AugmentedRoadView(ROAD_CAM)
|
||||
road_camera_view = AugmentedRoadView(lambda: None, stream_type=ROAD_CAM)
|
||||
print("***press space to switch camera view***")
|
||||
try:
|
||||
for _ in gui_app.render():
|
||||
|
||||
@@ -34,8 +34,8 @@ class DriverCameraDialog(NavWidget):
|
||||
self._pm: messaging.PubMaster | None = None
|
||||
if not no_escape:
|
||||
# TODO: this can grow unbounded, should be given some thought
|
||||
device.add_interactive_timeout_callback(lambda: gui_app.set_modal_overlay(None))
|
||||
self.set_back_callback(lambda: gui_app.set_modal_overlay(None))
|
||||
device.add_interactive_timeout_callback(gui_app.pop_widget)
|
||||
self.set_back_callback(gui_app.pop_widget)
|
||||
self.set_back_enabled(not no_escape)
|
||||
|
||||
# Load eye icons
|
||||
@@ -87,7 +87,7 @@ class DriverCameraDialog(NavWidget):
|
||||
alignment=rl.GuiTextAlignment.TEXT_ALIGN_CENTER)
|
||||
rl.end_scissor_mode()
|
||||
self._publish_alert_sound(None)
|
||||
return -1
|
||||
return
|
||||
|
||||
driver_data = self._draw_face_detection(rect)
|
||||
if driver_data is not None:
|
||||
@@ -105,7 +105,7 @@ class DriverCameraDialog(NavWidget):
|
||||
self._render_dm_alerts(rect)
|
||||
|
||||
rl.end_scissor_mode()
|
||||
return -1
|
||||
return
|
||||
|
||||
def _publish_alert_sound(self, dm_state):
|
||||
"""Publish selfdriveState with only alertSound field set"""
|
||||
@@ -235,9 +235,9 @@ if __name__ == "__main__":
|
||||
gui_app.init_window("Driver Camera View (mici)")
|
||||
|
||||
driver_camera_view = DriverCameraDialog()
|
||||
gui_app.push_widget(driver_camera_view)
|
||||
try:
|
||||
for _ in gui_app.render():
|
||||
ui_state.update()
|
||||
driver_camera_view.render(rl.Rectangle(0, 0, gui_app.width, gui_app.height))
|
||||
finally:
|
||||
driver_camera_view.close()
|
||||
|
||||
@@ -37,14 +37,12 @@ KNOWN_LEAKS = {
|
||||
"openpilot.system.ui.widgets.confirm_dialog.ConfirmDialog",
|
||||
"openpilot.system.ui.widgets.label.Label",
|
||||
"openpilot.system.ui.widgets.button.Button",
|
||||
"openpilot.selfdrive.ui.mici.widgets.dialog.BigDialog",
|
||||
"openpilot.system.ui.widgets.html_render.HtmlRenderer",
|
||||
"openpilot.system.ui.widgets.NavBar",
|
||||
"openpilot.system.ui.widgets.inputbox.InputBox",
|
||||
"openpilot.system.ui.widgets.scroller_tici.Scroller",
|
||||
"openpilot.system.ui.widgets.scroller.Scroller",
|
||||
"openpilot.system.ui.widgets.label.UnifiedLabel",
|
||||
"openpilot.selfdrive.ui.mici.widgets.dialog.BigMultiOptionDialog",
|
||||
"openpilot.system.ui.widgets.mici_keyboard.MiciKeyboard",
|
||||
"openpilot.selfdrive.ui.mici.widgets.dialog.BigConfirmationDialogV2",
|
||||
"openpilot.system.ui.widgets.keyboard.Keyboard",
|
||||
|
||||
@@ -4,7 +4,7 @@ import pyray as rl
|
||||
from typing import Union
|
||||
from collections.abc import Callable
|
||||
from typing import cast
|
||||
from openpilot.system.ui.widgets import Widget, NavWidget, DialogResult
|
||||
from openpilot.system.ui.widgets import Widget, NavWidget
|
||||
from openpilot.system.ui.widgets.label import UnifiedLabel, gui_label
|
||||
from openpilot.system.ui.widgets.mici_keyboard import MiciKeyboard
|
||||
from openpilot.system.ui.lib.text_measure import measure_text_cached
|
||||
@@ -23,17 +23,8 @@ PADDING = 20
|
||||
class BigDialogBase(NavWidget, abc.ABC):
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
self._ret = DialogResult.NO_ACTION
|
||||
self.set_rect(rl.Rectangle(0, 0, gui_app.width, gui_app.height))
|
||||
self.set_back_callback(lambda: setattr(self, '_ret', DialogResult.CANCEL))
|
||||
|
||||
def _render(self, _) -> DialogResult:
|
||||
"""
|
||||
Allows `gui_app.set_modal_overlay(BigDialog(...))`.
|
||||
The overlay runner keeps calling until result != NO_ACTION.
|
||||
"""
|
||||
|
||||
return self._ret
|
||||
self.set_back_callback(gui_app.pop_widget)
|
||||
|
||||
|
||||
class BigDialog(BigDialogBase):
|
||||
@@ -44,7 +35,7 @@ class BigDialog(BigDialogBase):
|
||||
self._title = title
|
||||
self._description = description
|
||||
|
||||
def _render(self, _) -> DialogResult:
|
||||
def _render(self, _):
|
||||
super()._render(_)
|
||||
|
||||
# draw title
|
||||
@@ -74,8 +65,6 @@ class BigDialog(BigDialogBase):
|
||||
gui_label(desc_rect, desc_wrapped, 30, font_weight=FontWeight.MEDIUM,
|
||||
alignment=rl.GuiTextAlignment.TEXT_ALIGN_CENTER)
|
||||
|
||||
return self._ret
|
||||
|
||||
|
||||
class BigConfirmationDialogV2(BigDialogBase):
|
||||
def __init__(self, title: str, icon: str, red: bool = False,
|
||||
@@ -91,22 +80,21 @@ class BigConfirmationDialogV2(BigDialogBase):
|
||||
self._slider = RedBigSlider(title, icon_txt, confirm_callback=self._on_confirm)
|
||||
else:
|
||||
self._slider = BigSlider(title, icon_txt, confirm_callback=self._on_confirm)
|
||||
self._slider.set_enabled(lambda: not self._swiping_away)
|
||||
self._slider.set_enabled(lambda: self.enabled and not self._swiping_away) # self.enabled for nav stack
|
||||
|
||||
def _on_confirm(self):
|
||||
if self._exit_on_confirm:
|
||||
gui_app.pop_widget()
|
||||
if self._confirm_callback:
|
||||
self._confirm_callback()
|
||||
if self._exit_on_confirm:
|
||||
self._ret = DialogResult.CONFIRM
|
||||
|
||||
def _update_state(self):
|
||||
super()._update_state()
|
||||
if self._swiping_away and not self._slider.confirmed:
|
||||
self._slider.reset()
|
||||
|
||||
def _render(self, _) -> DialogResult:
|
||||
def _render(self, _):
|
||||
self._slider.render(self._rect)
|
||||
return self._ret
|
||||
|
||||
|
||||
class BigInputDialog(BigDialogBase):
|
||||
@@ -124,6 +112,7 @@ class BigInputDialog(BigDialogBase):
|
||||
font_weight=FontWeight.MEDIUM)
|
||||
self._keyboard = MiciKeyboard()
|
||||
self._keyboard.set_text(default_text)
|
||||
self._keyboard.set_enabled(lambda: self.enabled) # for nav stack
|
||||
self._minimum_length = minimum_length
|
||||
|
||||
self._backspace_held_time: float | None = None
|
||||
@@ -140,9 +129,10 @@ class BigInputDialog(BigDialogBase):
|
||||
self._top_right_button_rect = rl.Rectangle(0, 0, 0, 0)
|
||||
|
||||
def confirm_callback_wrapper():
|
||||
self._ret = DialogResult.CONFIRM
|
||||
text = self._keyboard.text()
|
||||
gui_app.pop_widget()
|
||||
if confirm_callback:
|
||||
confirm_callback(self._keyboard.text())
|
||||
confirm_callback(text)
|
||||
self._confirm_callback = confirm_callback_wrapper
|
||||
|
||||
def _update_state(self):
|
||||
@@ -238,8 +228,6 @@ class BigInputDialog(BigDialogBase):
|
||||
rl.draw_rectangle_lines_ex(self._top_right_button_rect, 1, rl.Color(0, 255, 0, 255))
|
||||
rl.draw_rectangle_lines_ex(self._top_left_button_rect, 1, rl.Color(0, 255, 0, 255))
|
||||
|
||||
return self._ret
|
||||
|
||||
def _handle_mouse_press(self, mouse_pos: MousePos):
|
||||
super()._handle_mouse_press(mouse_pos)
|
||||
# TODO: need to track where press was so enter and back can activate on release rather than press
|
||||
@@ -392,8 +380,6 @@ class BigMultiOptionDialog(BigDialogBase):
|
||||
super()._render(_)
|
||||
self._scroller.render(self._rect)
|
||||
|
||||
return self._ret
|
||||
|
||||
|
||||
class BigDialogButton(BigButton):
|
||||
def __init__(self, text: str, value: str = "", icon: Union[str, rl.Texture] = "", description: str = ""):
|
||||
@@ -404,4 +390,4 @@ class BigDialogButton(BigButton):
|
||||
super()._handle_mouse_release(mouse_pos)
|
||||
|
||||
dlg = BigDialog(self.text, self._description)
|
||||
gui_app.set_modal_overlay(dlg)
|
||||
gui_app.push_widget(dlg)
|
||||
|
||||
@@ -19,7 +19,7 @@ class PairingDialog(NavWidget):
|
||||
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
self.set_back_callback(lambda: gui_app.set_modal_overlay(None))
|
||||
self.set_back_callback(gui_app.pop_widget)
|
||||
self._params = Params()
|
||||
self._qr_texture: rl.Texture | None = None
|
||||
self._last_qr_generation = float("-inf")
|
||||
@@ -72,7 +72,7 @@ class PairingDialog(NavWidget):
|
||||
if ui_state.prime_state.is_paired():
|
||||
self._playing_dismiss_animation = True
|
||||
|
||||
def _render(self, rect: rl.Rectangle) -> int:
|
||||
def _render(self, rect: rl.Rectangle):
|
||||
self._check_qr_refresh()
|
||||
|
||||
self._render_qr_code()
|
||||
@@ -85,8 +85,6 @@ class PairingDialog(NavWidget):
|
||||
rl.draw_texture_ex(self._txt_pair, rl.Vector2(label_x, self._rect.y + self._rect.height - self._txt_pair.height - 16),
|
||||
0.0, 1.0, rl.Color(255, 255, 255, int(255 * 0.35)))
|
||||
|
||||
return -1
|
||||
|
||||
def _render_qr_code(self) -> None:
|
||||
if not self._qr_texture:
|
||||
error_font = gui_app.font(FontWeight.BOLD)
|
||||
@@ -107,10 +105,9 @@ class PairingDialog(NavWidget):
|
||||
if __name__ == "__main__":
|
||||
gui_app.init_window("pairing device")
|
||||
pairing = PairingDialog()
|
||||
gui_app.push_widget(pairing)
|
||||
try:
|
||||
for _ in gui_app.render():
|
||||
result = pairing.render(rl.Rectangle(0, 0, gui_app.width, gui_app.height))
|
||||
if result != -1:
|
||||
break
|
||||
pass
|
||||
finally:
|
||||
del pairing
|
||||
|
||||
@@ -219,7 +219,7 @@ class AugmentedRoadView(CameraView):
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
gui_app.init_window("OnRoad Camera View", new_modal=True)
|
||||
gui_app.init_window("OnRoad Camera View")
|
||||
road_camera_view = AugmentedRoadView(ROAD_CAM)
|
||||
gui_app.push_widget(road_camera_view)
|
||||
print("***press space to switch camera view***")
|
||||
|
||||
@@ -100,7 +100,7 @@ class DriverCameraDialog(CameraView):
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
gui_app.init_window("Driver Camera View", new_modal=True)
|
||||
gui_app.init_window("Driver Camera View")
|
||||
|
||||
driver_camera_view = DriverCameraDialog()
|
||||
gui_app.push_widget(driver_camera_view)
|
||||
|
||||
@@ -38,7 +38,7 @@ def run_replay(variant: LayoutVariant) -> None:
|
||||
from openpilot.system.ui.lib.application import gui_app # Import here for accurate coverage
|
||||
from openpilot.selfdrive.ui.tests.diff.replay_script import build_script
|
||||
|
||||
gui_app.init_window("ui diff test", fps=FPS, new_modal=variant == "tizi")
|
||||
gui_app.init_window("ui diff test", fps=FPS)
|
||||
|
||||
# Dynamically import main layout based on variant
|
||||
if variant == "mici":
|
||||
@@ -46,7 +46,6 @@ def run_replay(variant: LayoutVariant) -> None:
|
||||
else:
|
||||
from openpilot.selfdrive.ui.layouts.main import MainLayout
|
||||
main_layout = MainLayout()
|
||||
main_layout.set_rect(rl.Rectangle(0, 0, gui_app.width, gui_app.height))
|
||||
|
||||
pm = PubMaster(["deviceState", "pandaStates", "driverStateV2", "selfdriveState"])
|
||||
script = build_script(pm, main_layout, variant)
|
||||
@@ -59,7 +58,7 @@ def run_replay(variant: LayoutVariant) -> None:
|
||||
rl.get_time = lambda: frame / FPS
|
||||
|
||||
# Main loop to replay events and render frames
|
||||
for should_render in gui_app.render():
|
||||
for _ in gui_app.render():
|
||||
# Handle all events for the current frame
|
||||
while script_index < len(script) and script[script_index][0] == frame:
|
||||
_, event = script[script_index]
|
||||
@@ -82,9 +81,6 @@ def run_replay(variant: LayoutVariant) -> None:
|
||||
|
||||
ui_state.update()
|
||||
|
||||
if should_render:
|
||||
main_layout.render()
|
||||
|
||||
frame += 1
|
||||
|
||||
if script_index >= len(script):
|
||||
|
||||
@@ -83,7 +83,6 @@ if __name__ == "__main__":
|
||||
|
||||
gui_app.init_window("UI Profiling", fps=600)
|
||||
main_layout = MiciMainLayout()
|
||||
main_layout.set_rect(rl.Rectangle(0, 0, gui_app.width, gui_app.height))
|
||||
|
||||
print("Running...")
|
||||
patch_submaster(message_chunks)
|
||||
@@ -95,15 +94,13 @@ if __name__ == "__main__":
|
||||
yuv_buffer_size = W * H + (W // 2) * (H // 2) * 2
|
||||
yuv_data = np.random.randint(0, 256, yuv_buffer_size, dtype=np.uint8).tobytes()
|
||||
with cProfile.Profile() as pr:
|
||||
for should_render in gui_app.render():
|
||||
for _ in gui_app.render():
|
||||
if ui_state.sm.frame >= len(message_chunks):
|
||||
break
|
||||
if ui_state.sm.frame % 3 == 0:
|
||||
eof = int((ui_state.sm.frame % 3) * 0.05 * 1e9)
|
||||
vipc.send(VisionStreamType.VISION_STREAM_ROAD, yuv_data, ui_state.sm.frame % 3, eof, eof)
|
||||
ui_state.update()
|
||||
if should_render:
|
||||
main_layout.render()
|
||||
pr.dump_stats(f'{args.output}_deterministic.stats')
|
||||
|
||||
rl.close_window()
|
||||
|
||||
+3
-9
@@ -1,6 +1,5 @@
|
||||
#!/usr/bin/env python3
|
||||
import os
|
||||
import pyray as rl
|
||||
|
||||
from openpilot.system.hardware import TICI
|
||||
from openpilot.common.realtime import config_realtime_process, set_core_affinity
|
||||
@@ -16,20 +15,15 @@ def main():
|
||||
cores = {5, }
|
||||
config_realtime_process(0, 51)
|
||||
|
||||
gui_app.init_window("UI")
|
||||
if BIG_UI:
|
||||
gui_app.init_window("UI", new_modal=True)
|
||||
main_layout = MainLayout()
|
||||
MainLayout()
|
||||
else:
|
||||
gui_app.init_window("UI")
|
||||
main_layout = MiciMainLayout()
|
||||
main_layout.set_rect(rl.Rectangle(0, 0, gui_app.width, gui_app.height))
|
||||
MiciMainLayout()
|
||||
|
||||
for should_render in gui_app.render():
|
||||
ui_state.update()
|
||||
if should_render:
|
||||
if not BIG_UI:
|
||||
main_layout.render()
|
||||
|
||||
# reaffine after power save offlines our core
|
||||
if TICI and os.sched_getaffinity(0) != cores:
|
||||
try:
|
||||
|
||||
@@ -160,7 +160,7 @@ class PairingDialog(Widget):
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
gui_app.init_window("pairing device", new_modal=True)
|
||||
gui_app.init_window("pairing device")
|
||||
pairing = PairingDialog()
|
||||
gui_app.push_widget(pairing)
|
||||
try:
|
||||
|
||||
@@ -12,7 +12,6 @@ import subprocess
|
||||
from contextlib import contextmanager
|
||||
from collections.abc import Callable
|
||||
from collections import deque
|
||||
from dataclasses import dataclass
|
||||
from enum import StrEnum
|
||||
from pathlib import Path
|
||||
from typing import NamedTuple
|
||||
@@ -115,12 +114,6 @@ def font_fallback(font: rl.Font) -> rl.Font:
|
||||
return font
|
||||
|
||||
|
||||
@dataclass
|
||||
class ModalOverlay:
|
||||
overlay: object = None
|
||||
callback: Callable | None = None
|
||||
|
||||
|
||||
class MousePos(NamedTuple):
|
||||
x: float
|
||||
y: float
|
||||
@@ -226,13 +219,9 @@ class GuiApplication:
|
||||
self._last_fps_log_time: float = time.monotonic()
|
||||
self._frame = 0
|
||||
self._window_close_requested = False
|
||||
self._modal_overlay = ModalOverlay()
|
||||
self._modal_overlay_shown = False
|
||||
self._modal_overlay_tick: Callable[[], None] | None = None
|
||||
|
||||
# TODO: move over the entire ui and deprecate
|
||||
self._new_modal = False
|
||||
self._nav_stack: list[object] = []
|
||||
self._nav_stack_tick: Callable[[], None] | None = None
|
||||
self._nav_stack_widgets_to_render = 1 if self.big_ui() else 2
|
||||
|
||||
self._mouse = MouseState(self._scale)
|
||||
self._mouse_events: list[MouseEvent] = []
|
||||
@@ -266,7 +255,7 @@ class GuiApplication:
|
||||
def request_close(self):
|
||||
self._window_close_requested = True
|
||||
|
||||
def init_window(self, title: str, fps: int = _DEFAULT_FPS, new_modal: bool = False):
|
||||
def init_window(self, title: str, fps: int = _DEFAULT_FPS):
|
||||
with self._startup_profile_context():
|
||||
def _close(sig, frame):
|
||||
self.close()
|
||||
@@ -274,8 +263,6 @@ class GuiApplication:
|
||||
signal.signal(signal.SIGINT, _close)
|
||||
atexit.register(self.close)
|
||||
|
||||
self._new_modal = new_modal
|
||||
|
||||
flags = rl.ConfigFlags.FLAG_MSAA_4X_HINT
|
||||
if ENABLE_VSYNC:
|
||||
flags |= rl.ConfigFlags.FLAG_VSYNC_HINT
|
||||
@@ -380,44 +367,48 @@ class GuiApplication:
|
||||
break
|
||||
|
||||
def push_widget(self, widget: object):
|
||||
assert self._new_modal
|
||||
if widget in self._nav_stack:
|
||||
cloudlog.warning("Widget already in stack, cannot push again!")
|
||||
return
|
||||
|
||||
# disable previous widget to prevent input processing
|
||||
if len(self._nav_stack) > 0:
|
||||
prev_widget = self._nav_stack[-1]
|
||||
# TODO: change these to touch_valid
|
||||
prev_widget.set_enabled(False)
|
||||
|
||||
self._nav_stack.append(widget)
|
||||
widget.show_event()
|
||||
|
||||
def pop_widget(self):
|
||||
assert self._new_modal
|
||||
|
||||
if len(self._nav_stack) < 2:
|
||||
cloudlog.warning("At least one widget should remain on the stack, ignoring pop")
|
||||
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)
|
||||
|
||||
widget = self._nav_stack.pop()
|
||||
widget.hide_event()
|
||||
|
||||
def set_modal_overlay(self, overlay, callback: Callable | None = None):
|
||||
assert not self._new_modal, "set_modal_overlay is deprecated, use push_widget instead"
|
||||
def pop_widgets_to(self, widget):
|
||||
if widget not in self._nav_stack:
|
||||
cloudlog.warning("Widget not in stack, cannot pop to it!")
|
||||
return
|
||||
|
||||
if self._modal_overlay.overlay is not None:
|
||||
if hasattr(self._modal_overlay.overlay, 'hide_event'):
|
||||
self._modal_overlay.overlay.hide_event()
|
||||
# pops all widgets after specified widget
|
||||
while len(self._nav_stack) > 0 and self._nav_stack[-1] != widget:
|
||||
self.pop_widget()
|
||||
|
||||
if self._modal_overlay.callback is not None:
|
||||
self._modal_overlay.callback(-1)
|
||||
def get_active_widget(self):
|
||||
if len(self._nav_stack) > 0:
|
||||
return self._nav_stack[-1]
|
||||
return None
|
||||
|
||||
self._modal_overlay = ModalOverlay(overlay=overlay, callback=callback)
|
||||
|
||||
def set_modal_overlay_tick(self, tick_function: Callable | None):
|
||||
self._modal_overlay_tick = tick_function
|
||||
def set_nav_stack_tick(self, tick_function: Callable | None):
|
||||
self._nav_stack_tick = tick_function
|
||||
|
||||
def set_should_render(self, should_render: bool):
|
||||
self._should_render = should_render
|
||||
@@ -561,23 +552,15 @@ class GuiApplication:
|
||||
rl.begin_drawing()
|
||||
rl.clear_background(rl.BLACK)
|
||||
|
||||
if self._new_modal:
|
||||
# Only render last widget
|
||||
for widget in self._nav_stack[-1:]:
|
||||
widget.render(rl.Rectangle(0, 0, self.width, self.height))
|
||||
# Allow a Widget to still run a function regardless of the stack depth
|
||||
if self._nav_stack_tick is not None:
|
||||
self._nav_stack_tick()
|
||||
|
||||
# Yield to allow caller to run non-rendering related code
|
||||
yield True
|
||||
# Only render top widgets
|
||||
for widget in self._nav_stack[-self._nav_stack_widgets_to_render:]:
|
||||
widget.render(rl.Rectangle(0, 0, self.width, self.height))
|
||||
|
||||
else:
|
||||
# Handle modal overlay rendering and input processing
|
||||
if self._handle_modal_overlay():
|
||||
# Allow a Widget to still run a function while overlay is shown
|
||||
if self._modal_overlay_tick is not None:
|
||||
self._modal_overlay_tick()
|
||||
yield False
|
||||
else:
|
||||
yield True
|
||||
yield True
|
||||
|
||||
if self._render_texture:
|
||||
rl.end_texture_mode()
|
||||
@@ -631,33 +614,6 @@ class GuiApplication:
|
||||
def height(self):
|
||||
return self._height
|
||||
|
||||
def _handle_modal_overlay(self) -> bool:
|
||||
if self._modal_overlay.overlay:
|
||||
if hasattr(self._modal_overlay.overlay, 'render'):
|
||||
result = self._modal_overlay.overlay.render(rl.Rectangle(0, 0, self.width, self.height))
|
||||
elif callable(self._modal_overlay.overlay):
|
||||
result = self._modal_overlay.overlay()
|
||||
else:
|
||||
raise Exception
|
||||
|
||||
# Send show event to Widget
|
||||
if not self._modal_overlay_shown and hasattr(self._modal_overlay.overlay, 'show_event'):
|
||||
self._modal_overlay.overlay.show_event()
|
||||
self._modal_overlay_shown = True
|
||||
|
||||
if result >= 0:
|
||||
# Clear the overlay and execute the callback
|
||||
original_modal = self._modal_overlay
|
||||
self._modal_overlay = ModalOverlay()
|
||||
if hasattr(original_modal.overlay, 'hide_event'):
|
||||
original_modal.overlay.hide_event()
|
||||
if original_modal.callback is not None:
|
||||
original_modal.callback(result)
|
||||
return True
|
||||
else:
|
||||
self._modal_overlay_shown = False
|
||||
return False
|
||||
|
||||
def _load_fonts(self):
|
||||
for font_weight_file in FontWeight:
|
||||
with as_file(FONT_DIR) as fspath:
|
||||
|
||||
@@ -150,10 +150,10 @@ def main():
|
||||
if mode == ResetMode.FORMAT:
|
||||
reset.start_reset()
|
||||
|
||||
for should_render in gui_app.render():
|
||||
if should_render:
|
||||
if not reset.render(rl.Rectangle(0, 0, gui_app.width, gui_app.height)):
|
||||
break
|
||||
gui_app.push_widget(reset)
|
||||
|
||||
for _ in gui_app.render():
|
||||
pass
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
||||
+55
-86
@@ -19,7 +19,7 @@ from openpilot.system.hardware import HARDWARE
|
||||
from openpilot.system.ui.lib.application import gui_app, FontWeight
|
||||
from openpilot.system.ui.lib.wifi_manager import WifiManager
|
||||
from openpilot.system.ui.lib.scroll_panel2 import GuiScrollPanel2
|
||||
from openpilot.system.ui.widgets import Widget, DialogResult
|
||||
from openpilot.system.ui.widgets import Widget
|
||||
from openpilot.system.ui.widgets.button import (IconButton, SmallButton, WideRoundedButton, SmallerRoundedButton,
|
||||
SmallCircleIconButton, WidishRoundedButton, SmallRedPillButton,
|
||||
FullRoundedButton)
|
||||
@@ -96,10 +96,9 @@ class SetupState(IntEnum):
|
||||
NETWORK_SETUP = 1
|
||||
NETWORK_SETUP_CUSTOM_SOFTWARE = 2
|
||||
SOFTWARE_SELECTION = 3
|
||||
CUSTOM_SOFTWARE = 4
|
||||
DOWNLOADING = 5
|
||||
DOWNLOAD_FAILED = 6
|
||||
CUSTOM_SOFTWARE_WARNING = 7
|
||||
DOWNLOADING = 4
|
||||
DOWNLOAD_FAILED = 5
|
||||
CUSTOM_SOFTWARE_WARNING = 6
|
||||
|
||||
|
||||
class StartPage(Widget):
|
||||
@@ -128,7 +127,9 @@ class SoftwareSelectionPage(Widget):
|
||||
super().__init__()
|
||||
|
||||
self._openpilot_slider = LargerSlider("slide to use\nopenpilot", use_openpilot_callback)
|
||||
self._openpilot_slider.set_enabled(lambda: self.enabled)
|
||||
self._custom_software_slider = LargerSlider("slide to use\ncustom software", use_custom_software_callback, green=False)
|
||||
self._custom_software_slider.set_enabled(lambda: self.enabled)
|
||||
|
||||
def reset(self):
|
||||
self._openpilot_slider.reset()
|
||||
@@ -390,9 +391,11 @@ class FailedPage(Widget):
|
||||
|
||||
self._reboot_button = SmallRedPillButton("reboot")
|
||||
self._reboot_button.set_click_callback(reboot_callback)
|
||||
self._reboot_button.set_enabled(lambda: self.enabled) # for nav stack
|
||||
|
||||
self._retry_button = WideRoundedButton("retry")
|
||||
self._retry_button.set_click_callback(retry_callback)
|
||||
self._retry_button.set_enabled(lambda: self.enabled) # for nav stack
|
||||
|
||||
def set_reason(self, reason: str):
|
||||
self._reason_label.set_text(reason)
|
||||
@@ -427,15 +430,10 @@ class FailedPage(Widget):
|
||||
))
|
||||
|
||||
|
||||
class NetworkSetupState(IntEnum):
|
||||
MAIN = 0
|
||||
WIFI_PANEL = 1
|
||||
|
||||
|
||||
class NetworkSetupPage(Widget):
|
||||
def __init__(self, wifi_manager, continue_callback: Callable, back_callback: Callable):
|
||||
super().__init__()
|
||||
self._wifi_ui = WifiUIMici(wifi_manager, back_callback=lambda: self.set_state(NetworkSetupState.MAIN))
|
||||
self._wifi_ui = WifiUIMici(wifi_manager)
|
||||
|
||||
self._no_wifi_txt = gui_app.texture("icons_mici/settings/network/wifi_strength_slash.png", 58, 50)
|
||||
self._wifi_full_txt = gui_app.texture("icons_mici/settings/network/wifi_strength_full.png", 58, 50)
|
||||
@@ -445,78 +443,54 @@ class NetworkSetupPage(Widget):
|
||||
back_txt = gui_app.texture("icons_mici/setup/back_new.png", 37, 32)
|
||||
self._back_button = SmallCircleIconButton(back_txt)
|
||||
self._back_button.set_click_callback(back_callback)
|
||||
self._back_button.set_enabled(lambda: self.enabled) # for nav stack
|
||||
|
||||
self._wifi_button = SmallerRoundedButton("wifi")
|
||||
self._wifi_button.set_click_callback(lambda: self.set_state(NetworkSetupState.WIFI_PANEL))
|
||||
self._wifi_button.set_click_callback(lambda: gui_app.push_widget(self._wifi_ui))
|
||||
self._wifi_button.set_enabled(lambda: self.enabled)
|
||||
|
||||
self._continue_button = WidishRoundedButton("continue")
|
||||
self._continue_button.set_enabled(False)
|
||||
self._continue_button.set_click_callback(continue_callback)
|
||||
|
||||
self._state = NetworkSetupState.MAIN
|
||||
self._prev_has_internet = False
|
||||
|
||||
def set_state(self, state: NetworkSetupState):
|
||||
if self._state == NetworkSetupState.WIFI_PANEL and state != NetworkSetupState.WIFI_PANEL:
|
||||
self._wifi_ui.hide_event()
|
||||
self._state = state
|
||||
if state == NetworkSetupState.WIFI_PANEL:
|
||||
self._wifi_ui.show_event()
|
||||
|
||||
def set_has_internet(self, has_internet: bool):
|
||||
if has_internet:
|
||||
self._network_header.set_title("connected to internet")
|
||||
self._network_header.set_icon(self._wifi_full_txt)
|
||||
self._continue_button.set_enabled(True)
|
||||
self._continue_button.set_enabled(self.enabled)
|
||||
else:
|
||||
self._network_header.set_title(self._waiting_text)
|
||||
self._network_header.set_icon(self._no_wifi_txt)
|
||||
self._continue_button.set_enabled(False)
|
||||
|
||||
if has_internet and not self._prev_has_internet:
|
||||
self.set_state(NetworkSetupState.MAIN)
|
||||
self._prev_has_internet = has_internet
|
||||
|
||||
def show_event(self):
|
||||
super().show_event()
|
||||
self._state = NetworkSetupState.MAIN
|
||||
|
||||
def hide_event(self):
|
||||
super().hide_event()
|
||||
if self._state == NetworkSetupState.WIFI_PANEL:
|
||||
self._wifi_ui.hide_event()
|
||||
|
||||
def _render(self, _):
|
||||
if self._state == NetworkSetupState.MAIN:
|
||||
self._network_header.render(rl.Rectangle(
|
||||
self._rect.x + 16,
|
||||
self._rect.y + 16,
|
||||
self._rect.width - 32,
|
||||
self._network_header.rect.height,
|
||||
))
|
||||
self._network_header.render(rl.Rectangle(
|
||||
self._rect.x + 16,
|
||||
self._rect.y + 16,
|
||||
self._rect.width - 32,
|
||||
self._network_header.rect.height,
|
||||
))
|
||||
|
||||
self._back_button.render(rl.Rectangle(
|
||||
self._rect.x + 8,
|
||||
self._rect.y + self._rect.height - self._back_button.rect.height,
|
||||
self._back_button.rect.width,
|
||||
self._back_button.rect.height,
|
||||
))
|
||||
self._back_button.render(rl.Rectangle(
|
||||
self._rect.x + 8,
|
||||
self._rect.y + self._rect.height - self._back_button.rect.height,
|
||||
self._back_button.rect.width,
|
||||
self._back_button.rect.height,
|
||||
))
|
||||
|
||||
self._wifi_button.render(rl.Rectangle(
|
||||
self._rect.x + 8 + self._back_button.rect.width + 10,
|
||||
self._rect.y + self._rect.height - self._wifi_button.rect.height,
|
||||
self._wifi_button.rect.width,
|
||||
self._wifi_button.rect.height,
|
||||
))
|
||||
self._wifi_button.render(rl.Rectangle(
|
||||
self._rect.x + 8 + self._back_button.rect.width + 10,
|
||||
self._rect.y + self._rect.height - self._wifi_button.rect.height,
|
||||
self._wifi_button.rect.width,
|
||||
self._wifi_button.rect.height,
|
||||
))
|
||||
|
||||
self._continue_button.render(rl.Rectangle(
|
||||
self._rect.x + self._rect.width - self._continue_button.rect.width - 8,
|
||||
self._rect.y + self._rect.height - self._continue_button.rect.height,
|
||||
self._continue_button.rect.width,
|
||||
self._continue_button.rect.height,
|
||||
))
|
||||
else:
|
||||
self._wifi_ui.render(self._rect)
|
||||
self._continue_button.render(rl.Rectangle(
|
||||
self._rect.x + self._rect.width - self._continue_button.rect.width - 8,
|
||||
self._rect.y + self._rect.height - self._continue_button.rect.height,
|
||||
self._continue_button.rect.width,
|
||||
self._continue_button.rect.height,
|
||||
))
|
||||
|
||||
|
||||
class Setup(Widget):
|
||||
@@ -533,28 +507,33 @@ class Setup(Widget):
|
||||
self._network_monitor = NetworkConnectivityMonitor()
|
||||
self._network_monitor.start()
|
||||
self._prev_has_internet = False
|
||||
gui_app.set_modal_overlay_tick(self._modal_overlay_tick)
|
||||
gui_app.set_nav_stack_tick(self._nav_stack_tick)
|
||||
|
||||
self._start_page = StartPage()
|
||||
self._start_page.set_click_callback(self._getting_started_button_callback)
|
||||
|
||||
self._network_setup_page = NetworkSetupPage(self._wifi_manager, self._network_setup_continue_button_callback,
|
||||
self._network_setup_back_button_callback)
|
||||
# TODO: change these to touch_valid
|
||||
self._network_setup_page.set_enabled(lambda: self.enabled) # for nav stack
|
||||
|
||||
self._software_selection_page = SoftwareSelectionPage(self._software_selection_continue_button_callback,
|
||||
self._software_selection_custom_software_button_callback)
|
||||
self._software_selection_page.set_enabled(lambda: self.enabled) # for nav stack
|
||||
|
||||
self._download_failed_page = FailedPage(HARDWARE.reboot, self._download_failed_startover_button_callback)
|
||||
self._download_failed_page.set_enabled(lambda: self.enabled) # for nav stack
|
||||
|
||||
self._custom_software_warning_page = CustomSoftwareWarningPage(self._software_selection_custom_software_continue,
|
||||
self._custom_software_warning_back_button_callback)
|
||||
self._custom_software_warning_page.set_enabled(lambda: self.enabled) # for nav stack
|
||||
|
||||
self._downloading_page = DownloadingPage()
|
||||
|
||||
def _modal_overlay_tick(self):
|
||||
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.set_modal_overlay(None)
|
||||
gui_app.pop_widgets_to(self)
|
||||
self._prev_has_internet = has_internet
|
||||
|
||||
def _update_state(self):
|
||||
@@ -582,8 +561,6 @@ class Setup(Widget):
|
||||
self._software_selection_page.render(rect)
|
||||
elif self.state == SetupState.CUSTOM_SOFTWARE_WARNING:
|
||||
self._custom_software_warning_page.render(rect)
|
||||
elif self.state == SetupState.CUSTOM_SOFTWARE:
|
||||
self.render_custom_software()
|
||||
elif self.state == SetupState.DOWNLOADING:
|
||||
self.render_downloading(rect)
|
||||
elif self.state == SetupState.DOWNLOAD_FAILED:
|
||||
@@ -614,14 +591,19 @@ class Setup(Widget):
|
||||
if self.state == SetupState.NETWORK_SETUP:
|
||||
self.download(OPENPILOT_URL)
|
||||
elif self.state == SetupState.NETWORK_SETUP_CUSTOM_SOFTWARE:
|
||||
self._set_state(SetupState.CUSTOM_SOFTWARE)
|
||||
def handle_keyboard_result(text):
|
||||
url = text.strip()
|
||||
if url:
|
||||
self.download(url)
|
||||
|
||||
keyboard = BigInputDialog("custom software URL", confirm_callback=handle_keyboard_result)
|
||||
gui_app.push_widget(keyboard)
|
||||
|
||||
def close(self):
|
||||
self._network_monitor.stop()
|
||||
|
||||
def render_network_setup(self, rect: rl.Rectangle):
|
||||
has_internet = self._network_monitor.network_connected.is_set()
|
||||
self._prev_has_internet = has_internet
|
||||
self._network_setup_page.set_has_internet(has_internet)
|
||||
self._network_setup_page.render(rect)
|
||||
|
||||
@@ -629,19 +611,6 @@ class Setup(Widget):
|
||||
self._downloading_page.set_progress(self.download_progress)
|
||||
self._downloading_page.render(rect)
|
||||
|
||||
def render_custom_software(self):
|
||||
def handle_keyboard_result(text):
|
||||
url = text.strip()
|
||||
if url:
|
||||
self.download(url)
|
||||
|
||||
def handle_keyboard_exit(result):
|
||||
if result == DialogResult.CANCEL:
|
||||
self._set_state(SetupState.SOFTWARE_SELECTION)
|
||||
|
||||
keyboard = BigInputDialog("custom software URL...", confirm_callback=handle_keyboard_result)
|
||||
gui_app.set_modal_overlay(keyboard, callback=handle_keyboard_exit)
|
||||
|
||||
def use_openpilot(self):
|
||||
if os.path.isdir(INSTALL_PATH) and os.path.isfile(VALID_CACHE_PATH):
|
||||
os.remove(VALID_CACHE_PATH)
|
||||
@@ -738,9 +707,9 @@ def main():
|
||||
try:
|
||||
gui_app.init_window("Setup")
|
||||
setup = Setup()
|
||||
for should_render in gui_app.render():
|
||||
if should_render:
|
||||
setup.render(rl.Rectangle(0, 0, gui_app.width, gui_app.height))
|
||||
gui_app.push_widget(setup)
|
||||
for _ in gui_app.render():
|
||||
pass
|
||||
setup.close()
|
||||
except Exception as e:
|
||||
print(f"Setup error: {e}")
|
||||
|
||||
@@ -38,6 +38,7 @@ class Updater(Widget):
|
||||
|
||||
self._network_setup_page = NetworkSetupPage(self._wifi_manager, self._network_setup_continue_callback,
|
||||
self._network_setup_back_callback)
|
||||
self._network_setup_page.set_enabled(lambda: self.enabled) # for nav stack
|
||||
|
||||
self._network_monitor = NetworkConnectivityMonitor()
|
||||
self._network_monitor.start()
|
||||
@@ -182,9 +183,9 @@ def main():
|
||||
try:
|
||||
gui_app.init_window("System Update")
|
||||
updater = Updater(updater_path, manifest_path)
|
||||
for should_render in gui_app.render():
|
||||
if should_render:
|
||||
updater.render(rl.Rectangle(0, 0, gui_app.width, gui_app.height))
|
||||
gui_app.push_widget(updater)
|
||||
for _ in gui_app.render():
|
||||
pass
|
||||
updater.close()
|
||||
except Exception as e:
|
||||
print(f"Updater error: {e}")
|
||||
|
||||
@@ -116,7 +116,7 @@ def main():
|
||||
elif sys.argv[1] == "--format":
|
||||
mode = ResetMode.FORMAT
|
||||
|
||||
gui_app.init_window("System Reset", 20, new_modal=True)
|
||||
gui_app.init_window("System Reset", 20)
|
||||
reset = Reset(mode)
|
||||
|
||||
if mode == ResetMode.FORMAT:
|
||||
|
||||
@@ -436,7 +436,7 @@ class Setup(Widget):
|
||||
|
||||
def main():
|
||||
try:
|
||||
gui_app.init_window("Setup", 20, new_modal=True)
|
||||
gui_app.init_window("Setup", 20)
|
||||
setup = Setup()
|
||||
gui_app.push_widget(setup)
|
||||
for _ in gui_app.render():
|
||||
|
||||
@@ -160,7 +160,7 @@ def main():
|
||||
manifest_path = sys.argv[2]
|
||||
|
||||
try:
|
||||
gui_app.init_window("System Update", new_modal=True)
|
||||
gui_app.init_window("System Update")
|
||||
gui_app.push_widget(Updater(updater_path, manifest_path))
|
||||
for _ in gui_app.render():
|
||||
pass
|
||||
|
||||
@@ -108,6 +108,10 @@ class Widget(abc.ABC):
|
||||
# Keep track of whether mouse down started within the widget's rectangle
|
||||
if self.enabled and self.__was_awake:
|
||||
self._process_mouse_events()
|
||||
else:
|
||||
# TODO: ideally we emit release events when going disabled
|
||||
self.__is_pressed = [False] * MAX_TOUCH_SLOTS
|
||||
self.__tracking_is_pressed = [False] * MAX_TOUCH_SLOTS
|
||||
|
||||
self.__was_awake = device.awake
|
||||
|
||||
@@ -181,6 +185,7 @@ class Widget(abc.ABC):
|
||||
|
||||
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)
|
||||
|
||||
def hide_event(self):
|
||||
"""Optionally handle hide event. Parent must manually call this"""
|
||||
@@ -261,6 +266,7 @@ class NavWidget(Widget, abc.ABC):
|
||||
self._back_callback = callback
|
||||
|
||||
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 not self.back_enabled:
|
||||
@@ -320,13 +326,14 @@ class NavWidget(Widget, abc.ABC):
|
||||
if not self._set_up:
|
||||
self._set_up = True
|
||||
if hasattr(self, '_scroller'):
|
||||
# TODO: use touch_valid
|
||||
original_enabled = self._scroller._enabled
|
||||
self._scroller.set_enabled(lambda: not self._swiping_away and (original_enabled() if callable(original_enabled) else
|
||||
original_enabled))
|
||||
self._scroller.set_enabled(lambda: self.enabled and not self._swiping_away and (original_enabled() if callable(original_enabled) else
|
||||
original_enabled))
|
||||
elif hasattr(self, '_scroll_panel'):
|
||||
original_enabled = self._scroll_panel.enabled
|
||||
self._scroll_panel.set_enabled(lambda: not self._swiping_away and (original_enabled() if callable(original_enabled) else
|
||||
original_enabled))
|
||||
self._scroll_panel.set_enabled(lambda: self.enabled and not self._swiping_away and (original_enabled() if callable(original_enabled) else
|
||||
original_enabled))
|
||||
|
||||
if self._trigger_animate_in:
|
||||
self._pos_filter.x = self._rect.height
|
||||
@@ -336,6 +343,10 @@ class NavWidget(Widget, abc.ABC):
|
||||
|
||||
new_y = 0.0
|
||||
|
||||
if not self.enabled:
|
||||
self._back_button_start_pos = None
|
||||
|
||||
# TODO: why is this not in handle_mouse_event? have to hack above
|
||||
if self._back_button_start_pos is not None:
|
||||
last_mouse_event = gui_app.last_mouse_event
|
||||
# push entire widget as user drags it away
|
||||
@@ -363,6 +374,14 @@ class NavWidget(Widget, abc.ABC):
|
||||
|
||||
self.set_position(self._rect.x, new_y)
|
||||
|
||||
def _layout(self):
|
||||
# Dim whatever is behind this widget, fading with position (runs after _update_state so position is correct)
|
||||
overlay_alpha = int(200 * max(0.0, min(1.0, 1.0 - self._rect.y / self._rect.height))) if self._rect.height > 0 else 0
|
||||
rl.draw_rectangle(0, 0, int(self._rect.width), int(self._rect.height), rl.Color(0, 0, 0, overlay_alpha))
|
||||
|
||||
bounce_height = 20
|
||||
rl.draw_rectangle(int(self._rect.x), int(self._rect.y), int(self._rect.width), int(self._rect.height + bounce_height), rl.BLACK)
|
||||
|
||||
def render(self, rect: rl.Rectangle | None = None) -> bool | int | None:
|
||||
ret = super().render(rect)
|
||||
|
||||
@@ -379,10 +398,6 @@ class NavWidget(Widget, abc.ABC):
|
||||
else:
|
||||
self._nav_bar_y_filter.update(NAV_BAR_MARGIN)
|
||||
|
||||
# draw black above widget when dismissing
|
||||
if self._rect.y > 0:
|
||||
rl.draw_rectangle(int(self._rect.x), 0, int(self._rect.width), int(self._rect.y), rl.BLACK)
|
||||
|
||||
self._nav_bar.set_position(bar_x, round(self._nav_bar_y_filter.x))
|
||||
self._nav_bar.render()
|
||||
|
||||
|
||||
@@ -272,7 +272,7 @@ if __name__ == "__main__":
|
||||
print("Canceled")
|
||||
gui_app.request_close()
|
||||
|
||||
gui_app.init_window("Keyboard", new_modal=True)
|
||||
gui_app.init_window("Keyboard")
|
||||
keyboard = Keyboard(min_text_size=8, show_password_toggle=True, callback=callback)
|
||||
keyboard.set_title("Keyboard Input", "Type your text below")
|
||||
|
||||
|
||||
@@ -61,7 +61,7 @@ class Key(Widget):
|
||||
self._x_filter.x = x
|
||||
self._y_filter.x = local_y
|
||||
# keep track of original position so dragging around feels consistent. also move touch area down a bit
|
||||
self.original_position = rl.Vector2(x, y + KEY_TOUCH_AREA_OFFSET)
|
||||
self.original_position = rl.Vector2(x, local_y + KEY_TOUCH_AREA_OFFSET)
|
||||
self._position_initialized = True
|
||||
|
||||
if not smooth:
|
||||
@@ -227,6 +227,8 @@ class MiciKeyboard(Widget):
|
||||
for current_row, row in zip(self._current_keys, keys, strict=False):
|
||||
# not all layouts have the same number of keys
|
||||
for current_key, key in zip_repeat(current_row, row):
|
||||
# reset parent rect for new keys
|
||||
key.set_parent_rect(self._rect)
|
||||
current_pos = current_key.get_position()
|
||||
key.set_position(current_pos[0], current_pos[1], smooth=False)
|
||||
|
||||
@@ -264,7 +266,8 @@ class MiciKeyboard(Widget):
|
||||
for key in row:
|
||||
mouse_pos = gui_app.last_mouse_event.pos
|
||||
# approximate distance for comparison is accurate enough
|
||||
dist = abs(key.original_position.x - mouse_pos.x) + abs(key.original_position.y - mouse_pos.y)
|
||||
# use local y coords so parent widget offset (e.g. during NavWidget animate-in) doesn't affect hit testing
|
||||
dist = abs(key.original_position.x - mouse_pos.x) + abs(key.original_position.y - (mouse_pos.y - self._rect.y))
|
||||
if dist < closest_key[1]:
|
||||
if self._closest_key[0] is None or key is self._closest_key[0] or dist < self._closest_key[1] - KEY_DRAG_HYSTERESIS:
|
||||
closest_key = (key, dist)
|
||||
|
||||
@@ -481,7 +481,7 @@ class WifiManagerUI(Widget):
|
||||
|
||||
|
||||
def main():
|
||||
gui_app.init_window("Wi-Fi Manager", new_modal=True)
|
||||
gui_app.init_window("Wi-Fi Manager")
|
||||
gui_app.push_widget(WifiManagerUI(WifiManager()))
|
||||
|
||||
for _ in gui_app.render():
|
||||
|
||||
Reference in New Issue
Block a user