mirror of
https://github.com/firestar5683/StarPilot.git
synced 2026-07-03 20:42:09 +08:00
BigUI WIP: Apperance + Cleanup
This commit is contained in:
@@ -0,0 +1,611 @@
|
||||
from __future__ import annotations
|
||||
import re
|
||||
from pathlib import Path
|
||||
|
||||
from openpilot.system.hardware import HARDWARE
|
||||
from openpilot.system.hardware.hw import Paths
|
||||
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.option_dialog import MultiOptionDialog
|
||||
|
||||
from openpilot.selfdrive.ui.lib.starpilot_state import starpilot_state
|
||||
from openpilot.selfdrive.ui.layouts.settings.starpilot.panel import _SettingsPage
|
||||
from openpilot.selfdrive.ui.layouts.settings.starpilot.aethergrid import (
|
||||
AETHER_LIST_METRICS,
|
||||
AetherSliderDialog,
|
||||
DEFAULT_PANEL_STYLE,
|
||||
)
|
||||
from openpilot.selfdrive.ui.layouts.settings.starpilot.longitudinal import (
|
||||
SettingRow,
|
||||
SettingSection,
|
||||
AetherSettingsView,
|
||||
)
|
||||
|
||||
PANEL_STYLE = DEFAULT_PANEL_STYLE
|
||||
UTILITY_ROW_HEIGHT = AETHER_LIST_METRICS.utility_row_height
|
||||
ROW_HEIGHT = AETHER_LIST_METRICS.row_height
|
||||
|
||||
# ── Theme paths & config (preserved from themes.py) ──
|
||||
|
||||
if HARDWARE.get_device_type() == "pc":
|
||||
THEME_SAVE_PATH = Path(Paths.comma_home()) / "starpilot" / "data" / "themes"
|
||||
else:
|
||||
THEME_SAVE_PATH = Path("/data/themes")
|
||||
|
||||
HOLIDAY_THEME_NAMES = {
|
||||
"new_years": "New Year's",
|
||||
"valentines_day": "Valentine's Day",
|
||||
"st_patricks_day": "St. Patrick's Day",
|
||||
"world_frog_day": "World Frog Day",
|
||||
"april_fools": "April Fools",
|
||||
"easter_week": "Easter",
|
||||
"may_the_fourth": "May the Fourth",
|
||||
"cinco_de_mayo": "Cinco de Mayo",
|
||||
"stitch_day": "Stitch Day",
|
||||
"fourth_of_july": "Fourth of July",
|
||||
"halloween_week": "Halloween",
|
||||
"thanksgiving_week": "Thanksgiving",
|
||||
"christmas_week": "Christmas",
|
||||
}
|
||||
|
||||
THEME_KEY_CONFIG = {
|
||||
"BootLogo": {
|
||||
"default": "starpilot",
|
||||
"kind": "files",
|
||||
"path": THEME_SAVE_PATH / "bootlogos",
|
||||
"extra": [],
|
||||
},
|
||||
"ColorScheme": {
|
||||
"default": "stock",
|
||||
"kind": "themes",
|
||||
"path": THEME_SAVE_PATH / "theme_packs",
|
||||
"subfolder": "colors",
|
||||
"extra": [("stock", "Stock"), *HOLIDAY_THEME_NAMES.items()],
|
||||
},
|
||||
"DistanceIconPack": {
|
||||
"default": "stock",
|
||||
"kind": "themes",
|
||||
"path": THEME_SAVE_PATH / "theme_packs",
|
||||
"subfolder": "distance_icons",
|
||||
"extra": [("stock", "Stock"), *HOLIDAY_THEME_NAMES.items()],
|
||||
},
|
||||
"IconPack": {
|
||||
"default": "stock",
|
||||
"kind": "themes",
|
||||
"path": THEME_SAVE_PATH / "theme_packs",
|
||||
"subfolder": "icons",
|
||||
"extra": [("stock", "Stock"), *HOLIDAY_THEME_NAMES.items()],
|
||||
},
|
||||
"SignalAnimation": {
|
||||
"default": "stock",
|
||||
"kind": "themes",
|
||||
"path": THEME_SAVE_PATH / "theme_packs",
|
||||
"subfolder": "signals",
|
||||
"extra": [("none", "None"), *HOLIDAY_THEME_NAMES.items()],
|
||||
},
|
||||
"SoundPack": {
|
||||
"default": "stock",
|
||||
"kind": "themes",
|
||||
"path": THEME_SAVE_PATH / "theme_packs",
|
||||
"subfolder": "sounds",
|
||||
"extra": [("stock", "Stock"), *HOLIDAY_THEME_NAMES.items()],
|
||||
},
|
||||
"WheelIcon": {
|
||||
"default": "stock",
|
||||
"kind": "files",
|
||||
"path": THEME_SAVE_PATH / "steering_wheels",
|
||||
"extra": [("none", "None"), ("stock", "Stock"), *HOLIDAY_THEME_NAMES.items()],
|
||||
},
|
||||
}
|
||||
|
||||
COLOR_PRESETS = ["Stock", "#FFFFFF", "#178644", "#3B82F6", "#E63956", "#8B5CF6", "#F59E0B"]
|
||||
CAMERA_VIEWS = ["Auto", "Driver", "Standard", "Wide"]
|
||||
|
||||
|
||||
def _theme_display_name(value: str) -> str:
|
||||
if not value:
|
||||
return "Stock"
|
||||
lowered = value.lower()
|
||||
if lowered in HOLIDAY_THEME_NAMES:
|
||||
return HOLIDAY_THEME_NAMES[lowered]
|
||||
if lowered == "stock":
|
||||
return "Stock"
|
||||
if lowered == "none":
|
||||
return "None"
|
||||
base, creator = (value.split("~", 1) + [""])[:2] if "~" in value else (value, "")
|
||||
user_created_suffixes = ("-user_created", "_user_created", "-user-created", "_user-created")
|
||||
user_created = False
|
||||
for suffix in user_created_suffixes:
|
||||
if base.endswith(suffix):
|
||||
base = base[:-len(suffix)]
|
||||
user_created = True
|
||||
break
|
||||
parts = [part for part in re.split(r"[-_]+", base) if part]
|
||||
display = " ".join(part[:1].upper() + part[1:] for part in parts) if parts else value
|
||||
if user_created:
|
||||
display += " (User Created)"
|
||||
if creator:
|
||||
display += f" - by: {creator}"
|
||||
return display
|
||||
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
# Theme Personalize sub-panel
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
|
||||
class StarPilotAppearancePersonalizeLayout(_SettingsPage):
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
self._build_view()
|
||||
|
||||
def _build_view(self):
|
||||
sections: list[SettingSection] = [
|
||||
SettingSection(tr_noop("Theme Components"), [
|
||||
SettingRow("BootLogo", "value", tr_noop("Boot Logo"),
|
||||
subtitle="",
|
||||
get_value=lambda: self._get_theme_value("BootLogo"),
|
||||
on_click=lambda: self._show_theme_selector("BootLogo")),
|
||||
SettingRow("ColorScheme", "value", tr_noop("Color Scheme"),
|
||||
subtitle="",
|
||||
get_value=lambda: self._get_theme_value("ColorScheme"),
|
||||
on_click=lambda: self._show_theme_selector("ColorScheme")),
|
||||
SettingRow("DistanceIconPack", "value", tr_noop("Distance Icons"),
|
||||
subtitle="",
|
||||
get_value=lambda: self._get_theme_value("DistanceIconPack"),
|
||||
on_click=lambda: self._show_theme_selector("DistanceIconPack")),
|
||||
SettingRow("IconPack", "value", tr_noop("Icon Pack"),
|
||||
subtitle="",
|
||||
get_value=lambda: self._get_theme_value("IconPack"),
|
||||
on_click=lambda: self._show_theme_selector("IconPack")),
|
||||
SettingRow("SignalAnimation", "value", tr_noop("Turn Signals"),
|
||||
subtitle="",
|
||||
get_value=lambda: self._get_theme_value("SignalAnimation"),
|
||||
on_click=lambda: self._show_theme_selector("SignalAnimation")),
|
||||
SettingRow("SoundPack", "value", tr_noop("Sound Pack"),
|
||||
subtitle="",
|
||||
get_value=lambda: self._get_theme_value("SoundPack"),
|
||||
on_click=lambda: self._show_theme_selector("SoundPack")),
|
||||
SettingRow("WheelIcon", "value", tr_noop("Steering Wheel"),
|
||||
subtitle="",
|
||||
get_value=lambda: self._get_theme_value("WheelIcon"),
|
||||
on_click=lambda: self._show_theme_selector("WheelIcon")),
|
||||
], row_height=UTILITY_ROW_HEIGHT),
|
||||
]
|
||||
self._manager_view = AetherSettingsView(
|
||||
self, sections,
|
||||
header_title=tr_noop("Personalize"),
|
||||
header_subtitle=tr_noop("Customize the overall look and feel of openpilot."),
|
||||
panel_style=PANEL_STYLE,
|
||||
)
|
||||
|
||||
def _get_downloaded_slugs(self, key: str) -> list[str]:
|
||||
config = THEME_KEY_CONFIG[key]
|
||||
path = config["path"]
|
||||
if not path.exists():
|
||||
return []
|
||||
slugs = set()
|
||||
if config["kind"] == "files":
|
||||
for entry in path.iterdir():
|
||||
if entry.is_file():
|
||||
slugs.add(entry.stem)
|
||||
else:
|
||||
subfolder = config["subfolder"]
|
||||
for entry in path.iterdir():
|
||||
if entry.is_dir() and (entry / subfolder).exists():
|
||||
slugs.add(entry.name)
|
||||
return sorted(slugs, key=str.casefold)
|
||||
|
||||
def _build_theme_options(self, key: str) -> tuple[list[str], dict[str, str], str]:
|
||||
config = THEME_KEY_CONFIG[key]
|
||||
current_slug = self._params.get(key, encoding='utf-8') or config["default"]
|
||||
options_map = {}
|
||||
for slug in self._get_downloaded_slugs(key):
|
||||
display = _theme_display_name(slug)
|
||||
if display not in options_map:
|
||||
options_map[display] = slug
|
||||
for slug, display in config["extra"]:
|
||||
options_map[display] = slug
|
||||
current_display = _theme_display_name(current_slug)
|
||||
if current_display not in options_map:
|
||||
options_map[current_display] = current_slug
|
||||
options = sorted(options_map.keys(), key=str.casefold)
|
||||
return options, options_map, current_display
|
||||
|
||||
def _get_theme_value(self, key: str) -> str:
|
||||
default = THEME_KEY_CONFIG[key]["default"]
|
||||
return _theme_display_name(self._params.get(key, encoding='utf-8') or default)
|
||||
|
||||
def _show_theme_selector(self, key):
|
||||
themes, option_map, current = self._build_theme_options(key)
|
||||
if not themes:
|
||||
return
|
||||
|
||||
def on_select(res):
|
||||
if res == DialogResult.CONFIRM and dialog.selection:
|
||||
selected_slug = option_map.get(dialog.selection)
|
||||
if selected_slug is None:
|
||||
return
|
||||
self._params.put(key, selected_slug)
|
||||
|
||||
dialog = MultiOptionDialog(tr(key), themes, current, callback=on_select)
|
||||
gui_app.push_widget(dialog)
|
||||
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
# Unified Appearance panel
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
|
||||
class StarPilotAppearanceLayout(_SettingsPage):
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
self._sub_panels = {
|
||||
"personalize": StarPilotAppearancePersonalizeLayout(),
|
||||
}
|
||||
self._wire_sub_panels()
|
||||
self._build_view()
|
||||
|
||||
def _build_view(self):
|
||||
tab_defs = [
|
||||
{"id": "display", "title": tr_noop("Display"), "subtitle": tr_noop("Screen visibility")},
|
||||
{"id": "widgets", "title": tr_noop("Widgets"), "subtitle": tr_noop("Driving indicators")},
|
||||
{"id": "convenience", "title": tr_noop("Convenience"), "subtitle": tr_noop("QOL & navigation")},
|
||||
{"id": "model", "title": tr_noop("Model"), "subtitle": tr_noop("Path visualization")},
|
||||
{"id": "theme", "title": tr_noop("Theme"), "subtitle": tr_noop("Customization")},
|
||||
]
|
||||
|
||||
po = lambda: self._params.get_bool("PedalsOnUI")
|
||||
ol = lambda: starpilot_state.car_state.hasOpenpilotLongitudinal
|
||||
bsm = lambda: starpilot_state.car_state.hasBSM
|
||||
|
||||
sections: list[SettingSection] = [
|
||||
# ═══ Tab 1: Display — screen visibility toggles ═══
|
||||
SettingSection(tr_noop("Screen Elements"), [
|
||||
SettingRow("AdvancedCustomUI", "toggle", tr_noop("Advanced UI Controls"),
|
||||
subtitle=tr_noop("Fine-tune which elements appear on screen."),
|
||||
get_state=lambda: self._params.get_bool("AdvancedCustomUI"),
|
||||
set_state=lambda s: self._params.put_bool("AdvancedCustomUI", s)),
|
||||
SettingRow("HideSpeed", "toggle", tr_noop("Hide Speed"),
|
||||
subtitle="",
|
||||
get_state=lambda: self._params.get_bool("HideSpeed"),
|
||||
set_state=lambda s: self._params.put_bool("HideSpeed", s)),
|
||||
SettingRow("HideMaxSpeed", "toggle", tr_noop("Hide Max Speed"),
|
||||
subtitle="",
|
||||
get_state=lambda: self._params.get_bool("HideMaxSpeed"),
|
||||
set_state=lambda s: self._params.put_bool("HideMaxSpeed", s)),
|
||||
SettingRow("HideAlerts", "toggle", tr_noop("Hide Alerts"),
|
||||
subtitle="",
|
||||
get_state=lambda: self._params.get_bool("HideAlerts"),
|
||||
set_state=lambda s: self._params.put_bool("HideAlerts", s)),
|
||||
], tab_key="display", column_pair="display", row_height=UTILITY_ROW_HEIGHT),
|
||||
|
||||
SettingSection(tr_noop("Speed Info"), [
|
||||
SettingRow("HideSpeedLimit", "toggle", tr_noop("Hide Speed Limit"),
|
||||
subtitle="",
|
||||
get_state=lambda: self._params.get_bool("HideSpeedLimit"),
|
||||
set_state=lambda s: self._params.put_bool("HideSpeedLimit", s)),
|
||||
SettingRow("HideLeadMarker", "toggle", tr_noop("Hide Lead Marker"),
|
||||
subtitle="",
|
||||
get_state=lambda: self._params.get_bool("HideLeadMarker"),
|
||||
set_state=lambda s: self._params.put_bool("HideLeadMarker", s),
|
||||
visible=ol),
|
||||
SettingRow("WheelSpeed", "toggle", tr_noop("Wheel Speed"),
|
||||
subtitle="",
|
||||
get_state=lambda: self._params.get_bool("WheelSpeed"),
|
||||
set_state=lambda s: self._params.put_bool("WheelSpeed", s)),
|
||||
], tab_key="display", column_pair="display", row_height=UTILITY_ROW_HEIGHT),
|
||||
|
||||
# ═══ Tab 2: Widgets — driving screen widget toggles ═══
|
||||
SettingSection(tr_noop("Path Overlays"), [
|
||||
SettingRow("CustomUI", "toggle", tr_noop("Driving Screen Widgets"),
|
||||
subtitle=tr_noop("Show interactive indicators on the driving screen."),
|
||||
get_state=lambda: self._params.get_bool("CustomUI"),
|
||||
set_state=lambda s: self._params.put_bool("CustomUI", s)),
|
||||
SettingRow("AccelerationPath", "toggle", tr_noop("Acceleration Path"),
|
||||
subtitle="",
|
||||
get_state=lambda: self._params.get_bool("AccelerationPath"),
|
||||
set_state=lambda s: self._params.put_bool("AccelerationPath", s),
|
||||
visible=ol),
|
||||
SettingRow("AdjacentPath", "toggle", tr_noop("Adjacent Lanes"),
|
||||
subtitle="",
|
||||
get_state=lambda: self._params.get_bool("AdjacentPath"),
|
||||
set_state=lambda s: self._params.put_bool("AdjacentPath", s)),
|
||||
SettingRow("AdjacentPathMetrics", "toggle", tr_noop("Adjacent Lane Metrics"),
|
||||
subtitle="",
|
||||
get_state=lambda: self._params.get_bool("AdjacentPathMetrics"),
|
||||
set_state=lambda s: self._params.put_bool("AdjacentPathMetrics", s)),
|
||||
SettingRow("BlindSpotPath", "toggle", tr_noop("Blind Spot Path"),
|
||||
subtitle="",
|
||||
get_state=lambda: self._params.get_bool("BlindSpotPath"),
|
||||
set_state=lambda s: self._params.put_bool("BlindSpotPath", s),
|
||||
visible=bsm),
|
||||
], tab_key="widgets", column_pair="widgets", row_height=UTILITY_ROW_HEIGHT),
|
||||
|
||||
SettingSection(tr_noop("Dashboard Controls"), [
|
||||
SettingRow("Compass", "toggle", tr_noop("Compass"),
|
||||
subtitle="",
|
||||
get_state=lambda: self._params.get_bool("Compass"),
|
||||
set_state=lambda s: self._params.put_bool("Compass", s)),
|
||||
SettingRow("OnroadDistanceButton", "toggle", tr_noop("Personality Button"),
|
||||
subtitle="",
|
||||
get_state=lambda: self._params.get_bool("OnroadDistanceButton"),
|
||||
set_state=lambda s: self._params.put_bool("OnroadDistanceButton", s)),
|
||||
SettingRow("PedalsOnUI", "toggle", tr_noop("Pedal Indicators"),
|
||||
subtitle="",
|
||||
get_state=lambda: self._params.get_bool("PedalsOnUI"),
|
||||
set_state=lambda s: self._params.put_bool("PedalsOnUI", s),
|
||||
visible=ol),
|
||||
SettingRow("DynamicPedalsOnUI", "toggle", tr_noop("Dynamic Pedals"),
|
||||
subtitle="",
|
||||
get_state=lambda: self._params.get_bool("DynamicPedalsOnUI"),
|
||||
set_state=lambda s: self._set_exclusive_pedal("DynamicPedalsOnUI", "StaticPedalsOnUI", s),
|
||||
visible=lambda: po() and ol()),
|
||||
SettingRow("StaticPedalsOnUI", "toggle", tr_noop("Static Pedals"),
|
||||
subtitle="",
|
||||
get_state=lambda: self._params.get_bool("StaticPedalsOnUI"),
|
||||
set_state=lambda s: self._set_exclusive_pedal("StaticPedalsOnUI", "DynamicPedalsOnUI", s),
|
||||
visible=lambda: po() and ol()),
|
||||
SettingRow("RotatingWheel", "toggle", tr_noop("Rotating Wheel"),
|
||||
subtitle="",
|
||||
get_state=lambda: self._params.get_bool("RotatingWheel"),
|
||||
set_state=lambda s: self._params.put_bool("RotatingWheel", s)),
|
||||
], tab_key="widgets", column_pair="widgets", row_height=UTILITY_ROW_HEIGHT),
|
||||
|
||||
# ═══ Tab 3: Convenience — QOL + Navigation ═══
|
||||
SettingSection(tr_noop("Quality of Life"), [
|
||||
SettingRow("QOLVisuals", "toggle", tr_noop("Quality of Life"),
|
||||
subtitle=tr_noop("Convenience features for everyday driving."),
|
||||
get_state=lambda: self._params.get_bool("QOLVisuals"),
|
||||
set_state=lambda s: self._params.put_bool("QOLVisuals", s)),
|
||||
SettingRow("CameraView", "value", tr_noop("Camera View"),
|
||||
subtitle="",
|
||||
get_value=lambda: tr(CAMERA_VIEWS[self._params.get_int("CameraView")]),
|
||||
on_click=self._show_camera_view_selector),
|
||||
SettingRow("DriverCamera", "toggle", tr_noop("Driver Camera"),
|
||||
subtitle="",
|
||||
get_state=lambda: self._params.get_bool("DriverCamera"),
|
||||
set_state=lambda s: self._params.put_bool("DriverCamera", s)),
|
||||
SettingRow("StoppedTimer", "toggle", tr_noop("Stopped Timer"),
|
||||
subtitle="",
|
||||
get_state=lambda: self._params.get_bool("StoppedTimer"),
|
||||
set_state=lambda s: self._params.put_bool("StoppedTimer", s)),
|
||||
], tab_key="convenience", column_pair="convenience", row_height=UTILITY_ROW_HEIGHT),
|
||||
|
||||
SettingSection(tr_noop("Navigation"), [
|
||||
SettingRow("NavigationUI", "toggle", tr_noop("Navigation Widgets"),
|
||||
subtitle=tr_noop("Show navigation info on the driving screen."),
|
||||
get_state=lambda: self._params.get_bool("NavigationUI"),
|
||||
set_state=lambda s: self._params.put_bool("NavigationUI", s)),
|
||||
SettingRow("RoadNameUI", "toggle", tr_noop("Road Name"),
|
||||
subtitle="",
|
||||
get_state=lambda: self._params.get_bool("RoadNameUI"),
|
||||
set_state=lambda s: self._params.put_bool("RoadNameUI", s)),
|
||||
SettingRow("ShowSpeedLimits", "toggle", tr_noop("Speed Limits"),
|
||||
subtitle="",
|
||||
get_state=lambda: self._params.get_bool("ShowSpeedLimits"),
|
||||
set_state=lambda s: self._params.put_bool("ShowSpeedLimits", s)),
|
||||
SettingRow("UseVienna", "toggle", tr_noop("Vienna Signs"),
|
||||
subtitle="",
|
||||
get_state=lambda: self._params.get_bool("UseVienna"),
|
||||
set_state=lambda s: self._params.put_bool("UseVienna", s)),
|
||||
], tab_key="convenience", column_pair="convenience", row_height=UTILITY_ROW_HEIGHT),
|
||||
|
||||
# ═══ Tab 4: Model — path/lane visualization ═══
|
||||
SettingSection(tr_noop("Path & Lanes"), [
|
||||
SettingRow("ModelUI", "toggle", tr_noop("Model UI"),
|
||||
subtitle=tr_noop("Display the driving model path, lanes, and road edges."),
|
||||
get_state=lambda: self._params.get_bool("ModelUI"),
|
||||
set_state=lambda s: self._params.put_bool("ModelUI", s)),
|
||||
SettingRow("DynamicPathWidth", "toggle", tr_noop("Dynamic Path"),
|
||||
subtitle="",
|
||||
get_state=lambda: self._params.get_bool("DynamicPathWidth"),
|
||||
set_state=lambda s: self._params.put_bool("DynamicPathWidth", s)),
|
||||
SettingRow("LaneLinesWidth", "value", tr_noop("Lane Line Width"),
|
||||
subtitle="",
|
||||
get_value=lambda: self._get_lane_lines_display(),
|
||||
on_click=lambda: self._show_int_selector("LaneLinesWidth", 0, 24, self._get_lane_lines_unit())),
|
||||
SettingRow("LaneLinesColor", "value", tr_noop("Lane Line Color"),
|
||||
subtitle="",
|
||||
get_value=lambda: self._get_color_display("LaneLinesColor"),
|
||||
on_click=lambda: self._show_color_selector("LaneLinesColor")),
|
||||
SettingRow("PathWidth", "value", tr_noop("Path Width"),
|
||||
subtitle="",
|
||||
get_value=lambda: self._get_path_width_display(),
|
||||
on_click=self._show_path_width_selector),
|
||||
], tab_key="model", column_pair="model", row_height=UTILITY_ROW_HEIGHT),
|
||||
|
||||
SettingSection(tr_noop("Edges & Colors"), [
|
||||
SettingRow("PathEdgeWidth", "value", tr_noop("Path Edge Width"),
|
||||
subtitle="",
|
||||
get_value=lambda: f"{self._params.get_int('PathEdgeWidth')}%",
|
||||
on_click=lambda: self._show_int_selector("PathEdgeWidth", 0, 100, "%")),
|
||||
SettingRow("PathEdgesColor", "value", tr_noop("Path Edge Color"),
|
||||
subtitle="",
|
||||
get_value=lambda: self._get_color_display("PathEdgesColor"),
|
||||
on_click=lambda: self._show_color_selector("PathEdgesColor")),
|
||||
SettingRow("PathColor", "value", tr_noop("Path Color"),
|
||||
subtitle="",
|
||||
get_value=lambda: self._get_color_display("PathColor"),
|
||||
on_click=lambda: self._show_color_selector("PathColor")),
|
||||
SettingRow("RoadEdgesWidth", "value", tr_noop("Road Edge Width"),
|
||||
subtitle="",
|
||||
get_value=lambda: self._get_road_edges_display(),
|
||||
on_click=lambda: self._show_int_selector("RoadEdgesWidth", 0, 24, self._get_road_edges_unit())),
|
||||
SettingRow("BorderWidth", "value", tr_noop("Border Width"),
|
||||
subtitle="",
|
||||
get_value=lambda: f"{int(round(self._params.get_float('BorderWidth')))}%",
|
||||
on_click=lambda: self._show_float_selector("BorderWidth", 25, 250, 5, "%")),
|
||||
], tab_key="model", column_pair="model", row_height=UTILITY_ROW_HEIGHT),
|
||||
|
||||
# ═══ Tab 5: Theme — customization ═══
|
||||
SettingSection(tr_noop("Customization"), [
|
||||
SettingRow("CustomThemes", "toggle", tr_noop("Custom Themes"),
|
||||
subtitle=tr_noop("Enable custom theme assets on the driving screen."),
|
||||
get_state=lambda: self._params.get_bool("CustomThemes"),
|
||||
set_state=lambda s: self._params.put_bool("CustomThemes", s)),
|
||||
SettingRow("Personalize", "value", tr_noop("Personalize openpilot"),
|
||||
subtitle=tr_noop("Choose boot logo, color scheme, icons, sounds, and more."),
|
||||
get_value=lambda: tr_noop("Customize"),
|
||||
navigate_to="personalize"),
|
||||
SettingRow("HolidayThemes", "toggle", tr_noop("Holiday Themes"),
|
||||
subtitle="",
|
||||
get_state=lambda: self._params.get_bool("HolidayThemes"),
|
||||
set_state=lambda s: self._params.put_bool("HolidayThemes", s)),
|
||||
SettingRow("RainbowPath", "toggle", tr_noop("Rainbow Path"),
|
||||
subtitle="",
|
||||
get_state=lambda: self._params.get_bool("RainbowPath"),
|
||||
set_state=lambda s: self._params.put_bool("RainbowPath", s)),
|
||||
], tab_key="theme", column_pair="theme", row_height=UTILITY_ROW_HEIGHT),
|
||||
|
||||
SettingSection(tr_noop("Options"), [
|
||||
SettingRow("RandomEvents", "toggle", tr_noop("Random Events"),
|
||||
subtitle="",
|
||||
get_state=lambda: self._params.get_bool("RandomEvents"),
|
||||
set_state=lambda s: self._params.put_bool("RandomEvents", s)),
|
||||
SettingRow("RandomThemes", "toggle", tr_noop("Random Themes"),
|
||||
subtitle="",
|
||||
get_state=lambda: self._params.get_bool("RandomThemes"),
|
||||
set_state=lambda s: self._params.put_bool("RandomThemes", s)),
|
||||
SettingRow("StartupAlert", "value", tr_noop("Startup Alert"),
|
||||
subtitle="",
|
||||
get_value=self._get_startup_alert_display,
|
||||
on_click=self._show_startup_alert_selector),
|
||||
], tab_key="theme", column_pair="theme", row_height=UTILITY_ROW_HEIGHT),
|
||||
]
|
||||
|
||||
self._manager_view = AetherSettingsView(
|
||||
self, sections,
|
||||
header_title=tr_noop("Appearance"),
|
||||
header_subtitle=tr_noop("Customize your display, driving widgets, model visualization, and themes."),
|
||||
tab_defs=tab_defs,
|
||||
panel_style=PANEL_STYLE,
|
||||
)
|
||||
|
||||
# ── Widget helpers ──
|
||||
|
||||
def _set_exclusive_pedal(self, key, other_key, state):
|
||||
self._params.put_bool(key, state)
|
||||
if state:
|
||||
self._params.put_bool(other_key, False)
|
||||
|
||||
# ── Camera view ──
|
||||
|
||||
def _show_camera_view_selector(self):
|
||||
current = self._params.get_int("CameraView")
|
||||
|
||||
def on_select(res):
|
||||
if res == DialogResult.CONFIRM and dialog.selection:
|
||||
idx = CAMERA_VIEWS.index(dialog.selection)
|
||||
self._params.put_int("CameraView", idx)
|
||||
|
||||
dialog = MultiOptionDialog(tr("Camera View"), CAMERA_VIEWS, CAMERA_VIEWS[current], callback=on_select)
|
||||
gui_app.push_widget(dialog)
|
||||
|
||||
# ── Color selectors ──
|
||||
|
||||
def _get_color_display(self, key):
|
||||
val = self._params.get(key, encoding='utf-8') or ""
|
||||
if not val:
|
||||
return "Stock"
|
||||
return val.upper()
|
||||
|
||||
def _show_color_selector(self, key):
|
||||
current = self._params.get(key, encoding='utf-8') or "Stock"
|
||||
|
||||
def on_select(res):
|
||||
if res == DialogResult.CONFIRM and dialog.selection:
|
||||
if dialog.selection == "Stock":
|
||||
self._params.remove(key)
|
||||
else:
|
||||
self._params.put(key, dialog.selection)
|
||||
|
||||
dialog = MultiOptionDialog(tr(key), COLOR_PRESETS, current, callback=on_select)
|
||||
gui_app.push_widget(dialog)
|
||||
|
||||
# ── Numeric sliders (int / float) ──
|
||||
|
||||
def _show_int_selector(self, key, min_v, max_v, unit=""):
|
||||
def on_close(res, val):
|
||||
if res == DialogResult.CONFIRM:
|
||||
self._params.put_int(key, int(val))
|
||||
gui_app.push_widget(AetherSliderDialog(tr(key), min_v, max_v, 1, self._params.get_int(key), on_close,
|
||||
unit=unit, color="#8B5CF6"))
|
||||
|
||||
def _show_float_selector(self, key, min_v, max_v, step, unit="", convert=None, unconvert=None):
|
||||
current = self._params.get_float(key)
|
||||
if convert:
|
||||
current = convert(current)
|
||||
|
||||
def on_close(res, val):
|
||||
if res == DialogResult.CONFIRM:
|
||||
v = float(val)
|
||||
if unconvert:
|
||||
v = unconvert(v)
|
||||
self._params.put_float(key, v)
|
||||
|
||||
gui_app.push_widget(AetherSliderDialog(tr(key), min_v, max_v, step, current, on_close,
|
||||
unit=unit, color="#8B5CF6"))
|
||||
|
||||
# ── Unit-aware display helpers ──
|
||||
|
||||
def _is_metric(self):
|
||||
return self._params.get_bool("IsMetric")
|
||||
|
||||
def _get_lane_lines_unit(self):
|
||||
return "cm" if self._is_metric() else "in"
|
||||
|
||||
def _get_lane_lines_display(self):
|
||||
val = self._params.get_int("LaneLinesWidth")
|
||||
if self._is_metric():
|
||||
return f"{int(val * 2.54)}cm"
|
||||
return f"{val}in"
|
||||
|
||||
def _get_road_edges_unit(self):
|
||||
return "cm" if self._is_metric() else "in"
|
||||
|
||||
def _get_road_edges_display(self):
|
||||
val = self._params.get_int("RoadEdgesWidth")
|
||||
if self._is_metric():
|
||||
return f"{int(val * 2.54)}cm"
|
||||
return f"{val}in"
|
||||
|
||||
def _get_path_width_display(self):
|
||||
val = self._params.get_float("PathWidth")
|
||||
if self._is_metric():
|
||||
return f"{val / 3.28084:.1f}m"
|
||||
return f"{val:.1f}ft"
|
||||
|
||||
def _show_path_width_selector(self):
|
||||
if self._is_metric():
|
||||
self._show_float_selector("PathWidth", 0, 10, 0.1, "m", convert=lambda v: v / 3.28084, unconvert=lambda v: v * 3.28084)
|
||||
else:
|
||||
self._show_float_selector("PathWidth", 0, 10, 0.1, "ft")
|
||||
|
||||
# ── Startup alert ──
|
||||
|
||||
def _get_startup_alert_display(self):
|
||||
current_top = self._params.get("StartupMessageTop", encoding='utf-8') or ""
|
||||
if current_top == "Be ready to take over at any time":
|
||||
return "Stock"
|
||||
if current_top == "Hop in and buckle up!":
|
||||
return "StarPilot"
|
||||
return "Clear"
|
||||
|
||||
def _show_startup_alert_selector(self):
|
||||
options = ["Stock", "StarPilot", "Clear"]
|
||||
current = self._get_startup_alert_display()
|
||||
|
||||
def on_select(res):
|
||||
if res == DialogResult.CONFIRM and dialog.selection:
|
||||
if dialog.selection == "Stock":
|
||||
self._params.put("StartupMessageTop", "Be ready to take over at any time")
|
||||
self._params.put("StartupMessageBottom", "Always keep hands on wheel and eyes on road")
|
||||
elif dialog.selection == "StarPilot":
|
||||
self._params.put("StartupMessageTop", "Hop in and buckle up!")
|
||||
self._params.put("StartupMessageBottom", "Human-tested, frog-approved")
|
||||
else:
|
||||
self._params.remove("StartupMessageTop")
|
||||
self._params.remove("StartupMessageBottom")
|
||||
|
||||
dialog = MultiOptionDialog(tr("Startup Alert"), options, current, callback=on_select)
|
||||
gui_app.push_widget(dialog)
|
||||
@@ -14,8 +14,7 @@ from openpilot.selfdrive.ui.layouts.settings.starpilot.longitudinal import StarP
|
||||
from openpilot.selfdrive.ui.layouts.settings.starpilot.lateral import StarPilotLateralLayout
|
||||
from openpilot.selfdrive.ui.layouts.settings.starpilot.maps import StarPilotMapsLayout
|
||||
from openpilot.selfdrive.ui.layouts.settings.starpilot.system_settings import StarPilotSystemLayout
|
||||
from openpilot.selfdrive.ui.layouts.settings.starpilot.visuals import StarPilotVisualsLayout
|
||||
from openpilot.selfdrive.ui.layouts.settings.starpilot.themes import StarPilotThemesLayout
|
||||
from openpilot.selfdrive.ui.layouts.settings.starpilot.appearance import StarPilotAppearanceLayout
|
||||
from openpilot.selfdrive.ui.layouts.settings.starpilot.vehicle import StarPilotVehicleSettingsLayout
|
||||
|
||||
from openpilot.selfdrive.ui.layouts.settings.starpilot.aethergrid import TileGrid, HubTile, RadioTileGroup, SPACING
|
||||
@@ -27,37 +26,37 @@ class StarPilotLayout(Widget):
|
||||
{
|
||||
"title": "Alerts & Sounds",
|
||||
"icon": "icon_sound.png",
|
||||
"buttons": [("MANAGE", "SOUNDS", 0)],
|
||||
"panel": "SOUNDS",
|
||||
"color": "#E63956",
|
||||
},
|
||||
{
|
||||
"title": "Driving Controls",
|
||||
"icon": "icon_steering.png",
|
||||
"buttons": [("DRIVING MODEL", "DRIVING_MODEL", 0), ("GAS / BRAKE", "LONGITUDINAL", 0), ("STEERING", "LATERAL", 0)],
|
||||
"buttons": [("DRIVING MODEL", "DRIVING_MODEL"), ("GAS / BRAKE", "LONGITUDINAL"), ("STEERING", "LATERAL")],
|
||||
"color": "#3B82F6",
|
||||
},
|
||||
{
|
||||
"title": "Map Data",
|
||||
"icon": "icon_navigate.png",
|
||||
"buttons": [("MAP DATA", "MAPS", 0)],
|
||||
"panel": "MAPS",
|
||||
"color": "#10B981",
|
||||
},
|
||||
{
|
||||
"title": "System",
|
||||
"icon": "icon_system.png",
|
||||
"buttons": [("MANAGE", "SYSTEM", 0)],
|
||||
"panel": "SYSTEM",
|
||||
"color": "#D946EF",
|
||||
},
|
||||
{
|
||||
"title": "Appearance",
|
||||
"icon": "icon_display.png",
|
||||
"buttons": [("APPEARANCE", "VISUALS", 0), ("THEME", "THEMES", 0)],
|
||||
"panel": "VISUALS",
|
||||
"color": "#8B5CF6",
|
||||
},
|
||||
{
|
||||
"title": "Vehicle Settings",
|
||||
"icon": "icon_vehicle.png",
|
||||
"buttons": [("VEHICLE SETTINGS", "VEHICLE", 0)],
|
||||
"panel": "VEHICLE",
|
||||
"color": "#64748B",
|
||||
},
|
||||
]
|
||||
@@ -82,8 +81,7 @@ class StarPilotLayout(Widget):
|
||||
StarPilotPanelType.LONGITUDINAL: StarPilotPanelInfo(tr_noop("Gas / Brake"), StarPilotLongitudinalLayout()),
|
||||
StarPilotPanelType.LATERAL: StarPilotPanelInfo(tr_noop("Steering"), StarPilotLateralLayout()),
|
||||
StarPilotPanelType.MAPS: StarPilotPanelInfo(tr_noop("Map Data"), StarPilotMapsLayout()),
|
||||
StarPilotPanelType.VISUALS: StarPilotPanelInfo(tr_noop("Appearance"), StarPilotVisualsLayout()),
|
||||
StarPilotPanelType.THEMES: StarPilotPanelInfo(tr_noop("Themes"), StarPilotThemesLayout()),
|
||||
StarPilotPanelType.VISUALS: StarPilotPanelInfo(tr_noop("Appearance"), StarPilotAppearanceLayout()),
|
||||
StarPilotPanelType.VEHICLE: StarPilotPanelInfo(tr_noop("Vehicle Settings"), StarPilotVehicleSettingsLayout()),
|
||||
}
|
||||
|
||||
@@ -94,7 +92,6 @@ class StarPilotLayout(Widget):
|
||||
StarPilotPanelType.LATERAL,
|
||||
StarPilotPanelType.MAPS,
|
||||
StarPilotPanelType.VISUALS,
|
||||
StarPilotPanelType.THEMES,
|
||||
StarPilotPanelType.VEHICLE,
|
||||
)
|
||||
|
||||
@@ -115,8 +112,7 @@ class StarPilotLayout(Widget):
|
||||
elif self._current_panel != StarPilotPanelType.MAIN:
|
||||
if self._current_category_idx is not None:
|
||||
cat_info = self.CATEGORIES[self._current_category_idx]
|
||||
vis_btns = cat_info["buttons"]
|
||||
if len(vis_btns) > 1:
|
||||
if "buttons" in cat_info:
|
||||
self._set_current_panel(StarPilotPanelType.MAIN)
|
||||
else:
|
||||
self._current_category_idx = None
|
||||
@@ -134,8 +130,7 @@ class StarPilotLayout(Widget):
|
||||
if self._current_panel != StarPilotPanelType.MAIN:
|
||||
if self._current_category_idx is not None:
|
||||
cat_info = self.CATEGORIES[self._current_category_idx]
|
||||
vis_btns = cat_info["buttons"]
|
||||
depth = 2 if len(vis_btns) > 1 else 1
|
||||
depth = 2 if "buttons" in cat_info else 1
|
||||
else:
|
||||
depth = 1
|
||||
# Deep nesting check
|
||||
@@ -184,25 +179,19 @@ class StarPilotLayout(Widget):
|
||||
"LATERAL": StarPilotPanelType.LATERAL,
|
||||
"MAPS": StarPilotPanelType.MAPS,
|
||||
"VISUALS": StarPilotPanelType.VISUALS,
|
||||
"THEMES": StarPilotPanelType.THEMES,
|
||||
"VEHICLE": StarPilotPanelType.VEHICLE,
|
||||
}
|
||||
|
||||
if self._current_category_idx is None:
|
||||
# Main Categories Grid
|
||||
for i, cat in enumerate(self.CATEGORIES):
|
||||
visible_buttons = cat["buttons"]
|
||||
if not visible_buttons:
|
||||
continue
|
||||
|
||||
def on_click(idx=i):
|
||||
cat_info = self.CATEGORIES[idx]
|
||||
vis_btns = cat_info["buttons"]
|
||||
if len(vis_btns) == 1:
|
||||
self._current_category_idx = idx
|
||||
self._set_current_panel(panel_type_map[vis_btns[0][1]])
|
||||
self._current_category_idx = idx
|
||||
panel_key = cat_info.get("panel")
|
||||
if panel_key is not None:
|
||||
self._set_current_panel(panel_type_map[panel_key])
|
||||
else:
|
||||
self._current_category_idx = idx
|
||||
self._rebuild_grid()
|
||||
if self._depth_callback:
|
||||
self._depth_callback(1)
|
||||
@@ -221,7 +210,7 @@ class StarPilotLayout(Widget):
|
||||
cat = self.CATEGORIES[self._current_category_idx]
|
||||
visible_buttons = cat["buttons"]
|
||||
|
||||
for label, panel_key, _ in visible_buttons:
|
||||
for label, panel_key in visible_buttons:
|
||||
p_type = panel_type_map[panel_key]
|
||||
def on_btn_click(p=p_type):
|
||||
self._set_current_panel(p)
|
||||
@@ -229,7 +218,7 @@ class StarPilotLayout(Widget):
|
||||
tile = HubTile(
|
||||
title=tr(label),
|
||||
desc="",
|
||||
icon_path=f"{STARPILOT_ICONS_DIR}/{cat['icon']}", # Reuse category icon for sub-tiles
|
||||
icon_path=f"{STARPILOT_ICONS_DIR}/{cat['icon']}",
|
||||
on_click=on_btn_click,
|
||||
starpilot_icon=True,
|
||||
bg_color=cat.get("color")
|
||||
|
||||
@@ -1,119 +0,0 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from collections.abc import Callable
|
||||
from dataclasses import dataclass
|
||||
|
||||
import pyray as rl
|
||||
|
||||
from openpilot.system.ui.lib.multilang import tr
|
||||
from openpilot.system.ui.widgets import Widget
|
||||
|
||||
from openpilot.selfdrive.ui.layouts.settings.starpilot.aethergrid import RadioTileGroup, SPACING
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class TabSectionSpec:
|
||||
key: str
|
||||
label: str
|
||||
panel: Widget
|
||||
|
||||
|
||||
class TabbedSectionHost(Widget):
|
||||
def __init__(self, sections: list[TabSectionSpec]):
|
||||
super().__init__()
|
||||
if not sections:
|
||||
raise ValueError("TabbedSectionHost requires at least one section")
|
||||
|
||||
self._sections = {spec.key: spec.panel for spec in sections}
|
||||
self._section_order = [spec.key for spec in sections]
|
||||
self._active_section = self._section_order[0]
|
||||
self._navigate_callback: Callable | None = None
|
||||
self._back_callback: Callable | None = None
|
||||
self._current_sub_panel = ""
|
||||
self._tab_height = SPACING.tab_height
|
||||
self._panel_top = self._tab_height + SPACING.tab_panel_gap
|
||||
self._section_tabs = RadioTileGroup("", [tr(spec.label) for spec in sections], 0, self._on_tab_change)
|
||||
|
||||
for key, panel in self._sections.items():
|
||||
if hasattr(panel, "set_navigate_callback"):
|
||||
panel.set_navigate_callback(lambda sub_panel, section_key=key: self._on_child_navigate(section_key, sub_panel))
|
||||
if hasattr(panel, "set_back_callback"):
|
||||
panel.set_back_callback(self._go_back)
|
||||
|
||||
def set_navigate_callback(self, callback: Callable):
|
||||
self._navigate_callback = callback
|
||||
|
||||
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
|
||||
if not sub_panel:
|
||||
panel = self._sections[self._active_section]
|
||||
if hasattr(panel, "set_current_sub_panel"):
|
||||
panel.set_current_sub_panel("")
|
||||
return
|
||||
|
||||
if ":" in sub_panel:
|
||||
section_key, child_panel = sub_panel.split(":", 1)
|
||||
self._activate_section(section_key, child_panel)
|
||||
elif sub_panel in self._sections:
|
||||
self._activate_section(sub_panel)
|
||||
else:
|
||||
panel = self._sections[self._active_section]
|
||||
if hasattr(panel, "set_current_sub_panel"):
|
||||
panel.set_current_sub_panel(sub_panel)
|
||||
|
||||
def _on_tab_change(self, index: int):
|
||||
if 0 <= index < len(self._section_order):
|
||||
self._current_sub_panel = ""
|
||||
self._activate_section(self._section_order[index], "")
|
||||
if self._navigate_callback:
|
||||
self._navigate_callback("")
|
||||
|
||||
def _activate_section(self, section_key: str, child_panel: str = ""):
|
||||
if section_key not in self._sections:
|
||||
return
|
||||
|
||||
previous = self._active_section
|
||||
if section_key != previous:
|
||||
previous_panel = self._sections[previous]
|
||||
if hasattr(previous_panel, "set_current_sub_panel"):
|
||||
previous_panel.set_current_sub_panel("")
|
||||
self._sections[previous].hide_event()
|
||||
self._active_section = section_key
|
||||
self._sections[section_key].show_event()
|
||||
|
||||
self._section_tabs.set_index(self._section_order.index(section_key))
|
||||
panel = self._sections[section_key]
|
||||
if hasattr(panel, "set_current_sub_panel"):
|
||||
panel.set_current_sub_panel(child_panel)
|
||||
|
||||
def _on_child_navigate(self, section_key: str, sub_panel: str):
|
||||
self._current_sub_panel = f"{section_key}:{sub_panel}" if sub_panel else section_key
|
||||
if self._navigate_callback:
|
||||
self._navigate_callback(self._current_sub_panel)
|
||||
|
||||
def _go_back(self):
|
||||
self._current_sub_panel = ""
|
||||
panel = self._sections[self._active_section]
|
||||
if hasattr(panel, "set_current_sub_panel"):
|
||||
panel.set_current_sub_panel("")
|
||||
if self._back_callback:
|
||||
self._back_callback()
|
||||
|
||||
def _render(self, rect: rl.Rectangle):
|
||||
tab_rect = rl.Rectangle(rect.x, rect.y, rect.width, self._tab_height)
|
||||
panel_rect = rl.Rectangle(rect.x, rect.y + self._panel_top, rect.width, rect.height - self._panel_top)
|
||||
self._section_tabs.render(tab_rect)
|
||||
self._sections[self._active_section].render(panel_rect)
|
||||
|
||||
def show_event(self):
|
||||
super().show_event()
|
||||
self._section_tabs.show_event()
|
||||
self._sections[self._active_section].show_event()
|
||||
|
||||
def hide_event(self):
|
||||
super().hide_event()
|
||||
self._section_tabs.hide_event()
|
||||
self._sections[self._active_section].hide_event()
|
||||
@@ -1,330 +0,0 @@
|
||||
from __future__ import annotations
|
||||
from pathlib import Path
|
||||
import re
|
||||
|
||||
from openpilot.system.hardware import HARDWARE
|
||||
from openpilot.system.hardware.hw import Paths
|
||||
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.option_dialog import MultiOptionDialog
|
||||
from openpilot.selfdrive.ui.layouts.settings.starpilot.panel import StarPilotPanel
|
||||
|
||||
if HARDWARE.get_device_type() == "pc":
|
||||
THEME_SAVE_PATH = Path(Paths.comma_home()) / "starpilot" / "data" / "themes"
|
||||
else:
|
||||
THEME_SAVE_PATH = Path("/data/themes")
|
||||
|
||||
HOLIDAY_THEME_NAMES = {
|
||||
"new_years": "New Year's",
|
||||
"valentines_day": "Valentine's Day",
|
||||
"st_patricks_day": "St. Patrick's Day",
|
||||
"world_frog_day": "World Frog Day",
|
||||
"april_fools": "April Fools",
|
||||
"easter_week": "Easter",
|
||||
"may_the_fourth": "May the Fourth",
|
||||
"cinco_de_mayo": "Cinco de Mayo",
|
||||
"stitch_day": "Stitch Day",
|
||||
"fourth_of_july": "Fourth of July",
|
||||
"halloween_week": "Halloween",
|
||||
"thanksgiving_week": "Thanksgiving",
|
||||
"christmas_week": "Christmas",
|
||||
}
|
||||
|
||||
THEME_KEY_CONFIG = {
|
||||
"BootLogo": {
|
||||
"default": "starpilot",
|
||||
"kind": "files",
|
||||
"path": THEME_SAVE_PATH / "bootlogos",
|
||||
"extra": [],
|
||||
},
|
||||
"ColorScheme": {
|
||||
"default": "stock",
|
||||
"kind": "themes",
|
||||
"path": THEME_SAVE_PATH / "theme_packs",
|
||||
"subfolder": "colors",
|
||||
"extra": [("stock", "Stock"), *HOLIDAY_THEME_NAMES.items()],
|
||||
},
|
||||
"DistanceIconPack": {
|
||||
"default": "stock",
|
||||
"kind": "themes",
|
||||
"path": THEME_SAVE_PATH / "theme_packs",
|
||||
"subfolder": "distance_icons",
|
||||
"extra": [("stock", "Stock"), *HOLIDAY_THEME_NAMES.items()],
|
||||
},
|
||||
"IconPack": {
|
||||
"default": "stock",
|
||||
"kind": "themes",
|
||||
"path": THEME_SAVE_PATH / "theme_packs",
|
||||
"subfolder": "icons",
|
||||
"extra": [("stock", "Stock"), *HOLIDAY_THEME_NAMES.items()],
|
||||
},
|
||||
"SignalAnimation": {
|
||||
"default": "stock",
|
||||
"kind": "themes",
|
||||
"path": THEME_SAVE_PATH / "theme_packs",
|
||||
"subfolder": "signals",
|
||||
"extra": [("none", "None"), *HOLIDAY_THEME_NAMES.items()],
|
||||
},
|
||||
"SoundPack": {
|
||||
"default": "stock",
|
||||
"kind": "themes",
|
||||
"path": THEME_SAVE_PATH / "theme_packs",
|
||||
"subfolder": "sounds",
|
||||
"extra": [("stock", "Stock"), *HOLIDAY_THEME_NAMES.items()],
|
||||
},
|
||||
"WheelIcon": {
|
||||
"default": "stock",
|
||||
"kind": "files",
|
||||
"path": THEME_SAVE_PATH / "steering_wheels",
|
||||
"extra": [("none", "None"), ("stock", "Stock"), *HOLIDAY_THEME_NAMES.items()],
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
class StarPilotThemesLayout(StarPilotPanel):
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
|
||||
self._sub_panels = {
|
||||
"personalize": StarPilotPersonalizeLayout(),
|
||||
}
|
||||
|
||||
self.CATEGORIES = [
|
||||
{
|
||||
"title": tr_noop("Custom Themes"),
|
||||
"type": "toggle",
|
||||
"get_state": lambda: self._params.get_bool("CustomThemes"),
|
||||
"set_state": lambda s: self._params.put_bool("CustomThemes", s),
|
||||
"icon": "toggle_icons/icon_frog.png",
|
||||
"color": "#542A71",
|
||||
},
|
||||
{
|
||||
"title": tr_noop("Personalize openpilot"),
|
||||
"panel": "personalize",
|
||||
"icon": "toggle_icons/icon_frog.png",
|
||||
"color": "#542A71",
|
||||
"desc": tr_noop("Customize the overall look and feel."),
|
||||
"visible": lambda: self._params.get_bool("CustomThemes"),
|
||||
},
|
||||
{
|
||||
"title": tr_noop("Holiday Themes"),
|
||||
"type": "toggle",
|
||||
"get_state": lambda: self._params.get_bool("HolidayThemes"),
|
||||
"set_state": lambda s: self._params.put_bool("HolidayThemes", s),
|
||||
"icon": "toggle_icons/icon_calendar.png",
|
||||
"color": "#542A71",
|
||||
},
|
||||
{
|
||||
"title": tr_noop("Rainbow Path"),
|
||||
"type": "toggle",
|
||||
"get_state": lambda: self._params.get_bool("RainbowPath"),
|
||||
"set_state": lambda s: self._params.put_bool("RainbowPath", s),
|
||||
"icon": "toggle_icons/icon_rainbow.png",
|
||||
"color": "#542A71",
|
||||
},
|
||||
{
|
||||
"title": tr_noop("Random Events"),
|
||||
"type": "toggle",
|
||||
"get_state": lambda: self._params.get_bool("RandomEvents"),
|
||||
"set_state": lambda s: self._params.put_bool("RandomEvents", s),
|
||||
"icon": "toggle_icons/icon_random.png",
|
||||
"color": "#542A71",
|
||||
},
|
||||
{
|
||||
"title": tr_noop("Random Themes"),
|
||||
"type": "toggle",
|
||||
"get_state": lambda: self._params.get_bool("RandomThemes"),
|
||||
"set_state": lambda s: self._params.put_bool("RandomThemes", s),
|
||||
"icon": "toggle_icons/icon_random_themes.png",
|
||||
"color": "#542A71",
|
||||
"visible": lambda: self._params.get_bool("CustomThemes"),
|
||||
},
|
||||
{"title": tr_noop("Startup Alert"), "type": "hub", "on_click": self._on_startup_alert, "color": "#542A71"},
|
||||
]
|
||||
|
||||
for name, panel in self._sub_panels.items():
|
||||
if hasattr(panel, 'set_navigate_callback'):
|
||||
panel.set_navigate_callback(self._navigate_to)
|
||||
if hasattr(panel, 'set_back_callback'):
|
||||
panel.set_back_callback(self._go_back)
|
||||
|
||||
self._rebuild_grid()
|
||||
|
||||
def _on_startup_alert(self):
|
||||
options = ["Stock", "StarPilot", "Clear"]
|
||||
current_top = self._params.get("StartupMessageTop", encoding='utf-8') or ""
|
||||
if current_top == "Be ready to take over at any time":
|
||||
current = "Stock"
|
||||
elif current_top == "Hop in and buckle up!":
|
||||
current = "StarPilot"
|
||||
else:
|
||||
current = "Clear"
|
||||
|
||||
def on_select(res):
|
||||
if res == DialogResult.CONFIRM and dialog.selection:
|
||||
if dialog.selection == "Stock":
|
||||
self._params.put("StartupMessageTop", "Be ready to take over at any time")
|
||||
self._params.put("StartupMessageBottom", "Always keep hands on wheel and eyes on road")
|
||||
elif dialog.selection == "StarPilot":
|
||||
self._params.put("StartupMessageTop", "Hop in and buckle up!")
|
||||
self._params.put("StartupMessageBottom", "Human-tested, frog-approved")
|
||||
else:
|
||||
self._params.remove("StartupMessageTop")
|
||||
self._params.remove("StartupMessageBottom")
|
||||
|
||||
dialog = MultiOptionDialog(tr("Startup Alert"), options, current, callback=on_select)
|
||||
gui_app.push_widget(dialog)
|
||||
|
||||
|
||||
class StarPilotPersonalizeLayout(StarPilotPanel):
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
self.CATEGORIES = [
|
||||
{
|
||||
"title": tr_noop("Boot Logo"),
|
||||
"type": "value",
|
||||
"get_value": lambda: self._get_theme_value("BootLogo"),
|
||||
"on_click": lambda: self._show_theme_selector("BootLogo"),
|
||||
"color": "#542A71",
|
||||
"visible": lambda: self._params.get_bool("CustomThemes"),
|
||||
},
|
||||
{
|
||||
"title": tr_noop("Color Scheme"),
|
||||
"type": "value",
|
||||
"get_value": lambda: self._get_theme_value("ColorScheme"),
|
||||
"on_click": lambda: self._show_theme_selector("ColorScheme"),
|
||||
"color": "#542A71",
|
||||
"visible": lambda: self._params.get_bool("CustomThemes"),
|
||||
},
|
||||
{
|
||||
"title": tr_noop("Distance Icons"),
|
||||
"type": "value",
|
||||
"get_value": lambda: self._get_theme_value("DistanceIconPack"),
|
||||
"on_click": lambda: self._show_theme_selector("DistanceIconPack"),
|
||||
"color": "#542A71",
|
||||
"visible": lambda: self._params.get_bool("CustomThemes"),
|
||||
},
|
||||
{
|
||||
"title": tr_noop("Icon Pack"),
|
||||
"type": "value",
|
||||
"get_value": lambda: self._get_theme_value("IconPack"),
|
||||
"on_click": lambda: self._show_theme_selector("IconPack"),
|
||||
"color": "#542A71",
|
||||
"visible": lambda: self._params.get_bool("CustomThemes"),
|
||||
},
|
||||
{
|
||||
"title": tr_noop("Turn Signals"),
|
||||
"type": "value",
|
||||
"get_value": lambda: self._get_theme_value("SignalAnimation"),
|
||||
"on_click": lambda: self._show_theme_selector("SignalAnimation"),
|
||||
"color": "#542A71",
|
||||
"visible": lambda: self._params.get_bool("CustomThemes"),
|
||||
},
|
||||
{
|
||||
"title": tr_noop("Sound Pack"),
|
||||
"type": "value",
|
||||
"get_value": lambda: self._get_theme_value("SoundPack"),
|
||||
"on_click": lambda: self._show_theme_selector("SoundPack"),
|
||||
"color": "#542A71",
|
||||
"visible": lambda: self._params.get_bool("CustomThemes"),
|
||||
},
|
||||
{
|
||||
"title": tr_noop("Steering Wheel"),
|
||||
"type": "value",
|
||||
"get_value": lambda: self._get_theme_value("WheelIcon"),
|
||||
"on_click": lambda: self._show_theme_selector("WheelIcon"),
|
||||
"color": "#542A71",
|
||||
"visible": lambda: self._params.get_bool("CustomThemes"),
|
||||
},
|
||||
]
|
||||
self._rebuild_grid()
|
||||
|
||||
@staticmethod
|
||||
def _display_name(value: str) -> str:
|
||||
if not value:
|
||||
return "Stock"
|
||||
|
||||
lowered = value.lower()
|
||||
if lowered in HOLIDAY_THEME_NAMES:
|
||||
return HOLIDAY_THEME_NAMES[lowered]
|
||||
if lowered == "stock":
|
||||
return "Stock"
|
||||
if lowered == "none":
|
||||
return "None"
|
||||
|
||||
base, creator = (value.split("~", 1) + [""])[:2] if "~" in value else (value, "")
|
||||
user_created_suffixes = ("-user_created", "_user_created", "-user-created", "_user-created")
|
||||
user_created = False
|
||||
for suffix in user_created_suffixes:
|
||||
if base.endswith(suffix):
|
||||
base = base[:-len(suffix)]
|
||||
user_created = True
|
||||
break
|
||||
|
||||
parts = [part for part in re.split(r"[-_]+", base) if part]
|
||||
display = " ".join(part[:1].upper() + part[1:] for part in parts) if parts else value
|
||||
if user_created:
|
||||
display += " (User Created)"
|
||||
if creator:
|
||||
display += f" - by: {creator}"
|
||||
return display
|
||||
|
||||
def _get_downloaded_slugs(self, key: str) -> list[str]:
|
||||
config = THEME_KEY_CONFIG[key]
|
||||
path = config["path"]
|
||||
if not path.exists():
|
||||
return []
|
||||
|
||||
slugs = set()
|
||||
if config["kind"] == "files":
|
||||
for entry in path.iterdir():
|
||||
if entry.is_file():
|
||||
slugs.add(entry.stem)
|
||||
else:
|
||||
subfolder = config["subfolder"]
|
||||
for entry in path.iterdir():
|
||||
if entry.is_dir() and (entry / subfolder).exists():
|
||||
slugs.add(entry.name)
|
||||
|
||||
return sorted(slugs, key=str.casefold)
|
||||
|
||||
def _build_theme_options(self, key: str) -> tuple[list[str], dict[str, str], str]:
|
||||
config = THEME_KEY_CONFIG[key]
|
||||
current_slug = self._params.get(key, encoding='utf-8') or config["default"]
|
||||
|
||||
options_map = {}
|
||||
for slug in self._get_downloaded_slugs(key):
|
||||
display = self._display_name(slug)
|
||||
if display not in options_map:
|
||||
options_map[display] = slug
|
||||
|
||||
for slug, display in config["extra"]:
|
||||
options_map[display] = slug
|
||||
|
||||
current_display = self._display_name(current_slug)
|
||||
if current_display not in options_map:
|
||||
options_map[current_display] = current_slug
|
||||
|
||||
options = sorted(options_map.keys(), key=str.casefold)
|
||||
return options, options_map, current_display
|
||||
|
||||
def _get_theme_value(self, key: str) -> str:
|
||||
default = THEME_KEY_CONFIG[key]["default"]
|
||||
return self._display_name(self._params.get(key, encoding='utf-8') or default)
|
||||
|
||||
def _show_theme_selector(self, key):
|
||||
themes, option_map, current = self._build_theme_options(key)
|
||||
if not themes:
|
||||
return
|
||||
|
||||
def on_select(res):
|
||||
if res == DialogResult.CONFIRM and dialog.selection:
|
||||
selected_slug = option_map.get(dialog.selection)
|
||||
if selected_slug is None:
|
||||
return
|
||||
self._params.put(key, selected_slug)
|
||||
self._rebuild_grid()
|
||||
|
||||
dialog = MultiOptionDialog(tr(key), themes, current, callback=on_select)
|
||||
gui_app.push_widget(dialog)
|
||||
@@ -1,578 +0,0 @@
|
||||
from __future__ import annotations
|
||||
from openpilot.selfdrive.ui.lib.starpilot_state import starpilot_state
|
||||
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.option_dialog import MultiOptionDialog
|
||||
from openpilot.selfdrive.ui.layouts.settings.starpilot.panel import StarPilotPanel, create_tile_panel
|
||||
from openpilot.selfdrive.ui.layouts.settings.starpilot.tabbed_panel import TabSectionSpec, TabbedSectionHost
|
||||
from openpilot.selfdrive.ui.layouts.settings.starpilot.aethergrid import TileGrid, ToggleTile, AetherSliderDialog
|
||||
class StarPilotVisualsLayout(StarPilotPanel):
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
display_panel = create_tile_panel([
|
||||
{"title": tr_noop("Advanced UI Controls"), "panel": "advanced", "icon": "toggle_icons/icon_advanced_device.png", "color": "#8B5CF6"},
|
||||
{"title": tr_noop("Quality of Life"), "panel": "qol", "icon": "toggle_icons/icon_quality_of_life.png", "color": "#8B5CF6"},
|
||||
], {
|
||||
"advanced": StarPilotAdvancedVisualsLayout(),
|
||||
"qol": StarPilotVisualQOLLayout(),
|
||||
})
|
||||
|
||||
self._section_tabs = TabbedSectionHost([
|
||||
TabSectionSpec("display", "Display", display_panel),
|
||||
TabSectionSpec("widgets", "Widgets", StarPilotVisualWidgetsLayout()),
|
||||
TabSectionSpec("model", "Model", StarPilotModelUILayout()),
|
||||
TabSectionSpec("navigation", "Nav", StarPilotNavigationVisualsLayout()),
|
||||
])
|
||||
|
||||
def set_navigate_callback(self, callback):
|
||||
self._section_tabs.set_navigate_callback(callback)
|
||||
|
||||
def set_back_callback(self, callback):
|
||||
self._section_tabs.set_back_callback(callback)
|
||||
|
||||
def _render(self, rect):
|
||||
self._section_tabs.render(rect)
|
||||
|
||||
def set_current_sub_panel(self, sub_panel: str):
|
||||
self._section_tabs.set_current_sub_panel(sub_panel)
|
||||
|
||||
def show_event(self):
|
||||
self._section_tabs.show_event()
|
||||
|
||||
def hide_event(self):
|
||||
self._section_tabs.hide_event()
|
||||
|
||||
|
||||
class StarPilotAdvancedVisualsLayout(StarPilotPanel):
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
self.CATEGORIES = [
|
||||
{
|
||||
"title": tr_noop("Advanced UI Controls"),
|
||||
"type": "toggle",
|
||||
"get_state": lambda: self._params.get_bool("AdvancedCustomUI"),
|
||||
"set_state": lambda s: self._params.put_bool("AdvancedCustomUI", s),
|
||||
"icon": "toggle_icons/icon_advanced_device.png",
|
||||
"color": "#8B5CF6",
|
||||
},
|
||||
{
|
||||
"title": tr_noop("Hide Speed"),
|
||||
"type": "toggle",
|
||||
"key": "HideSpeed",
|
||||
"get_state": lambda: self._params.get_bool("HideSpeed"),
|
||||
"set_state": lambda s: self._params.put_bool("HideSpeed", s),
|
||||
"icon": "toggle_icons/icon_display.png",
|
||||
"color": "#8B5CF6",
|
||||
"visible": lambda: self._params.get_bool("AdvancedCustomUI"),
|
||||
},
|
||||
{
|
||||
"title": tr_noop("Hide Lead Marker"),
|
||||
"type": "toggle",
|
||||
"key": "HideLeadMarker",
|
||||
"get_state": lambda: self._params.get_bool("HideLeadMarker"),
|
||||
"set_state": lambda s: self._params.put_bool("HideLeadMarker", s),
|
||||
"icon": "toggle_icons/icon_display.png",
|
||||
"color": "#8B5CF6",
|
||||
"visible": lambda: self._params.get_bool("AdvancedCustomUI"),
|
||||
},
|
||||
{
|
||||
"title": tr_noop("Hide Max Speed"),
|
||||
"type": "toggle",
|
||||
"key": "HideMaxSpeed",
|
||||
"get_state": lambda: self._params.get_bool("HideMaxSpeed"),
|
||||
"set_state": lambda s: self._params.put_bool("HideMaxSpeed", s),
|
||||
"icon": "toggle_icons/icon_display.png",
|
||||
"color": "#8B5CF6",
|
||||
"visible": lambda: self._params.get_bool("AdvancedCustomUI"),
|
||||
},
|
||||
{
|
||||
"title": tr_noop("Hide Alerts"),
|
||||
"type": "toggle",
|
||||
"key": "HideAlerts",
|
||||
"get_state": lambda: self._params.get_bool("HideAlerts"),
|
||||
"set_state": lambda s: self._params.put_bool("HideAlerts", s),
|
||||
"icon": "toggle_icons/icon_display.png",
|
||||
"color": "#8B5CF6",
|
||||
"visible": lambda: self._params.get_bool("AdvancedCustomUI"),
|
||||
},
|
||||
{
|
||||
"title": tr_noop("Hide Speed Limit"),
|
||||
"type": "toggle",
|
||||
"key": "HideSpeedLimit",
|
||||
"get_state": lambda: self._params.get_bool("HideSpeedLimit"),
|
||||
"set_state": lambda s: self._params.put_bool("HideSpeedLimit", s),
|
||||
"icon": "toggle_icons/icon_display.png",
|
||||
"color": "#8B5CF6",
|
||||
"visible": lambda: self._params.get_bool("AdvancedCustomUI"),
|
||||
},
|
||||
{
|
||||
"title": tr_noop("Wheel Speed"),
|
||||
"type": "toggle",
|
||||
"key": "WheelSpeed",
|
||||
"get_state": lambda: self._params.get_bool("WheelSpeed"),
|
||||
"set_state": lambda s: self._params.put_bool("WheelSpeed", s),
|
||||
"icon": "toggle_icons/icon_display.png",
|
||||
"color": "#8B5CF6",
|
||||
"visible": lambda: self._params.get_bool("AdvancedCustomUI"),
|
||||
},
|
||||
]
|
||||
self._rebuild_grid()
|
||||
|
||||
def _rebuild_grid(self):
|
||||
if not self.CATEGORIES:
|
||||
return
|
||||
if self._tile_grid is None:
|
||||
self._tile_grid = TileGrid(columns=None, padding=20)
|
||||
self._tile_grid.clear()
|
||||
|
||||
for cat in self.CATEGORIES:
|
||||
key = cat.get("key")
|
||||
visible = True
|
||||
|
||||
if key == "HideLeadMarker":
|
||||
visible &= starpilot_state.car_state.hasOpenpilotLongitudinal
|
||||
|
||||
if not visible:
|
||||
continue
|
||||
|
||||
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"))
|
||||
self._tile_grid.add_tile(tile)
|
||||
|
||||
|
||||
class StarPilotVisualWidgetsLayout(StarPilotPanel):
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
self.CATEGORIES = [
|
||||
{
|
||||
"title": tr_noop("Driving Screen Widgets"),
|
||||
"type": "toggle",
|
||||
"get_state": lambda: self._params.get_bool("CustomUI"),
|
||||
"set_state": lambda s: self._params.put_bool("CustomUI", s),
|
||||
"icon": "toggle_icons/icon_display.png",
|
||||
"color": "#8B5CF6",
|
||||
},
|
||||
{
|
||||
"title": tr_noop("Acceleration Path"),
|
||||
"type": "toggle",
|
||||
"key": "AccelerationPath",
|
||||
"get_state": lambda: self._params.get_bool("AccelerationPath"),
|
||||
"set_state": lambda s: self._params.put_bool("AccelerationPath", s),
|
||||
"icon": "toggle_icons/icon_road.png",
|
||||
"color": "#8B5CF6",
|
||||
"visible": lambda: self._params.get_bool("CustomUI"),
|
||||
},
|
||||
{
|
||||
"title": tr_noop("Adjacent Lanes"),
|
||||
"type": "toggle",
|
||||
"key": "AdjacentPath",
|
||||
"get_state": lambda: self._params.get_bool("AdjacentPath"),
|
||||
"set_state": lambda s: self._params.put_bool("AdjacentPath", s),
|
||||
"icon": "toggle_icons/icon_road.png",
|
||||
"color": "#8B5CF6",
|
||||
"visible": lambda: self._params.get_bool("CustomUI"),
|
||||
},
|
||||
{
|
||||
"title": tr_noop("Adjacent Lane Metrics"),
|
||||
"type": "toggle",
|
||||
"key": "AdjacentPathMetrics",
|
||||
"get_state": lambda: self._params.get_bool("AdjacentPathMetrics"),
|
||||
"set_state": lambda s: self._params.put_bool("AdjacentPathMetrics", s),
|
||||
"icon": "toggle_icons/icon_road.png",
|
||||
"color": "#8B5CF6",
|
||||
"visible": lambda: self._params.get_bool("CustomUI"),
|
||||
},
|
||||
{
|
||||
"title": tr_noop("Blind Spot Path"),
|
||||
"type": "toggle",
|
||||
"key": "BlindSpotPath",
|
||||
"get_state": lambda: self._params.get_bool("BlindSpotPath"),
|
||||
"set_state": lambda s: self._params.put_bool("BlindSpotPath", s),
|
||||
"icon": "toggle_icons/icon_road.png",
|
||||
"color": "#8B5CF6",
|
||||
"visible": lambda: self._params.get_bool("CustomUI"),
|
||||
},
|
||||
{
|
||||
"title": tr_noop("Compass"),
|
||||
"type": "toggle",
|
||||
"key": "Compass",
|
||||
"get_state": lambda: self._params.get_bool("Compass"),
|
||||
"set_state": lambda s: self._params.put_bool("Compass", s),
|
||||
"icon": "toggle_icons/icon_navigate.png",
|
||||
"color": "#8B5CF6",
|
||||
"visible": lambda: self._params.get_bool("CustomUI"),
|
||||
},
|
||||
{
|
||||
"title": tr_noop("Personality Button"),
|
||||
"type": "toggle",
|
||||
"key": "OnroadDistanceButton",
|
||||
"get_state": lambda: self._params.get_bool("OnroadDistanceButton"),
|
||||
"set_state": lambda s: self._params.put_bool("OnroadDistanceButton", s),
|
||||
"icon": "toggle_icons/icon_personality.png",
|
||||
"color": "#8B5CF6",
|
||||
"visible": lambda: self._params.get_bool("CustomUI"),
|
||||
},
|
||||
{
|
||||
"title": tr_noop("Pedal Indicators"),
|
||||
"type": "toggle",
|
||||
"key": "PedalsOnUI",
|
||||
"get_state": lambda: self._params.get_bool("PedalsOnUI"),
|
||||
"set_state": lambda s: self._params.put_bool("PedalsOnUI", s),
|
||||
"icon": "toggle_icons/icon_display.png",
|
||||
"color": "#8B5CF6",
|
||||
"visible": lambda: self._params.get_bool("CustomUI"),
|
||||
},
|
||||
{
|
||||
"title": tr_noop("Dynamic Pedals"),
|
||||
"type": "toggle",
|
||||
"key": "DynamicPedalsOnUI",
|
||||
"get_state": lambda: self._params.get_bool("DynamicPedalsOnUI"),
|
||||
"set_state": lambda s: self._set_exclusive_pedal("DynamicPedalsOnUI", "StaticPedalsOnUI", s),
|
||||
"icon": "toggle_icons/icon_display.png",
|
||||
"color": "#8B5CF6",
|
||||
"visible": lambda: self._params.get_bool("CustomUI") and self._params.get_bool("PedalsOnUI"),
|
||||
},
|
||||
{
|
||||
"title": tr_noop("Static Pedals"),
|
||||
"type": "toggle",
|
||||
"key": "StaticPedalsOnUI",
|
||||
"get_state": lambda: self._params.get_bool("StaticPedalsOnUI"),
|
||||
"set_state": lambda s: self._set_exclusive_pedal("StaticPedalsOnUI", "DynamicPedalsOnUI", s),
|
||||
"icon": "toggle_icons/icon_display.png",
|
||||
"color": "#8B5CF6",
|
||||
"visible": lambda: self._params.get_bool("CustomUI") and self._params.get_bool("PedalsOnUI"),
|
||||
},
|
||||
{
|
||||
"title": tr_noop("Rotating Wheel"),
|
||||
"type": "toggle",
|
||||
"key": "RotatingWheel",
|
||||
"get_state": lambda: self._params.get_bool("RotatingWheel"),
|
||||
"set_state": lambda s: self._params.put_bool("RotatingWheel", s),
|
||||
"icon": "toggle_icons/icon_steering.png",
|
||||
"color": "#8B5CF6",
|
||||
"visible": lambda: self._params.get_bool("CustomUI"),
|
||||
},
|
||||
]
|
||||
self._rebuild_grid()
|
||||
|
||||
def _set_exclusive_pedal(self, key, other_key, state):
|
||||
self._params.put_bool(key, state)
|
||||
if state:
|
||||
self._params.put_bool(other_key, False)
|
||||
self._rebuild_grid()
|
||||
|
||||
def _rebuild_grid(self):
|
||||
if not self.CATEGORIES:
|
||||
return
|
||||
if self._tile_grid is None:
|
||||
self._tile_grid = TileGrid(columns=None, padding=20)
|
||||
self._tile_grid.clear()
|
||||
|
||||
pedals_on_ui = self._params.get_bool("PedalsOnUI")
|
||||
|
||||
for cat in self.CATEGORIES:
|
||||
key = cat.get("key")
|
||||
visible = True
|
||||
|
||||
if key == "AccelerationPath":
|
||||
visible &= starpilot_state.car_state.hasOpenpilotLongitudinal
|
||||
elif key == "BlindSpotPath":
|
||||
visible &= starpilot_state.car_state.hasBSM
|
||||
elif key == "PedalsOnUI":
|
||||
visible &= starpilot_state.car_state.hasOpenpilotLongitudinal
|
||||
elif key == "DynamicPedalsOnUI":
|
||||
visible &= starpilot_state.car_state.hasOpenpilotLongitudinal and pedals_on_ui
|
||||
elif key == "StaticPedalsOnUI":
|
||||
visible &= starpilot_state.car_state.hasOpenpilotLongitudinal and pedals_on_ui
|
||||
|
||||
if not visible:
|
||||
continue
|
||||
|
||||
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"))
|
||||
self._tile_grid.add_tile(tile)
|
||||
|
||||
|
||||
class StarPilotModelUILayout(StarPilotPanel):
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
self.CATEGORIES = [
|
||||
{
|
||||
"title": tr_noop("Model UI"),
|
||||
"type": "toggle",
|
||||
"get_state": lambda: self._params.get_bool("ModelUI"),
|
||||
"set_state": lambda s: self._params.put_bool("ModelUI", s),
|
||||
"icon": "toggle_icons/icon_road.png",
|
||||
"color": "#8B5CF6",
|
||||
},
|
||||
{
|
||||
"title": tr_noop("Dynamic Path"),
|
||||
"type": "toggle",
|
||||
"key": "DynamicPathWidth",
|
||||
"get_state": lambda: self._params.get_bool("DynamicPathWidth"),
|
||||
"set_state": lambda s: self._params.put_bool("DynamicPathWidth", s),
|
||||
"icon": "toggle_icons/icon_road.png",
|
||||
"color": "#8B5CF6",
|
||||
"visible": lambda: self._params.get_bool("ModelUI"),
|
||||
},
|
||||
{
|
||||
"title": tr_noop("Lane Line Width"),
|
||||
"type": "value",
|
||||
"key": "LaneLinesWidth",
|
||||
"get_value": lambda: self._get_lane_lines_display(),
|
||||
"on_click": lambda: self._show_int_selector("LaneLinesWidth", 0, 24, self._get_lane_lines_unit()),
|
||||
"icon": "toggle_icons/icon_road.png",
|
||||
"color": "#8B5CF6",
|
||||
"visible": lambda: self._params.get_bool("ModelUI"),
|
||||
},
|
||||
{
|
||||
"title": tr_noop("Lane Line Color"),
|
||||
"type": "value",
|
||||
"key": "LaneLinesColor",
|
||||
"get_value": lambda: self._get_color_display("LaneLinesColor"),
|
||||
"on_click": lambda: self._show_color_selector("LaneLinesColor"),
|
||||
"icon": "toggle_icons/icon_road.png",
|
||||
"color": "#8B5CF6",
|
||||
"visible": lambda: self._params.get_bool("ModelUI"),
|
||||
},
|
||||
{
|
||||
"title": tr_noop("Border Width"),
|
||||
"type": "value",
|
||||
"key": "BorderWidth",
|
||||
"get_value": lambda: self._get_border_width_display(),
|
||||
"on_click": lambda: self._show_float_selector("BorderWidth", 25, 250, 5, "%"),
|
||||
"icon": "toggle_icons/icon_road.png",
|
||||
"color": "#8B5CF6",
|
||||
"visible": lambda: self._params.get_bool("ModelUI"),
|
||||
},
|
||||
{
|
||||
"title": tr_noop("Path Edge Width"),
|
||||
"type": "value",
|
||||
"key": "PathEdgeWidth",
|
||||
"get_value": lambda: f"{self._params.get_int('PathEdgeWidth')}%",
|
||||
"on_click": lambda: self._show_int_selector("PathEdgeWidth", 0, 100, "%"),
|
||||
"icon": "toggle_icons/icon_road.png",
|
||||
"color": "#8B5CF6",
|
||||
"visible": lambda: self._params.get_bool("ModelUI"),
|
||||
},
|
||||
{
|
||||
"title": tr_noop("Path Edge Color"),
|
||||
"type": "value",
|
||||
"key": "PathEdgesColor",
|
||||
"get_value": lambda: self._get_color_display("PathEdgesColor"),
|
||||
"on_click": lambda: self._show_color_selector("PathEdgesColor"),
|
||||
"icon": "toggle_icons/icon_road.png",
|
||||
"color": "#8B5CF6",
|
||||
"visible": lambda: self._params.get_bool("ModelUI"),
|
||||
},
|
||||
{
|
||||
"title": tr_noop("Path Width"),
|
||||
"type": "value",
|
||||
"key": "PathWidth",
|
||||
"get_value": lambda: self._get_path_width_display(),
|
||||
"on_click": lambda: self._show_path_width_selector(),
|
||||
"icon": "toggle_icons/icon_road.png",
|
||||
"color": "#8B5CF6",
|
||||
"visible": lambda: self._params.get_bool("ModelUI"),
|
||||
},
|
||||
{
|
||||
"title": tr_noop("Path Color"),
|
||||
"type": "value",
|
||||
"key": "PathColor",
|
||||
"get_value": lambda: self._get_color_display("PathColor"),
|
||||
"on_click": lambda: self._show_color_selector("PathColor"),
|
||||
"icon": "toggle_icons/icon_road.png",
|
||||
"color": "#8B5CF6",
|
||||
"visible": lambda: self._params.get_bool("ModelUI"),
|
||||
},
|
||||
{
|
||||
"title": tr_noop("Road Edge Width"),
|
||||
"type": "value",
|
||||
"key": "RoadEdgesWidth",
|
||||
"get_value": lambda: self._get_road_edges_display(),
|
||||
"on_click": lambda: self._show_int_selector("RoadEdgesWidth", 0, 24, self._get_road_edges_unit()),
|
||||
"icon": "toggle_icons/icon_road.png",
|
||||
"color": "#8B5CF6",
|
||||
"visible": lambda: self._params.get_bool("ModelUI"),
|
||||
},
|
||||
]
|
||||
self._rebuild_grid()
|
||||
|
||||
def _get_lane_lines_unit(self):
|
||||
return "cm" if self._params.get_bool("IsMetric") else "in"
|
||||
|
||||
def _get_lane_lines_display(self):
|
||||
val = self._params.get_int('LaneLinesWidth')
|
||||
if self._params.get_bool("IsMetric"):
|
||||
return f"{int(val * 2.54)}cm"
|
||||
return f"{val}in"
|
||||
|
||||
def _get_path_width_unit(self):
|
||||
return "m" if self._params.get_bool("IsMetric") else "ft"
|
||||
|
||||
def _get_border_width_display(self):
|
||||
return f"{int(round(self._params.get_float('BorderWidth')))}%"
|
||||
|
||||
def _get_path_width_display(self):
|
||||
val = self._params.get_float('PathWidth')
|
||||
if self._params.get_bool("IsMetric"):
|
||||
return f"{val / 3.28084:.1f}m"
|
||||
return f"{val:.1f}ft"
|
||||
|
||||
def _get_road_edges_unit(self):
|
||||
return "cm" if self._params.get_bool("IsMetric") else "in"
|
||||
|
||||
def _get_road_edges_display(self):
|
||||
val = self._params.get_int('RoadEdgesWidth')
|
||||
if self._params.get_bool("IsMetric"):
|
||||
return f"{int(val * 2.54)}cm"
|
||||
return f"{val}in"
|
||||
|
||||
def _show_path_width_selector(self):
|
||||
if self._params.get_bool("IsMetric"):
|
||||
self._show_float_selector("PathWidth", 0, 10, 0.1, "m", convert=lambda v: v / 3.28084, unconvert=lambda v: v * 3.28084)
|
||||
else:
|
||||
self._show_float_selector("PathWidth", 0, 10, 0.1, "ft")
|
||||
|
||||
def _show_int_selector(self, key, min_v, max_v, unit=""):
|
||||
def on_close(res, val):
|
||||
if res == DialogResult.CONFIRM:
|
||||
self._params.put_int(key, int(val))
|
||||
self._rebuild_grid()
|
||||
|
||||
gui_app.push_widget(AetherSliderDialog(tr(key), min_v, max_v, 1, self._params.get_int(key), on_close, unit=unit, color="#8B5CF6"))
|
||||
|
||||
def _show_float_selector(self, key, min_v, max_v, step, unit="", convert=None, unconvert=None):
|
||||
current = self._params.get_float(key)
|
||||
if convert:
|
||||
current = convert(current)
|
||||
|
||||
def on_close(res, val):
|
||||
if res == DialogResult.CONFIRM:
|
||||
v = float(val)
|
||||
if unconvert:
|
||||
v = unconvert(v)
|
||||
self._params.put_float(key, v)
|
||||
self._rebuild_grid()
|
||||
|
||||
gui_app.push_widget(AetherSliderDialog(tr(key), min_v, max_v, step, current, on_close, unit=unit, color="#8B5CF6"))
|
||||
|
||||
def _get_color_display(self, key):
|
||||
val = self._params.get(key, encoding='utf-8') or ""
|
||||
if not val:
|
||||
return "Stock"
|
||||
return val.upper()
|
||||
|
||||
def _show_color_selector(self, key):
|
||||
presets = ["Stock", "#FFFFFF", "#178644", "#3B82F6", "#E63956", "#8B5CF6", "#F59E0B"]
|
||||
current = self._params.get(key, encoding='utf-8') or "Stock"
|
||||
|
||||
def on_select(res):
|
||||
if res == DialogResult.CONFIRM and dialog.selection:
|
||||
if dialog.selection == "Stock":
|
||||
self._params.remove(key)
|
||||
else:
|
||||
self._params.put(key, dialog.selection)
|
||||
self._rebuild_grid()
|
||||
|
||||
dialog = MultiOptionDialog(tr(key), presets, current, callback=on_select)
|
||||
gui_app.push_widget(dialog)
|
||||
|
||||
|
||||
class StarPilotNavigationVisualsLayout(StarPilotPanel):
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
self.CATEGORIES = [
|
||||
{
|
||||
"title": tr_noop("Navigation Widgets"),
|
||||
"type": "toggle",
|
||||
"get_state": lambda: self._params.get_bool("NavigationUI"),
|
||||
"set_state": lambda s: self._params.put_bool("NavigationUI", s),
|
||||
"icon": "toggle_icons/icon_map.png",
|
||||
"color": "#8B5CF6",
|
||||
},
|
||||
{
|
||||
"title": tr_noop("Road Name"),
|
||||
"type": "toggle",
|
||||
"get_state": lambda: self._params.get_bool("RoadNameUI"),
|
||||
"set_state": lambda s: self._params.put_bool("RoadNameUI", s),
|
||||
"icon": "toggle_icons/icon_navigate.png",
|
||||
"color": "#8B5CF6",
|
||||
"visible": lambda: self._params.get_bool("NavigationUI"),
|
||||
},
|
||||
{
|
||||
"title": tr_noop("Speed Limits"),
|
||||
"type": "toggle",
|
||||
"get_state": lambda: self._params.get_bool("ShowSpeedLimits"),
|
||||
"set_state": lambda s: self._params.put_bool("ShowSpeedLimits", s),
|
||||
"icon": "toggle_icons/icon_speed_limit.png",
|
||||
"color": "#8B5CF6",
|
||||
"visible": lambda: self._params.get_bool("NavigationUI"),
|
||||
},
|
||||
{
|
||||
"title": tr_noop("Vienna Signs"),
|
||||
"type": "toggle",
|
||||
"get_state": lambda: self._params.get_bool("UseVienna"),
|
||||
"set_state": lambda s: self._params.put_bool("UseVienna", s),
|
||||
"icon": "toggle_icons/icon_speed_limit.png",
|
||||
"color": "#8B5CF6",
|
||||
"visible": lambda: self._params.get_bool("NavigationUI"),
|
||||
},
|
||||
]
|
||||
self._rebuild_grid()
|
||||
|
||||
|
||||
class StarPilotVisualQOLLayout(StarPilotPanel):
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
self.CAMERA_VIEWS = ["Auto", "Driver", "Standard", "Wide"]
|
||||
self.CATEGORIES = [
|
||||
{
|
||||
"title": tr_noop("Quality of Life"),
|
||||
"type": "toggle",
|
||||
"get_state": lambda: self._params.get_bool("QOLVisuals"),
|
||||
"set_state": lambda s: self._params.put_bool("QOLVisuals", s),
|
||||
"icon": "toggle_icons/icon_quality_of_life.png",
|
||||
"color": "#8B5CF6",
|
||||
},
|
||||
{
|
||||
"title": tr_noop("Camera View"),
|
||||
"type": "value",
|
||||
"key": "CameraView",
|
||||
"get_value": lambda: tr(self.CAMERA_VIEWS[self._params.get_int('CameraView')]),
|
||||
"on_click": lambda: self._show_camera_view_selector(),
|
||||
"icon": "toggle_icons/icon_display.png",
|
||||
"color": "#8B5CF6",
|
||||
"visible": lambda: self._params.get_bool("QOLVisuals"),
|
||||
},
|
||||
{
|
||||
"title": tr_noop("Driver Camera"),
|
||||
"type": "toggle",
|
||||
"get_state": lambda: self._params.get_bool("DriverCamera"),
|
||||
"set_state": lambda s: self._params.put_bool("DriverCamera", s),
|
||||
"icon": "toggle_icons/icon_display.png",
|
||||
"color": "#8B5CF6",
|
||||
"visible": lambda: self._params.get_bool("QOLVisuals"),
|
||||
},
|
||||
{
|
||||
"title": tr_noop("Stopped Timer"),
|
||||
"type": "toggle",
|
||||
"get_state": lambda: self._params.get_bool("StoppedTimer"),
|
||||
"set_state": lambda s: self._params.put_bool("StoppedTimer", s),
|
||||
"icon": "toggle_icons/icon_display.png",
|
||||
"color": "#8B5CF6",
|
||||
"visible": lambda: self._params.get_bool("QOLVisuals"),
|
||||
},
|
||||
]
|
||||
self._rebuild_grid()
|
||||
|
||||
def _show_camera_view_selector(self):
|
||||
current = self._params.get_int("CameraView")
|
||||
|
||||
def on_select(res):
|
||||
if res == DialogResult.CONFIRM and dialog.selection:
|
||||
idx = self.CAMERA_VIEWS.index(dialog.selection)
|
||||
self._params.put_int("CameraView", idx)
|
||||
self._rebuild_grid()
|
||||
|
||||
dialog = MultiOptionDialog(tr("Camera View"), self.CAMERA_VIEWS, self.CAMERA_VIEWS[current], callback=on_select)
|
||||
gui_app.push_widget(dialog)
|
||||
Reference in New Issue
Block a user