BigUI WIP: We need tabs

This commit is contained in:
firestarsdog
2026-04-07 00:01:31 -04:00
parent 3ca76720fc
commit 5f3c593640
8 changed files with 237 additions and 111 deletions
@@ -83,8 +83,7 @@ class AetherTile(Widget):
self._is_pressed = False
def _handle_mouse_event(self, mouse_event):
if self._is_pressed and not rl.check_collision_point_rec(mouse_event.pos, self._hit_rect):
self._is_pressed = False
if not rl.check_collision_point_rec(mouse_event.pos, self._hit_rect):
self._plate_target = 0.0
def _animate_plate(self, dt: float):
@@ -216,35 +215,47 @@ class HubTile(AetherTile):
class ToggleTile(AetherTile):
def __init__(self, title: str, get_state: Callable[[], bool], set_state: Callable[[bool], None], icon_path: str | None = None,
bg_color: rl.Color | str | None = None, desc: str = ""):
bg_color: rl.Color | str | None = None, desc: str = "", is_enabled: Callable[[], bool] | None = None, disabled_label: str = ""):
if bg_color: super().__init__(surface_color=bg_color)
else: super().__init__(surface_color=rl.Color(0, 163, 0, 255))
self.title = title
self.desc = desc
self.get_state = get_state
self.set_state = set_state
self.set_enabled(is_enabled or True)
self._icon = gui_app.starpilot_texture(icon_path, 80, 80) if icon_path else None
self._font = gui_app.font(FontWeight.BOLD)
self._font_desc = gui_app.font(FontWeight.NORMAL)
self._active_color = self.surface_color
self._inactive_color = rl.Color(120, 120, 120, 255)
self._disabled_color = rl.Color(75, 75, 75, 255)
self._disabled_label = disabled_label
def _handle_mouse_release(self, mouse_pos: MousePos):
if self._is_pressed:
if rl.check_collision_point_rec(mouse_pos, self._hit_rect):
if rl.check_collision_point_rec(mouse_pos, self._hit_rect) and self.enabled:
self.set_state(not self.get_state())
self._plate_target = 0.0
self._is_pressed = False
def _render(self, rect: rl.Rectangle):
enabled = self.enabled
active = self.get_state()
self.surface_color = self._active_color if active else self._inactive_color
if enabled:
self.surface_color = self._active_color if active else self._inactive_color
else:
self.surface_color = self._disabled_color
self._plate_offset = 0.0
self._plate_target = 0.0
face = self._render_layers(rect)
line_heights = [28, 30]
_, ty = self._centered_content(face, self._icon, 0.75, 28, len(line_heights), line_heights)
max_w = face.width - 40
self._draw_text_fit(self._font, self.title, rl.Vector2(face.x + 20, ty), max_w, 28, align_center=True, uppercase=True)
state_text = tr("ON") if active else tr("OFF")
if enabled:
state_text = tr("ON") if active else tr("OFF")
else:
state_text = tr(self._disabled_label) if self._disabled_label else tr("LOCKED")
self._draw_text_fit(self._font, state_text, rl.Vector2(face.x + 20, ty + 28 + 8), max_w, 30, align_center=True, uppercase=True)
if self.desc:
@@ -557,10 +568,15 @@ class RadioTileGroup(Widget):
self._option_targets.append(0.0)
for i in range(len(self._option_offsets)):
self._option_offsets[i] += (self._option_targets[i] - self._option_offsets[i]) * (1 - math.exp(-dt / PLATE_TAU))
title_size = measure_text_cached(self._font_title, self.title, 40)
rl.draw_text_ex(self._font_title, self.title, rl.Vector2(round(rect.x), round(rect.y + (rect.height - title_size.y) / 2)), 40, 0, rl.WHITE)
padding, option_w = 20, 200
start_x = rect.x + rect.width - (len(self.options) * (option_w + padding))
padding = 20
option_w = 240 if len(self.options) <= 3 else 200
total_width = len(self.options) * option_w + max(0, len(self.options) - 1) * padding
if self.title:
title_size = measure_text_cached(self._font_title, self.title, 40)
rl.draw_text_ex(self._font_title, self.title, rl.Vector2(round(rect.x), round(rect.y + (rect.height - title_size.y) / 2)), 40, 0, rl.WHITE)
start_x = rect.x + rect.width - total_width
else:
start_x = rect.x + (rect.width - total_width) / 2
for i, opt in enumerate(self.options):
r = rl.Rectangle(start_x + i * (option_w + padding), rect.y, option_w, rect.height)
self._option_rects.append(r)
@@ -582,9 +598,10 @@ class RadioTileGroup(Widget):
class TileGrid(Widget):
def __init__(self, columns: int | None = None, padding: int = 20):
def __init__(self, columns: int | None = None, padding: int = 20, uniform_width: bool = False):
super().__init__()
self._columns, self.padding, self.tiles = columns, padding, []
self._uniform_width = uniform_width
def add_tile(self, tile: Widget): self.tiles.append(tile)
@@ -607,13 +624,20 @@ class TileGrid(Widget):
else: cols = 4
rows = (count + cols - 1) // cols
tile_h = (rect.height - (self.padding * (rows - 1))) / rows
uniform_tile_w = (rect.width - (self.padding * (cols - 1))) / cols if self._uniform_width else 0
tile_idx = 0
for r in range(rows):
remaining = count - tile_idx
if remaining <= 0: break
items_in_row = min(cols, remaining)
row_tile_w = (rect.width - (self.padding * (items_in_row - 1))) / items_in_row
if self._uniform_width:
row_tile_w = uniform_tile_w
row_width = (row_tile_w * items_in_row) + (self.padding * (items_in_row - 1))
row_x = rect.x + (rect.width - row_width) / 2
else:
row_tile_w = (rect.width - (self.padding * (items_in_row - 1))) / items_in_row
row_x = rect.x
for c in range(items_in_row):
tile = tiles_to_render[tile_idx]
tile.render(rl.Rectangle(rect.x + c * (row_tile_w + self.padding), rect.y + r * (tile_h + self.padding), row_tile_w, tile_h))
tile.render(rl.Rectangle(row_x + c * (row_tile_w + self.padding), rect.y + r * (tile_h + self.padding), row_tile_w, tile_h))
tile_idx += 1
@@ -13,6 +13,7 @@ from openpilot.system.ui.widgets.confirm_dialog import ConfirmDialog, alert_dial
from openpilot.system.ui.widgets.selection_dialog import SelectionDialog
from openpilot.system.ui.widgets.input_dialog import InputDialog
from openpilot.selfdrive.ui.layouts.settings.starpilot.panel import StarPilotPanel
from openpilot.selfdrive.ui.layouts.settings.starpilot.aethergrid import TileGrid
LEGACY_STARPILOT_PARAM_RENAMES = {
"FrogPilotApiToken": "StarPilotApiToken",
@@ -51,6 +52,7 @@ class StarPilotDataLayout(StarPilotPanel):
"toggle_backups": StarPilotToggleBackupsLayout(),
"storage": StarPilotStorageLayout(),
}
self._tile_grid = TileGrid(columns=2, padding=20, uniform_width=True)
for name, panel in self._sub_panels.items():
if hasattr(panel, 'set_navigate_callback'):
@@ -91,6 +93,7 @@ class StarPilotDataLayout(StarPilotPanel):
class StarPilotBackupsLayout(StarPilotPanel):
def __init__(self):
super().__init__()
self._tile_grid = TileGrid(columns=2, padding=20, uniform_width=True)
self.CATEGORIES = [
{"title": tr_noop("Create Backup"), "type": "hub", "on_click": self._on_create_backup, "color": "#D43D8A"},
{"title": tr_noop("Restore Backup"), "type": "hub", "on_click": self._on_restore_backup, "color": "#D43D8A"},
@@ -158,6 +161,7 @@ class StarPilotBackupsLayout(StarPilotPanel):
class StarPilotToggleBackupsLayout(StarPilotPanel):
def __init__(self):
super().__init__()
self._tile_grid = TileGrid(columns=2, padding=20, uniform_width=True)
self.CATEGORIES = [
{"title": tr_noop("Create Toggle Backup"), "type": "hub", "on_click": self._on_create, "color": "#D43D8A"},
{"title": tr_noop("Restore Toggle Backup"), "type": "hub", "on_click": self._on_restore, "color": "#D43D8A"},
@@ -232,8 +236,9 @@ class StarPilotToggleBackupsLayout(StarPilotPanel):
class StarPilotStorageLayout(StarPilotPanel):
def __init__(self):
super().__init__()
self._tile_grid = TileGrid(columns=2, padding=20, uniform_width=True)
self.CATEGORIES = [
{"title": tr_noop("Driving Data"), "type": "value", "get_value": self._get_storage, "on_click": lambda: None, "color": "#D43D8A"},
{"title": tr_noop("Driving Data"), "type": "value", "get_value": self._get_storage, "on_click": lambda: None, "color": "#D43D8A", "is_enabled": lambda: False},
]
self._rebuild_grid()
@@ -9,7 +9,7 @@ from openpilot.system.ui.widgets import DialogResult
from openpilot.system.ui.widgets.confirm_dialog import ConfirmDialog
from openpilot.system.ui.widgets.selection_dialog import SelectionDialog
from openpilot.selfdrive.ui.layouts.settings.starpilot.panel import StarPilotPanel
from openpilot.selfdrive.ui.layouts.settings.starpilot.aethergrid import AetherSliderDialog
from openpilot.selfdrive.ui.layouts.settings.starpilot.aethergrid import AetherSliderDialog, TileGrid
class StarPilotDeviceLayout(StarPilotPanel):
@@ -61,6 +61,7 @@ class StarPilotDeviceLayout(StarPilotPanel):
"screen": StarPilotScreenLayout(),
"device_settings": StarPilotDeviceManagementLayout(),
}
self._tile_grid = TileGrid(columns=2, padding=20, uniform_width=True)
for name, panel in self._sub_panels.items():
if hasattr(panel, 'set_navigate_callback'):
@@ -128,6 +129,7 @@ class StarPilotDeviceLayout(StarPilotPanel):
class StarPilotScreenLayout(StarPilotPanel):
def __init__(self):
super().__init__()
self._tile_grid = TileGrid(columns=2, padding=20, uniform_width=True)
self.CATEGORIES = [
{
"title": tr_noop("Screen Settings"),
@@ -222,6 +224,7 @@ class StarPilotScreenLayout(StarPilotPanel):
class StarPilotDeviceManagementLayout(StarPilotPanel):
def __init__(self):
super().__init__()
self._tile_grid = TileGrid(columns=2, padding=20, uniform_width=True)
self.CATEGORIES = [
{
"title": tr_noop("Device Settings"),
@@ -16,6 +16,7 @@ from openpilot.selfdrive.ui.layouts.settings.starpilot.maps import StarPilotMaps
from openpilot.selfdrive.ui.layouts.settings.starpilot.navigation import StarPilotNavigationLayout
from openpilot.selfdrive.ui.layouts.settings.starpilot.data import StarPilotDataLayout
from openpilot.selfdrive.ui.layouts.settings.starpilot.device import StarPilotDeviceLayout
from openpilot.selfdrive.ui.layouts.settings.starpilot.system_settings import StarPilotSystemLayout
from openpilot.selfdrive.ui.layouts.settings.starpilot.utilities import StarPilotUtilitiesLayout
from openpilot.selfdrive.ui.layouts.settings.starpilot.visuals import StarPilotVisualsLayout
from openpilot.selfdrive.ui.layouts.settings.starpilot.themes import StarPilotThemesLayout
@@ -52,8 +53,8 @@ class StarPilotLayout(Widget):
{
"title": "System Settings",
"icon": "icon_system.png",
"desc": "Manage backups, device settings, screen options, storage, and tools to keep StarPilot running smoothly.",
"buttons": [("DATA", "DATA", 0), ("DEVICE CONTROLS", "DEVICE", 0), ("UTILITIES", "UTILITIES", 0)],
"desc": "Adjust device behavior, manage storage and backups, and access maintenance tools from one touch-friendly settings page.",
"buttons": [("MANAGE", "SYSTEM", 0)],
"color": "#D946EF",
},
{
@@ -87,6 +88,7 @@ class StarPilotLayout(Widget):
self._panels = {
StarPilotPanelType.MAIN: StarPilotPanelInfo("", None),
StarPilotPanelType.SOUNDS: StarPilotPanelInfo(tr_noop("Sounds"), StarPilotSoundsLayout()),
StarPilotPanelType.SYSTEM: StarPilotPanelInfo(tr_noop("System Settings"), StarPilotSystemLayout()),
StarPilotPanelType.DRIVING_MODEL: StarPilotPanelInfo(tr_noop("Driving Model"), StarPilotDrivingModelLayout()),
StarPilotPanelType.LONGITUDINAL: StarPilotPanelInfo(tr_noop("Gas / Brake"), StarPilotLongitudinalLayout()),
StarPilotPanelType.LATERAL: StarPilotPanelInfo(tr_noop("Steering"), StarPilotLateralLayout()),
@@ -101,11 +103,14 @@ class StarPilotLayout(Widget):
StarPilotPanelType.WHEEL: StarPilotPanelInfo(tr_noop("Wheel Controls"), StarPilotWheelLayout()),
}
self._setup_longitudinal_sub_panels()
self._setup_sounds_sub_panels()
self._setup_lateral_sub_panels()
self._setup_navigation_sub_panels()
self._setup_maps_sub_panels()
self._setup_sub_panels(
StarPilotPanelType.LONGITUDINAL,
StarPilotPanelType.SOUNDS,
StarPilotPanelType.SYSTEM,
StarPilotPanelType.LATERAL,
StarPilotPanelType.NAVIGATION,
StarPilotPanelType.MAPS,
)
self._main_grid = TileGrid(columns=None, padding=20)
self._rebuild_grid()
@@ -162,66 +167,28 @@ class StarPilotLayout(Widget):
self._update_depth()
def _update_sub_panel_visibility(self):
if self._current_panel == StarPilotPanelType.LONGITUDINAL:
longitudinal = self._panels[StarPilotPanelType.LONGITUDINAL].instance
if longitudinal:
current_sub = self._get_current_sub_panel()
if hasattr(longitudinal, 'set_current_sub_panel'):
longitudinal.set_current_sub_panel(current_sub)
elif self._current_panel == StarPilotPanelType.SOUNDS:
sounds = self._panels[StarPilotPanelType.SOUNDS].instance
if sounds:
current_sub = self._get_current_sub_panel()
if hasattr(sounds, '_navigate_to'):
sounds._current_sub_panel = current_sub
elif self._current_panel == StarPilotPanelType.NAVIGATION:
nav = self._panels[StarPilotPanelType.NAVIGATION].instance
if nav:
current_sub = self._get_current_sub_panel()
if hasattr(nav, '_navigate_to'):
nav._current_sub_panel = current_sub
elif self._current_panel == StarPilotPanelType.MAPS:
maps = self._panels[StarPilotPanelType.MAPS].instance
if maps:
current_sub = self._get_current_sub_panel()
if hasattr(maps, '_navigate_to'):
maps._current_sub_panel = current_sub
panel = self._panels[self._current_panel].instance
current_sub = self._get_current_sub_panel()
if panel and hasattr(panel, 'set_current_sub_panel'):
panel.set_current_sub_panel(current_sub)
def _get_current_sub_panel(self) -> str:
if self._panel_stack and self._panel_stack[-1][0] == self._current_panel:
return self._panel_stack[-1][1]
return ""
def _setup_longitudinal_sub_panels(self):
longitudinal = self._panels[StarPilotPanelType.LONGITUDINAL].instance
if longitudinal and hasattr(longitudinal, 'set_navigate_callback'):
longitudinal.set_navigate_callback(self._push_sub_panel)
def _setup_sounds_sub_panels(self):
sounds = self._panels[StarPilotPanelType.SOUNDS].instance
if sounds and hasattr(sounds, 'set_navigate_callback'):
sounds.set_navigate_callback(self._push_sub_panel)
def _setup_lateral_sub_panels(self):
lateral = self._panels[StarPilotPanelType.LATERAL].instance
if lateral and hasattr(lateral, 'set_navigate_callback'):
lateral.set_navigate_callback(self._push_sub_panel)
def _setup_navigation_sub_panels(self):
nav = self._panels[StarPilotPanelType.NAVIGATION].instance
if nav and hasattr(nav, 'set_navigate_callback'):
nav.set_navigate_callback(self._push_sub_panel)
def _setup_maps_sub_panels(self):
maps = self._panels[StarPilotPanelType.MAPS].instance
if maps and hasattr(maps, 'set_navigate_callback'):
maps.set_navigate_callback(self._push_sub_panel)
def _setup_sub_panels(self, *panel_types: StarPilotPanelType):
for panel_type in panel_types:
panel = self._panels[panel_type].instance
if panel and hasattr(panel, 'set_navigate_callback'):
panel.set_navigate_callback(self._push_sub_panel)
def _rebuild_grid(self):
self._main_grid.clear()
panel_type_map = {
"SOUNDS": StarPilotPanelType.SOUNDS,
"SYSTEM": StarPilotPanelType.SYSTEM,
"DRIVING_MODEL": StarPilotPanelType.DRIVING_MODEL,
"LONGITUDINAL": StarPilotPanelType.LONGITUDINAL,
"LATERAL": StarPilotPanelType.LATERAL,
@@ -25,6 +25,7 @@ class StarPilotPanelType(IntEnum):
THEMES = 11
VEHICLE = 12
WHEEL = 13
SYSTEM = 14
@dataclass
@@ -55,6 +56,9 @@ class StarPilotPanel(Widget):
def set_back_callback(self, callback: Callable):
self._back_callback = callback
def set_current_sub_panel(self, sub_panel: str):
self._current_sub_panel = sub_panel
def _rebuild_grid(self):
if not self.CATEGORIES:
return
@@ -84,9 +88,9 @@ class StarPilotPanel(Widget):
bg_color=cat.get("color"),
)
elif tile_type == "toggle":
tile = ToggleTile(title=tr(cat["title"]), get_state=cat["get_state"], set_state=cat["set_state"], icon_path=cat.get("icon"), bg_color=cat.get("color"), desc=tr(cat.get("desc", "")))
tile = ToggleTile(title=tr(cat["title"]), get_state=cat["get_state"], set_state=cat["set_state"], icon_path=cat.get("icon"), bg_color=cat.get("color"), desc=tr(cat.get("desc", "")), is_enabled=cat.get("is_enabled"), disabled_label=cat.get("disabled_label", ""))
elif tile_type == "value":
tile = ValueTile(title=tr(cat["title"]), get_value=cat["get_value"], on_click=cat["on_click"], icon_path=cat.get("icon"), bg_color=cat.get("color"), desc=tr(cat.get("desc", "")))
tile = ValueTile(title=tr(cat["title"]), get_value=cat["get_value"], on_click=cat["on_click"], icon_path=cat.get("icon"), bg_color=cat.get("color"), is_enabled=cat.get("is_enabled"), desc=tr(cat.get("desc", "")))
else:
continue
@@ -2,16 +2,17 @@ from __future__ import annotations
import subprocess
from pathlib import Path
import pyray as rl
from openpilot.common.basedir import BASEDIR
from openpilot.starpilot.common.starpilot_variables import ACTIVE_THEME_PATH
from openpilot.system.ui.lib.application import gui_app
from openpilot.system.ui.lib.multilang import tr, tr_noop
from openpilot.system.ui.widgets import DialogResult
from openpilot.system.ui.widgets.selection_dialog import SelectionDialog
from openpilot.selfdrive.ui.ui_state import ui_state
from openpilot.selfdrive.ui.lib.starpilot_state import starpilot_state
from openpilot.selfdrive.ui.layouts.settings.starpilot.panel import StarPilotPanel
from openpilot.selfdrive.ui.layouts.settings.starpilot.aethergrid import TileGrid, ToggleTile, AetherSliderDialog
from openpilot.selfdrive.ui.layouts.settings.starpilot.aethergrid import TileGrid, ToggleTile, AetherSliderDialog, RadioTileGroup
class StarPilotSoundsLayout(StarPilotPanel):
COOLDOWN_KEY = "SwitchbackModeCooldown"
@@ -36,41 +37,73 @@ class StarPilotSoundsLayout(StarPilotPanel):
def __init__(self):
super().__init__()
self._section_names = ["volume_control", "custom_alerts"]
self._active_section = self._section_names[0]
self._sub_panels = {
"volume_control": StarPilotVolumeControlLayout(),
"custom_alerts": StarPilotCustomAlertsLayout(),
}
self.CATEGORIES = [
{
"title": tr_noop("Alert Volume Controller"),
"panel": "volume_control",
"desc": tr_noop("Adjust volume levels for different alert types."),
"icon": "toggle_icons/icon_mute.png",
"color": "#E63956"
},
{
"title": tr_noop("StarPilot Alerts"),
"panel": "custom_alerts",
"desc": tr_noop("Enable or disable specific StarPilot-only alerts."),
"icon": "toggle_icons/icon_green_light.png",
"color": "#E63956"
},
]
self._section_tabs = RadioTileGroup(
"",
[tr("Volumes"), tr("Alerts")],
0,
self._on_section_change,
)
for name, panel in self._sub_panels.items():
if hasattr(panel, 'set_navigate_callback'):
panel.set_navigate_callback(self._navigate_to)
panel.set_navigate_callback(lambda sub_panel, section_name=name: self._navigate_to_child(section_name, sub_panel))
if hasattr(panel, 'set_back_callback'):
panel.set_back_callback(self._go_back)
self._rebuild_grid()
def _on_section_change(self, index: int):
if 0 <= index < len(self._section_names):
self._set_active_section(self._section_names[index])
def refresh_visibility(self):
for panel in self._sub_panels.values():
if hasattr(panel, 'refresh_visibility'):
panel.refresh_visibility()
def _set_active_section(self, section_name: str, child_panel: str = ""):
if section_name not in self._sub_panels:
return
if section_name != self._active_section:
self._sub_panels[self._active_section].hide_event()
self._active_section = section_name
self._sub_panels[self._active_section].show_event()
self._section_tabs.set_index(self._section_names.index(section_name))
panel = self._sub_panels[section_name]
if hasattr(panel, 'set_current_sub_panel'):
panel.set_current_sub_panel(child_panel)
def _navigate_to_child(self, section_name: str, child_panel: str):
self._set_active_section(section_name, child_panel)
if self._navigate_callback:
self._navigate_callback(f"{section_name}:{child_panel}")
def set_current_sub_panel(self, sub_panel: str):
super().set_current_sub_panel(sub_panel)
if not sub_panel:
self._set_active_section(self._active_section, "")
return
if ":" in sub_panel:
section_name, child_panel = sub_panel.split(":", 1)
self._set_active_section(section_name, child_panel)
elif sub_panel in self._section_names:
self._set_active_section(sub_panel)
def _render(self, rect):
tab_rect = rl.Rectangle(rect.x, rect.y, rect.width, 110)
panel_rect = rl.Rectangle(rect.x, rect.y + 140, rect.width, rect.height - 140)
self._section_tabs.render(tab_rect)
self._sub_panels[self._active_section].render(panel_rect)
def show_event(self):
super().show_event()
self._sub_panels[self._active_section].show_event()
def hide_event(self):
super().hide_event()
self._sub_panels[self._active_section].hide_event()
class StarPilotVolumeControlLayout(StarPilotPanel):
COOLDOWN_INFO = {"title": tr_noop("Switchback Mode Cooldown"), "icon": "toggle_icons/icon_mute.png", "min": 0, "max": 30}
@@ -90,6 +123,7 @@ class StarPilotVolumeControlLayout(StarPilotPanel):
def __init__(self):
super().__init__()
self._init_sound_player()
self._tile_grid = TileGrid(columns=2, padding=20, uniform_width=True)
self.CATEGORIES = []
for key in StarPilotSoundsLayout.VOLUME_KEYS:
@@ -217,6 +251,7 @@ class StarPilotCustomAlertsLayout(StarPilotPanel):
def __init__(self):
super().__init__()
self._tile_grid = TileGrid(columns=2, padding=20, uniform_width=True)
self.CATEGORIES = []
for key in StarPilotSoundsLayout.CUSTOM_ALERTS_KEYS:
info = self.ALERT_INFO[key]
@@ -231,32 +266,32 @@ class StarPilotCustomAlertsLayout(StarPilotPanel):
})
self._rebuild_grid()
def refresh_visibility(self):
self._rebuild_grid()
def _rebuild_grid(self):
# Override to add custom BSM/SLC visibility logic
if not self.CATEGORIES: return
if self._tile_grid is None: self._tile_grid = TileGrid(columns=None, padding=20)
if not self.CATEGORIES:
return
self._tile_grid.clear()
for cat in self.CATEGORIES:
key = cat.get("key")
visible = True
is_enabled = lambda: True
disabled_label = ""
if key == "LoudBlindspotAlert":
visible &= starpilot_state.car_state.hasBSM
is_enabled = lambda: starpilot_state.car_state.hasBSM
disabled_label = tr_noop("Needs BSM")
elif key == "SpeedLimitChangedAlert":
visible &= self._params.get_bool("ShowSpeedLimits") or (
is_enabled = lambda: self._params.get_bool("ShowSpeedLimits") or (
starpilot_state.car_state.hasOpenpilotLongitudinal and self._params.get_bool("SpeedLimitController")
)
disabled_label = tr_noop("Needs Speed Limits")
if not visible: continue
tile = ToggleTile( title=tr(cat["title"]),
tile = ToggleTile(
title=tr(cat["title"]),
get_state=cat["get_state"],
set_state=cat["set_state"],
icon_path=cat.get("icon"),
bg_color=cat.get("color")
bg_color=cat.get("color"),
is_enabled=is_enabled,
disabled_label=tr(disabled_label) if disabled_label else "",
)
self._tile_grid.add_tile(tile)
@@ -0,0 +1,86 @@
from __future__ import annotations
import pyray as rl
from openpilot.system.ui.lib.multilang import tr
from openpilot.selfdrive.ui.layouts.settings.starpilot.panel import StarPilotPanel
from openpilot.selfdrive.ui.layouts.settings.starpilot.aethergrid import RadioTileGroup
from openpilot.selfdrive.ui.layouts.settings.starpilot.data import StarPilotDataLayout
from openpilot.selfdrive.ui.layouts.settings.starpilot.device import StarPilotDeviceLayout
from openpilot.selfdrive.ui.layouts.settings.starpilot.utilities import StarPilotUtilitiesLayout
class StarPilotSystemLayout(StarPilotPanel):
def __init__(self):
super().__init__()
self._section_names = ["device", "data_and_backups", "utilities"]
self._active_section = self._section_names[0]
self._sub_panels = {
"device": StarPilotDeviceLayout(),
"data_and_backups": StarPilotDataLayout(),
"utilities": StarPilotUtilitiesLayout(),
}
self._section_tabs = RadioTileGroup(
"",
[tr("Device"), tr("Data"), tr("Utilities")],
0,
self._on_section_change,
)
for name, panel in self._sub_panels.items():
if hasattr(panel, 'set_navigate_callback'):
panel.set_navigate_callback(lambda sub_panel, section_name=name: self._navigate_to_child(section_name, sub_panel))
if hasattr(panel, 'set_back_callback'):
panel.set_back_callback(self._go_back)
def _on_section_change(self, index: int):
if 0 <= index < len(self._section_names):
self._set_active_section(self._section_names[index])
def _set_active_section(self, section_name: str, child_panel: str = ""):
if section_name not in self._sub_panels:
return
if section_name != self._active_section:
self._sub_panels[self._active_section].hide_event()
self._active_section = section_name
self._sub_panels[self._active_section].show_event()
self._section_tabs.set_index(self._section_names.index(section_name))
panel = self._sub_panels[section_name]
if hasattr(panel, 'set_current_sub_panel'):
panel.set_current_sub_panel(child_panel)
def _navigate_to_child(self, section_name: str, child_panel: str):
self._set_active_section(section_name, child_panel)
if self._navigate_callback:
self._navigate_callback(f"{section_name}:{child_panel}")
def set_current_sub_panel(self, sub_panel: str):
super().set_current_sub_panel(sub_panel)
if not sub_panel:
self._set_active_section(self._active_section, "")
return
if ":" in sub_panel:
section_name, child_panel = sub_panel.split(":", 1)
self._set_active_section(section_name, child_panel)
elif sub_panel in self._section_names:
self._set_active_section(sub_panel)
def _render(self, rect):
tab_rect = rl.Rectangle(rect.x, rect.y, rect.width, 110)
panel_rect = rl.Rectangle(rect.x, rect.y + 140, rect.width, rect.height - 140)
self._section_tabs.render(tab_rect)
self._sub_panels[self._active_section].render(panel_rect)
def show_event(self):
super().show_event()
self._sub_panels[self._active_section].show_event()
def hide_event(self):
super().hide_event()
self._sub_panels[self._active_section].hide_event()
@@ -8,6 +8,7 @@ from openpilot.system.ui.widgets.confirm_dialog import ConfirmDialog, alert_dial
from openpilot.system.ui.widgets.selection_dialog import SelectionDialog
from openpilot.system.ui.widgets.input_dialog import InputDialog
from openpilot.selfdrive.ui.layouts.settings.starpilot.panel import StarPilotPanel
from openpilot.selfdrive.ui.layouts.settings.starpilot.aethergrid import TileGrid
EXCLUDED_KEYS = {
"AvailableModels",
@@ -38,6 +39,7 @@ REPORT_CATEGORIES = [
class StarPilotUtilitiesLayout(StarPilotPanel):
def __init__(self):
super().__init__()
self._tile_grid = TileGrid(columns=2, padding=20, uniform_width=True)
self.CATEGORIES = [
{
"title": tr_noop("Debug Mode"),