Compare commits

...

2 Commits

Author SHA1 Message Date
Jason Wen 099143ad9d [mici] ui: fix swipe-down in sunnylink and models panels (#1896)
* [mici] ui: fix swipe-down in sunnylink and models panels

* update mici replay test

* [mici] ui: push model sub-views as separate widgets for correct back navigation
2026-08-05 23:22:36 -04:00
Jason Wen 6909aa95ff ui: use sunnypilot and sunnylink branding fonts (#1895)
* ui: use sunnypilot and sunnylink branding fonts

* split
2026-08-05 22:53:04 -04:00
9 changed files with 104 additions and 26 deletions
+1
View File
@@ -13,6 +13,7 @@ from openpilot.selfdrive.ui.body.layouts.onroad import BodyLayout
if gui_app.sunnypilot_ui():
from openpilot.selfdrive.ui.sunnypilot.layouts.settings.settings import SettingsLayoutSP as SettingsLayout
from openpilot.selfdrive.ui.sunnypilot.layouts.home import HomeLayoutSP as HomeLayout
class MainState(IntEnum):
@@ -13,6 +13,7 @@ from openpilot.system.ui.lib.application import gui_app
if gui_app.sunnypilot_ui():
from openpilot.selfdrive.ui.sunnypilot.mici.layouts.settings import SettingsLayoutSP as SettingsLayout
from openpilot.selfdrive.ui.sunnypilot.mici.layouts.home import MiciHomeLayoutSP as MiciHomeLayout
ONROAD_DELAY = 2.5 # seconds
@@ -0,0 +1,68 @@
"""
Copyright (c) 2021-, Haibin Wen, sunnypilot, and a number of other contributors.
This file is part of sunnypilot and is licensed under the MIT License.
See the LICENSE.md file in the root directory for more details.
"""
import pyray as rl
from openpilot.selfdrive.ui.layouts.home import HomeLayout, HomeLayoutState, HEAD_BUTTON_FONT_SIZE, SPACING
from openpilot.system.ui.lib.application import gui_app, FontWeight
from openpilot.system.ui.lib.text_measure import measure_text_cached
from openpilot.system.ui.lib.multilang import tr, trn
from openpilot.system.ui.widgets.label import gui_label
BRAND_FONT_SIZE = 48
BRAND_DESC_SPACING = 12
class HomeLayoutSP(HomeLayout):
def _render_header(self):
font = gui_app.font(FontWeight.MEDIUM)
version_text_width = self.header_rect.width
if self.update_available:
version_text_width -= self.update_notif_rect.width
highlight_color = rl.Color(75, 95, 255, 255) if self.current_state == HomeLayoutState.UPDATE else rl.Color(54, 77, 239, 255)
rl.draw_rectangle_rounded(self.update_notif_rect, 0.3, 10, highlight_color)
text = tr("UPDATE")
text_size = measure_text_cached(font, text, HEAD_BUTTON_FONT_SIZE)
text_x = self.update_notif_rect.x + (self.update_notif_rect.width - text_size.x) // 2
text_y = self.update_notif_rect.y + (self.update_notif_rect.height - text_size.y) // 2
rl.draw_text_ex(font, text, rl.Vector2(int(text_x), int(text_y)), HEAD_BUTTON_FONT_SIZE, 0, rl.WHITE)
if self.alert_count > 0:
version_text_width -= self.alert_notif_rect.width
highlight_color = rl.Color(255, 70, 70, 255) if self.current_state == HomeLayoutState.ALERTS else rl.Color(226, 44, 44, 255)
rl.draw_rectangle_rounded(self.alert_notif_rect, 0.3, 10, highlight_color)
alert_text = trn("{} ALERT", "{} ALERTS", self.alert_count).format(self.alert_count)
text_size = measure_text_cached(font, alert_text, HEAD_BUTTON_FONT_SIZE)
text_x = self.alert_notif_rect.x + (self.alert_notif_rect.width - text_size.x) // 2
text_y = self.alert_notif_rect.y + (self.alert_notif_rect.height - text_size.y) // 2
rl.draw_text_ex(font, alert_text, rl.Vector2(int(text_x), int(text_y)), HEAD_BUTTON_FONT_SIZE, 0, rl.WHITE)
if self.update_available or self.alert_count > 0:
version_text_width -= SPACING * 1.5
version_right = self.header_rect.x + self.header_rect.width
version_left = version_right - version_text_width
brand = "sunnypilot"
description = self.params.get("UpdaterCurrentDescription") or ""
desc_width = 0
if description:
desc_size = measure_text_cached(gui_app.font(FontWeight.NORMAL), description, BRAND_FONT_SIZE)
desc_width = desc_size.x
desc_rect = rl.Rectangle(version_right - desc_width, self.header_rect.y, desc_width, self.header_rect.height)
gui_label(desc_rect, description, BRAND_FONT_SIZE, rl.WHITE, alignment=rl.GuiTextAlignment.TEXT_ALIGN_RIGHT)
brand_size = measure_text_cached(gui_app.font(FontWeight.AUDIOWIDE), brand, BRAND_FONT_SIZE)
spacing = BRAND_DESC_SPACING if description else 0
brand_x = version_right - desc_width - spacing - brand_size.x
brand_rect = rl.Rectangle(max(version_left, brand_x), self.header_rect.y, brand_size.x, self.header_rect.height)
gui_label(brand_rect, brand, BRAND_FONT_SIZE, rl.WHITE, font_weight=FontWeight.AUDIOWIDE)
@@ -20,7 +20,7 @@ class SunnylinkConsentPage(Widget):
self._done_callback = done_callback
self._step = 0
self._title = self._child(Label(tr("sunnylink"), font_size=90, font_weight=FontWeight.BOLD, text_alignment=rl.GuiTextAlignment.TEXT_ALIGN_LEFT))
self._title = self._child(Label(tr("sunnylink"), font_size=90, font_weight=FontWeight.AUDIOWIDE, text_alignment=rl.GuiTextAlignment.TEXT_ALIGN_LEFT))
self._content = [
{
@@ -0,0 +1,15 @@
"""
Copyright (c) 2021-, Haibin Wen, sunnypilot, and a number of other contributors.
This file is part of sunnypilot and is licensed under the MIT License.
See the LICENSE.md file in the root directory for more details.
"""
from openpilot.selfdrive.ui.mici.layouts.home import MiciHomeLayout
from openpilot.system.ui.lib.application import FontWeight
from openpilot.system.ui.widgets.label import UnifiedLabel
class MiciHomeLayoutSP(MiciHomeLayout):
def __init__(self):
super().__init__()
self._openpilot_label = UnifiedLabel("sunnypilot", font_size=88, font_weight=FontWeight.AUDIOWIDE, max_width=480, wrap_text=False)
@@ -4,7 +4,6 @@ Copyright (c) 2021-, Haibin Wen, sunnypilot, and a number of other contributors.
This file is part of sunnypilot and is licensed under the MIT License.
See the LICENSE.md file in the root directory for more details.
"""
from collections.abc import Callable
import pyray as rl
from openpilot.cereal import custom
@@ -48,10 +47,8 @@ class CurrentModelInfo(Widget):
self.info_text.render()
class ModelsLayoutMici(NavScroller):
def __init__(self, back_callback: Callable):
def __init__(self):
super().__init__()
self.set_back_callback(back_callback)
self.original_back_callback = back_callback
self.focused_widget = None
self.current_model_info = CurrentModelInfo()
@@ -85,12 +82,10 @@ class ModelsLayoutMici(NavScroller):
return folders
def _show_selection_view(self, items, back_callback: Callable):
self._scroller._items = items
for item in items:
item.set_touch_valid_callback(lambda: self._scroller.scroll_panel.is_touch_valid() and self._scroller.enabled)
self._scroller.scroll_panel.set_offset(0)
self.set_back_callback(back_callback)
def _push_selection_view(self, items):
scroller = NavScroller()
scroller._scroller.add_widgets(items)
gui_app.push_widget(scroller)
def _show_folders(self):
self.focused_widget = self.select_model_btn
@@ -112,15 +107,18 @@ class ModelsLayoutMici(NavScroller):
folder_buttons.insert(0, btn)
else:
folder_buttons.append(btn)
self._show_selection_view(folder_buttons, self._reset_main_view)
self._push_selection_view(folder_buttons)
def _pop_to_main(self):
gui_app.pop_widgets_to(self)
def _select_model(self, bundle):
ui_state.params.put("ModelManager_DownloadIndex", bundle.index)
self._reset_main_view()
self._pop_to_main()
def _select_default(self):
ui_state.params.remove("ModelManager_ActiveBundle")
self._reset_main_view()
self._pop_to_main()
def _select_folder(self, folder_name):
favs = ui_state.params.get("ModelManager_Favs")
@@ -135,13 +133,7 @@ class ModelsLayoutMici(NavScroller):
btn = BigButton(txt)
btn.set_click_callback(lambda b=bundle: self._select_model(b))
btns.append(btn)
self._show_selection_view(btns, self._show_folders)
def _reset_main_view(self):
self._scroller._items = self.main_items # type: ignore[assignment] # ty: ignore[invalid-assignment]
self.set_back_callback(self.original_back_callback)
self._scroller.scroll_panel.set_offset(0)
self._scroller.scroll_to(0)
self._push_selection_view(btns)
def hide_event(self):
super().hide_event()
@@ -32,11 +32,11 @@ class SettingsLayoutSP(OP.SettingsLayout):
BIG_ICON_SIZE)
self.icon_offroad_slider = gui_app.texture("icons_mici/settings/device/lkas.png", BIG_ICON_SIZE, BIG_ICON_SIZE)
sunnylink_panel = SunnylinkLayoutMici(back_callback=gui_app.pop_widget)
sunnylink_panel = SunnylinkLayoutMici()
sunnylink_btn = SettingsBigButton(tr("sunnylink"), "", gui_app.texture("icons_mici/settings/developer/ssh.png", 55, 55))
sunnylink_btn.set_click_callback(lambda: gui_app.push_widget(sunnylink_panel))
models_panel = ModelsLayoutMici(back_callback=gui_app.pop_widget)
models_panel = ModelsLayoutMici()
models_btn = SettingsBigButton(tr("models"), "", gui_app.texture("../../sunnypilot/selfdrive/assets/offroad/icon_models.png", ICON_SIZE, ICON_SIZE))
models_btn.set_click_callback(lambda: gui_app.push_widget(models_panel))
@@ -6,7 +6,6 @@ See the LICENSE.md file in the root directory for more details.
"""
import pyray as rl
from collections.abc import Callable
from openpilot.cereal import custom
from openpilot.selfdrive.ui.mici.widgets.button import BigButton, BigToggle
@@ -54,9 +53,8 @@ class SunnylinkInfo(Widget):
self.sponsor_text.render()
class SunnylinkLayoutMici(NavScroller):
def __init__(self, back_callback: Callable):
def __init__(self):
super().__init__()
self.set_back_callback(back_callback)
self._restore_in_progress = False
self._backup_in_progress = False
self._sunnylink_enabled = ui_state.params.get("SunnylinkEnabled")
@@ -338,8 +338,11 @@ def build_mici_script(pm: PubMaster, main_layout, script: Script) -> None:
settings_cases: Cases = [
lambda: scroll_through_cases(toggle_cases),
None, # sunnylink (just open and close)
None, # models (just open and close)
lambda: scroll_through_cases(network_cases),
lambda: scroll_through_cases(device_cases),
lambda: script.wait(WAIT_SHORT), # software
lambda: script.wait(WAIT_SHORT), # pairing
lambda: run_actions(lambda: swipe_up(height * 3), lambda: swipe_down(height * 3)), # firehose (scroll down and back up)
lambda: scroll_through_cases(developer_cases),