Setup: improvements (#37264)
* pressed state for larger sliders
* wifibutton
* fix
* clean up
* some work
* don't nee this now
* stash
* more
* new pressed bigcircle
* black
* interp
* just check position
* clean up and fix slider reset
* fix custom
* no speed
* stash
* even chatter couldn't figure this one out
* makes sense to combine together, less split mentality
* clean that up
* fix lag
* match ui.py prio to eliminate lag on wifiui show event. separately, why is this slow?
* night mode
* delay scroll over
* fix auto scrolling
* stash
* waiting looks disabled
* clean up and don't reset sliders until user goes back
* rm
* fix
* add termsheader back
* fix callbacks
* ctrl alt l
* fix text spacing
* clean up
* stash
* fix style
* i want to go back
* guard on exit
* kinda useless stuff
* Revert "kinda useless stuff"
This reverts commit a4acbac31523408f358c5f68262cb630aa13ad8e.
* Revert "guard on exit"
This reverts commit 63ccfbf64edfbe1a144a441681f5ec78d8021ff7.
* wide
* setup pressed!
* grow animation
* 10s after initial
* slow fast
* start onboarding (terms)
* rm duplicate page
* add qr code
* final grey
* fix visual lag on first start
* clean up dead code
* dont exit from cancel
* revert grey
* clean up, REVIEW ME
* Revert "clean up, REVIEW ME"
This reverts commit c66fa60947c5f922520e7cf58c630b4bbe2d0177.
* reboot slider
* kb fix
* Revert "kb fix"
This reverts commit 883039448e6c37ae1d25d4f75ada6e96b6736358.
* ./ goes to letters
* Revert "./ goes to letters"
This reverts commit 0d97442427edb1a000638863a3f2181204ddc160.
* clean up
* some more clean up
* more
* clean up
* rename block
* reset pending scroll so it can't use stale data in rare sequence
* remove unused assets
* clean up imports
* fix updater
* clean up
* fix double reboot
* demo time - reset to setup on reboot
* let manager restart
* Revert "demo time - reset to setup on reboot"
This reverts commit 9468657e8438a1ce8fcb5266403b7bb3539f131f.
* url... and no grow animation on start button
* one next button
* grow instead of shake wifi button
* 36 pt font size in setup
* touch up onboarding a lil
* Revert "rm cpp bz2 (#37332)"
This reverts commit f4a36f7f74.
* more onboarding and clean up
* clean up
* wow what an amazing future clean up
* back to software select
* fix
* copy
* fix dm confirmation dialog not disabling widget underneath, all fixed with real nav stack in here
* uploading
* lint
* add review terms to device w/ close button
* todo
* remove old Terms vertical scrolling classes
* use new Scroller!
* installer
* tweak to match figma exactly
* revert
* fixup updater
* demo day
* demo day v2
* ... for percent while finishing setup
* demo day v3
* demo day v4
* remove ...
* demo day v6 -- "why does it do that!!"
* demo day v7 -- no flash
* hmm
* demo day v7
* prebuilt
* revert demo day
* scroll after pop animation
* back -> retry
* stash fixes
* damn, need back_callback
* scroll over immediately if already in network setup
* tweaks
* going down is confusing
* more
* Revert "more"
This reverts commit 29ce75b1f81eb40e7527a71d27842d9a66802206.
* Revert "going down is confusing"
This reverts commit 0cd2ae30d4135db1ccba6478429b45e886714e9c.
* dupl
* nl
* sort functions
* more clean up from merge
* move
* more
* dismiss to download (hack)
* Revert "dismiss to download (hack)"
This reverts commit 53c45ed1f63db1f0cebbce0dfab1777c8658f505.
* onboarding work
* set brightness and timeout in root onboarding only
* clean up
* type
* keep 5m for settings preview
* switch back to letters on . or /
* reset first step scroller
* custom software warning goes down network comes up and back cb fix
* clean up
* smaller qr
* ReviewTermsPage just for device as NavWidget
* clean up
* installer: stay on 100%
* reset has internet while in wifiui
* try this
* try this
* see what error we get exactly
see what error we get exactly
* not final solution but see how good
* rm
* copy changes
* reset on disconnect
* for separate pr
* Revert "reset on disconnect"
This reverts commit 552372fa4d497ba7d9de7f2edb730ee63798ffa4.
* revert this, too buggy
* fix for updater
* sort
* fix test
* minor cleanup
* more leaks than this rn
* onboarding clean up
* clean up application
* click delay to small button
* clean up
* reset more state
* fix training guide not cleaning up driverview
* Revert "fix training guide not cleaning up driverview"
This reverts commit cac7c5f436056cc9e747f80905d390790fb83c22.
* simpler fix :(
* nice catch, if you go back to terms it will reset 300s timeout and brightness
* duplicate show
* unused
This commit is contained in:
BIN
selfdrive/assets/icons_mici/setup/cancel.png
LFS
Normal file
BIN
selfdrive/assets/icons_mici/setup/cancel.png
LFS
Normal file
Binary file not shown.
BIN
selfdrive/assets/icons_mici/setup/continue.png
LFS
Normal file
BIN
selfdrive/assets/icons_mici/setup/continue.png
LFS
Normal file
Binary file not shown.
BIN
selfdrive/assets/icons_mici/setup/continue_disabled.png
LFS
Normal file
BIN
selfdrive/assets/icons_mici/setup/continue_disabled.png
LFS
Normal file
Binary file not shown.
BIN
selfdrive/assets/icons_mici/setup/continue_pressed.png
LFS
Normal file
BIN
selfdrive/assets/icons_mici/setup/continue_pressed.png
LFS
Normal file
Binary file not shown.
Binary file not shown.
@@ -81,16 +81,14 @@ void run(const char* cmd) {
|
||||
}
|
||||
|
||||
void finishInstall() {
|
||||
BeginDrawing();
|
||||
ClearBackground(BLACK);
|
||||
if (tici_device) {
|
||||
if (tici_device) {
|
||||
BeginDrawing();
|
||||
ClearBackground(BLACK);
|
||||
const char *m = "Finishing install...";
|
||||
int text_width = MeasureText(m, FONT_SIZE);
|
||||
DrawTextEx(font_display, m, (Vector2){(float)(GetScreenWidth() - text_width)/2 + FONT_SIZE, (float)(GetScreenHeight() - FONT_SIZE)/2}, FONT_SIZE, 0, WHITE);
|
||||
} else {
|
||||
DrawTextEx(font_display, "finishing setup", (Vector2){12, 0}, 77, 0, (Color){255, 255, 255, (unsigned char)(255 * 0.9)});
|
||||
}
|
||||
EndDrawing();
|
||||
EndDrawing();
|
||||
}
|
||||
util::sleep_for(60 * 1000);
|
||||
}
|
||||
|
||||
|
||||
@@ -53,7 +53,7 @@ class MiciMainLayout(Scroller):
|
||||
gui_app.push_widget(self)
|
||||
|
||||
# Start onboarding if terms or training not completed, make sure to push after self
|
||||
self._onboarding_window = OnboardingWindow()
|
||||
self._onboarding_window = OnboardingWindow(lambda: gui_app.pop_widgets_to(self))
|
||||
if not self._onboarding_window.completed:
|
||||
gui_app.push_widget(self._onboarding_window)
|
||||
|
||||
@@ -79,7 +79,7 @@ class MiciMainLayout(Scroller):
|
||||
|
||||
def _handle_transitions(self):
|
||||
# Don't pop if onboarding
|
||||
if gui_app.get_active_widget() == self._onboarding_window:
|
||||
if gui_app.widget_in_stack(self._onboarding_window):
|
||||
return
|
||||
|
||||
if ui_state.started != self._prev_onroad:
|
||||
@@ -105,7 +105,7 @@ class MiciMainLayout(Scroller):
|
||||
|
||||
def _on_interactive_timeout(self):
|
||||
# Don't pop if onboarding
|
||||
if gui_app.get_active_widget() == self._onboarding_window:
|
||||
if gui_app.widget_in_stack(self._onboarding_window):
|
||||
return
|
||||
|
||||
if ui_state.started:
|
||||
|
||||
@@ -1,29 +1,23 @@
|
||||
from enum import IntEnum
|
||||
|
||||
import weakref
|
||||
import math
|
||||
import numpy as np
|
||||
import qrcode
|
||||
import pyray as rl
|
||||
from collections.abc import Callable
|
||||
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.button import SmallButton, SmallCircleIconButton
|
||||
from openpilot.system.ui.widgets.label import UnifiedLabel
|
||||
from openpilot.system.ui.widgets.slider import SmallSlider
|
||||
from openpilot.system.ui.mici_setup import TermsHeader, TermsPage as SetupTermsPage
|
||||
from openpilot.selfdrive.ui.ui_state import ui_state, device
|
||||
from openpilot.selfdrive.ui.mici.onroad.driver_state import DriverStateRenderer
|
||||
from openpilot.selfdrive.ui.mici.onroad.driver_camera_dialog import BaseDriverCameraDialog
|
||||
from openpilot.system.ui.widgets.button import SmallCircleIconButton
|
||||
from openpilot.system.ui.widgets.scroller import NavScroller, Scroller
|
||||
from openpilot.system.ui.widgets.nav_widget import NavWidget
|
||||
from openpilot.system.ui.mici_setup import GreyBigButton, BigPillButton
|
||||
from openpilot.system.ui.widgets.label import gui_label
|
||||
from openpilot.system.ui.lib.multilang import tr
|
||||
from openpilot.system.version import terms_version, training_version
|
||||
|
||||
|
||||
class OnboardingState(IntEnum):
|
||||
TERMS = 0
|
||||
ONBOARDING = 1
|
||||
DECLINE = 2
|
||||
from openpilot.selfdrive.ui.ui_state import ui_state, device
|
||||
from openpilot.selfdrive.ui.mici.widgets.button import BigCircleButton
|
||||
from openpilot.selfdrive.ui.mici.widgets.dialog import BigConfirmationDialogV2
|
||||
from openpilot.selfdrive.ui.mici.onroad.driver_state import DriverStateRenderer
|
||||
from openpilot.selfdrive.ui.mici.onroad.driver_camera_dialog import BaseDriverCameraDialog
|
||||
|
||||
|
||||
class DriverCameraSetupDialog(BaseDriverCameraDialog):
|
||||
@@ -57,91 +51,62 @@ class DriverCameraSetupDialog(BaseDriverCameraDialog):
|
||||
rl.end_scissor_mode()
|
||||
|
||||
|
||||
class TrainingGuidePreDMTutorial(SetupTermsPage):
|
||||
def __init__(self, continue_callback):
|
||||
super().__init__(continue_callback, continue_text="continue")
|
||||
self._title_header = TermsHeader("driver monitoring setup", gui_app.texture("icons_mici/setup/green_dm.png", 60, 60))
|
||||
class TrainingGuidePreDMTutorial(NavScroller):
|
||||
def __init__(self, continue_callback: Callable[[], None]):
|
||||
super().__init__()
|
||||
|
||||
self._dm_label = UnifiedLabel("Next, we'll ensure comma four is mounted properly.\n\nIf it does not have a clear view of the driver, " +
|
||||
"unplug and remount before continuing.", 42,
|
||||
FontWeight.ROMAN)
|
||||
continue_button = BigPillButton("next")
|
||||
continue_button.set_click_callback(continue_callback)
|
||||
|
||||
self._scroller.add_widgets([
|
||||
GreyBigButton("driver monitoring\ncheck", "scroll to continue",
|
||||
gui_app.texture("icons_mici/setup/green_dm.png", 64, 64)),
|
||||
GreyBigButton("", "Next, we'll check if comma four can detect the driver properly."),
|
||||
GreyBigButton("", "openpilot uses the cabin camera to check if the driver is distracted."),
|
||||
GreyBigButton("", "If it does not have a clear view of the driver, unplug and remount before continuing."),
|
||||
continue_button,
|
||||
])
|
||||
|
||||
def show_event(self):
|
||||
super().show_event()
|
||||
# Get driver monitoring model ready for next step
|
||||
ui_state.params.put_bool("IsDriverViewEnabled", True)
|
||||
|
||||
@property
|
||||
def _content_height(self):
|
||||
return self._dm_label.rect.y + self._dm_label.rect.height - self._scroll_panel.get_offset()
|
||||
|
||||
def _render_content(self, scroll_offset):
|
||||
self._title_header.render(rl.Rectangle(
|
||||
self._rect.x + 16,
|
||||
self._rect.y + 16 + scroll_offset,
|
||||
self._title_header.rect.width,
|
||||
self._title_header.rect.height,
|
||||
))
|
||||
|
||||
self._dm_label.render(rl.Rectangle(
|
||||
self._rect.x + 16,
|
||||
self._title_header.rect.y + self._title_header.rect.height + 16,
|
||||
self._rect.width - 32,
|
||||
self._dm_label.get_content_height(int(self._rect.width - 32)),
|
||||
))
|
||||
ui_state.params.put_bool_nonblocking("IsDriverViewEnabled", True)
|
||||
|
||||
|
||||
class DMBadFaceDetected(SetupTermsPage):
|
||||
def __init__(self, continue_callback, back_callback):
|
||||
super().__init__(continue_callback, back_callback, continue_text="power off")
|
||||
self._title_header = TermsHeader("make sure comma four can see your face", gui_app.texture("icons_mici/setup/orange_dm.png", 60, 60))
|
||||
self._dm_label = UnifiedLabel("Re-mount if your face is occluded or driver monitoring has difficulty tracking your face.", 42, FontWeight.ROMAN)
|
||||
class DMBadFaceDetected(NavScroller):
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
|
||||
@property
|
||||
def _content_height(self):
|
||||
return self._dm_label.rect.y + self._dm_label.rect.height - self._scroll_panel.get_offset()
|
||||
back_button = BigPillButton("back")
|
||||
back_button.set_click_callback(self.dismiss)
|
||||
|
||||
def _render_content(self, scroll_offset):
|
||||
self._title_header.render(rl.Rectangle(
|
||||
self._rect.x + 16,
|
||||
self._rect.y + 16 + scroll_offset,
|
||||
self._title_header.rect.width,
|
||||
self._title_header.rect.height,
|
||||
))
|
||||
|
||||
self._dm_label.render(rl.Rectangle(
|
||||
self._rect.x + 16,
|
||||
self._title_header.rect.y + self._title_header.rect.height + 16,
|
||||
self._rect.width - 32,
|
||||
self._dm_label.get_content_height(int(self._rect.width - 32)),
|
||||
))
|
||||
self._scroller.add_widgets([
|
||||
GreyBigButton("looking for driver", "make sure comma\nfour can see your face",
|
||||
gui_app.texture("icons_mici/setup/orange_dm.png", 64, 64)),
|
||||
GreyBigButton("", "Remount if your face is blocked, or driver monitoring has difficulty tracking your face."),
|
||||
back_button,
|
||||
])
|
||||
|
||||
|
||||
class TrainingGuideDMTutorial(Widget):
|
||||
class TrainingGuideDMTutorial(NavWidget):
|
||||
PROGRESS_DURATION = 4
|
||||
LOOKING_THRESHOLD_DEG = 30.0
|
||||
|
||||
def __init__(self, continue_callback):
|
||||
def __init__(self, continue_callback: Callable[[], None]):
|
||||
super().__init__()
|
||||
|
||||
self_ref = weakref.ref(self)
|
||||
|
||||
self._back_button = SmallCircleIconButton(gui_app.texture("icons_mici/setup/driver_monitoring/dm_question.png", 28, 48))
|
||||
self._back_button.set_click_callback(lambda: self_ref() and self_ref()._show_bad_face_page())
|
||||
self._back_button.set_click_callback(lambda: gui_app.push_widget(self._bad_face_page))
|
||||
self._back_button.set_touch_valid_callback(lambda: self.enabled and not self.is_dismissing) # for nav stack
|
||||
self._good_button = SmallCircleIconButton(gui_app.texture("icons_mici/setup/driver_monitoring/dm_check.png", 42, 42))
|
||||
self._good_button.set_touch_valid_callback(lambda: self.enabled and not self.is_dismissing) # for nav stack
|
||||
|
||||
# Wrap the continue callback to restore settings
|
||||
def wrapped_continue_callback():
|
||||
device.set_offroad_brightness(None)
|
||||
continue_callback()
|
||||
|
||||
self._good_button.set_click_callback(wrapped_continue_callback)
|
||||
self._good_button.set_click_callback(continue_callback)
|
||||
self._good_button.set_enabled(False)
|
||||
|
||||
self._progress = FirstOrderFilter(0.0, 0.5, 1 / gui_app.target_fps)
|
||||
self._dialog = DriverCameraSetupDialog()
|
||||
self._bad_face_page = DMBadFaceDetected(HARDWARE.shutdown, lambda: self_ref() and self_ref()._hide_bad_face_page())
|
||||
self._should_show_bad_face_page = False
|
||||
self._bad_face_page = DMBadFaceDetected()
|
||||
|
||||
# Disable driver monitoring model when device times out for inactivity
|
||||
def inactivity_callback():
|
||||
@@ -149,23 +114,11 @@ class TrainingGuideDMTutorial(Widget):
|
||||
|
||||
device.add_interactive_timeout_callback(inactivity_callback)
|
||||
|
||||
def _show_bad_face_page(self):
|
||||
self._bad_face_page.show_event()
|
||||
self.hide_event()
|
||||
self._should_show_bad_face_page = True
|
||||
|
||||
def _hide_bad_face_page(self):
|
||||
self._bad_face_page.hide_event()
|
||||
self.show_event()
|
||||
self._should_show_bad_face_page = False
|
||||
|
||||
def show_event(self):
|
||||
super().show_event()
|
||||
self._dialog.show_event()
|
||||
self._progress.x = 0.0
|
||||
|
||||
device.set_offroad_brightness(100)
|
||||
|
||||
def _update_state(self):
|
||||
super()._update_state()
|
||||
if device.awake and not ui_state.params.get_bool("IsDriverViewEnabled"):
|
||||
@@ -185,7 +138,8 @@ class TrainingGuideDMTutorial(Widget):
|
||||
looking_center = False
|
||||
|
||||
# stay at 100% once reached
|
||||
if (dm_state.faceDetected and looking_center) or self._progress.x > 0.99:
|
||||
in_bad_face = gui_app.get_active_widget() == self._bad_face_page
|
||||
if ((dm_state.faceDetected and looking_center) or self._progress.x > 0.99) and not in_bad_face:
|
||||
slow = self._progress.x < 0.25
|
||||
duration = self.PROGRESS_DURATION * 2 if slow else self.PROGRESS_DURATION
|
||||
self._progress.x += 1.0 / (duration * gui_app.target_fps)
|
||||
@@ -196,9 +150,6 @@ class TrainingGuideDMTutorial(Widget):
|
||||
self._good_button.set_enabled(self._progress.x >= 0.999)
|
||||
|
||||
def _render(self, _):
|
||||
if self._should_show_bad_face_page:
|
||||
return self._bad_face_page.render(self._rect)
|
||||
|
||||
self._dialog.render(self._rect)
|
||||
|
||||
rl.draw_rectangle_gradient_v(int(self._rect.x), int(self._rect.y + self._rect.height - 80),
|
||||
@@ -255,228 +206,193 @@ class TrainingGuideDMTutorial(Widget):
|
||||
))
|
||||
|
||||
# rounded border
|
||||
rl.begin_scissor_mode(int(self._rect.x), int(self._rect.y), int(self._rect.width), int(self._rect.height))
|
||||
rl.draw_rectangle_rounded_lines_ex(self._rect, 0.2 * 1.02, 10, 50, rl.BLACK)
|
||||
rl.end_scissor_mode()
|
||||
|
||||
|
||||
class TrainingGuideRecordFront(SetupTermsPage):
|
||||
def __init__(self, continue_callback):
|
||||
def on_back():
|
||||
ui_state.params.put_bool("RecordFront", False)
|
||||
continue_callback()
|
||||
|
||||
def on_continue():
|
||||
ui_state.params.put_bool("RecordFront", True)
|
||||
continue_callback()
|
||||
|
||||
super().__init__(on_continue, back_callback=on_back, back_text="no", continue_text="yes")
|
||||
self._title_header = TermsHeader("improve driver monitoring", gui_app.texture("icons_mici/setup/green_dm.png", 60, 60))
|
||||
|
||||
self._dm_label = UnifiedLabel("Do you want to upload driver camera data?", 42,
|
||||
FontWeight.ROMAN)
|
||||
|
||||
def show_event(self):
|
||||
super().show_event()
|
||||
# Disable driver monitoring model after last step
|
||||
ui_state.params.put_bool("IsDriverViewEnabled", False)
|
||||
|
||||
@property
|
||||
def _content_height(self):
|
||||
return self._dm_label.rect.y + self._dm_label.rect.height - self._scroll_panel.get_offset()
|
||||
|
||||
def _render_content(self, scroll_offset):
|
||||
self._title_header.render(rl.Rectangle(
|
||||
self._rect.x + 16,
|
||||
self._rect.y + 16 + scroll_offset,
|
||||
self._title_header.rect.width,
|
||||
self._title_header.rect.height,
|
||||
))
|
||||
|
||||
self._dm_label.render(rl.Rectangle(
|
||||
self._rect.x + 16,
|
||||
self._title_header.rect.y + self._title_header.rect.height + 16,
|
||||
self._rect.width - 32,
|
||||
self._dm_label.get_content_height(int(self._rect.width - 32)),
|
||||
))
|
||||
|
||||
|
||||
class TrainingGuideAttentionNotice(SetupTermsPage):
|
||||
def __init__(self, continue_callback):
|
||||
super().__init__(continue_callback, continue_text="continue")
|
||||
self._title_header = TermsHeader("driver assistance", gui_app.texture("icons_mici/setup/warning.png", 60, 60))
|
||||
self._warning_label = UnifiedLabel("1. openpilot is a driver assistance system.\n\n" +
|
||||
"2. You must pay attention at all times.\n\n" +
|
||||
"3. You must be ready to take over at any time.\n\n" +
|
||||
"4. You are fully responsible for driving the car.", 42,
|
||||
FontWeight.ROMAN)
|
||||
|
||||
@property
|
||||
def _content_height(self):
|
||||
return self._warning_label.rect.y + self._warning_label.rect.height - self._scroll_panel.get_offset()
|
||||
|
||||
def _render_content(self, scroll_offset):
|
||||
self._title_header.render(rl.Rectangle(
|
||||
self._rect.x + 16,
|
||||
self._rect.y + 16 + scroll_offset,
|
||||
self._title_header.rect.width,
|
||||
self._title_header.rect.height,
|
||||
))
|
||||
|
||||
self._warning_label.render(rl.Rectangle(
|
||||
self._rect.x + 16,
|
||||
self._title_header.rect.y + self._title_header.rect.height + 16,
|
||||
self._rect.width - 32,
|
||||
self._warning_label.get_content_height(int(self._rect.width - 32)),
|
||||
))
|
||||
|
||||
|
||||
class TrainingGuide(Widget):
|
||||
def __init__(self, completed_callback=None):
|
||||
class TrainingGuideRecordFront(NavScroller):
|
||||
def __init__(self, continue_callback: Callable[[], None]):
|
||||
super().__init__()
|
||||
self._completed_callback = completed_callback
|
||||
self._step = 0
|
||||
|
||||
self_ref = weakref.ref(self)
|
||||
def show_accept_dialog():
|
||||
def on_accept():
|
||||
ui_state.params.put_bool_nonblocking("RecordFront", True)
|
||||
continue_callback()
|
||||
|
||||
def on_continue():
|
||||
if obj := self_ref():
|
||||
obj._advance_step()
|
||||
gui_app.push_widget(BigConfirmationDialogV2("allow data uploading", "icons_mici/setup/driver_monitoring/dm_check.png", exit_on_confirm=False,
|
||||
confirm_callback=on_accept))
|
||||
|
||||
def show_decline_dialog():
|
||||
def on_decline():
|
||||
ui_state.params.put_bool_nonblocking("RecordFront", False)
|
||||
continue_callback()
|
||||
|
||||
gui_app.push_widget(BigConfirmationDialogV2("no, don't upload", "icons_mici/setup/cancel.png", exit_on_confirm=False, confirm_callback=on_decline))
|
||||
|
||||
self._accept_button = BigCircleButton("icons_mici/setup/driver_monitoring/dm_check.png")
|
||||
self._accept_button.set_click_callback(show_accept_dialog)
|
||||
|
||||
self._decline_button = BigCircleButton("icons_mici/setup/cancel.png")
|
||||
self._decline_button.set_click_callback(show_decline_dialog)
|
||||
|
||||
self._scroller.add_widgets([
|
||||
GreyBigButton("driver camera data", "do you want to share video data for training?",
|
||||
gui_app.texture("icons_mici/setup/green_dm.png", 64, 64)),
|
||||
GreyBigButton("", "Sharing your data with comma helps improve openpilot for everyone."),
|
||||
self._accept_button,
|
||||
self._decline_button,
|
||||
])
|
||||
|
||||
|
||||
class TrainingGuideAttentionNotice(Scroller):
|
||||
def __init__(self, continue_callback: Callable[[], None]):
|
||||
super().__init__()
|
||||
|
||||
continue_button = BigPillButton("next")
|
||||
continue_button.set_click_callback(continue_callback)
|
||||
|
||||
self._scroller.add_widgets([
|
||||
GreyBigButton("what is openpilot?", "scroll to continue",
|
||||
gui_app.texture("icons_mici/setup/green_info.png", 64, 64)),
|
||||
GreyBigButton("", "1. openpilot is a driver assistance system."),
|
||||
GreyBigButton("", "2. You must pay attention at all times."),
|
||||
GreyBigButton("", "3. You must be ready to take over at any time."),
|
||||
GreyBigButton("", "4. You are fully responsible for driving the car."),
|
||||
continue_button,
|
||||
])
|
||||
|
||||
|
||||
class TrainingGuide(NavWidget):
|
||||
def __init__(self, completed_callback: Callable[[], None]):
|
||||
super().__init__()
|
||||
|
||||
self._steps = [
|
||||
TrainingGuideAttentionNotice(continue_callback=on_continue),
|
||||
TrainingGuidePreDMTutorial(continue_callback=on_continue),
|
||||
TrainingGuideDMTutorial(continue_callback=on_continue),
|
||||
TrainingGuideRecordFront(continue_callback=on_continue),
|
||||
TrainingGuideAttentionNotice(continue_callback=lambda: gui_app.push_widget(self._steps[1])),
|
||||
TrainingGuidePreDMTutorial(continue_callback=lambda: gui_app.push_widget(self._steps[2])),
|
||||
TrainingGuideDMTutorial(continue_callback=lambda: gui_app.push_widget(self._steps[3])),
|
||||
TrainingGuideRecordFront(continue_callback=completed_callback),
|
||||
]
|
||||
|
||||
self._steps[0].set_enabled(lambda: self.enabled and not self.is_dismissing) # for nav stack
|
||||
|
||||
def show_event(self):
|
||||
super().show_event()
|
||||
device.set_override_interactive_timeout(300)
|
||||
self._steps[0].show_event()
|
||||
|
||||
def hide_event(self):
|
||||
super().hide_event()
|
||||
device.set_override_interactive_timeout(None)
|
||||
def _render(self, _):
|
||||
self._steps[0].render(self._rect)
|
||||
|
||||
def _advance_step(self):
|
||||
if self._step < len(self._steps) - 1:
|
||||
self._step += 1
|
||||
self._steps[self._step].show_event()
|
||||
else:
|
||||
self._step = 0
|
||||
if self._completed_callback:
|
||||
self._completed_callback()
|
||||
|
||||
class QRCodeWidget(Widget):
|
||||
def __init__(self, url: str, size: int = 170):
|
||||
super().__init__()
|
||||
self.set_rect(rl.Rectangle(0, 0, size, size))
|
||||
self._size = size
|
||||
self._qr_texture: rl.Texture | None = None
|
||||
self._generate_qr(url)
|
||||
|
||||
def _generate_qr(self, url: str):
|
||||
qr = qrcode.QRCode(version=1, error_correction=qrcode.constants.ERROR_CORRECT_L, box_size=10, border=0)
|
||||
qr.add_data(url)
|
||||
qr.make(fit=True)
|
||||
|
||||
pil_img = qr.make_image(fill_color="white", back_color="black").convert('RGBA')
|
||||
img_array = np.array(pil_img, dtype=np.uint8)
|
||||
|
||||
rl_image = rl.Image()
|
||||
rl_image.data = rl.ffi.cast("void *", img_array.ctypes.data)
|
||||
rl_image.width = pil_img.width
|
||||
rl_image.height = pil_img.height
|
||||
rl_image.mipmaps = 1
|
||||
rl_image.format = rl.PixelFormat.PIXELFORMAT_UNCOMPRESSED_R8G8B8A8
|
||||
|
||||
self._qr_texture = rl.load_texture_from_image(rl_image)
|
||||
|
||||
def _render(self, _):
|
||||
if self._qr_texture:
|
||||
scale = self._size / self._qr_texture.height
|
||||
rl.draw_texture_ex(self._qr_texture, rl.Vector2(self._rect.x, self._rect.y), 0.0, scale, rl.WHITE)
|
||||
|
||||
def __del__(self):
|
||||
if self._qr_texture and self._qr_texture.id != 0:
|
||||
rl.unload_texture(self._qr_texture)
|
||||
|
||||
|
||||
class TermsPage(Scroller):
|
||||
def __init__(self, on_accept, on_decline):
|
||||
super().__init__()
|
||||
|
||||
def show_accept_dialog():
|
||||
gui_app.push_widget(BigConfirmationDialogV2("accept\nterms", "icons_mici/setup/driver_monitoring/dm_check.png",
|
||||
confirm_callback=on_accept))
|
||||
|
||||
def show_decline_dialog():
|
||||
gui_app.push_widget(BigConfirmationDialogV2("decline &\nuninstall", "icons_mici/setup/cancel.png",
|
||||
red=True, exit_on_confirm=False, confirm_callback=on_decline))
|
||||
|
||||
self._accept_button = BigCircleButton("icons_mici/setup/driver_monitoring/dm_check.png")
|
||||
self._accept_button.set_click_callback(show_accept_dialog)
|
||||
|
||||
self._decline_button = BigCircleButton("icons_mici/setup/cancel.png", red=True)
|
||||
self._decline_button.set_click_callback(show_decline_dialog)
|
||||
|
||||
self._scroller.add_widgets([
|
||||
GreyBigButton("terms and\nconditions", "scroll to continue",
|
||||
gui_app.texture("icons_mici/setup/green_info.png", 64, 64)),
|
||||
GreyBigButton("swipe for QR code", "or go to https://comma.ai/terms",
|
||||
gui_app.texture("icons_mici/setup/small_slider/slider_arrow.png", 64, 56, flip_x=True)),
|
||||
QRCodeWidget("https://comma.ai/terms"),
|
||||
GreyBigButton("", "You must accept the Terms & Conditions to use openpilot."),
|
||||
self._accept_button,
|
||||
self._decline_button,
|
||||
])
|
||||
|
||||
def _render(self, _):
|
||||
rl.draw_rectangle_rec(self._rect, rl.BLACK)
|
||||
if self._step < len(self._steps):
|
||||
self._steps[self._step].render(self._rect)
|
||||
|
||||
|
||||
class DeclinePage(Widget):
|
||||
def __init__(self, back_callback=None):
|
||||
super().__init__()
|
||||
self._uninstall_slider = SmallSlider("uninstall openpilot", self._on_uninstall)
|
||||
|
||||
self._back_button = SmallButton("back")
|
||||
self._back_button.set_click_callback(back_callback)
|
||||
|
||||
self._warning_header = TermsHeader("you must accept the\nterms to use openpilot",
|
||||
gui_app.texture("icons_mici/setup/red_warning.png", 66, 60))
|
||||
|
||||
def _on_uninstall(self):
|
||||
ui_state.params.put_bool("DoUninstall", True)
|
||||
gui_app.request_close()
|
||||
|
||||
def _render(self, _):
|
||||
self._warning_header.render(rl.Rectangle(
|
||||
self._rect.x + 16,
|
||||
self._rect.y + 16,
|
||||
self._warning_header.rect.width,
|
||||
self._warning_header.rect.height,
|
||||
))
|
||||
|
||||
self._back_button.set_opacity(1 - self._uninstall_slider.slider_percentage)
|
||||
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._uninstall_slider.render(rl.Rectangle(
|
||||
self._rect.x + self._rect.width - self._uninstall_slider.rect.width,
|
||||
self._rect.y + self._rect.height - self._uninstall_slider.rect.height,
|
||||
self._uninstall_slider.rect.width,
|
||||
self._uninstall_slider.rect.height,
|
||||
))
|
||||
|
||||
|
||||
class TermsPage(SetupTermsPage):
|
||||
def __init__(self, on_accept=None, on_decline=None):
|
||||
super().__init__(on_accept, on_decline, "decline")
|
||||
|
||||
info_txt = gui_app.texture("icons_mici/setup/green_info.png", 60, 60)
|
||||
self._title_header = TermsHeader("terms & conditions", info_txt)
|
||||
|
||||
self._terms_label = UnifiedLabel("You must accept the Terms and Conditions to use openpilot. " +
|
||||
"Read the latest terms at https://comma.ai/terms before continuing.", 36,
|
||||
FontWeight.ROMAN)
|
||||
|
||||
@property
|
||||
def _content_height(self):
|
||||
return self._terms_label.rect.y + self._terms_label.rect.height - self._scroll_panel.get_offset()
|
||||
|
||||
def _render_content(self, scroll_offset):
|
||||
self._title_header.set_position(self._rect.x + 16, self._rect.y + 12 + scroll_offset)
|
||||
self._title_header.render()
|
||||
|
||||
self._terms_label.render(rl.Rectangle(
|
||||
self._rect.x + 16,
|
||||
self._title_header.rect.y + self._title_header.rect.height + self.ITEM_SPACING,
|
||||
self._rect.width - 100,
|
||||
self._terms_label.get_content_height(int(self._rect.width - 100)),
|
||||
))
|
||||
super()._render(_)
|
||||
|
||||
|
||||
class OnboardingWindow(Widget):
|
||||
def __init__(self):
|
||||
def __init__(self, completed_callback: Callable[[], None]):
|
||||
super().__init__()
|
||||
self._completed_callback = completed_callback
|
||||
self._accepted_terms: bool = ui_state.params.get("HasAcceptedTerms") == terms_version
|
||||
self._training_done: bool = ui_state.params.get("CompletedTrainingVersion") == training_version
|
||||
|
||||
self._state = OnboardingState.TERMS if not self._accepted_terms else OnboardingState.ONBOARDING
|
||||
|
||||
self.set_rect(rl.Rectangle(0, 0, gui_app.width, gui_app.height))
|
||||
|
||||
# Windows
|
||||
self._terms = TermsPage(on_accept=self._on_terms_accepted, on_decline=self._on_terms_declined)
|
||||
self._terms = TermsPage(on_accept=self._on_terms_accepted, on_decline=self._on_uninstall)
|
||||
self._terms.set_enabled(lambda: self.enabled) # for nav stack
|
||||
self._training_guide = TrainingGuide(completed_callback=self._on_completed_training)
|
||||
self._decline_page = DeclinePage(back_callback=self._on_decline_back)
|
||||
self._training_guide.set_enabled(lambda: self.enabled) # for nav stack
|
||||
|
||||
def _on_uninstall(self):
|
||||
ui_state.params.put_bool("DoUninstall", True)
|
||||
|
||||
def show_event(self):
|
||||
super().show_event()
|
||||
device.set_override_interactive_timeout(300)
|
||||
device.set_offroad_brightness(100)
|
||||
|
||||
def hide_event(self):
|
||||
super().hide_event()
|
||||
# FIXME: when nav stack sends hide event to widget 2 below on push, this needs to be moved
|
||||
device.set_override_interactive_timeout(None)
|
||||
device.set_offroad_brightness(None)
|
||||
|
||||
@property
|
||||
def completed(self) -> bool:
|
||||
return self._accepted_terms and self._training_done
|
||||
|
||||
def _on_terms_declined(self):
|
||||
self._state = OnboardingState.DECLINE
|
||||
|
||||
def _on_decline_back(self):
|
||||
self._state = OnboardingState.TERMS
|
||||
|
||||
def close(self):
|
||||
ui_state.params.put_bool("IsDriverViewEnabled", False)
|
||||
gui_app.pop_widget()
|
||||
ui_state.params.put_bool_nonblocking("IsDriverViewEnabled", False)
|
||||
self._completed_callback()
|
||||
|
||||
def _on_terms_accepted(self):
|
||||
ui_state.params.put("HasAcceptedTerms", terms_version)
|
||||
self._state = OnboardingState.ONBOARDING
|
||||
gui_app.push_widget(self._training_guide)
|
||||
|
||||
def _on_completed_training(self):
|
||||
ui_state.params.put("CompletedTrainingVersion", training_version)
|
||||
@@ -484,9 +400,4 @@ class OnboardingWindow(Widget):
|
||||
|
||||
def _render(self, _):
|
||||
rl.draw_rectangle_rec(self._rect, rl.BLACK)
|
||||
if self._state == OnboardingState.TERMS:
|
||||
self._terms.render(self._rect)
|
||||
elif self._state == OnboardingState.ONBOARDING:
|
||||
self._training_guide.render(self._rect)
|
||||
elif self._state == OnboardingState.DECLINE:
|
||||
self._decline_page.render(self._rect)
|
||||
self._terms.render(self._rect)
|
||||
|
||||
@@ -13,15 +13,39 @@ from openpilot.selfdrive.ui.mici.widgets.dialog import BigDialog, BigConfirmatio
|
||||
from openpilot.selfdrive.ui.mici.widgets.pairing_dialog import PairingDialog
|
||||
from openpilot.selfdrive.ui.mici.onroad.driver_camera_dialog import DriverCameraDialog
|
||||
from openpilot.selfdrive.ui.mici.layouts.onboarding import TrainingGuide, TermsPage
|
||||
from openpilot.system.ui.mici_setup import BigPillButton
|
||||
from openpilot.system.ui.lib.application import gui_app, FontWeight, MousePos
|
||||
from openpilot.system.ui.lib.multilang import tr
|
||||
from openpilot.system.ui.widgets import Widget
|
||||
from openpilot.selfdrive.ui.ui_state import ui_state
|
||||
from openpilot.selfdrive.ui.ui_state import device, ui_state
|
||||
from openpilot.system.ui.widgets.label import MiciLabel
|
||||
from openpilot.system.ui.widgets.html_render import HtmlModal, HtmlRenderer
|
||||
from openpilot.system.athena.registration import UNREGISTERED_DONGLE_ID
|
||||
|
||||
|
||||
class ReviewTermsPage(TermsPage, NavScroller):
|
||||
"""TermsPage with NavWidget swipe-to-dismiss for reviewing in device settings."""
|
||||
def __init__(self):
|
||||
super().__init__(on_accept=self.dismiss, on_decline=self.dismiss)
|
||||
self._accept_button.set_visible(False)
|
||||
self._decline_button.set_visible(False)
|
||||
|
||||
close_button = BigPillButton("close")
|
||||
close_button.set_click_callback(self.dismiss)
|
||||
self._scroller.add_widget(close_button)
|
||||
|
||||
|
||||
class ReviewTrainingGuide(TrainingGuide):
|
||||
def show_event(self):
|
||||
super().show_event()
|
||||
device.set_override_interactive_timeout(300)
|
||||
|
||||
def hide_event(self):
|
||||
super().hide_event()
|
||||
device.set_override_interactive_timeout(None)
|
||||
ui_state.params.put_bool_nonblocking("IsDriverViewEnabled", False)
|
||||
|
||||
|
||||
class MiciFccModal(NavRawScrollPanel):
|
||||
def __init__(self, file_path: str | None = None, text: str | None = None):
|
||||
super().__init__()
|
||||
@@ -311,11 +335,11 @@ class DeviceLayoutMici(NavScroller):
|
||||
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(lambda: gui_app.push_widget(TrainingGuide(completed_callback=gui_app.pop_widget)))
|
||||
review_training_guide_btn.set_click_callback(lambda: gui_app.push_widget(ReviewTrainingGuide(completed_callback=lambda: gui_app.pop_widgets_to(self))))
|
||||
review_training_guide_btn.set_enabled(lambda: ui_state.is_offroad())
|
||||
|
||||
terms_btn = BigButton("terms &\nconditions", "", "icons_mici/settings/device/info.png")
|
||||
terms_btn.set_click_callback(lambda: gui_app.push_widget(TermsPage(on_accept=gui_app.pop_widget)))
|
||||
terms_btn.set_click_callback(lambda: gui_app.push_widget(ReviewTermsPage()))
|
||||
terms_btn.set_enabled(lambda: ui_state.is_offroad())
|
||||
|
||||
self._scroller.add_widgets([
|
||||
|
||||
@@ -68,7 +68,9 @@ def test_dialogs_do_not_leak():
|
||||
|
||||
for ctor in (
|
||||
# mici
|
||||
MiciDriverCameraDialog, MiciTrainingGuide, MiciOnboardingWindow, MiciPairingDialog,
|
||||
MiciDriverCameraDialog, MiciPairingDialog,
|
||||
lambda: MiciTrainingGuide(lambda: None),
|
||||
lambda: MiciOnboardingWindow(lambda: None),
|
||||
lambda: BigDialog("test", "test"),
|
||||
lambda: BigConfirmationDialogV2("test", "icons_mici/settings/network/new/trash.png"),
|
||||
lambda: BigInputDialog("test"),
|
||||
|
||||
@@ -120,6 +120,7 @@ class BigButton(Widget):
|
||||
self._scale_filter = BounceFilter(1.0, 0.1, 1 / gui_app.target_fps)
|
||||
self._click_delay = 0.075
|
||||
self._shake_start: float | None = None
|
||||
self._grow_animation_until: float | None = None
|
||||
|
||||
self._rotate_icon_t: float | None = None
|
||||
|
||||
@@ -145,6 +146,9 @@ class BigButton(Widget):
|
||||
self._txt_pressed_bg = gui_app.texture("icons_mici/buttons/button_rectangle_pressed.png", 402, 180)
|
||||
self._txt_disabled_bg = gui_app.texture("icons_mici/buttons/button_rectangle_disabled.png", 402, 180)
|
||||
|
||||
def set_touch_valid_callback(self, touch_callback: Callable[[], bool]) -> None:
|
||||
super().set_touch_valid_callback(lambda: touch_callback() and self._grow_animation_until is None)
|
||||
|
||||
def _width_hint(self) -> int:
|
||||
# Single line if scrolling, so hide behind icon if exists
|
||||
icon_size = self._icon_size[0] if self._txt_icon and self._scroll and self.value else 0
|
||||
@@ -182,6 +186,9 @@ class BigButton(Widget):
|
||||
def trigger_shake(self):
|
||||
self._shake_start = rl.get_time()
|
||||
|
||||
def trigger_grow_animation(self, duration: float = 0.65):
|
||||
self._grow_animation_until = rl.get_time() + duration
|
||||
|
||||
@property
|
||||
def _shake_offset(self) -> float:
|
||||
SHAKE_DURATION = 0.5
|
||||
@@ -197,6 +204,10 @@ class BigButton(Widget):
|
||||
super().set_position(x + self._shake_offset, y)
|
||||
|
||||
def _handle_background(self) -> tuple[rl.Texture, float, float, float]:
|
||||
if self._grow_animation_until is not None:
|
||||
if rl.get_time() >= self._grow_animation_until:
|
||||
self._grow_animation_until = None
|
||||
|
||||
# draw _txt_default_bg
|
||||
txt_bg = self._txt_default_bg
|
||||
if not self.enabled:
|
||||
@@ -204,7 +215,7 @@ class BigButton(Widget):
|
||||
elif self.is_pressed:
|
||||
txt_bg = self._txt_pressed_bg
|
||||
|
||||
scale = self._scale_filter.update(PRESSED_SCALE if self.is_pressed else 1.0)
|
||||
scale = self._scale_filter.update(PRESSED_SCALE if self.is_pressed or self._grow_animation_until is not None else 1.0)
|
||||
btn_x = self._rect.x + (self._rect.width * (1 - scale)) / 2
|
||||
btn_y = self._rect.y + (self._rect.height * (1 - scale)) / 2
|
||||
return txt_bg, btn_x, btn_y, scale
|
||||
|
||||
@@ -431,6 +431,9 @@ class GuiApplication:
|
||||
return self._nav_stack[-1]
|
||||
return None
|
||||
|
||||
def widget_in_stack(self, widget: object) -> bool:
|
||||
return widget in self._nav_stack
|
||||
|
||||
def add_nav_stack_tick(self, tick_function: Callable[[], None]):
|
||||
if tick_function not in self._nav_stack_ticks:
|
||||
self._nav_stack_ticks.append(tick_function)
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
#!/usr/bin/env python3
|
||||
from abc import abstractmethod
|
||||
import os
|
||||
import re
|
||||
import threading
|
||||
@@ -14,21 +13,21 @@ import pyray as rl
|
||||
|
||||
from cereal import log
|
||||
from openpilot.common.filter_simple import FirstOrderFilter
|
||||
from openpilot.system.hardware import HARDWARE, TICI
|
||||
from openpilot.common.realtime import config_realtime_process, set_core_affinity
|
||||
from openpilot.common.swaglog import cloudlog
|
||||
from openpilot.common.utils import run_cmd
|
||||
from openpilot.system.hardware import HARDWARE, TICI
|
||||
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
|
||||
from openpilot.system.ui.widgets.nav_widget import NavWidget
|
||||
from openpilot.system.ui.widgets.button import (IconButton, SmallButton, WideRoundedButton, SmallerRoundedButton,
|
||||
SmallCircleIconButton, WidishRoundedButton, FullRoundedButton)
|
||||
from openpilot.system.ui.widgets.button import SmallButton
|
||||
from openpilot.system.ui.widgets.label import UnifiedLabel
|
||||
from openpilot.system.ui.widgets.scroller import Scroller, NavScroller, ITEM_SPACING
|
||||
from openpilot.system.ui.widgets.slider import LargerSlider, SmallSlider
|
||||
from openpilot.selfdrive.ui.mici.layouts.settings.network import WifiUIMici
|
||||
from openpilot.selfdrive.ui.mici.layouts.settings.network import WifiNetworkButton, WifiUIMici
|
||||
from openpilot.selfdrive.ui.mici.widgets.dialog import BigInputDialog
|
||||
from openpilot.selfdrive.ui.mici.widgets.button import BigButton
|
||||
|
||||
NetworkType = log.DeviceState.NetworkType
|
||||
|
||||
@@ -122,9 +121,9 @@ class SoftwareSelectionPage(NavWidget):
|
||||
use_custom_software_callback: Callable):
|
||||
super().__init__()
|
||||
|
||||
self._openpilot_slider = LargerSlider("slide to use\nopenpilot", use_openpilot_callback)
|
||||
self._openpilot_slider = LargerSlider("slide to install\nopenpilot", use_openpilot_callback)
|
||||
self._openpilot_slider.set_enabled(lambda: self.enabled and not self.is_dismissing)
|
||||
self._custom_software_slider = LargerSlider("slide to use\ncustom software", use_custom_software_callback, green=False)
|
||||
self._custom_software_slider = LargerSlider("slide to install\nother software", use_custom_software_callback, green=False)
|
||||
self._custom_software_slider.set_enabled(lambda: self.enabled and not self.is_dismissing)
|
||||
|
||||
def show_event(self):
|
||||
@@ -161,199 +160,24 @@ class SoftwareSelectionPage(NavWidget):
|
||||
self._custom_software_slider.render(custom_software_rect)
|
||||
|
||||
|
||||
class TermsHeader(Widget):
|
||||
def __init__(self, text: str, icon_texture: rl.Texture):
|
||||
super().__init__()
|
||||
|
||||
self._title = UnifiedLabel(text, 36, text_color=rl.Color(255, 255, 255, int(255 * 0.9)),
|
||||
font_weight=FontWeight.BOLD, alignment_vertical=rl.GuiTextAlignmentVertical.TEXT_ALIGN_MIDDLE,
|
||||
line_height=0.8)
|
||||
self._icon_texture = icon_texture
|
||||
|
||||
self.set_rect(rl.Rectangle(0, 0, gui_app.width - 16 * 2, self._icon_texture.height))
|
||||
|
||||
def set_title(self, text: str):
|
||||
self._title.set_text(text)
|
||||
|
||||
def set_icon(self, icon_texture: rl.Texture):
|
||||
self._icon_texture = icon_texture
|
||||
|
||||
def _render(self, _):
|
||||
rl.draw_texture_ex(self._icon_texture, rl.Vector2(self._rect.x, self._rect.y),
|
||||
0.0, 1.0, rl.WHITE)
|
||||
|
||||
# May expand outside parent rect
|
||||
title_content_height = self._title.get_content_height(int(self._rect.width - self._icon_texture.width - 16))
|
||||
title_rect = rl.Rectangle(
|
||||
self._rect.x + self._icon_texture.width + 16,
|
||||
self._rect.y + (self._rect.height - title_content_height) / 2,
|
||||
self._rect.width - self._icon_texture.width - 16,
|
||||
title_content_height,
|
||||
)
|
||||
self._title.render(title_rect)
|
||||
|
||||
|
||||
class TermsPage(Widget):
|
||||
ITEM_SPACING = 20
|
||||
|
||||
def __init__(self, continue_callback: Callable, back_callback: Callable | None = None,
|
||||
back_text: str = "back", continue_text: str = "accept"):
|
||||
super().__init__()
|
||||
|
||||
# TODO: use Scroller
|
||||
self._scroll_panel = GuiScrollPanel2(horizontal=False)
|
||||
|
||||
self._continue_text = continue_text
|
||||
self._continue_slider: bool = continue_text in ("reboot", "power off")
|
||||
self._continue_button: WideRoundedButton | FullRoundedButton | SmallSlider
|
||||
if self._continue_slider:
|
||||
self._continue_button = SmallSlider(continue_text, confirm_callback=continue_callback)
|
||||
self._scroll_panel.set_enabled(lambda: not self._continue_button.is_pressed)
|
||||
elif back_callback is not None:
|
||||
self._continue_button = WideRoundedButton(continue_text)
|
||||
else:
|
||||
self._continue_button = FullRoundedButton(continue_text)
|
||||
self._continue_button.set_enabled(False)
|
||||
self._continue_button.set_opacity(0.0)
|
||||
self._continue_button.set_touch_valid_callback(self._scroll_panel.is_touch_valid)
|
||||
if not self._continue_slider:
|
||||
self._continue_button.set_click_callback(continue_callback)
|
||||
|
||||
self._enable_back = back_callback is not None
|
||||
self._back_button = SmallButton(back_text)
|
||||
self._back_button.set_opacity(0.0)
|
||||
self._back_button.set_touch_valid_callback(self._scroll_panel.is_touch_valid)
|
||||
self._back_button.set_click_callback(back_callback)
|
||||
|
||||
self._scroll_down_indicator = IconButton(gui_app.texture("icons_mici/setup/scroll_down_indicator.png", 64, 78))
|
||||
self._scroll_down_indicator.set_enabled(False)
|
||||
|
||||
def reset(self):
|
||||
self._scroll_panel.set_offset(0)
|
||||
self._continue_button.set_enabled(False)
|
||||
self._continue_button.set_opacity(0.0)
|
||||
self._back_button.set_enabled(False)
|
||||
self._back_button.set_opacity(0.0)
|
||||
self._scroll_down_indicator.set_opacity(1.0)
|
||||
|
||||
def show_event(self):
|
||||
super().show_event()
|
||||
self.reset()
|
||||
|
||||
@property
|
||||
@abstractmethod
|
||||
def _content_height(self):
|
||||
pass
|
||||
|
||||
@property
|
||||
def _scrolled_down_offset(self):
|
||||
return -self._content_height + (self._continue_button.rect.height + 16 + 30)
|
||||
|
||||
@abstractmethod
|
||||
def _render_content(self, scroll_offset):
|
||||
pass
|
||||
|
||||
def _render(self, _):
|
||||
rl.draw_rectangle_rec(self._rect, rl.BLACK)
|
||||
scroll_offset = round(self._scroll_panel.update(self._rect, self._content_height + self._continue_button.rect.height + 16))
|
||||
|
||||
if scroll_offset <= self._scrolled_down_offset:
|
||||
# don't show back if not enabled
|
||||
if self._enable_back:
|
||||
self._back_button.set_enabled(True)
|
||||
self._back_button.set_opacity(1.0, smooth=True)
|
||||
self._continue_button.set_enabled(True)
|
||||
self._continue_button.set_opacity(1.0, smooth=True)
|
||||
self._scroll_down_indicator.set_opacity(0.0, smooth=True)
|
||||
else:
|
||||
self._back_button.set_enabled(False)
|
||||
self._back_button.set_opacity(0.0, smooth=True)
|
||||
self._continue_button.set_enabled(False)
|
||||
self._continue_button.set_opacity(0.0, smooth=True)
|
||||
self._scroll_down_indicator.set_opacity(1.0, smooth=True)
|
||||
|
||||
# Render content
|
||||
self._render_content(scroll_offset)
|
||||
|
||||
# black gradient at top and bottom for scrolling content
|
||||
rl.draw_rectangle_gradient_v(int(self._rect.x), int(self._rect.y),
|
||||
int(self._rect.width), 20, rl.BLACK, rl.BLANK)
|
||||
rl.draw_rectangle_gradient_v(int(self._rect.x), int(self._rect.y + self._rect.height - 20),
|
||||
int(self._rect.width), 20, rl.BLANK, rl.BLACK)
|
||||
|
||||
# fade out back button as slider is moved
|
||||
if self._continue_slider and scroll_offset <= self._scrolled_down_offset:
|
||||
self._back_button.set_opacity(1.0 - self._continue_button.slider_percentage)
|
||||
self._back_button.set_visible(self._continue_button.slider_percentage < 0.99)
|
||||
|
||||
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,
|
||||
))
|
||||
|
||||
continue_x = self._rect.x + 8
|
||||
if self._enable_back:
|
||||
continue_x = self._rect.x + self._rect.width - self._continue_button.rect.width - 8
|
||||
if self._continue_slider:
|
||||
continue_x += 8
|
||||
self._continue_button.render(rl.Rectangle(
|
||||
continue_x,
|
||||
self._rect.y + self._rect.height - self._continue_button.rect.height,
|
||||
self._continue_button.rect.width,
|
||||
self._continue_button.rect.height,
|
||||
))
|
||||
|
||||
self._scroll_down_indicator.render(rl.Rectangle(
|
||||
self._rect.x + self._rect.width - self._scroll_down_indicator.rect.width - 8,
|
||||
self._rect.y + self._rect.height - self._scroll_down_indicator.rect.height - 8,
|
||||
self._scroll_down_indicator.rect.width,
|
||||
self._scroll_down_indicator.rect.height,
|
||||
))
|
||||
|
||||
|
||||
class CustomSoftwareWarningPage(TermsPage):
|
||||
class CustomSoftwareWarningPage(NavScroller):
|
||||
def __init__(self, continue_callback: Callable, back_callback: Callable):
|
||||
super().__init__(continue_callback, back_callback)
|
||||
super().__init__()
|
||||
self.set_back_callback(back_callback)
|
||||
|
||||
self._title_header = TermsHeader("use caution installing\n3rd party software",
|
||||
gui_app.texture("icons_mici/setup/warning.png", 66, 60))
|
||||
self._body = UnifiedLabel("• It has not been tested by comma.\n" +
|
||||
"• It may not comply with relevant safety standards.\n" +
|
||||
"• It may cause damage to your device and/or vehicle.\n", 36, text_color=rl.Color(255, 255, 255, int(255 * 0.9)),
|
||||
font_weight=FontWeight.ROMAN)
|
||||
self._continue_button = BigPillButton("next")
|
||||
self._continue_button.set_click_callback(continue_callback)
|
||||
|
||||
self._restore_header = TermsHeader("how to backup &\nrestore", gui_app.texture("icons_mici/setup/restore.png", 60, 60))
|
||||
self._restore_body = UnifiedLabel("To restore your device to a factory state later, use https://flash.comma.ai",
|
||||
36, text_color=rl.Color(255, 255, 255, int(255 * 0.9)),
|
||||
font_weight=FontWeight.ROMAN)
|
||||
|
||||
@property
|
||||
def _content_height(self):
|
||||
return self._restore_body.rect.y + self._restore_body.rect.height - self._scroll_panel.get_offset()
|
||||
|
||||
def _render_content(self, scroll_offset):
|
||||
self._title_header.set_position(self._rect.x + 16, self._rect.y + 8 + scroll_offset)
|
||||
self._title_header.render()
|
||||
|
||||
body_rect = rl.Rectangle(
|
||||
self._rect.x + 8,
|
||||
self._title_header.rect.y + self._title_header.rect.height + self.ITEM_SPACING,
|
||||
self._rect.width - 50,
|
||||
self._body.get_content_height(int(self._rect.width - 50)),
|
||||
)
|
||||
self._body.render(body_rect)
|
||||
|
||||
self._restore_header.set_position(self._rect.x + 16, self._body.rect.y + self._body.rect.height + self.ITEM_SPACING)
|
||||
self._restore_header.render()
|
||||
|
||||
self._restore_body.render(rl.Rectangle(
|
||||
self._rect.x + 8,
|
||||
self._restore_header.rect.y + self._restore_header.rect.height + self.ITEM_SPACING,
|
||||
self._rect.width - 50,
|
||||
self._restore_body.get_content_height(int(self._rect.width - 50)),
|
||||
))
|
||||
self._scroller.add_widgets([
|
||||
GreyBigButton("use caution", "when installing\n3rd party software",
|
||||
gui_app.texture("icons_mici/setup/warning.png", 64, 58)),
|
||||
GreyBigButton("", "• It has not been tested by comma"),
|
||||
GreyBigButton("", "• It may not comply with relevant safety standards."),
|
||||
GreyBigButton("", "• It may cause damage to your device and/or vehicle."),
|
||||
GreyBigButton("how to restore to a\nfactory state later", "https://flash.comma.ai",
|
||||
gui_app.texture("icons_mici/setup/restore.png", 64, 64)),
|
||||
self._continue_button,
|
||||
])
|
||||
|
||||
|
||||
class DownloadingPage(Widget):
|
||||
@@ -391,11 +215,9 @@ class DownloadingPage(Widget):
|
||||
))
|
||||
|
||||
|
||||
class FailedPage(NavWidget):
|
||||
class FailedPageBase(Widget):
|
||||
def __init__(self, reboot_callback: Callable, retry_callback: Callable, title: str = "download failed"):
|
||||
super().__init__()
|
||||
self.set_back_callback(retry_callback)
|
||||
|
||||
self._title_label = UnifiedLabel(title, 64, text_color=rl.Color(255, 255, 255, int(255 * 0.9)),
|
||||
font_weight=FontWeight.DISPLAY)
|
||||
self._reason_label = UnifiedLabel("", 36, text_color=rl.Color(255, 255, 255, int(255 * 0.9 * 0.65)),
|
||||
@@ -446,11 +268,86 @@ class FailedPage(NavWidget):
|
||||
))
|
||||
|
||||
|
||||
class NetworkSetupPage(NavWidget):
|
||||
class FailedPage(FailedPageBase, NavWidget):
|
||||
def __init__(self, reboot_callback: Callable, retry_callback: Callable, title: str = "download failed"):
|
||||
super().__init__(reboot_callback, retry_callback, title)
|
||||
self.set_back_callback(retry_callback)
|
||||
|
||||
|
||||
class GreyBigButton(BigButton):
|
||||
"""Users should manage newlines with this class themselves"""
|
||||
|
||||
LABEL_HORIZONTAL_PADDING = 30
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
self.set_touch_valid_callback(lambda: False)
|
||||
|
||||
self._rect.width = 476
|
||||
|
||||
self._label.set_font_size(36)
|
||||
self._label.set_font_weight(FontWeight.BOLD)
|
||||
self._label.set_line_height(1.0)
|
||||
|
||||
self._sub_label.set_font_size(36)
|
||||
self._sub_label.set_text_color(rl.Color(255, 255, 255, int(255 * 0.9)))
|
||||
self._sub_label.set_font_weight(FontWeight.DISPLAY_REGULAR)
|
||||
self._sub_label.set_alignment_vertical(rl.GuiTextAlignmentVertical.TEXT_ALIGN_MIDDLE if not self._label.text else
|
||||
rl.GuiTextAlignmentVertical.TEXT_ALIGN_BOTTOM)
|
||||
self._sub_label.set_line_height(0.95)
|
||||
|
||||
@property
|
||||
def LABEL_VERTICAL_PADDING(self):
|
||||
return BigButton.LABEL_VERTICAL_PADDING if self._label.text else 18
|
||||
|
||||
def _width_hint(self) -> int:
|
||||
return int(self._rect.width - self.LABEL_HORIZONTAL_PADDING * 2)
|
||||
|
||||
def _render(self, _):
|
||||
rl.draw_rectangle_rounded(self._rect, 0.4, 10, rl.Color(255, 255, 255, int(255 * 0.15)))
|
||||
self._draw_content(self._rect.y)
|
||||
|
||||
|
||||
class BigPillButton(BigButton):
|
||||
def __init__(self, *args, green: bool = False, disabled_background: bool = False, **kwargs):
|
||||
self._green = green
|
||||
self._disabled_background = disabled_background
|
||||
super().__init__(*args, **kwargs)
|
||||
|
||||
self._label.set_font_size(48)
|
||||
self._label.set_alignment(rl.GuiTextAlignment.TEXT_ALIGN_CENTER)
|
||||
self._label.set_alignment_vertical(rl.GuiTextAlignmentVertical.TEXT_ALIGN_MIDDLE)
|
||||
|
||||
def _load_images(self):
|
||||
if self._green:
|
||||
self._txt_default_bg = gui_app.texture("icons_mici/setup/start_button.png", 402, 180)
|
||||
self._txt_pressed_bg = gui_app.texture("icons_mici/setup/start_button_pressed.png", 402, 180)
|
||||
else:
|
||||
self._txt_default_bg = gui_app.texture("icons_mici/setup/continue.png", 402, 180)
|
||||
self._txt_pressed_bg = gui_app.texture("icons_mici/setup/continue_pressed.png", 402, 180)
|
||||
self._txt_disabled_bg = gui_app.texture("icons_mici/setup/continue_disabled.png", 402, 180)
|
||||
|
||||
def set_green(self, green: bool):
|
||||
if self._green != green:
|
||||
self._green = green
|
||||
self._load_images()
|
||||
|
||||
def _update_label_layout(self):
|
||||
# Don't change label text size
|
||||
pass
|
||||
|
||||
def _handle_background(self) -> tuple[rl.Texture, float, float, float]:
|
||||
txt_bg, btn_x, btn_y, scale = super()._handle_background()
|
||||
|
||||
if self._disabled_background:
|
||||
txt_bg = self._txt_disabled_bg
|
||||
return txt_bg, btn_x, btn_y, scale
|
||||
|
||||
|
||||
class NetworkSetupPageBase(Scroller):
|
||||
def __init__(self, network_monitor: NetworkConnectivityMonitor, continue_callback: Callable[[bool], None],
|
||||
back_callback: Callable[[], None] | None):
|
||||
disable_connect_hint: bool = False):
|
||||
super().__init__()
|
||||
self.set_back_callback(back_callback)
|
||||
|
||||
self._wifi_manager = WifiManager()
|
||||
self._wifi_manager.set_active(True)
|
||||
@@ -459,83 +356,106 @@ class NetworkSetupPage(NavWidget):
|
||||
self._prev_has_internet = False
|
||||
self._wifi_ui = WifiUIMici(self._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)
|
||||
self._waiting_text = "waiting for internet..."
|
||||
self._network_header = TermsHeader(self._waiting_text, self._no_wifi_txt)
|
||||
self._connect_button = GreyBigButton("connect to\ninternet", "swipe down to go back",
|
||||
gui_app.texture("icons_mici/setup/small_slider/slider_arrow.png", 64, 56, flip_x=True))
|
||||
self._connect_button.set_visible(not disable_connect_hint)
|
||||
|
||||
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 = WifiNetworkButton(self._wifi_manager)
|
||||
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._show_time = 0.0
|
||||
self._pending_has_internet_scroll = False
|
||||
self._pending_continue_grow_animation = False
|
||||
self._pending_wifi_grow_animation = False
|
||||
|
||||
def on_waiting_click():
|
||||
offset = (self._wifi_button.rect.x + self._wifi_button.rect.width / 2) - (self._rect.x + self._rect.width / 2)
|
||||
self._scroller.scroll_to(offset, smooth=True, block_interaction=True)
|
||||
# trigger grow when wifi button in view
|
||||
self._pending_wifi_grow_animation = True
|
||||
|
||||
self._waiting_button = BigPillButton("waiting for\ninternet...", disabled_background=True)
|
||||
self._waiting_button.set_click_callback(on_waiting_click)
|
||||
self._continue_button = BigPillButton("install openpilot", green=True)
|
||||
self._continue_button.set_click_callback(lambda: continue_callback(self._custom_software))
|
||||
|
||||
self._scroller.add_widgets([
|
||||
self._connect_button,
|
||||
self._wifi_button,
|
||||
self._continue_button,
|
||||
self._waiting_button,
|
||||
])
|
||||
|
||||
gui_app.add_nav_stack_tick(self._nav_stack_tick)
|
||||
|
||||
def show_event(self):
|
||||
super().show_event()
|
||||
self._show_time = rl.get_time()
|
||||
self._prev_has_internet = False
|
||||
self._network_monitor.reset()
|
||||
self._set_has_internet(False)
|
||||
self._pending_has_internet_scroll = False
|
||||
self._pending_continue_grow_animation = False
|
||||
self._pending_wifi_grow_animation = False
|
||||
|
||||
def _nav_stack_tick(self):
|
||||
self._wifi_manager.process_callbacks()
|
||||
|
||||
has_internet = self._network_monitor.network_connected.is_set()
|
||||
if has_internet != self._prev_has_internet:
|
||||
self._set_has_internet(has_internet)
|
||||
if has_internet:
|
||||
gui_app.pop_widgets_to(self)
|
||||
self._prev_has_internet = has_internet
|
||||
if has_internet and not self._prev_has_internet:
|
||||
self._pending_has_internet_scroll = True
|
||||
self._prev_has_internet = has_internet
|
||||
|
||||
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(lambda: 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 self._pending_has_internet_scroll:
|
||||
# Scrolls over to continue button, then grows once in view
|
||||
elapsed = rl.get_time() - self._show_time
|
||||
if elapsed > 0.5:
|
||||
self._pending_has_internet_scroll = False
|
||||
|
||||
def scroll_to_download():
|
||||
self._scroller._layout()
|
||||
end_offset = -(self._scroller.content_size - self._rect.width)
|
||||
remaining = self._scroller.scroll_panel.get_offset() - end_offset
|
||||
self._scroller.scroll_to(remaining, smooth=True, block_interaction=True)
|
||||
self._pending_continue_grow_animation = True
|
||||
|
||||
# Animate WifiUi down first before scroll
|
||||
gui_app.pop_widgets_to(self, scroll_to_download)
|
||||
|
||||
def set_custom_software(self, custom_software: bool):
|
||||
self._custom_software = custom_software
|
||||
self._continue_button.set_text("install openpilot" if not custom_software else "choose software")
|
||||
self._continue_button.set_green(not custom_software)
|
||||
|
||||
def _render(self, _):
|
||||
self._network_header.render(rl.Rectangle(
|
||||
self._rect.x + 16,
|
||||
self._rect.y + 16,
|
||||
self._rect.width - 32,
|
||||
self._network_header.rect.height,
|
||||
))
|
||||
def set_is_updater(self):
|
||||
self._continue_button.set_text("download\n& install")
|
||||
self._continue_button.set_green(False)
|
||||
|
||||
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,
|
||||
))
|
||||
def _update_state(self):
|
||||
super()._update_state()
|
||||
|
||||
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,
|
||||
))
|
||||
if self._pending_continue_grow_animation:
|
||||
btn_right = self._continue_button.rect.x + self._continue_button.rect.width
|
||||
visible_right = self._rect.x + self._rect.width
|
||||
if btn_right < visible_right + 50:
|
||||
self._pending_continue_grow_animation = False
|
||||
self._continue_button.trigger_grow_animation()
|
||||
|
||||
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,
|
||||
))
|
||||
if self._pending_wifi_grow_animation and abs(self._wifi_button.rect.x - ITEM_SPACING) < 50:
|
||||
self._pending_wifi_grow_animation = False
|
||||
self._wifi_button.trigger_grow_animation()
|
||||
|
||||
if self._network_monitor.network_connected.is_set():
|
||||
self._continue_button.set_visible(True)
|
||||
self._waiting_button.set_visible(False)
|
||||
else:
|
||||
self._continue_button.set_visible(False)
|
||||
self._waiting_button.set_visible(True)
|
||||
|
||||
|
||||
class NetworkSetupPage(NetworkSetupPageBase, NavScroller):
|
||||
def __init__(self, network_monitor: NetworkConnectivityMonitor, continue_callback: Callable[[bool], None],
|
||||
back_callback: Callable[[], None] | None):
|
||||
super().__init__(network_monitor, continue_callback)
|
||||
self.set_back_callback(back_callback)
|
||||
|
||||
|
||||
class Setup(Widget):
|
||||
@@ -557,13 +477,13 @@ class Setup(Widget):
|
||||
self._start_page.set_click_callback(getting_started_button_callback)
|
||||
self._start_page.set_enabled(lambda: self.enabled) # for nav stack
|
||||
|
||||
self._network_setup_page = NetworkSetupPage(self._network_monitor, self._network_setup_continue_button_callback,
|
||||
self._pop_to_software_selection)
|
||||
self._network_setup_page = NetworkSetupPage(self._network_monitor, self._network_setup_continue_callback, self._pop_to_software_selection)
|
||||
|
||||
self._software_selection_page = SoftwareSelectionPage(self._use_openpilot, lambda: gui_app.push_widget(self._custom_software_warning_page))
|
||||
|
||||
self._download_failed_page = FailedPage(HARDWARE.reboot, self._pop_to_software_selection)
|
||||
|
||||
self._custom_software_warning_page = CustomSoftwareWarningPage(self._software_selection_custom_software_continue, self._pop_to_software_selection)
|
||||
self._custom_software_warning_page = CustomSoftwareWarningPage(lambda: self._push_network_setup(True), self._pop_to_software_selection)
|
||||
|
||||
self._downloading_page = DownloadingPage()
|
||||
|
||||
@@ -602,17 +522,14 @@ class Setup(Widget):
|
||||
time.sleep(0.1)
|
||||
gui_app.request_close()
|
||||
else:
|
||||
self._push_network_setup(custom_software=False)
|
||||
self._push_network_setup()
|
||||
|
||||
def _push_network_setup(self, custom_software: bool):
|
||||
def _push_network_setup(self, custom_software: bool = False):
|
||||
# to fire the correct continue callback later
|
||||
self._network_setup_page.set_custom_software(custom_software)
|
||||
gui_app.push_widget(self._network_setup_page)
|
||||
gui_app.pop_widgets_to(self._software_selection_page, lambda: gui_app.push_widget(self._network_setup_page))
|
||||
|
||||
def _software_selection_custom_software_continue(self):
|
||||
gui_app.pop_widgets_to(self._software_selection_page, instant=True) # don't reset sliders
|
||||
self._push_network_setup(custom_software=True)
|
||||
|
||||
def _network_setup_continue_button_callback(self, custom_software):
|
||||
def _network_setup_continue_callback(self, custom_software: bool):
|
||||
if not custom_software:
|
||||
gui_app.pop_widgets_to(self._software_selection_page, instant=True) # don't reset sliders
|
||||
self._download(OPENPILOT_URL)
|
||||
|
||||
@@ -7,11 +7,10 @@ from enum import IntEnum
|
||||
|
||||
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.widgets import Widget
|
||||
from openpilot.system.ui.widgets.label import UnifiedLabel
|
||||
from openpilot.system.ui.widgets.button import FullRoundedButton
|
||||
from openpilot.system.ui.mici_setup import NetworkSetupPage, FailedPage, NetworkConnectivityMonitor
|
||||
from openpilot.system.ui.mici_setup import NetworkSetupPageBase, FailedPageBase, NetworkConnectivityMonitor
|
||||
|
||||
|
||||
class Screen(IntEnum):
|
||||
@@ -32,16 +31,14 @@ class Updater(Widget):
|
||||
self.progress_text = "loading"
|
||||
self.process = None
|
||||
self.update_thread = None
|
||||
self._wifi_manager = WifiManager()
|
||||
self._wifi_manager.set_active(True)
|
||||
|
||||
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()
|
||||
|
||||
self._network_setup_page = NetworkSetupPageBase(self._network_monitor, self._network_setup_continue_callback,
|
||||
disable_connect_hint=True)
|
||||
self._network_setup_page.set_is_updater()
|
||||
self._network_setup_page.set_enabled(lambda: self.enabled) # for nav stack
|
||||
|
||||
# Buttons
|
||||
self._continue_button = FullRoundedButton("continue")
|
||||
self._continue_button.set_click_callback(lambda: self.set_current_screen(Screen.WIFI))
|
||||
@@ -52,8 +49,8 @@ class Updater(Widget):
|
||||
text_color=rl.Color(255, 255, 255, int(255 * 0.9)),
|
||||
font_weight=FontWeight.ROMAN)
|
||||
|
||||
self._update_failed_page = FailedPage(HARDWARE.reboot, self._update_failed_retry_callback,
|
||||
title="update failed")
|
||||
self._update_failed_page = FailedPageBase(HARDWARE.reboot, self._update_failed_retry_callback,
|
||||
title="update failed")
|
||||
|
||||
self._progress_title_label = UnifiedLabel("", 64, text_color=rl.Color(255, 255, 255, int(255 * 0.9)),
|
||||
font_weight=FontWeight.DISPLAY, line_height=0.8)
|
||||
@@ -61,10 +58,7 @@ class Updater(Widget):
|
||||
font_weight=FontWeight.ROMAN,
|
||||
alignment_vertical=rl.GuiTextAlignmentVertical.TEXT_ALIGN_BOTTOM)
|
||||
|
||||
def _network_setup_back_callback(self):
|
||||
self.set_current_screen(Screen.PROMPT)
|
||||
|
||||
def _network_setup_continue_callback(self):
|
||||
def _network_setup_continue_callback(self, _):
|
||||
self.install_update()
|
||||
|
||||
def _update_failed_retry_callback(self):
|
||||
@@ -160,14 +154,10 @@ class Updater(Widget):
|
||||
rect.height,
|
||||
))
|
||||
|
||||
def _update_state(self):
|
||||
self._wifi_manager.process_callbacks()
|
||||
|
||||
def _render(self, rect: rl.Rectangle):
|
||||
if self.current_screen == Screen.PROMPT:
|
||||
self.render_prompt_screen(rect)
|
||||
elif self.current_screen == Screen.WIFI:
|
||||
self._network_setup_page.set_has_internet(self._network_monitor.network_connected.is_set())
|
||||
self._network_setup_page.render(rect)
|
||||
elif self.current_screen == Screen.PROGRESS:
|
||||
self.render_progress_screen(rect)
|
||||
|
||||
@@ -228,6 +228,7 @@ class SmallCircleIconButton(Widget):
|
||||
class SmallButton(Widget):
|
||||
def __init__(self, text: str):
|
||||
super().__init__()
|
||||
self._click_delay = 0.075
|
||||
self._opacity_filter = FirstOrderFilter(1.0, 0.1, 1 / gui_app.target_fps)
|
||||
|
||||
self._load_assets()
|
||||
|
||||
@@ -63,7 +63,7 @@ class NavWidget(Widget, abc.ABC):
|
||||
self._playing_dismiss_animation = False # released and animating away
|
||||
self._y_pos_filter = BounceFilter(0.0, 0.1, 1 / gui_app.target_fps, bounce=1)
|
||||
|
||||
self._back_callback: Callable[[], None] | None = None # persistent callback for any back navigation
|
||||
self._back_callback: Callable[[], None] | None = None # persistent callback for user-initiated back navigation
|
||||
self._dismiss_callback: Callable[[], None] | None = None # transient callback for programmatic dismiss
|
||||
|
||||
# TODO: move this state into NavBar
|
||||
@@ -150,12 +150,12 @@ class NavWidget(Widget, abc.ABC):
|
||||
if new_y > self._rect.height + DISMISS_PUSH_OFFSET - 10:
|
||||
gui_app.pop_widget()
|
||||
|
||||
if self._back_callback is not None:
|
||||
self._back_callback()
|
||||
|
||||
# Only one callback should ever be fired
|
||||
if self._dismiss_callback is not None:
|
||||
self._dismiss_callback()
|
||||
self._dismiss_callback = None
|
||||
elif self._back_callback is not None:
|
||||
self._back_callback()
|
||||
|
||||
self._playing_dismiss_animation = False
|
||||
self._drag_start_pos = None
|
||||
|
||||
Reference in New Issue
Block a user