mirror of
https://github.com/firestar5683/StarPilot.git
synced 2026-06-27 17:42:04 +08:00
raylib: training guide (#36224)
* fix regulatory * debug slow loading * easy gather step coords * gotcha * and fix * dm option * fix final * fixes * progress bar! * "vibe coding is great" * wtf gpt5 * jfc * hand crafted >> vibe * it's slow so only load images if we're doing any kind of training * tf * format * clean up * clean up * no float * cmt * more clean up * clean up * eww * rm * no debug * match y * clean that up * here too * windows
This commit is contained in:
@@ -8,6 +8,7 @@ from openpilot.selfdrive.ui.layouts.settings.settings import SettingsLayout, Pan
|
||||
from openpilot.selfdrive.ui.onroad.augmented_road_view import AugmentedRoadView
|
||||
from openpilot.selfdrive.ui.ui_state import device, ui_state
|
||||
from openpilot.system.ui.widgets import Widget
|
||||
from openpilot.selfdrive.ui.layouts.onboarding import OnboardingWindow
|
||||
|
||||
|
||||
ONROAD_FPS = 20
|
||||
@@ -41,6 +42,11 @@ class MainLayout(Widget):
|
||||
# Set callbacks
|
||||
self._setup_callbacks()
|
||||
|
||||
# Start onboarding if terms or training not completed
|
||||
self._onboarding_window = OnboardingWindow()
|
||||
if not self._onboarding_window.completed:
|
||||
gui_app.set_modal_overlay(self._onboarding_window)
|
||||
|
||||
def _render(self, _):
|
||||
self._handle_onroad_transition()
|
||||
self._render_main_content()
|
||||
|
||||
@@ -0,0 +1,197 @@
|
||||
import os
|
||||
import re
|
||||
from enum import IntEnum
|
||||
|
||||
import pyray as rl
|
||||
from openpilot.common.basedir import BASEDIR
|
||||
from openpilot.system.ui.lib.application import FontWeight, gui_app
|
||||
from openpilot.system.ui.widgets import Widget
|
||||
from openpilot.system.ui.widgets.button import Button, ButtonStyle
|
||||
from openpilot.system.ui.widgets.label import Label, TextAlignment
|
||||
from openpilot.selfdrive.ui.ui_state import ui_state
|
||||
|
||||
DEBUG = False
|
||||
|
||||
STEP_RECTS = [rl.Rectangle(104, 800, 633, 175), rl.Rectangle(1835, 0, 2159, 1080), rl.Rectangle(1835, 0, 2156, 1080),
|
||||
rl.Rectangle(1526, 473, 427, 472), rl.Rectangle(1643, 441, 217, 223), rl.Rectangle(1835, 0, 2155, 1080),
|
||||
rl.Rectangle(1786, 591, 267, 236), rl.Rectangle(1353, 0, 804, 1080), rl.Rectangle(1458, 485, 633, 211),
|
||||
rl.Rectangle(95, 794, 1158, 187), rl.Rectangle(1560, 170, 392, 397), rl.Rectangle(1835, 0, 2159, 1080),
|
||||
rl.Rectangle(1351, 0, 807, 1080), rl.Rectangle(1835, 0, 2158, 1080), rl.Rectangle(1531, 82, 441, 920),
|
||||
rl.Rectangle(1336, 438, 490, 393), rl.Rectangle(1835, 0, 2159, 1080), rl.Rectangle(1835, 0, 2159, 1080),
|
||||
rl.Rectangle(87, 795, 1187, 186)]
|
||||
|
||||
DM_RECORD_STEP = 9
|
||||
DM_RECORD_YES_RECT = rl.Rectangle(695, 794, 558, 187)
|
||||
|
||||
RESTART_TRAINING_RECT = rl.Rectangle(87, 795, 472, 186)
|
||||
|
||||
|
||||
class OnboardingState(IntEnum):
|
||||
TERMS = 0
|
||||
ONBOARDING = 1
|
||||
DECLINE = 2
|
||||
|
||||
|
||||
class TrainingGuide(Widget):
|
||||
def __init__(self, completed_callback=None):
|
||||
super().__init__()
|
||||
self._completed_callback = completed_callback
|
||||
|
||||
self._step = 0
|
||||
self._load_images()
|
||||
|
||||
def _load_images(self):
|
||||
self._images = []
|
||||
paths = [fn for fn in os.listdir(os.path.join(BASEDIR, "selfdrive/assets/training")) if re.match(r'^step\d*\.png$', fn)]
|
||||
paths = sorted(paths, key=lambda x: int(re.search(r'\d+', x).group()))
|
||||
for fn in paths:
|
||||
path = os.path.join(BASEDIR, "selfdrive/assets/training", fn)
|
||||
self._images.append(gui_app.texture(path, gui_app.width, gui_app.height))
|
||||
|
||||
def _handle_mouse_release(self, mouse_pos):
|
||||
if rl.check_collision_point_rec(mouse_pos, STEP_RECTS[self._step]):
|
||||
# Record DM camera?
|
||||
if self._step == DM_RECORD_STEP:
|
||||
yes = rl.check_collision_point_rec(mouse_pos, DM_RECORD_YES_RECT)
|
||||
print(f"putting RecordFront to {yes}")
|
||||
ui_state.params.put_bool("RecordFront", yes)
|
||||
|
||||
# Restart training?
|
||||
elif self._step == len(self._images) - 1:
|
||||
if rl.check_collision_point_rec(mouse_pos, RESTART_TRAINING_RECT):
|
||||
self._step = -1
|
||||
|
||||
self._step += 1
|
||||
|
||||
# Finished?
|
||||
if self._step >= len(self._images):
|
||||
self._step = 0
|
||||
if self._completed_callback:
|
||||
self._completed_callback()
|
||||
|
||||
def _render(self, _):
|
||||
rl.draw_texture(self._images[self._step], 0, 0, rl.WHITE)
|
||||
|
||||
# progress bar
|
||||
if 0 < self._step < len(STEP_RECTS) - 1:
|
||||
h = 20
|
||||
w = int((self._step / (len(STEP_RECTS) - 1)) * self._rect.width)
|
||||
rl.draw_rectangle(int(self._rect.x), int(self._rect.y + self._rect.height - h),
|
||||
w, h, rl.Color(70, 91, 234, 255))
|
||||
|
||||
if DEBUG:
|
||||
rl.draw_rectangle_lines_ex(STEP_RECTS[self._step], 3, rl.RED)
|
||||
|
||||
return -1
|
||||
|
||||
|
||||
class TermsPage(Widget):
|
||||
def __init__(self, on_accept=None, on_decline=None):
|
||||
super().__init__()
|
||||
self._on_accept = on_accept
|
||||
self._on_decline = on_decline
|
||||
|
||||
self._title = Label("Welcome to openpilot", font_size=90, font_weight=FontWeight.BOLD, text_alignment=TextAlignment.LEFT)
|
||||
self._desc = Label("You must accept the Terms and Conditions to use openpilot. Read the latest terms at https://comma.ai/terms before continuing.",
|
||||
font_size=90, font_weight=FontWeight.MEDIUM, text_alignment=TextAlignment.LEFT)
|
||||
|
||||
self._decline_btn = Button("Decline", click_callback=on_decline)
|
||||
self._accept_btn = Button("Agree", button_style=ButtonStyle.PRIMARY, click_callback=on_accept)
|
||||
|
||||
def _render(self, _):
|
||||
welcome_x = self._rect.x + 165
|
||||
welcome_y = self._rect.y + 165
|
||||
welcome_rect = rl.Rectangle(welcome_x, welcome_y, self._rect.width - welcome_x, 90)
|
||||
self._title.render(welcome_rect)
|
||||
|
||||
desc_x = welcome_x
|
||||
# TODO: Label doesn't top align when wrapping
|
||||
desc_y = welcome_y - 100
|
||||
desc_rect = rl.Rectangle(desc_x, desc_y, self._rect.width - desc_x, self._rect.height - desc_y - 250)
|
||||
self._desc.render(desc_rect)
|
||||
|
||||
btn_y = self._rect.y + self._rect.height - 160 - 45
|
||||
btn_width = (self._rect.width - 45 * 3) / 2
|
||||
self._decline_btn.render(rl.Rectangle(self._rect.x + 45, btn_y, btn_width, 160))
|
||||
self._accept_btn.render(rl.Rectangle(self._rect.x + 45 * 2 + btn_width, btn_y, btn_width, 160))
|
||||
|
||||
if DEBUG:
|
||||
rl.draw_rectangle_lines_ex(welcome_rect, 3, rl.RED)
|
||||
rl.draw_rectangle_lines_ex(desc_rect, 3, rl.RED)
|
||||
|
||||
return -1
|
||||
|
||||
|
||||
class DeclinePage(Widget):
|
||||
def __init__(self, back_callback=None):
|
||||
super().__init__()
|
||||
self._text = Label("You must accept the Terms and Conditions in order to use openpilot.",
|
||||
font_size=90, font_weight=FontWeight.MEDIUM, text_alignment=TextAlignment.LEFT)
|
||||
self._back_btn = Button("Back", click_callback=back_callback)
|
||||
self._uninstall_btn = Button("Decline, uninstall openpilot", button_style=ButtonStyle.DANGER,
|
||||
click_callback=self._on_uninstall_clicked)
|
||||
|
||||
def _on_uninstall_clicked(self):
|
||||
ui_state.params.put_bool("DoUninstall", True)
|
||||
gui_app.request_close()
|
||||
|
||||
def _render(self, _):
|
||||
btn_y = self._rect.y + self._rect.height - 160 - 45
|
||||
btn_width = (self._rect.width - 45 * 3) / 2
|
||||
self._back_btn.render(rl.Rectangle(self._rect.x + 45, btn_y, btn_width, 160))
|
||||
self._uninstall_btn.render(rl.Rectangle(self._rect.x + 45 * 2 + btn_width, btn_y, btn_width, 160))
|
||||
|
||||
# text rect in middle of top and button
|
||||
text_height = btn_y - (200 + 45)
|
||||
text_rect = rl.Rectangle(self._rect.x + 165, self._rect.y + (btn_y - text_height) / 2 + 10, self._rect.width - (165 * 2), text_height)
|
||||
if DEBUG:
|
||||
rl.draw_rectangle_lines_ex(text_rect, 3, rl.RED)
|
||||
self._text.render(text_rect)
|
||||
|
||||
|
||||
class OnboardingWindow(Widget):
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
self._current_terms_version = ui_state.params.get("TermsVersion")
|
||||
self._current_training_version = ui_state.params.get("TrainingVersion")
|
||||
self._accepted_terms: bool = ui_state.params.get("HasAcceptedTerms") == self._current_terms_version
|
||||
self._training_done: bool = ui_state.params.get("CompletedTrainingVersion") == self._current_training_version
|
||||
|
||||
self._state = OnboardingState.TERMS if not self._accepted_terms else OnboardingState.ONBOARDING
|
||||
|
||||
# Windows
|
||||
self._terms = TermsPage(on_accept=self._on_terms_accepted, on_decline=self._on_terms_declined)
|
||||
self._training_guide: TrainingGuide | None = None
|
||||
self._decline_page = DeclinePage(back_callback=self._on_decline_back)
|
||||
|
||||
@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 _on_terms_accepted(self):
|
||||
ui_state.params.put("HasAcceptedTerms", self._current_terms_version)
|
||||
self._state = OnboardingState.ONBOARDING
|
||||
if self._training_done:
|
||||
gui_app.set_modal_overlay(None)
|
||||
|
||||
def _on_completed_training(self):
|
||||
ui_state.params.put("CompletedTrainingVersion", self._current_training_version)
|
||||
gui_app.set_modal_overlay(None)
|
||||
|
||||
def _render(self, _):
|
||||
if self._training_guide is None:
|
||||
self._training_guide = TrainingGuide(completed_callback=self._on_completed_training)
|
||||
|
||||
if self._state == OnboardingState.TERMS:
|
||||
self._terms.render(self._rect)
|
||||
if self._state == OnboardingState.ONBOARDING:
|
||||
self._training_guide.render(self._rect)
|
||||
elif self._state == OnboardingState.DECLINE:
|
||||
self._decline_page.render(self._rect)
|
||||
return -1
|
||||
@@ -5,6 +5,7 @@ from openpilot.common.basedir import BASEDIR
|
||||
from openpilot.common.params import Params
|
||||
from openpilot.selfdrive.ui.onroad.driver_camera_dialog import DriverCameraDialog
|
||||
from openpilot.selfdrive.ui.ui_state import ui_state
|
||||
from openpilot.selfdrive.ui.layouts.onboarding import TrainingGuide
|
||||
from openpilot.selfdrive.ui.widgets.pairing_dialog import PairingDialog
|
||||
from openpilot.system.hardware import TICI
|
||||
from openpilot.system.ui.lib.application import gui_app
|
||||
@@ -36,6 +37,7 @@ class DeviceLayout(Widget):
|
||||
self._driver_camera: DriverCameraDialog | None = None
|
||||
self._pair_device_dialog: PairingDialog | None = None
|
||||
self._fcc_dialog: HtmlRenderer | None = None
|
||||
self._training_guide: TrainingGuide | None = None
|
||||
|
||||
items = self._initialize_items()
|
||||
self._scroller = Scroller(items, line_separator=True, spacing=0)
|
||||
@@ -145,9 +147,11 @@ class DeviceLayout(Widget):
|
||||
def _on_regulatory(self):
|
||||
if not self._fcc_dialog:
|
||||
self._fcc_dialog = HtmlRenderer(os.path.join(BASEDIR, "selfdrive/assets/offroad/fcc.html"))
|
||||
gui_app.set_modal_overlay(self._fcc_dialog, callback=lambda result: setattr(self, '_fcc_dialog', None))
|
||||
|
||||
gui_app.set_modal_overlay(self._fcc_dialog,
|
||||
callback=lambda result: setattr(self, '_fcc_dialog', None),
|
||||
)
|
||||
|
||||
def _on_review_training_guide(self): pass
|
||||
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)
|
||||
|
||||
Reference in New Issue
Block a user