ui: add navigation stack for tici (#37275)
* 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! * revert selfdrive/ui * let's do tici first * bring back ui * not sure why __del__, SetupWidget was never deleted? * device "done" * network "done!!" * toggles "done" * software "done" * developer "done" * fix onboarding * use new modal for debug windows * and aug * setup "done" * clean up * updater "done" * reset "done" * pop first before callbacks in case callbacks push * fix cmt * not needed * remove two commented functions for mici * clean up application * typing * static * not sure what this means * fix big * more static * actually great catch * fix cmt
This commit is contained in:
@@ -36,10 +36,12 @@ class MainLayout(Widget):
|
||||
# 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 _render(self, _):
|
||||
self._handle_onroad_transition()
|
||||
|
||||
@@ -81,6 +81,9 @@ class TrainingGuide(Widget):
|
||||
if self._completed_callback:
|
||||
self._completed_callback()
|
||||
|
||||
# NOTE: this pops OnboardingWindow during real onboarding
|
||||
gui_app.pop_widget()
|
||||
|
||||
def _update_state(self):
|
||||
if len(self._image_objs):
|
||||
self._textures.append(gui_app._load_texture_from_image(self._image_objs.pop(0)))
|
||||
@@ -194,11 +197,10 @@ class OnboardingWindow(Widget):
|
||||
ui_state.params.put("HasAcceptedTerms", terms_version)
|
||||
self._state = OnboardingState.ONBOARDING
|
||||
if self._training_done:
|
||||
gui_app.set_modal_overlay(None)
|
||||
gui_app.pop_widget()
|
||||
|
||||
def _on_completed_training(self):
|
||||
ui_state.params.put("CompletedTrainingVersion", training_version)
|
||||
gui_app.set_modal_overlay(None)
|
||||
|
||||
def _render(self, _):
|
||||
if self._training_guide is None:
|
||||
|
||||
@@ -164,7 +164,7 @@ class DeveloperLayout(Widget):
|
||||
|
||||
def _on_alpha_long_enabled(self, state: bool):
|
||||
if state:
|
||||
def confirm_callback(result: int):
|
||||
def confirm_callback(result: DialogResult):
|
||||
if result == DialogResult.CONFIRM:
|
||||
self._params.put_bool("AlphaLongitudinalEnabled", True)
|
||||
self._params.put_bool("OnroadCycleRequested", True)
|
||||
@@ -176,8 +176,8 @@ class DeveloperLayout(Widget):
|
||||
content = (f"<h1>{self._alpha_long_toggle.title}</h1><br>" +
|
||||
f"<p>{self._alpha_long_toggle.description}</p>")
|
||||
|
||||
dlg = ConfirmDialog(content, tr("Enable"), rich=True)
|
||||
gui_app.set_modal_overlay(dlg, callback=confirm_callback)
|
||||
dlg = ConfirmDialog(content, tr("Enable"), rich=True, callback=confirm_callback)
|
||||
gui_app.push_widget(dlg)
|
||||
|
||||
else:
|
||||
self._params.put_bool("AlphaLongitudinalEnabled", False)
|
||||
|
||||
@@ -33,8 +33,6 @@ class DeviceLayout(Widget):
|
||||
|
||||
self._params = Params()
|
||||
self._select_language_dialog: MultiOptionDialog | None = None
|
||||
self._driver_camera: DriverCameraDialog | None = None
|
||||
self._pair_device_dialog: PairingDialog | None = None
|
||||
self._fcc_dialog: HtmlModal | None = None
|
||||
self._training_guide: TrainingGuide | None = None
|
||||
|
||||
@@ -44,7 +42,8 @@ class DeviceLayout(Widget):
|
||||
ui_state.add_offroad_transition_callback(self._offroad_transition)
|
||||
|
||||
def _initialize_items(self):
|
||||
self._pair_device_btn = button_item(lambda: tr("Pair Device"), lambda: tr("PAIR"), lambda: tr(DESCRIPTIONS['pair_device']), callback=self._pair_device)
|
||||
self._pair_device_btn = button_item(lambda: tr("Pair Device"), lambda: tr("PAIR"), lambda: tr(DESCRIPTIONS['pair_device']),
|
||||
callback=lambda: gui_app.push_widget(PairingDialog()))
|
||||
self._pair_device_btn.set_visible(lambda: not ui_state.prime_state.is_paired())
|
||||
|
||||
self._reset_calib_btn = button_item(lambda: tr("Reset Calibration"), lambda: tr("RESET"), lambda: tr(DESCRIPTIONS['reset_calibration']),
|
||||
@@ -59,7 +58,7 @@ class DeviceLayout(Widget):
|
||||
text_item(lambda: tr("Serial"), self._params.get("HardwareSerial") or (lambda: tr("N/A"))),
|
||||
self._pair_device_btn,
|
||||
button_item(lambda: tr("Driver Camera"), lambda: tr("PREVIEW"), lambda: tr(DESCRIPTIONS['driver_camera']),
|
||||
callback=self._show_driver_camera, enabled=ui_state.is_offroad),
|
||||
callback=lambda: gui_app.push_widget(DriverCameraDialog()), enabled=ui_state.is_offroad),
|
||||
self._reset_calib_btn,
|
||||
button_item(lambda: tr("Review Training Guide"), lambda: tr("REVIEW"), lambda: tr(DESCRIPTIONS['review_guide']),
|
||||
self._on_review_training_guide, enabled=ui_state.is_offroad),
|
||||
@@ -79,29 +78,23 @@ class DeviceLayout(Widget):
|
||||
self._scroller.render(rect)
|
||||
|
||||
def _show_language_dialog(self):
|
||||
def handle_language_selection(result: int):
|
||||
if result == 1 and self._select_language_dialog:
|
||||
def handle_language_selection(result: DialogResult):
|
||||
if result == DialogResult.CONFIRM and self._select_language_dialog:
|
||||
selected_language = multilang.languages[self._select_language_dialog.selection]
|
||||
multilang.change_language(selected_language)
|
||||
self._update_calib_description()
|
||||
self._select_language_dialog = None
|
||||
|
||||
self._select_language_dialog = MultiOptionDialog(tr("Select a language"), multilang.languages, multilang.codes[multilang.language],
|
||||
option_font_weight=FontWeight.UNIFONT)
|
||||
gui_app.set_modal_overlay(self._select_language_dialog, callback=handle_language_selection)
|
||||
|
||||
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))
|
||||
option_font_weight=FontWeight.UNIFONT, callback=handle_language_selection)
|
||||
gui_app.push_widget(self._select_language_dialog)
|
||||
|
||||
def _reset_calibration_prompt(self):
|
||||
if ui_state.engaged:
|
||||
gui_app.set_modal_overlay(alert_dialog(tr("Disengage to Reset Calibration")))
|
||||
gui_app.push_widget(alert_dialog(tr("Disengage to Reset Calibration")))
|
||||
return
|
||||
|
||||
def reset_calibration(result: int):
|
||||
def reset_calibration(result: DialogResult):
|
||||
# Check engaged again in case it changed while the dialog was open
|
||||
if ui_state.engaged or result != DialogResult.CONFIRM:
|
||||
return
|
||||
@@ -114,8 +107,8 @@ class DeviceLayout(Widget):
|
||||
self._params.put_bool("OnroadCycleRequested", True)
|
||||
self._update_calib_description()
|
||||
|
||||
dialog = ConfirmDialog(tr("Are you sure you want to reset calibration?"), tr("Reset"))
|
||||
gui_app.set_modal_overlay(dialog, callback=reset_calibration)
|
||||
dialog = ConfirmDialog(tr("Are you sure you want to reset calibration?"), tr("Reset"), callback=reset_calibration)
|
||||
gui_app.push_widget(dialog)
|
||||
|
||||
def _update_calib_description(self):
|
||||
desc = tr(DESCRIPTIONS['reset_calibration'])
|
||||
@@ -167,42 +160,34 @@ class DeviceLayout(Widget):
|
||||
|
||||
def _reboot_prompt(self):
|
||||
if ui_state.engaged:
|
||||
gui_app.set_modal_overlay(alert_dialog(tr("Disengage to Reboot")))
|
||||
gui_app.push_widget(alert_dialog(tr("Disengage to Reboot")))
|
||||
return
|
||||
|
||||
dialog = ConfirmDialog(tr("Are you sure you want to reboot?"), tr("Reboot"))
|
||||
gui_app.set_modal_overlay(dialog, callback=self._perform_reboot)
|
||||
def perform_reboot(result: DialogResult):
|
||||
if not ui_state.engaged and result == DialogResult.CONFIRM:
|
||||
self._params.put_bool_nonblocking("DoReboot", True)
|
||||
|
||||
def _perform_reboot(self, result: int):
|
||||
if not ui_state.engaged and result == DialogResult.CONFIRM:
|
||||
self._params.put_bool_nonblocking("DoReboot", True)
|
||||
dialog = ConfirmDialog(tr("Are you sure you want to reboot?"), tr("Reboot"), callback=perform_reboot)
|
||||
gui_app.push_widget(dialog)
|
||||
|
||||
def _power_off_prompt(self):
|
||||
if ui_state.engaged:
|
||||
gui_app.set_modal_overlay(alert_dialog(tr("Disengage to Power Off")))
|
||||
gui_app.push_widget(alert_dialog(tr("Disengage to Power Off")))
|
||||
return
|
||||
|
||||
dialog = ConfirmDialog(tr("Are you sure you want to power off?"), tr("Power Off"))
|
||||
gui_app.set_modal_overlay(dialog, callback=self._perform_power_off)
|
||||
def perform_power_off(result: DialogResult):
|
||||
if not ui_state.engaged and result == DialogResult.CONFIRM:
|
||||
self._params.put_bool_nonblocking("DoShutdown", True)
|
||||
|
||||
def _perform_power_off(self, result: int):
|
||||
if not ui_state.engaged and result == DialogResult.CONFIRM:
|
||||
self._params.put_bool_nonblocking("DoShutdown", True)
|
||||
|
||||
def _pair_device(self):
|
||||
if not self._pair_device_dialog:
|
||||
self._pair_device_dialog = PairingDialog()
|
||||
gui_app.set_modal_overlay(self._pair_device_dialog, callback=lambda result: setattr(self, '_pair_device_dialog', None))
|
||||
dialog = ConfirmDialog(tr("Are you sure you want to power off?"), tr("Power Off"), callback=perform_power_off)
|
||||
gui_app.push_widget(dialog)
|
||||
|
||||
def _on_regulatory(self):
|
||||
if not self._fcc_dialog:
|
||||
self._fcc_dialog = HtmlModal(os.path.join(BASEDIR, "selfdrive/assets/offroad/fcc.html"))
|
||||
gui_app.set_modal_overlay(self._fcc_dialog)
|
||||
gui_app.push_widget(self._fcc_dialog)
|
||||
|
||||
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)
|
||||
self._training_guide = TrainingGuide()
|
||||
gui_app.push_widget(self._training_guide)
|
||||
|
||||
@@ -165,12 +165,12 @@ class SoftwareLayout(Widget):
|
||||
os.system("pkill -SIGHUP -f system.updated.updated")
|
||||
|
||||
def _on_uninstall(self):
|
||||
def handle_uninstall_confirmation(result):
|
||||
def handle_uninstall_confirmation(result: DialogResult):
|
||||
if result == DialogResult.CONFIRM:
|
||||
ui_state.params.put_bool("DoUninstall", True)
|
||||
|
||||
dialog = ConfirmDialog(tr("Are you sure you want to uninstall?"), tr("Uninstall"))
|
||||
gui_app.set_modal_overlay(dialog, callback=handle_uninstall_confirmation)
|
||||
dialog = ConfirmDialog(tr("Are you sure you want to uninstall?"), tr("Uninstall"), callback=handle_uninstall_confirmation)
|
||||
gui_app.push_widget(dialog)
|
||||
|
||||
def _on_install_update(self):
|
||||
# Trigger reboot to install update
|
||||
@@ -189,9 +189,8 @@ class SoftwareLayout(Widget):
|
||||
branches.insert(0, b)
|
||||
|
||||
current_target = ui_state.params.get("UpdaterTargetBranch") or ""
|
||||
self._branch_dialog = MultiOptionDialog(tr("Select a branch"), branches, current_target)
|
||||
|
||||
def handle_selection(result):
|
||||
def handle_selection(result: DialogResult):
|
||||
# Confirmed selection
|
||||
if result == DialogResult.CONFIRM and self._branch_dialog is not None and self._branch_dialog.selection:
|
||||
selection = self._branch_dialog.selection
|
||||
@@ -200,4 +199,5 @@ class SoftwareLayout(Widget):
|
||||
os.system("pkill -SIGUSR1 -f system.updated.updated")
|
||||
self._branch_dialog = None
|
||||
|
||||
gui_app.set_modal_overlay(self._branch_dialog, callback=handle_selection)
|
||||
self._branch_dialog = MultiOptionDialog(tr("Select a branch"), branches, current_target, callback=handle_selection)
|
||||
gui_app.push_widget(self._branch_dialog)
|
||||
|
||||
@@ -214,7 +214,7 @@ class TogglesLayout(Widget):
|
||||
def _handle_experimental_mode_toggle(self, state: bool):
|
||||
confirmed = self._params.get_bool("ExperimentalModeConfirmed")
|
||||
if state and not confirmed:
|
||||
def confirm_callback(result: int):
|
||||
def confirm_callback(result: DialogResult):
|
||||
if result == DialogResult.CONFIRM:
|
||||
self._params.put_bool("ExperimentalMode", True)
|
||||
self._params.put_bool("ExperimentalModeConfirmed", True)
|
||||
@@ -225,8 +225,8 @@ class TogglesLayout(Widget):
|
||||
# show confirmation dialog
|
||||
content = (f"<h1>{self._toggles['ExperimentalMode'].title}</h1><br>" +
|
||||
f"<p>{self._toggles['ExperimentalMode'].description}</p>")
|
||||
dlg = ConfirmDialog(content, tr("Enable"), rich=True)
|
||||
gui_app.set_modal_overlay(dlg, callback=confirm_callback)
|
||||
dlg = ConfirmDialog(content, tr("Enable"), rich=True, callback=confirm_callback)
|
||||
gui_app.push_widget(dlg)
|
||||
else:
|
||||
self._update_experimental_mode_icon()
|
||||
self._params.put_bool("ExperimentalMode", state)
|
||||
|
||||
@@ -219,8 +219,9 @@ class AugmentedRoadView(CameraView):
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
gui_app.init_window("OnRoad Camera View")
|
||||
gui_app.init_window("OnRoad Camera View", new_modal=True)
|
||||
road_camera_view = AugmentedRoadView(ROAD_CAM)
|
||||
gui_app.push_widget(road_camera_view)
|
||||
print("***press space to switch camera view***")
|
||||
try:
|
||||
for _ in gui_app.render():
|
||||
@@ -229,6 +230,5 @@ if __name__ == "__main__":
|
||||
if WIDE_CAM in road_camera_view.available_streams:
|
||||
stream = ROAD_CAM if road_camera_view.stream_type == WIDE_CAM else WIDE_CAM
|
||||
road_camera_view.switch_stream(stream)
|
||||
road_camera_view.render(rl.Rectangle(0, 0, gui_app.width, gui_app.height))
|
||||
finally:
|
||||
road_camera_view.close()
|
||||
|
||||
@@ -14,7 +14,7 @@ class DriverCameraDialog(CameraView):
|
||||
super().__init__("camerad", VisionStreamType.VISION_STREAM_DRIVER)
|
||||
self.driver_state_renderer = DriverStateRenderer()
|
||||
# TODO: this can grow unbounded, should be given some thought
|
||||
device.add_interactive_timeout_callback(lambda: gui_app.set_modal_overlay(None))
|
||||
device.add_interactive_timeout_callback(gui_app.pop_widget)
|
||||
ui_state.params.put_bool("IsDriverViewEnabled", True)
|
||||
|
||||
def hide_event(self):
|
||||
@@ -24,7 +24,7 @@ class DriverCameraDialog(CameraView):
|
||||
|
||||
def _handle_mouse_release(self, _):
|
||||
super()._handle_mouse_release(_)
|
||||
gui_app.set_modal_overlay(None)
|
||||
gui_app.pop_widget()
|
||||
|
||||
def __del__(self):
|
||||
self.close()
|
||||
@@ -100,12 +100,12 @@ class DriverCameraDialog(CameraView):
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
gui_app.init_window("Driver Camera View")
|
||||
gui_app.init_window("Driver Camera View", new_modal=True)
|
||||
|
||||
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()
|
||||
|
||||
@@ -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)
|
||||
gui_app.init_window("ui diff test", fps=FPS, new_modal=variant == "tizi")
|
||||
|
||||
# Dynamically import main layout based on variant
|
||||
if variant == "mici":
|
||||
|
||||
@@ -9,21 +9,26 @@ from openpilot.selfdrive.ui.layouts.main import MainLayout
|
||||
from openpilot.selfdrive.ui.mici.layouts.main import MiciMainLayout
|
||||
from openpilot.selfdrive.ui.ui_state import ui_state
|
||||
|
||||
BIG_UI = gui_app.big_ui()
|
||||
|
||||
|
||||
def main():
|
||||
cores = {5, }
|
||||
config_realtime_process(0, 51)
|
||||
|
||||
gui_app.init_window("UI")
|
||||
if gui_app.big_ui():
|
||||
if BIG_UI:
|
||||
gui_app.init_window("UI", new_modal=True)
|
||||
main_layout = MainLayout()
|
||||
else:
|
||||
gui_app.init_window("UI")
|
||||
main_layout = MiciMainLayout()
|
||||
main_layout.set_rect(rl.Rectangle(0, 0, gui_app.width, gui_app.height))
|
||||
main_layout.set_rect(rl.Rectangle(0, 0, gui_app.width, gui_app.height))
|
||||
|
||||
for should_render in gui_app.render():
|
||||
ui_state.update()
|
||||
if should_render:
|
||||
main_layout.render()
|
||||
if not BIG_UI:
|
||||
main_layout.render()
|
||||
|
||||
# reaffine after power save offlines our core
|
||||
if TICI and os.sched_getaffinity(0) != cores:
|
||||
|
||||
@@ -26,7 +26,7 @@ class PairingDialog(Widget):
|
||||
self.qr_texture: rl.Texture | None = None
|
||||
self.last_qr_generation = float('-inf')
|
||||
self._close_btn = IconButton(gui_app.texture("icons/close.png", 80, 80))
|
||||
self._close_btn.set_click_callback(lambda: gui_app.set_modal_overlay(None))
|
||||
self._close_btn.set_click_callback(gui_app.pop_widget)
|
||||
|
||||
def _get_pairing_url(self) -> str:
|
||||
try:
|
||||
@@ -69,7 +69,7 @@ class PairingDialog(Widget):
|
||||
|
||||
def _update_state(self):
|
||||
if ui_state.prime_state.is_paired():
|
||||
gui_app.set_modal_overlay(None)
|
||||
gui_app.pop_widget()
|
||||
|
||||
def _render(self, rect: rl.Rectangle) -> int:
|
||||
rl.clear_background(rl.Color(224, 224, 224, 255))
|
||||
@@ -160,12 +160,11 @@ class PairingDialog(Widget):
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
gui_app.init_window("pairing device")
|
||||
gui_app.init_window("pairing device", new_modal=True)
|
||||
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
|
||||
|
||||
@@ -15,7 +15,6 @@ class SetupWidget(Widget):
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
self._open_settings_callback = None
|
||||
self._pairing_dialog: PairingDialog | None = None
|
||||
self._pair_device_btn = Button(lambda: tr("Pair device"), self._show_pairing, button_style=ButtonStyle.PRIMARY)
|
||||
self._open_settings_btn = Button(lambda: tr("Open"), lambda: self._open_settings_callback() if self._open_settings_callback else None,
|
||||
button_style=ButtonStyle.PRIMARY)
|
||||
@@ -86,16 +85,11 @@ class SetupWidget(Widget):
|
||||
button_rect = rl.Rectangle(x, y, w, button_height)
|
||||
self._open_settings_btn.render(button_rect)
|
||||
|
||||
def _show_pairing(self):
|
||||
@staticmethod
|
||||
def _show_pairing():
|
||||
if not system_time_valid():
|
||||
dlg = alert_dialog(tr("Please connect to Wi-Fi to complete initial pairing"))
|
||||
gui_app.set_modal_overlay(dlg)
|
||||
gui_app.push_widget(dlg)
|
||||
return
|
||||
|
||||
if not self._pairing_dialog:
|
||||
self._pairing_dialog = PairingDialog()
|
||||
gui_app.set_modal_overlay(self._pairing_dialog, lambda result: setattr(self, '_pairing_dialog', None))
|
||||
|
||||
def __del__(self):
|
||||
if self._pairing_dialog:
|
||||
del self._pairing_dialog
|
||||
gui_app.push_widget(PairingDialog())
|
||||
|
||||
@@ -59,7 +59,7 @@ class SshKeyAction(ItemAction):
|
||||
# Show error dialog if there's an error
|
||||
if self._error_message:
|
||||
message = copy.copy(self._error_message)
|
||||
gui_app.set_modal_overlay(alert_dialog(message))
|
||||
gui_app.push_widget(alert_dialog(message))
|
||||
self._username = ""
|
||||
self._error_message = ""
|
||||
|
||||
@@ -87,7 +87,8 @@ class SshKeyAction(ItemAction):
|
||||
if self._state == SshKeyActionState.ADD:
|
||||
self._keyboard.reset()
|
||||
self._keyboard.set_title(tr("Enter your GitHub username"))
|
||||
gui_app.set_modal_overlay(self._keyboard, callback=self._on_username_submit)
|
||||
self._keyboard.set_callback(self._on_username_submit)
|
||||
gui_app.push_widget(self._keyboard)
|
||||
elif self._state == SshKeyActionState.REMOVE:
|
||||
self._params.remove("GithubUsername")
|
||||
self._params.remove("GithubSshKeys")
|
||||
|
||||
@@ -230,6 +230,10 @@ class GuiApplication:
|
||||
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._mouse = MouseState(self._scale)
|
||||
self._mouse_events: list[MouseEvent] = []
|
||||
self._last_mouse_event: MouseEvent = MouseEvent(MousePos(0, 0), 0, False, False, False, 0.0)
|
||||
@@ -262,7 +266,7 @@ class GuiApplication:
|
||||
def request_close(self):
|
||||
self._window_close_requested = True
|
||||
|
||||
def init_window(self, title: str, fps: int = _DEFAULT_FPS):
|
||||
def init_window(self, title: str, fps: int = _DEFAULT_FPS, new_modal: bool = False):
|
||||
with self._startup_profile_context():
|
||||
def _close(sig, frame):
|
||||
self.close()
|
||||
@@ -270,6 +274,8 @@ 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
|
||||
@@ -373,7 +379,34 @@ class GuiApplication:
|
||||
except Exception:
|
||||
break
|
||||
|
||||
def push_widget(self, widget: object):
|
||||
assert self._new_modal
|
||||
|
||||
# disable previous widget to prevent input processing
|
||||
if len(self._nav_stack) > 0:
|
||||
prev_widget = self._nav_stack[-1]
|
||||
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")
|
||||
return
|
||||
|
||||
# re-enable previous widget and pop current
|
||||
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"
|
||||
|
||||
if self._modal_overlay.overlay is not None:
|
||||
if hasattr(self._modal_overlay.overlay, 'hide_event'):
|
||||
self._modal_overlay.overlay.hide_event()
|
||||
@@ -528,15 +561,24 @@ class GuiApplication:
|
||||
rl.begin_drawing()
|
||||
rl.clear_background(rl.BLACK)
|
||||
|
||||
# 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:
|
||||
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))
|
||||
|
||||
# Yield to allow caller to run non-rendering related code
|
||||
yield True
|
||||
|
||||
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
|
||||
|
||||
if self._render_texture:
|
||||
rl.end_texture_mode()
|
||||
rl.begin_drawing()
|
||||
|
||||
@@ -36,13 +36,9 @@ class Reset(Widget):
|
||||
self._mode = mode
|
||||
self._previous_reset_state = None
|
||||
self._reset_state = ResetState.NONE
|
||||
self._cancel_button = Button("Cancel", self._cancel_callback)
|
||||
self._cancel_button = Button("Cancel", gui_app.request_close)
|
||||
self._confirm_button = Button("Confirm", self._confirm, button_style=ButtonStyle.PRIMARY)
|
||||
self._reboot_button = Button("Reboot", lambda: os.system("sudo reboot"))
|
||||
self._render_status = True
|
||||
|
||||
def _cancel_callback(self):
|
||||
self._render_status = False
|
||||
|
||||
def _do_erase(self):
|
||||
if PC:
|
||||
@@ -69,30 +65,30 @@ class Reset(Widget):
|
||||
elif self._reset_state != ResetState.RESETTING and (time.monotonic() - self._timeout_st) > TIMEOUT:
|
||||
exit(0)
|
||||
|
||||
def _render(self, rect: rl.Rectangle):
|
||||
label_rect = rl.Rectangle(rect.x + 140, rect.y, rect.width - 280, 100 * FONT_SCALE)
|
||||
def _render(self, _):
|
||||
content_rect = rl.Rectangle(45, 200, self._rect.width - 90, self._rect.height - 245)
|
||||
|
||||
label_rect = rl.Rectangle(content_rect.x + 140, content_rect.y, content_rect.width - 280, 100 * FONT_SCALE)
|
||||
gui_label(label_rect, "System Reset", 100, font_weight=FontWeight.BOLD)
|
||||
|
||||
text_rect = rl.Rectangle(rect.x + 140, rect.y + 140, rect.width - 280, rect.height - 90 - 100 * FONT_SCALE)
|
||||
text_rect = rl.Rectangle(content_rect.x + 140, content_rect.y + 140, content_rect.width - 280, content_rect.height - 90 - 100 * FONT_SCALE)
|
||||
gui_text_box(text_rect, self._get_body_text(), 90)
|
||||
|
||||
button_height = 160
|
||||
button_spacing = 50
|
||||
button_top = rect.y + rect.height - button_height
|
||||
button_width = (rect.width - button_spacing) / 2.0
|
||||
button_top = content_rect.y + content_rect.height - button_height
|
||||
button_width = (content_rect.width - button_spacing) / 2.0
|
||||
|
||||
if self._reset_state != ResetState.RESETTING:
|
||||
if self._mode == ResetMode.RECOVER:
|
||||
self._reboot_button.render(rl.Rectangle(rect.x, button_top, button_width, button_height))
|
||||
self._reboot_button.render(rl.Rectangle(content_rect.x, button_top, button_width, button_height))
|
||||
elif self._mode == ResetMode.USER_RESET:
|
||||
self._cancel_button.render(rl.Rectangle(rect.x, button_top, button_width, button_height))
|
||||
self._cancel_button.render(rl.Rectangle(content_rect.x, button_top, button_width, button_height))
|
||||
|
||||
if self._reset_state != ResetState.FAILED:
|
||||
self._confirm_button.render(rl.Rectangle(rect.x + button_width + 50, button_top, button_width, button_height))
|
||||
self._confirm_button.render(rl.Rectangle(content_rect.x + button_width + 50, button_top, button_width, button_height))
|
||||
else:
|
||||
self._reboot_button.render(rl.Rectangle(rect.x, button_top, rect.width, button_height))
|
||||
|
||||
return self._render_status
|
||||
self._reboot_button.render(rl.Rectangle(content_rect.x, button_top, content_rect.width, button_height))
|
||||
|
||||
def _confirm(self):
|
||||
if self._reset_state == ResetState.CONFIRM:
|
||||
@@ -120,16 +116,16 @@ def main():
|
||||
elif sys.argv[1] == "--format":
|
||||
mode = ResetMode.FORMAT
|
||||
|
||||
gui_app.init_window("System Reset", 20)
|
||||
gui_app.init_window("System Reset", 20, new_modal=True)
|
||||
reset = Reset(mode)
|
||||
|
||||
if mode == ResetMode.FORMAT:
|
||||
reset.start_reset()
|
||||
|
||||
for should_render in gui_app.render():
|
||||
if should_render:
|
||||
if not reset.render(rl.Rectangle(45, 200, gui_app.width - 90, gui_app.height - 245)):
|
||||
break
|
||||
gui_app.push_widget(reset)
|
||||
|
||||
for _ in gui_app.render():
|
||||
pass
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
||||
@@ -16,7 +16,7 @@ from openpilot.common.utils import run_cmd
|
||||
from openpilot.system.hardware import HARDWARE
|
||||
from openpilot.system.ui.lib.scroll_panel import GuiScrollPanel
|
||||
from openpilot.system.ui.lib.application import gui_app, FontWeight, FONT_SCALE
|
||||
from openpilot.system.ui.widgets import Widget
|
||||
from openpilot.system.ui.widgets import DialogResult, Widget
|
||||
from openpilot.system.ui.widgets.button import Button, ButtonStyle, ButtonRadio
|
||||
from openpilot.system.ui.widgets.keyboard import Keyboard
|
||||
from openpilot.system.ui.widgets.label import Label
|
||||
@@ -327,19 +327,20 @@ class Setup(Widget):
|
||||
def render_custom_software(self):
|
||||
def handle_keyboard_result(result):
|
||||
# Enter pressed
|
||||
if result == 1:
|
||||
if result == DialogResult.CONFIRM:
|
||||
url = self.keyboard.text
|
||||
self.keyboard.clear()
|
||||
if url:
|
||||
self.download(url)
|
||||
|
||||
# Cancel pressed
|
||||
elif result == 0:
|
||||
elif result == DialogResult.CANCEL:
|
||||
self.state = SetupState.SOFTWARE_SELECTION
|
||||
|
||||
self.keyboard.reset(min_text_size=1)
|
||||
self.keyboard.set_title("Enter URL", "for Custom Software")
|
||||
gui_app.set_modal_overlay(self.keyboard, callback=handle_keyboard_result)
|
||||
self.keyboard.set_callback(handle_keyboard_result)
|
||||
gui_app.push_widget(self.keyboard)
|
||||
|
||||
def use_openpilot(self):
|
||||
if os.path.isdir(INSTALL_PATH) and os.path.isfile(VALID_CACHE_PATH):
|
||||
@@ -435,11 +436,11 @@ class Setup(Widget):
|
||||
|
||||
def main():
|
||||
try:
|
||||
gui_app.init_window("Setup", 20)
|
||||
gui_app.init_window("Setup", 20, new_modal=True)
|
||||
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}")
|
||||
|
||||
@@ -160,11 +160,10 @@ def main():
|
||||
manifest_path = sys.argv[2]
|
||||
|
||||
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.init_window("System Update", new_modal=True)
|
||||
gui_app.push_widget(Updater(updater_path, manifest_path))
|
||||
for _ in gui_app.render():
|
||||
pass
|
||||
finally:
|
||||
# Make sure we clean up even if there's an error
|
||||
gui_app.close()
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import pyray as rl
|
||||
from collections.abc import Callable
|
||||
from openpilot.system.ui.lib.application import gui_app, FontWeight
|
||||
from openpilot.system.ui.lib.multilang import tr
|
||||
from openpilot.system.ui.widgets import DialogResult
|
||||
@@ -17,7 +18,7 @@ BACKGROUND_COLOR = rl.Color(27, 27, 27, 255)
|
||||
|
||||
|
||||
class ConfirmDialog(Widget):
|
||||
def __init__(self, text: str, confirm_text: str, cancel_text: str | None = None, rich: bool = False):
|
||||
def __init__(self, text: str, confirm_text: str, cancel_text: str | None = None, rich: bool = False, callback: Callable[[DialogResult], None] | None = None):
|
||||
super().__init__()
|
||||
if cancel_text is None:
|
||||
cancel_text = tr("Cancel")
|
||||
@@ -26,7 +27,7 @@ class ConfirmDialog(Widget):
|
||||
self._cancel_button = Button(cancel_text, self._cancel_button_callback)
|
||||
self._confirm_button = Button(confirm_text, self._confirm_button_callback, button_style=ButtonStyle.PRIMARY)
|
||||
self._rich = rich
|
||||
self._dialog_result = DialogResult.NO_ACTION
|
||||
self._callback = callback
|
||||
self._cancel_text = cancel_text
|
||||
self._scroller = Scroller([self._html_renderer], line_separator=False, spacing=0)
|
||||
|
||||
@@ -36,14 +37,15 @@ class ConfirmDialog(Widget):
|
||||
else:
|
||||
self._html_renderer.parse_html_content(text)
|
||||
|
||||
def reset(self):
|
||||
self._dialog_result = DialogResult.NO_ACTION
|
||||
|
||||
def _cancel_button_callback(self):
|
||||
self._dialog_result = DialogResult.CANCEL
|
||||
gui_app.pop_widget()
|
||||
if self._callback:
|
||||
self._callback(DialogResult.CANCEL)
|
||||
|
||||
def _confirm_button_callback(self):
|
||||
self._dialog_result = DialogResult.CONFIRM
|
||||
gui_app.pop_widget()
|
||||
if self._callback:
|
||||
self._callback(DialogResult.CONFIRM)
|
||||
|
||||
def _render(self, rect: rl.Rectangle):
|
||||
dialog_x = OUTER_MARGIN if not self._rich else RICH_OUTER_MARGIN
|
||||
@@ -73,9 +75,9 @@ class ConfirmDialog(Widget):
|
||||
self._scroller.render(text_rect)
|
||||
|
||||
if rl.is_key_pressed(rl.KeyboardKey.KEY_ENTER):
|
||||
self._dialog_result = DialogResult.CONFIRM
|
||||
self._confirm_button_callback()
|
||||
elif rl.is_key_pressed(rl.KeyboardKey.KEY_ESCAPE):
|
||||
self._dialog_result = DialogResult.CANCEL
|
||||
self._cancel_button_callback()
|
||||
|
||||
if self._cancel_text:
|
||||
self._confirm_button.render(confirm_button)
|
||||
@@ -85,8 +87,6 @@ class ConfirmDialog(Widget):
|
||||
full_confirm_button = rl.Rectangle(dialog_rect.x + MARGIN, button_y, full_button_width, BUTTON_HEIGHT)
|
||||
self._confirm_button.render(full_confirm_button)
|
||||
|
||||
return self._dialog_result
|
||||
|
||||
|
||||
def alert_dialog(message: str, button_text: str | None = None):
|
||||
if button_text is None:
|
||||
|
||||
@@ -260,7 +260,7 @@ class HtmlModal(Widget):
|
||||
super().__init__()
|
||||
self._content = HtmlRenderer(file_path=file_path, text=text)
|
||||
self._scroll_panel = GuiScrollPanel()
|
||||
self._ok_button = Button(tr("OK"), click_callback=lambda: gui_app.set_modal_overlay(None), button_style=ButtonStyle.PRIMARY)
|
||||
self._ok_button = Button(tr("OK"), click_callback=gui_app.pop_widget, button_style=ButtonStyle.PRIMARY)
|
||||
|
||||
def _render(self, rect: rl.Rectangle):
|
||||
margin = 50
|
||||
|
||||
@@ -1,12 +1,13 @@
|
||||
from functools import partial
|
||||
import time
|
||||
from typing import Literal
|
||||
from collections.abc import Callable
|
||||
|
||||
import pyray as rl
|
||||
|
||||
from openpilot.system.ui.lib.application import gui_app, FontWeight
|
||||
from openpilot.system.ui.lib.multilang import tr
|
||||
from openpilot.system.ui.widgets import Widget
|
||||
from openpilot.system.ui.widgets import DialogResult, Widget
|
||||
from openpilot.system.ui.widgets.button import ButtonStyle, Button
|
||||
from openpilot.system.ui.widgets.inputbox import InputBox
|
||||
from openpilot.system.ui.widgets.label import Label
|
||||
@@ -58,7 +59,8 @@ KEYBOARD_LAYOUTS = {
|
||||
|
||||
|
||||
class Keyboard(Widget):
|
||||
def __init__(self, max_text_size: int = 255, min_text_size: int = 0, password_mode: bool = False, show_password_toggle: bool = False):
|
||||
def __init__(self, max_text_size: int = 255, min_text_size: int = 0, password_mode: bool = False, show_password_toggle: bool = False,
|
||||
callback: Callable[[DialogResult], None] | None = None):
|
||||
super().__init__()
|
||||
self._layout_name: Literal["lowercase", "uppercase", "numbers", "specials"] = "lowercase"
|
||||
self._caps_lock = False
|
||||
@@ -71,13 +73,13 @@ class Keyboard(Widget):
|
||||
self._input_box = InputBox(max_text_size)
|
||||
self._password_mode = password_mode
|
||||
self._show_password_toggle = show_password_toggle
|
||||
self._callback = callback
|
||||
|
||||
# Backspace key repeat tracking
|
||||
self._backspace_pressed: bool = False
|
||||
self._backspace_press_time: float = 0.0
|
||||
self._backspace_last_repeat: float = 0.0
|
||||
|
||||
self._render_return_status = -1
|
||||
self._cancel_button = Button(lambda: tr("Cancel"), self._cancel_button_callback)
|
||||
|
||||
self._eye_button = Button("", self._eye_button_callback, button_style=ButtonStyle.TRANSPARENT)
|
||||
@@ -122,16 +124,23 @@ class Keyboard(Widget):
|
||||
self._title.set_text(title)
|
||||
self._sub_title.set_text(sub_title)
|
||||
|
||||
def set_callback(self, callback: Callable[[DialogResult], None] | None):
|
||||
self._callback = callback
|
||||
|
||||
def _eye_button_callback(self):
|
||||
self._password_mode = not self._password_mode
|
||||
|
||||
def _cancel_button_callback(self):
|
||||
self.clear()
|
||||
self._render_return_status = 0
|
||||
gui_app.pop_widget()
|
||||
if self._callback:
|
||||
self._callback(DialogResult.CANCEL)
|
||||
|
||||
def _key_callback(self, k):
|
||||
if k == ENTER_KEY:
|
||||
self._render_return_status = 1
|
||||
gui_app.pop_widget()
|
||||
if self._callback:
|
||||
self._callback(DialogResult.CONFIRM)
|
||||
else:
|
||||
self.handle_key_press(k)
|
||||
|
||||
@@ -197,8 +206,6 @@ class Keyboard(Widget):
|
||||
self._all_keys[key].set_enabled(is_enabled)
|
||||
self._all_keys[key].render(key_rect)
|
||||
|
||||
return self._render_return_status
|
||||
|
||||
def _render_input_area(self, input_rect: rl.Rectangle):
|
||||
if self._show_password_toggle:
|
||||
self._input_box.set_password_mode(self._password_mode)
|
||||
@@ -250,7 +257,6 @@ class Keyboard(Widget):
|
||||
def reset(self, min_text_size: int | None = None):
|
||||
if min_text_size is not None:
|
||||
self._min_text_size = min_text_size
|
||||
self._render_return_status = -1
|
||||
self._last_shift_press_time = 0
|
||||
self._backspace_pressed = False
|
||||
self._backspace_press_time = 0.0
|
||||
@@ -259,15 +265,18 @@ class Keyboard(Widget):
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
gui_app.init_window("Keyboard")
|
||||
keyboard = Keyboard(min_text_size=8, show_password_toggle=True)
|
||||
for _ in gui_app.render():
|
||||
keyboard.set_title("Keyboard Input", "Type your text below")
|
||||
result = keyboard.render(rl.Rectangle(0, 0, gui_app.width, gui_app.height))
|
||||
if result == 1:
|
||||
def callback(result: DialogResult):
|
||||
if result == DialogResult.CONFIRM:
|
||||
print(f"You typed: {keyboard.text}")
|
||||
gui_app.request_close()
|
||||
elif result == 0:
|
||||
elif result == DialogResult.CANCEL:
|
||||
print("Canceled")
|
||||
gui_app.request_close()
|
||||
gui_app.request_close()
|
||||
|
||||
gui_app.init_window("Keyboard", new_modal=True)
|
||||
keyboard = Keyboard(min_text_size=8, show_password_toggle=True, callback=callback)
|
||||
keyboard.set_title("Keyboard Input", "Type your text below")
|
||||
|
||||
gui_app.push_widget(keyboard)
|
||||
for _ in gui_app.render():
|
||||
pass
|
||||
gui_app.close()
|
||||
|
||||
@@ -7,7 +7,7 @@ from openpilot.system.ui.lib.application import gui_app
|
||||
from openpilot.system.ui.lib.multilang import tr
|
||||
from openpilot.system.ui.lib.scroll_panel import GuiScrollPanel
|
||||
from openpilot.system.ui.lib.wifi_manager import WifiManager, SecurityType, Network, MeteredType, normalize_ssid
|
||||
from openpilot.system.ui.widgets import Widget
|
||||
from openpilot.system.ui.widgets import DialogResult, Widget
|
||||
from openpilot.system.ui.widgets.button import ButtonStyle, Button
|
||||
from openpilot.system.ui.widgets.confirm_dialog import ConfirmDialog
|
||||
from openpilot.system.ui.widgets.keyboard import Keyboard
|
||||
@@ -187,8 +187,8 @@ class AdvancedNetworkSettings(Widget):
|
||||
self._wifi_manager.update_gsm_settings(roaming_state, self._params.get("GsmApn") or "", self._params.get_bool("GsmMetered"))
|
||||
|
||||
def _edit_apn(self):
|
||||
def update_apn(result):
|
||||
if result != 1:
|
||||
def update_apn(result: DialogResult):
|
||||
if result != DialogResult.CONFIRM:
|
||||
return
|
||||
|
||||
apn = self._keyboard.text.strip()
|
||||
@@ -203,7 +203,8 @@ class AdvancedNetworkSettings(Widget):
|
||||
self._keyboard.reset(min_text_size=0)
|
||||
self._keyboard.set_title(tr("Enter APN"), tr("leave blank for automatic configuration"))
|
||||
self._keyboard.set_text(current_apn)
|
||||
gui_app.set_modal_overlay(self._keyboard, update_apn)
|
||||
self._keyboard.set_callback(update_apn)
|
||||
gui_app.push_widget(self._keyboard)
|
||||
|
||||
def _toggle_cellular_metered(self):
|
||||
metered = self._cellular_metered_action.get_state()
|
||||
@@ -216,15 +217,18 @@ class AdvancedNetworkSettings(Widget):
|
||||
self._wifi_manager.set_current_network_metered(metered_type)
|
||||
|
||||
def _connect_to_hidden_network(self):
|
||||
def connect_hidden(result):
|
||||
if result != 1:
|
||||
def connect_hidden(result: DialogResult):
|
||||
if result != DialogResult.CONFIRM:
|
||||
return
|
||||
|
||||
ssid = self._keyboard.text
|
||||
if not ssid:
|
||||
return
|
||||
|
||||
def enter_password(result):
|
||||
def enter_password(result: DialogResult):
|
||||
if result != DialogResult.CONFIRM:
|
||||
return
|
||||
|
||||
password = self._keyboard.text
|
||||
if password == "":
|
||||
# connect without password
|
||||
@@ -235,15 +239,17 @@ class AdvancedNetworkSettings(Widget):
|
||||
|
||||
self._keyboard.reset(min_text_size=0)
|
||||
self._keyboard.set_title(tr("Enter password"), tr("for \"{}\"").format(ssid))
|
||||
gui_app.set_modal_overlay(self._keyboard, enter_password)
|
||||
self._keyboard.set_callback(enter_password)
|
||||
gui_app.push_widget(self._keyboard)
|
||||
|
||||
self._keyboard.reset(min_text_size=1)
|
||||
self._keyboard.set_title(tr("Enter SSID"), "")
|
||||
gui_app.set_modal_overlay(self._keyboard, connect_hidden)
|
||||
self._keyboard.set_callback(connect_hidden)
|
||||
gui_app.push_widget(self._keyboard)
|
||||
|
||||
def _edit_tethering_password(self):
|
||||
def update_password(result):
|
||||
if result != 1:
|
||||
def update_password(result: DialogResult):
|
||||
if result != DialogResult.CONFIRM:
|
||||
return
|
||||
|
||||
password = self._keyboard.text
|
||||
@@ -253,7 +259,8 @@ class AdvancedNetworkSettings(Widget):
|
||||
self._keyboard.reset(min_text_size=MIN_PASSWORD_LENGTH)
|
||||
self._keyboard.set_title(tr("Enter new tethering password"), "")
|
||||
self._keyboard.set_text(self._wifi_manager.tethering_password)
|
||||
gui_app.set_modal_overlay(self._keyboard, update_password)
|
||||
self._keyboard.set_callback(update_password)
|
||||
gui_app.push_widget(self._keyboard)
|
||||
|
||||
def _update_state(self):
|
||||
self._wifi_manager.process_callbacks()
|
||||
@@ -314,29 +321,29 @@ class WifiManagerUI(Widget):
|
||||
self.keyboard.set_title(tr("Wrong password") if self._password_retry else tr("Enter password"),
|
||||
tr("for \"{}\"").format(normalize_ssid(self._state_network.ssid)))
|
||||
self.keyboard.reset(min_text_size=MIN_PASSWORD_LENGTH)
|
||||
gui_app.set_modal_overlay(self.keyboard, lambda result: self._on_password_entered(cast(Network, self._state_network), result))
|
||||
self.keyboard.set_callback(lambda result: self._on_password_entered(cast(Network, self._state_network), result))
|
||||
gui_app.push_widget(self.keyboard)
|
||||
elif self.state == UIState.SHOW_FORGET_CONFIRM and self._state_network:
|
||||
confirm_dialog = ConfirmDialog("", tr("Forget"), tr("Cancel"))
|
||||
confirm_dialog = ConfirmDialog("", tr("Forget"), tr("Cancel"), callback=lambda result: self.on_forgot_confirm_finished(self._state_network, result))
|
||||
confirm_dialog.set_text(tr("Forget Wi-Fi Network \"{}\"?").format(normalize_ssid(self._state_network.ssid)))
|
||||
confirm_dialog.reset()
|
||||
gui_app.set_modal_overlay(confirm_dialog, callback=lambda result: self.on_forgot_confirm_finished(self._state_network, result))
|
||||
gui_app.push_widget(confirm_dialog)
|
||||
else:
|
||||
self._draw_network_list(rect)
|
||||
|
||||
def _on_password_entered(self, network: Network, result: int):
|
||||
if result == 1:
|
||||
def _on_password_entered(self, network: Network, result: DialogResult):
|
||||
if result == DialogResult.CONFIRM:
|
||||
password = self.keyboard.text
|
||||
self.keyboard.clear()
|
||||
|
||||
if len(password) >= MIN_PASSWORD_LENGTH:
|
||||
self.connect_to_network(network, password)
|
||||
elif result == 0:
|
||||
elif result == DialogResult.CANCEL:
|
||||
self.state = UIState.IDLE
|
||||
|
||||
def on_forgot_confirm_finished(self, network, result: int):
|
||||
if result == 1:
|
||||
def on_forgot_confirm_finished(self, network, result: DialogResult):
|
||||
if result == DialogResult.CONFIRM:
|
||||
self.forget_network(network)
|
||||
elif result == 0:
|
||||
elif result == DialogResult.CANCEL:
|
||||
self.state = UIState.IDLE
|
||||
|
||||
def _draw_network_list(self, rect: rl.Rectangle):
|
||||
@@ -474,11 +481,11 @@ class WifiManagerUI(Widget):
|
||||
|
||||
|
||||
def main():
|
||||
gui_app.init_window("Wi-Fi Manager")
|
||||
wifi_ui = WifiManagerUI(WifiManager())
|
||||
gui_app.init_window("Wi-Fi Manager", new_modal=True)
|
||||
gui_app.push_widget(WifiManagerUI(WifiManager()))
|
||||
|
||||
for _ in gui_app.render():
|
||||
wifi_ui.render(rl.Rectangle(50, 50, gui_app.width - 100, gui_app.height - 100))
|
||||
pass
|
||||
|
||||
gui_app.close()
|
||||
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import pyray as rl
|
||||
from openpilot.system.ui.lib.application import FontWeight
|
||||
from collections.abc import Callable
|
||||
from openpilot.system.ui.lib.application import gui_app, FontWeight
|
||||
from openpilot.system.ui.lib.multilang import tr
|
||||
from openpilot.system.ui.widgets import Widget, DialogResult
|
||||
from openpilot.system.ui.widgets.button import Button, ButtonStyle
|
||||
@@ -17,13 +18,13 @@ LIST_ITEM_SPACING = 25
|
||||
|
||||
|
||||
class MultiOptionDialog(Widget):
|
||||
def __init__(self, title, options, current="", option_font_weight=FontWeight.MEDIUM):
|
||||
def __init__(self, title, options, current="", option_font_weight=FontWeight.MEDIUM, callback: Callable[[DialogResult], None] | None = None):
|
||||
super().__init__()
|
||||
self.title = title
|
||||
self.options = options
|
||||
self.current = current
|
||||
self.selection = current
|
||||
self._result: DialogResult = DialogResult.NO_ACTION
|
||||
self._callback = callback
|
||||
|
||||
# Create scroller with option buttons
|
||||
self.option_buttons = [Button(option, click_callback=lambda opt=option: self._on_option_clicked(opt),
|
||||
@@ -36,7 +37,9 @@ class MultiOptionDialog(Widget):
|
||||
self.select_button = Button(lambda: tr("Select"), click_callback=lambda: self._set_result(DialogResult.CONFIRM), button_style=ButtonStyle.PRIMARY)
|
||||
|
||||
def _set_result(self, result: DialogResult):
|
||||
self._result = result
|
||||
gui_app.pop_widget()
|
||||
if self._callback:
|
||||
self._callback(result)
|
||||
|
||||
def _on_option_clicked(self, option):
|
||||
self.selection = option
|
||||
@@ -74,5 +77,3 @@ class MultiOptionDialog(Widget):
|
||||
select_rect = rl.Rectangle(content_rect.x + button_w + BUTTON_SPACING, button_y, button_w, BUTTON_HEIGHT)
|
||||
self.select_button.set_enabled(self.selection != self.current)
|
||||
self.select_button.render(select_rect)
|
||||
|
||||
return self._result
|
||||
|
||||
Reference in New Issue
Block a user