Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
81 commits
Select commit Hold shift + click to select a range
d36d52d
initial
sshane Feb 6, 2026
6d9fb09
start to support nav stack in settings panels + fix some navwidget bugs
sshane Feb 6, 2026
8ecc21f
add deprecation warning and move more to new nav stack
sshane Feb 6, 2026
6ec007e
fix overriding NavWidget enabled and do developer panel
sshane Feb 6, 2026
7dfb849
fix interactive timeout and do main
sshane Feb 6, 2026
0cdf32c
more device, not done yet
sshane Feb 6, 2026
fb26395
minor network fixes
sshane Feb 6, 2026
952d406
dcam dialog
sshane Feb 6, 2026
ebe52da
start onboarding
sshane Feb 6, 2026
3925b49
fix onboarding
sshane Feb 6, 2026
afed1e8
do mici setup
sshane Feb 6, 2026
3966feb
Merge remote-tracking branch 'upstream/master' into nav-stack
sshane Feb 6, 2026
150630f
Merge remote-tracking branch 'upstream/master' into nav-stack
sshane Feb 6, 2026
c13f4f0
remove now useless CUSTOM_SOFTWARE
sshane Feb 6, 2026
96a2544
support big ui with old modal overlay
sshane Feb 6, 2026
391c911
reset can be old modal overlay, but updater needs new since it uses w…
sshane Feb 6, 2026
14962b5
flip name truthiness to inspire excitement
sshane Feb 6, 2026
a0a9057
all *should* work, but will do pass later
sshane Feb 6, 2026
6cac211
clean up main
sshane Feb 6, 2026
0d383ed
clean up settiings
sshane Feb 6, 2026
62d89a6
clean up dialog and developer
sshane Feb 6, 2026
ca97c35
cleanup mici setup some
sshane Feb 6, 2026
78a81c8
rm one more
sshane Feb 6, 2026
9ef2255
fix keyboard
sshane Feb 6, 2026
02b3ddc
revert
sshane Feb 6, 2026
96c3c25
might as well but clarify
sshane Feb 6, 2026
da637a5
fix networkinfopage buttons
sshane Feb 6, 2026
67424e2
lint
sshane Feb 6, 2026
e77f5ce
nice clean up from cursor
sshane Feb 6, 2026
589f816
animate background fade with position
sshane Feb 6, 2026
7e2fd37
Merge remote-tracking branch 'upstream/master' into nav-stack
sshane Feb 7, 2026
a963af2
Merge remote-tracking branch 'upstream/master' into nav-stack
sshane Feb 7, 2026
f59d35a
fix device overlays
sshane Feb 7, 2026
3e9ee7a
cursor fix pt1
sshane Feb 7, 2026
46accb9
rm print
sshane Feb 7, 2026
09a2271
capital
sshane Feb 7, 2026
516a944
temp fix from cursor for onboarding not freeing space after reviewing…
sshane Feb 7, 2026
56a3e34
Merge remote-tracking branch 'upstream/master' into nav-stack
sshane Feb 19, 2026
ad144eb
fix home screen scroller snap not resetting
sshane Feb 20, 2026
488d651
stash
sshane Feb 20, 2026
61551a6
nice gradient on top
sshane Feb 20, 2026
8a14fe0
40
sshane Feb 20, 2026
12896b5
20
sshane Feb 20, 2026
e1d0ad8
no gradient
sshane Feb 20, 2026
6a691f3
return unused returns and always show regulatory btn
sshane Feb 20, 2026
188a587
Merge remote-tracking branch 'upstream/master' into nav-stack
sshane Feb 20, 2026
3b856a6
nice!
sshane Feb 20, 2026
c8b798a
Merge remote-tracking branch 'upstream/master' into nav-stack
sshane Feb 20, 2026
6d24630
clean up
sshane Feb 20, 2026
b179c0d
new_modal is always true!
sshane Feb 20, 2026
7b50d7c
more clean up
sshane Feb 20, 2026
482b9c3
clean up
sshane Feb 20, 2026
67b82d7
big only renders top 1
sshane Feb 20, 2026
5667d4c
fixup setup and updater
sshane Feb 21, 2026
3cfb226
stash
sshane Feb 21, 2026
2ffd48f
Revert "stash"
sshane Feb 21, 2026
fbae22a
fix mici keys coming in from top
sshane Feb 21, 2026
3650fed
clean up
sshane Feb 21, 2026
2edd93f
fix mici dialogs like tici, pop first incase call back pushes
sshane Feb 21, 2026
f69d106
clever way but not not
sshane Feb 21, 2026
258befc
Revert "clever way but not not"
sshane Feb 21, 2026
8cd7155
more setup
sshane Feb 21, 2026
5f145b0
mici keyboard: fix not disabling below
sshane Feb 21, 2026
78602b4
cmt
sshane Feb 21, 2026
6268924
fix wifi callbacks not running in rare case
sshane Feb 21, 2026
34dda14
clean up network
sshane Feb 21, 2026
d0d275b
clean up network
sshane Feb 21, 2026
3bd4d9d
clean up dialog
sshane Feb 21, 2026
bbf87f2
pairing
sshane Feb 21, 2026
cc73a63
rm
sshane Feb 21, 2026
4533080
todo
sshane Feb 21, 2026
d15c101
fix replay
sshane Feb 21, 2026
38aac9a
they push themselkves!
sshane Feb 21, 2026
e380ef2
clean up ui_state
sshane Feb 21, 2026
8a36526
clean up application
sshane Feb 21, 2026
b26ca17
clean up
sshane Feb 21, 2026
ee2ff33
Merge remote-tracking branch 'upstream/master' into nav-stack
sshane Feb 21, 2026
07d3f5f
stash
sshane Feb 21, 2026
c33f1dc
Revert "stash"
sshane Feb 21, 2026
a9e7da7
typing
sshane Feb 21, 2026
0f3c839
lint
sshane Feb 21, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
81 changes: 29 additions & 52 deletions selfdrive/ui/mici/layouts/main.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import pyray as rl
from enum import IntEnum
import cereal.messaging as messaging
from openpilot.selfdrive.ui.mici.layouts.home import MiciHomeLayout
from openpilot.selfdrive.ui.mici.layouts.settings.settings import SettingsLayout
Expand All @@ -15,18 +14,12 @@
ONROAD_DELAY = 2.5 # seconds


class MainState(IntEnum):
MAIN = 0
SETTINGS = 1


class MiciMainLayout(Widget):
def __init__(self):
super().__init__()

self._pm = messaging.PubMaster(['bookmarkButton'])

self._current_mode: MainState | None = None
self._prev_onroad = False
self._prev_standstill = False
self._onroad_time_delay: float | None = None
Expand All @@ -49,38 +42,31 @@ def __init__(self):
self._onroad_layout,
], spacing=0, pad_start=0, pad_end=0, scroll_indicator=False, edge_shadows=False)
self._scroller.set_reset_scroll_at_show(False)
self._scroller.set_enabled(lambda: self.enabled) # for nav stack

# Disable scrolling when onroad is interacting with bookmark
self._scroller.set_scrolling_enabled(lambda: not self._onroad_layout.is_swiping_left())

self._layouts = {
MainState.MAIN: self._scroller,
MainState.SETTINGS: self._settings_layout,
}

# Set callbacks
self._setup_callbacks()

# Start onboarding if terms or training not completed
gui_app.push_widget(self)

# Start onboarding if terms or training not completed, make sure to push after self
self._onboarding_window = OnboardingWindow()
if not self._onboarding_window.completed:
gui_app.set_modal_overlay(self._onboarding_window)
gui_app.push_widget(self._onboarding_window)

def _setup_callbacks(self):
self._home_layout.set_callbacks(on_settings=self._on_settings_clicked)
self._settings_layout.set_callbacks(on_close=self._on_settings_closed)
self._home_layout.set_callbacks(on_settings=lambda: gui_app.push_widget(self._settings_layout))
self._onroad_layout.set_click_callback(lambda: self._scroll_to(self._home_layout))
device.add_interactive_timeout_callback(self._set_mode_for_started)
device.add_interactive_timeout_callback(self._on_interactive_timeout)

def _scroll_to(self, layout: Widget):
layout_x = int(layout.rect.x)
self._scroller.scroll_to(layout_x, smooth=True)

def _render(self, _):
# Initial show event
if self._current_mode is None:
self._set_mode(MainState.MAIN)

if not self._setup:
if self._alerts_layout.active_alerts() > 0:
self._scroller.scroll_to(self._alerts_layout.rect.x)
Expand All @@ -89,59 +75,50 @@ def _render(self, _):
self._setup = True

# Render
if self._current_mode == MainState.MAIN:
self._scroller.render(self._rect)

elif self._current_mode == MainState.SETTINGS:
self._settings_layout.render(self._rect)
self._scroller.render(self._rect)

self._handle_transitions()

def _set_mode(self, mode: MainState):
if mode != self._current_mode:
if self._current_mode is not None:
self._layouts[self._current_mode].hide_event()
self._layouts[mode].show_event()
self._current_mode = mode

def _handle_transitions(self):
# Don't pop if onboarding
if gui_app.get_active_widget() == self._onboarding_window:
return

if ui_state.started != self._prev_onroad:
self._prev_onroad = ui_state.started

# onroad: after delay, pop nav stack and scroll to onroad
# offroad: immediately scroll to home, but don't pop nav stack (can stay in settings)
if ui_state.started:
self._onroad_time_delay = rl.get_time()
else:
self._set_mode_for_started(True)
self._scroll_to(self._home_layout)

# delay so we show home for a bit after starting
if self._onroad_time_delay is not None and rl.get_time() - self._onroad_time_delay >= ONROAD_DELAY:
self._set_mode_for_started(True)
gui_app.pop_widgets_to(self)
self._scroll_to(self._onroad_layout)
self._onroad_time_delay = None

# When car leaves standstill, pop nav stack and scroll to onroad
CS = ui_state.sm["carState"]
if not CS.standstill and self._prev_standstill:
self._set_mode(MainState.MAIN)
gui_app.pop_widgets_to(self)
self._scroll_to(self._onroad_layout)
self._prev_standstill = CS.standstill

def _set_mode_for_started(self, onroad_transition: bool = False):
def _on_interactive_timeout(self):
# Don't pop if onboarding
if gui_app.get_active_widget() == self._onboarding_window:
return

if ui_state.started:
CS = ui_state.sm["carState"]
# Only go onroad if car starts or is not at a standstill
if not CS.standstill or onroad_transition:
self._set_mode(MainState.MAIN)
# Don't pop if at standstill
if not ui_state.sm["carState"].standstill:
gui_app.pop_widgets_to(self)
self._scroll_to(self._onroad_layout)
else:
# Stay in settings if car turns off while in settings
if not onroad_transition or self._current_mode != MainState.SETTINGS:
self._set_mode(MainState.MAIN)
self._scroll_to(self._home_layout)

def _on_settings_clicked(self):
self._set_mode(MainState.SETTINGS)

def _on_settings_closed(self):
self._set_mode(MainState.MAIN)
gui_app.pop_widgets_to(self)
self._scroll_to(self._home_layout)

def _on_bookmark_clicked(self):
user_bookmark = messaging.new_message('bookmarkButton')
Expand Down
14 changes: 7 additions & 7 deletions selfdrive/ui/mici/layouts/onboarding.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
from openpilot.common.filter_simple import FirstOrderFilter
from openpilot.system.hardware import HARDWARE
from openpilot.system.ui.lib.application import FontWeight, gui_app
from openpilot.system.ui.widgets import Widget
from openpilot.system.ui.widgets import Widget, NavWidget
from openpilot.system.ui.widgets.button import SmallButton, SmallCircleIconButton
from openpilot.system.ui.widgets.label import UnifiedLabel
from openpilot.system.ui.widgets.slider import SmallSlider
Expand Down Expand Up @@ -42,7 +42,7 @@ def _render(self, rect):
gui_label(rect, tr("camera starting"), font_size=64, font_weight=FontWeight.BOLD,
alignment=rl.GuiTextAlignment.TEXT_ALIGN_CENTER)
rl.end_scissor_mode()
return -1
return

# Position dmoji on opposite side from driver
is_rhd = self.driver_state_renderer.is_rhd
Expand All @@ -55,7 +55,6 @@ def _render(self, rect):
self._draw_face_detection(rect)

rl.end_scissor_mode()
return -1


class TrainingGuidePreDMTutorial(SetupTermsPage):
Expand Down Expand Up @@ -367,9 +366,9 @@ def _advance_step(self):
self._completed_callback()

def _render(self, _):
rl.draw_rectangle_rec(self._rect, rl.BLACK)
if self._step < len(self._steps):
self._steps[self._step].render(self._rect)
return -1


class DeclinePage(Widget):
Expand Down Expand Up @@ -438,9 +437,11 @@ def _render_content(self, scroll_offset):
))


class OnboardingWindow(Widget):
class OnboardingWindow(NavWidget):
def __init__(self):
super().__init__()
self.set_back_enabled(False)

self._accepted_terms: bool = ui_state.params.get("HasAcceptedTerms") == terms_version
self._training_done: bool = ui_state.params.get("CompletedTrainingVersion") == training_version

Expand Down Expand Up @@ -473,7 +474,7 @@ def _on_decline_back(self):

def close(self):
ui_state.params.put_bool("IsDriverViewEnabled", False)
gui_app.set_modal_overlay(None)
gui_app.pop_widget()

def _on_terms_accepted(self):
ui_state.params.put("HasAcceptedTerms", terms_version)
Expand All @@ -490,4 +491,3 @@ def _render(self, _):
self._training_guide.render(self._rect)
elif self._state == OnboardingState.DECLINE:
self._decline_page.render(self._rect)
return -1
11 changes: 5 additions & 6 deletions selfdrive/ui/mici/layouts/settings/developer.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import pyray as rl
from collections.abc import Callable

from openpilot.common.time_helpers import system_time_valid
from openpilot.system.ui.widgets.scroller import Scroller
Expand All @@ -13,9 +12,9 @@


class DeveloperLayoutMici(NavWidget):
def __init__(self, back_callback: Callable):
def __init__(self):
super().__init__()
self.set_back_callback(back_callback)
self.set_back_callback(gui_app.pop_widget)

def github_username_callback(username: str):
if username:
Expand All @@ -25,16 +24,16 @@ def github_username_callback(username: str):
self._ssh_keys_btn.set_value(username)
else:
dlg = BigDialog("", ssh_keys._error_message)
gui_app.set_modal_overlay(dlg)
gui_app.push_widget(dlg)

def ssh_keys_callback():
github_username = ui_state.params.get("GithubUsername") or ""
dlg = BigInputDialog("enter GitHub username...", github_username, confirm_callback=github_username_callback)
if not system_time_valid():
dlg = BigDialog("Please connect to Wi-Fi to fetch your key", "")
gui_app.set_modal_overlay(dlg)
gui_app.push_widget(dlg)
return
gui_app.set_modal_overlay(dlg)
gui_app.push_widget(dlg)

txt_ssh = gui_app.texture("icons_mici/settings/developer/ssh.png", 56, 64)
github_username = ui_state.params.get("GithubUsername") or ""
Expand Down
38 changes: 11 additions & 27 deletions selfdrive/ui/mici/layouts/settings/device.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ class MiciFccModal(NavWidget):

def __init__(self, file_path: str | None = None, text: str | None = None):
super().__init__()
self.set_back_callback(lambda: gui_app.set_modal_overlay(None))
self.set_back_callback(gui_app.pop_widget)
self._content = HtmlRenderer(file_path=file_path, text=text)
self._scroll_panel = GuiScrollPanel2(horizontal=False)
self._fcc_logo = gui_app.texture("icons_mici/settings/device/fcc_logo.png", 76, 64)
Expand All @@ -47,8 +47,6 @@ def _render(self, rect: rl.Rectangle):

rl.draw_texture_ex(self._fcc_logo, fcc_pos, 0.0, 1.0, rl.WHITE)

return -1


def _engaged_confirmation_callback(callback: Callable, action_text: str):
if not ui_state.engaged:
Expand All @@ -74,10 +72,10 @@ def confirm_callback():
dlg: BigConfirmationDialogV2 | BigDialog = BigConfirmationDialogV2(f"slide to\n{action_text.lower()}", icon, red=red,
exit_on_confirm=action_text == "reset",
confirm_callback=confirm_callback)
gui_app.set_modal_overlay(dlg)
gui_app.push_widget(dlg)
else:
dlg = BigDialog(f"Disengage to {action_text}", "")
gui_app.set_modal_overlay(dlg)
gui_app.push_widget(dlg)


class DeviceInfoLayoutMici(Widget):
Expand Down Expand Up @@ -147,7 +145,7 @@ def _handle_mouse_release(self, mouse_pos: MousePos):
dlg = BigDialog(tr("Device must be registered with the comma.ai backend to pair"), "")
else:
dlg = PairingDialog()
gui_app.set_modal_overlay(dlg)
gui_app.push_widget(dlg)


UPDATER_TIMEOUT = 10.0 # seconds to wait for updater to respond
Expand All @@ -173,7 +171,7 @@ def offroad_transition(self):
def _handle_mouse_release(self, mouse_pos: MousePos):
if not system_time_valid():
dlg = BigDialog(tr("Please connect to Wi-Fi to update"), "")
gui_app.set_modal_overlay(dlg)
gui_app.push_widget(dlg)
return

self.set_enabled(False)
Expand Down Expand Up @@ -268,12 +266,10 @@ def _update_state(self):


class DeviceLayoutMici(NavWidget):
def __init__(self, back_callback: Callable):
def __init__(self):
super().__init__()

self._fcc_dialog: HtmlModal | None = None
self._driver_camera: DriverCameraDialog | None = None
self._training_guide: TrainingGuide | None = None

def power_off_callback():
ui_state.params.put_bool("DoShutdown", True)
Expand Down Expand Up @@ -309,11 +305,11 @@ def uninstall_openpilot_callback():
regulatory_btn.set_click_callback(self._on_regulatory)

driver_cam_btn = BigButton("driver\ncamera preview", "", "icons_mici/settings/device/cameras.png")
driver_cam_btn.set_click_callback(self._show_driver_camera)
driver_cam_btn.set_click_callback(lambda: gui_app.push_widget(DriverCameraDialog()))
driver_cam_btn.set_enabled(lambda: ui_state.is_offroad())

review_training_guide_btn = BigButton("review\ntraining guide", "", "icons_mici/settings/device/info.png")
review_training_guide_btn.set_click_callback(self._on_review_training_guide)
review_training_guide_btn.set_click_callback(lambda: gui_app.push_widget(TrainingGuide(completed_callback=gui_app.pop_widget)))
review_training_guide_btn.set_enabled(lambda: ui_state.is_offroad())

self._scroller = Scroller([
Expand All @@ -330,32 +326,20 @@ def uninstall_openpilot_callback():
], snap_items=False)

# Set up back navigation
self.set_back_callback(back_callback)
# TODO: can this somehow be generic in widgets/__init__.py or application.py?
self.set_back_callback(gui_app.pop_widget)

# Hide power off button when onroad
ui_state.add_offroad_transition_callback(self._offroad_transition)

def _on_regulatory(self):
if not self._fcc_dialog:
self._fcc_dialog = MiciFccModal(os.path.join(BASEDIR, "selfdrive/assets/offroad/mici_fcc.html"))
gui_app.set_modal_overlay(self._fcc_dialog)
gui_app.push_widget(self._fcc_dialog)

def _offroad_transition(self):
self._power_off_btn.set_visible(ui_state.is_offroad())

def _show_driver_camera(self):
if not self._driver_camera:
self._driver_camera = DriverCameraDialog()
gui_app.set_modal_overlay(self._driver_camera, callback=lambda result: setattr(self, '_driver_camera', None))

def _on_review_training_guide(self):
if not self._training_guide:
def completed_callback():
gui_app.set_modal_overlay(None)

self._training_guide = TrainingGuide(completed_callback=completed_callback)
gui_app.set_modal_overlay(self._training_guide, callback=lambda result: setattr(self, '_training_guide', None))

def show_event(self):
super().show_event()
self._scroller.show_event()
Expand Down
7 changes: 2 additions & 5 deletions selfdrive/ui/mici/layouts/settings/firehose.py
Original file line number Diff line number Diff line change
Expand Up @@ -132,9 +132,6 @@ def _render(self, rect: rl.Rectangle):
y = self._draw_wrapped_text(x, y, w, tr(answer), gui_app.font(FontWeight.ROMAN), 32, self.LIGHT_GRAY)
y += 20

# return value not used by NavWidget
return -1

def _draw_wrapped_text(self, x, y, width, text, font, font_size, color):
wrapped = wrap_text(font, text, font_size, width)
for line in wrapped:
Expand Down Expand Up @@ -223,6 +220,6 @@ def _update_loop(self):
class FirehoseLayout(FirehoseLayoutBase, NavWidget):
BACK_TOUCH_AREA_PERCENTAGE = 0.1

def __init__(self, back_callback):
def __init__(self):
super().__init__()
self.set_back_callback(back_callback)
self.set_back_callback(gui_app.pop_widget)
Loading