UI Replay

This commit is contained in:
firestar5683
2026-04-12 23:03:57 -05:00
parent 66f064d178
commit 7d7d39faca
17 changed files with 363 additions and 90 deletions
+1 -1
View File
@@ -1,2 +1,2 @@
extern const uint8_t gitversion[19];
const uint8_t gitversion[19] = "DEV-541c4d47-DEBUG";
const uint8_t gitversion[19] = "DEV-66f064d1-DEBUG";
+1 -1
View File
@@ -1 +1 @@
DEV-541c4d47-DEBUG
DEV-66f064d1-DEBUG
+9
View File
@@ -265,6 +265,14 @@ params.put_bool("ForceOffroad", False)
PY
}
seed_starpilot_theme() {
"${ROOT_DIR}/.venv/bin/python3" - <<'PY'
from openpilot.starpilot.common.starpilot_functions import seed_desktop_theme_assets
seed_desktop_theme_assets()
PY
}
build_replay() {
SP_DISABLE_AUTO_DEVICE_SCONS=1 "${ROOT_DIR}/.venv/bin/scons" --extras -j"${jobs}" tools/replay/replay
}
@@ -364,6 +372,7 @@ case " ${UI_TARGETS[*]-} " in
esac
seed_params
seed_starpilot_theme
echo "Starting replay: ${REPLAY_ARGS[*]}"
launch_replay
+9
View File
@@ -266,6 +266,14 @@ start_fake_wifi() {
FAKE_WIFI_PID=$!
}
seed_starpilot_theme() {
"${PY_BIN}" - <<'PY'
from openpilot.starpilot.common.starpilot_functions import seed_desktop_theme_assets
seed_desktop_theme_assets()
PY
}
if ! python_ui_runtime_ok >/dev/null 2>&1; then
echo "Preparing host Python UI runtime extensions..."
sync_deps
@@ -329,6 +337,7 @@ params.put_bool("OpenpilotEnabledToggle", True)
params.put_bool("IsDriverViewEnabled", False)
PY
seed_starpilot_theme
kill_stale_c4_ui
start_fake_wifi
"${PY_BIN}" selfdrive/ui/ui.py "$@"
+9
View File
@@ -120,6 +120,14 @@ start_fake_wifi() {
FAKE_WIFI_PID=$!
}
seed_starpilot_theme() {
"${ROOT_DIR}/.venv/bin/python3" - <<'PY'
from openpilot.starpilot.common.starpilot_functions import seed_desktop_theme_assets
seed_desktop_theme_assets()
PY
}
stop_fake_wifi() {
if [[ -n "${FAKE_WIFI_PID}" ]]; then
kill "${FAKE_WIFI_PID}" >/dev/null 2>&1 || true
@@ -225,6 +233,7 @@ if [[ "${SP_C3_COMPILE_ONLY:-0}" == "1" ]]; then
exit 0
fi
seed_starpilot_theme
start_fake_wifi
trap stop_fake_wifi EXIT
"${HOST_UI}" "$@"
+9
View File
@@ -266,6 +266,14 @@ start_fake_wifi() {
FAKE_WIFI_PID=$!
}
seed_starpilot_theme() {
"${PY_BIN}" - <<'PY'
from openpilot.starpilot.common.starpilot_functions import seed_desktop_theme_assets
seed_desktop_theme_assets()
PY
}
if ! python_ui_runtime_ok >/dev/null 2>&1; then
echo "Preparing host Python UI runtime extensions..."
sync_deps
@@ -329,6 +337,7 @@ params.put_bool("OpenpilotEnabledToggle", True)
params.put_bool("IsDriverViewEnabled", False)
PY
seed_starpilot_theme
kill_stale_raybig_ui
start_fake_wifi
"${PY_BIN}" selfdrive/ui/ui.py "$@"
+76
View File
@@ -0,0 +1,76 @@
import pyray as rl
from openpilot.selfdrive.ui.ui_state import UIStatus, UIState
CEM_DISABLED_OVERRIDE_STATUSES = {1}
CEM_MANUAL_OVERRIDE_STATUSES = {1, 2}
CEM_ACTIVE_STATUSES = {3, 4, 5, 6, 7, 8}
DISENGAGED_COLOR = rl.Color(18, 40, 57, 255)
AOL_COLOR = rl.Color(10, 186, 181, 255)
ENGAGED_COLOR = rl.Color(22, 127, 64, 255)
OVERRIDE_COLOR = rl.Color(137, 146, 141, 255)
EXPERIMENTAL_COLOR = rl.Color(218, 111, 37, 255)
CEM_OVERRIDE_COLOR = rl.Color(255, 214, 0, 255)
SWITCHBACK_COLOR = rl.Color(139, 108, 197, 255)
TRAFFIC_COLOR = rl.Color(201, 34, 49, 255)
def get_border_color(state: UIState):
enabled = state.sm["selfdriveState"].enabled
lateral_active = enabled or state.always_on_lateral_active
if state.status == UIStatus.OVERRIDE:
return OVERRIDE_COLOR
if state.switchback_mode_enabled and lateral_active:
return SWITCHBACK_COLOR
if state.traffic_mode_enabled and enabled:
return TRAFFIC_COLOR
if state.always_on_lateral_active:
return AOL_COLOR
# Only color the border for CEM/experimental while actually enabled.
if enabled and state.conditional_status in CEM_DISABLED_OVERRIDE_STATUSES:
return CEM_OVERRIDE_COLOR
if enabled and state.sm["selfdriveState"].experimentalMode:
return EXPERIMENTAL_COLOR
if state.status == UIStatus.ENGAGED:
return ENGAGED_COLOR
return DISENGAGED_COLOR
def get_path_edge_color(state: UIState):
if state.conditional_status in CEM_ACTIVE_STATUSES:
return EXPERIMENTAL_COLOR
return get_border_color(state)
def get_screen_edge_color(state: UIState):
enabled = state.sm["selfdriveState"].enabled
lateral_active = enabled or state.always_on_lateral_active
if state.status == UIStatus.OVERRIDE:
return OVERRIDE_COLOR
if state.switchback_mode_enabled and lateral_active:
return SWITCHBACK_COLOR
if state.always_on_lateral_active:
return AOL_COLOR
if state.conditional_status in CEM_DISABLED_OVERRIDE_STATUSES:
return CEM_OVERRIDE_COLOR
if state.sm["selfdriveState"].experimentalMode:
return EXPERIMENTAL_COLOR
if state.traffic_mode_enabled and enabled:
return TRAFFIC_COLOR
return get_border_color(state)
def get_experimental_mode_banner_text(state: UIState):
conditional_enabled = state.params.get_bool("ConditionalExperimental")
# With CEM enabled, only surface banner text for explicit manual override states.
# Automatic CEM transitions should only be reflected by path/border coloring.
if conditional_enabled:
if state.conditional_status in CEM_MANUAL_OVERRIDE_STATUSES:
return "OVERRIDDEN"
return None
if state.sm["selfdriveState"].experimentalMode:
return "EXPERIMENTAL"
return "CHILL"
+34 -56
View File
@@ -1,57 +1,35 @@
import pyray as rl
from openpilot.selfdrive.ui.ui_state import UIStatus, UIState
from openpilot.selfdrive.ui.lib.starpilot_status import (
AOL_COLOR,
CEM_ACTIVE_STATUSES,
CEM_DISABLED_OVERRIDE_STATUSES,
CEM_MANUAL_OVERRIDE_STATUSES,
CEM_OVERRIDE_COLOR,
DISENGAGED_COLOR,
ENGAGED_COLOR,
EXPERIMENTAL_COLOR,
OVERRIDE_COLOR,
SWITCHBACK_COLOR,
TRAFFIC_COLOR,
get_border_color,
get_experimental_mode_banner_text,
get_path_edge_color,
get_screen_edge_color,
)
CEM_DISABLED_OVERRIDE_STATUSES = {1}
CEM_MANUAL_OVERRIDE_STATUSES = {1, 2}
CEM_ACTIVE_STATUSES = {3, 4, 5, 6, 7, 8}
DISENGAGED_COLOR = rl.Color(18, 40, 57, 255)
AOL_COLOR = rl.Color(10, 186, 181, 255)
ENGAGED_COLOR = rl.Color(22, 127, 64, 255)
OVERRIDE_COLOR = rl.Color(137, 146, 141, 255)
EXPERIMENTAL_COLOR = rl.Color(218, 111, 37, 255)
CEM_OVERRIDE_COLOR = rl.Color(255, 214, 0, 255)
SWITCHBACK_COLOR = rl.Color(139, 108, 197, 255)
TRAFFIC_COLOR = rl.Color(201, 34, 49, 255)
def get_border_color(state: UIState):
enabled = state.sm["selfdriveState"].enabled
lateral_active = enabled or state.always_on_lateral_active
if state.status == UIStatus.OVERRIDE:
return OVERRIDE_COLOR
if state.switchback_mode_enabled and lateral_active:
return SWITCHBACK_COLOR
if state.traffic_mode_enabled and enabled:
return TRAFFIC_COLOR
if state.always_on_lateral_active:
return AOL_COLOR
# Only color the border for CEM/experimental while actually enabled.
if enabled and state.conditional_status in CEM_DISABLED_OVERRIDE_STATUSES:
return CEM_OVERRIDE_COLOR
if enabled and state.sm["selfdriveState"].experimentalMode:
return EXPERIMENTAL_COLOR
if state.status == UIStatus.ENGAGED:
return ENGAGED_COLOR
return DISENGAGED_COLOR
def get_path_edge_color(state: UIState):
if state.conditional_status in CEM_ACTIVE_STATUSES:
return EXPERIMENTAL_COLOR
return get_border_color(state)
def get_experimental_mode_banner_text(state: UIState):
conditional_enabled = state.params.get_bool("ConditionalExperimental")
# With CEM enabled, only surface banner text for explicit manual override states.
# Automatic CEM transitions should only be reflected by path/border coloring.
if conditional_enabled:
if state.conditional_status in CEM_MANUAL_OVERRIDE_STATUSES:
return "OVERRIDDEN"
return None
if state.sm["selfdriveState"].experimentalMode:
return "EXPERIMENTAL"
return "CHILL"
__all__ = [
"AOL_COLOR",
"CEM_ACTIVE_STATUSES",
"CEM_DISABLED_OVERRIDE_STATUSES",
"CEM_MANUAL_OVERRIDE_STATUSES",
"CEM_OVERRIDE_COLOR",
"DISENGAGED_COLOR",
"ENGAGED_COLOR",
"EXPERIMENTAL_COLOR",
"OVERRIDE_COLOR",
"SWITCHBACK_COLOR",
"TRAFFIC_COLOR",
"get_border_color",
"get_experimental_mode_banner_text",
"get_path_edge_color",
"get_screen_edge_color",
]
+2 -1
View File
@@ -11,6 +11,7 @@ from openpilot.selfdrive.ui.onroad.driver_state import DriverStateRenderer
from openpilot.selfdrive.ui.onroad.hud_renderer import HudRenderer
from openpilot.selfdrive.ui.onroad.model_renderer import ModelRenderer
from openpilot.selfdrive.ui.onroad.cameraview import CameraView
from openpilot.selfdrive.ui.lib.starpilot_status import get_screen_edge_color
from openpilot.system.ui.lib.application import gui_app, FontWeight
from openpilot.common.transformations.camera import DEVICE_CAMERAS, DeviceCameraConfig, view_frame_from_device_frame
from openpilot.common.transformations.orientation import rot_from_euler
@@ -215,7 +216,7 @@ class AugmentedRoadView(CameraView):
def _draw_border(self, rect: rl.Rectangle):
rl.draw_rectangle_lines_ex(rect, UI_BORDER_SIZE, rl.BLACK)
border_roundness = 0.12
border_color = BORDER_COLORS.get(ui_state.status, BORDER_COLORS[UIStatus.DISENGAGED])
border_color = get_screen_edge_color(ui_state)
border_rect = rl.Rectangle(rect.x + UI_BORDER_SIZE, rect.y + UI_BORDER_SIZE,
rect.width - 2 * UI_BORDER_SIZE, rect.height - 2 * UI_BORDER_SIZE)
rl.draw_rectangle_rounded_lines_ex(border_rect, border_roundness, 10, UI_BORDER_SIZE, border_color)
@@ -3,7 +3,7 @@ import time
from msgq.visionipc import VisionStreamType
from openpilot.common.params import Params
from openpilot.selfdrive.ui import UI_BORDER_SIZE
from openpilot.selfdrive.ui.onroad.augmented_road_view import AugmentedRoadView, BORDER_COLORS
from openpilot.selfdrive.ui.onroad.augmented_road_view import AugmentedRoadView
from openpilot.selfdrive.ui.onroad.starpilot.curve_speed_border import render_glow, render_filament
from openpilot.selfdrive.ui.onroad.starpilot.path import render_adjacent_paths, render_blind_spot_path, render_path_edges
from openpilot.selfdrive.ui.onroad.starpilot.personality_button import PersonalityButton, BTN_SIZE
@@ -11,11 +11,10 @@ from openpilot.selfdrive.ui.onroad.starpilot.slc_speed_limit import (
render_speed_limit, handle_slc_click, SET_SPEED_X_OFFSET, SET_SPEED_Y_OFFSET,
SET_SPEED_WIDTH_IMP, SET_SPEED_WIDTH_MET, SET_SPEED_HEIGHT, SIGN_MARGIN,
)
from openpilot.selfdrive.ui.ui_state import ui_state, UIStatus
from openpilot.selfdrive.ui.ui_state import ui_state
from openpilot.selfdrive.ui.lib.starpilot_status import get_screen_edge_color
from openpilot.system.ui.lib.application import MousePos, gui_app, FontWeight
AOL_COLOR = rl.Color(10, 186, 181, 255)
class StarPilotOnroadView(AugmentedRoadView):
def __init__(self, stream_type: VisionStreamType = VisionStreamType.VISION_STREAM_ROAD):
@@ -181,7 +180,7 @@ class StarPilotOnroadView(AugmentedRoadView):
render_glow(border_rect)
# Layer 4: Standard border
border_color = AOL_COLOR if ui_state.always_on_lateral_active else BORDER_COLORS.get(ui_state.status, BORDER_COLORS[UIStatus.DISENGAGED])
border_color = get_screen_edge_color(ui_state)
rl.draw_rectangle_rounded_lines_ex(border_rect, 0.12, 10, UI_BORDER_SIZE, border_color)
# Layer 5: Amber filament (on top of standard border)
+7 -1
View File
@@ -20,7 +20,7 @@ void drawIcon(QPainter &p, const QPoint &center, const QPixmap &img, const QBrus
}
// ExperimentalButton
ExperimentalButton::ExperimentalButton(QWidget *parent) : experimental_mode(false), engageable(false), QPushButton(parent) {
ExperimentalButton::ExperimentalButton(QWidget *parent) : QPushButton(parent), experimental_mode(false), engageable(false), steering_angle_deg(0) {
setFixedSize(btn_size, btn_size);
engage_img = loadPixmap("../assets/icons/chffr_wheel.png", {img_size, img_size});
@@ -86,6 +86,9 @@ void ExperimentalButton::paintEvent(QPaintEvent *event) {
drawIcon(p, QPoint(btn_size / 2, btn_size / 2), wheel_gif->currentPixmap(), background_color, (isDown() || !engageable) ? 0.6 : 1.0, steering_angle_deg);
} else if (!wheel_img.isNull()) {
drawIcon(p, QPoint(btn_size / 2, btn_size / 2), wheel_img, background_color, (isDown() || !engageable) ? 0.6 : 1.0, steering_angle_deg);
} else {
QPixmap img = experimental_mode ? experimental_img : engage_img;
drawIcon(p, QPoint(btn_size / 2, btn_size / 2), img, background_color, (isDown() || !engageable) ? 0.6 : 1.0, steering_angle_deg);
}
}
@@ -115,4 +118,7 @@ void ExperimentalButton::updateBackgroundColor() {
void ExperimentalButton::updateTheme() {
loadImage("../../starpilot/assets/active_theme/steering_wheel/wheel", wheel_img, wheel_gif, QSize(img_size, img_size), this);
if (!wheel_gif && wheel_img.isNull()) {
loadImage("../../starpilot/assets/stock_theme/steering_wheel/wheel", wheel_img, wheel_gif, QSize(img_size, img_size), this);
}
}
+14 -1
View File
@@ -58,14 +58,27 @@ void OnroadWindow::updateState(const UIState &s, const StarPilotUIState &fs) {
alerts->updateState(s, fs);
nvg->updateState(s, fs);
const StarPilotUIScene &starpilot_scene = fs.starpilot_scene;
const auto selfdriveState = (*s.sm)["selfdriveState"].getSelfdriveState();
QColor bgColor = bg_colors[s.status];
if (starpilot_scene.switchback_mode_enabled && (selfdriveState.getEnabled() || starpilot_scene.always_on_lateral_active)) {
bgColor = bg_colors[STATUS_SWITCHBACK_MODE_ENABLED];
} else if (starpilot_scene.always_on_lateral_active) {
bgColor = bg_colors[STATUS_ALWAYS_ON_LATERAL_ACTIVE];
} else if (starpilot_scene.conditional_status == 1) {
bgColor = bg_colors[STATUS_CEM_DISABLED];
} else if (selfdriveState.getExperimentalMode()) {
bgColor = bg_colors[STATUS_EXPERIMENTAL_MODE_ENABLED];
} else if (starpilot_scene.traffic_mode_enabled && selfdriveState.getEnabled()) {
bgColor = bg_colors[STATUS_TRAFFIC_MODE_ENABLED];
}
if (bg != bgColor) {
// repaint border
bg = bgColor;
update();
}
const StarPilotUIScene &starpilot_scene = fs.starpilot_scene;
const QJsonObject &starpilot_toggles = starpilot_scene.starpilot_toggles;
starpilot_nvg->alertHeight = alerts->alertHeight;
BIN
View File
Binary file not shown.
+113 -5
View File
@@ -1,9 +1,13 @@
#!/usr/bin/env python3
import glob
import io
import json
import os
import random
import requests
import shutil
import subprocess
import zipfile
from datetime import date, timedelta
from dateutil import easter
@@ -19,6 +23,7 @@ DOWNLOAD_PROGRESS_PARAM = "ThemeDownloadProgress"
HOLIDAY_THEME_PATH = Path(__file__).parent / "holiday_themes"
STOCKOP_THEME_PATH = Path(__file__).parent / "stock_theme"
LOCAL_RESOURCES_PATH = Path(os.getenv("STARPILOT_LOCAL_RESOURCES_PATH", "~/StarPilot-Resources")).expanduser()
HOLIDAY_SLUGS = {
"new_years": "New Year's",
@@ -64,6 +69,7 @@ class ThemeManager:
(THEME_SAVE_PATH / "bootlogos").mkdir(parents=True, exist_ok=True)
(THEME_SAVE_PATH / "theme_packs").mkdir(parents=True, exist_ok=True)
(THEME_SAVE_PATH / "steering_wheels").mkdir(parents=True, exist_ok=True)
self.sync_local_resources()
self.theme_sizes = load_json_file(self.theme_sizes_path)
@@ -77,6 +83,96 @@ class ThemeManager:
if boot_run:
self.copy_default_theme()
@staticmethod
def _local_resources_available():
return LOCAL_RESOURCES_PATH.is_dir() and (LOCAL_RESOURCES_PATH / ".git").exists()
@staticmethod
def _git_list_tree(ref):
result = subprocess.run(
["git", "-C", str(LOCAL_RESOURCES_PATH), "ls-tree", "-r", "--name-only", ref],
capture_output=True,
text=True,
check=True,
)
return [line for line in result.stdout.splitlines() if line]
@staticmethod
def _git_show_bytes(ref, path):
result = subprocess.run(
["git", "-C", str(LOCAL_RESOURCES_PATH), "show", f"{ref}:{path}"],
capture_output=True,
check=True,
)
return result.stdout
@staticmethod
def _directory_has_files(path):
return path.is_dir() and any(path.iterdir())
@staticmethod
def _write_file_if_missing(destination, data):
if destination.exists() and destination.stat().st_size > 0:
return False
destination.parent.mkdir(parents=True, exist_ok=True)
destination.write_bytes(data)
return True
@staticmethod
def _extract_zip_if_missing(zip_bytes, destination):
if ThemeManager._directory_has_files(destination):
return False
destination.mkdir(parents=True, exist_ok=True)
with zipfile.ZipFile(io.BytesIO(zip_bytes)) as archive:
archive.extractall(destination)
return True
def sync_local_resources(self):
if not self._local_resources_available():
return
try:
imported_assets = 0
for path in self._git_list_tree("Steering-Wheels"):
suffix = Path(path).suffix.lower()
if suffix not in {".gif", ".png", ".webp", ".jpg", ".jpeg"}:
continue
destination = THEME_SAVE_PATH / "steering_wheels" / Path(path).name
imported_assets += int(self._write_file_if_missing(destination, self._git_show_bytes("Steering-Wheels", path)))
for path in self._git_list_tree("Themes"):
path_obj = Path(path)
suffix = path_obj.suffix.lower()
if path_obj.parts[:1] == ("bootlogo",) and suffix in {".png", ".jpg", ".jpeg"}:
destination = THEME_SAVE_PATH / "bootlogos" / path_obj.name
imported_assets += int(self._write_file_if_missing(destination, self._git_show_bytes("Themes", path)))
continue
if suffix != ".zip" or len(path_obj.parts) != 2:
continue
theme_name, archive_name = path_obj.parts
component = Path(archive_name).stem.lower()
if component not in {"colors", "distance_icons", "icons", "signals", "sounds"}:
continue
destination = THEME_SAVE_PATH / "theme_packs" / theme_name / component
imported_assets += int(self._extract_zip_if_missing(self._git_show_bytes("Themes", path), destination))
for path in self._git_list_tree("Distance-Icons"):
path_obj = Path(path)
if path_obj.suffix.lower() != ".zip":
continue
destination = THEME_SAVE_PATH / "theme_packs" / path_obj.stem / "distance_icons"
imported_assets += int(self._extract_zip_if_missing(self._git_show_bytes("Distance-Icons", path), destination))
if imported_assets:
print(f"Imported {imported_assets} local theme assets from {LOCAL_RESOURCES_PATH}")
except (FileNotFoundError, subprocess.CalledProcessError, zipfile.BadZipFile, OSError) as error:
print(f"Failed to sync local theme resources from {LOCAL_RESOURCES_PATH}: {error}")
@staticmethod
def calculate_thanksgiving(year):
november_first = date(year, 11, 1)
@@ -645,9 +741,12 @@ class ThemeManager:
if self.downloading_theme:
return
self.sync_local_resources()
repo_url = get_repository_url(self.session)
if repo_url is None:
print("GitHub and GitLab are offline...")
self.update_theme_params([], [], [], [], [], [], [])
return
assets = self.fetch_assets(repo_url, starpilot_toggles)
@@ -734,11 +833,20 @@ class ThemeManager:
image_name = image.replace(" ", "_").lower()
matching_files = [images for images in wheel_location.iterdir() if images.stem.lower() in {image_name, "wheel"}]
if matching_files:
source_file = matching_files[0]
destination_file = wheel_save_location / f"wheel{source_file.suffix}"
destination_file.symlink_to(source_file)
print(f"Linked {destination_file} to {source_file}")
if not matching_files:
stock_location = STOCKOP_THEME_PATH / "steering_wheel"
matching_files = [images for images in stock_location.iterdir() if images.stem.lower() == "wheel"]
if matching_files:
print(f"Steering wheel '{image}' not found, using the stock steering wheel instead")
if not matching_files:
print(f"No steering wheel asset found for '{image}'")
return
source_file = matching_files[0]
destination_file = wheel_save_location / f"wheel{source_file.suffix}"
destination_file.symlink_to(source_file)
print(f"Linked {destination_file} to {source_file}")
def validate_themes(self, downloadable_boot_logos, downloadable_colors, downloadable_distance_icons, downloadable_icons, downloadable_signals, downloadable_sounds, downloadable_wheels, starpilot_toggles):
downloaded_data = self.params.get("ThemesDownloaded")
+32
View File
@@ -6,6 +6,7 @@ import threading
import time
from pathlib import Path
from types import SimpleNamespace
from cereal import messaging
from openpilot.common.basedir import BASEDIR
@@ -25,6 +26,37 @@ from openpilot.starpilot.common.starpilot_variables import (
)
def seed_desktop_theme_assets():
params = Params()
params_memory = Params(memory=True)
params_defaults = Params(return_defaults=True)
theme_manager = ThemeManager(params, params_memory, boot_run=True)
custom_themes = params_defaults.get_bool("CustomThemes")
random_themes = custom_themes and params_defaults.get_bool("RandomThemes")
starpilot_toggles = SimpleNamespace(
boot_logo=params_defaults.get("BootLogo", encoding="utf-8", default="starpilot"),
holiday_themes=params_defaults.get_bool("HolidayThemes"),
random_themes=random_themes,
random_themes_holidays=random_themes and params_defaults.get_bool("RandomThemesHolidays"),
color_scheme=params_defaults.get("ColorScheme", encoding="utf-8", default="stock") if custom_themes else "stock",
distance_icons=params_defaults.get("DistanceIconPack", encoding="utf-8", default="stock") if custom_themes else "stock",
icon_pack=params_defaults.get("IconPack", encoding="utf-8", default="stock") if custom_themes else "stock",
sound_pack=params_defaults.get("SoundPack", encoding="utf-8", default="stock") if custom_themes else "stock",
signal_icons=params_defaults.get("SignalAnimation", encoding="utf-8", default="stock") if custom_themes else "stock",
wheel_image=params_defaults.get("WheelIcon", encoding="utf-8", default="stock") if custom_themes else "stock",
)
theme_manager.update_active_theme(
time_validated=system_time_valid(),
starpilot_toggles=starpilot_toggles,
boot_run=True,
)
theme_manager.update_theme_asset("distance_icons", starpilot_toggles.distance_icons, boot_run=True)
theme_manager.update_wheel_image(starpilot_toggles.wheel_image, boot_run=True)
def starpilot_boot_functions(build_metadata, params):
params_memory = Params(memory=True)
+17 -5
View File
@@ -1,4 +1,5 @@
#include "starpilot/ui/qt/offroad/theme_settings.h"
#include "starpilot/ui/qt/offroad/theme_settings.h"
#include "system/hardware/hw.h"
bool isUserCreatedTheme(const QString &themeName) {
return themeName.endsWith("-user_created");
@@ -207,10 +208,21 @@ QString storeThemeName(const QString &input, const std::string &paramKey, Params
return getThemeName(paramKey, params);
}
StarPilotThemesPanel::StarPilotThemesPanel(StarPilotSettingsWindow *parent, bool forceOpen) : StarPilotListWidget(parent), parent(parent) {
forceOpenDescriptions = forceOpen;
QStackedLayout *themesLayout = new QStackedLayout();
StarPilotThemesPanel::StarPilotThemesPanel(StarPilotSettingsWindow *parent, bool forceOpen) : StarPilotListWidget(parent), parent(parent) {
forceOpenDescriptions = forceOpen;
if (Hardware::PC()) {
const QString themesRoot = QString::fromStdString(Path::comma_home() + "/starpilot/data/themes/");
bootLogosDirectory.setPath(themesRoot + "bootlogos/");
themePacksDirectory.setPath(themesRoot + "theme_packs/");
wheelsDirectory.setPath(themesRoot + "steering_wheels/");
}
QDir().mkpath(bootLogosDirectory.path());
QDir().mkpath(themePacksDirectory.path());
QDir().mkpath(wheelsDirectory.path());
QStackedLayout *themesLayout = new QStackedLayout();
addItem(themesLayout);
StarPilotListWidget *themesList = new StarPilotListWidget(this);
+26 -14
View File
@@ -65,16 +65,25 @@ void loadGif(const QString &gifPath, QSharedPointer<QMovie> &movie, const QSize
movie->start();
}
static QString resolveImagePath(const QString &basePath, const QStringList &extensions) {
for (const QString &extension : extensions) {
const QString candidate = basePath + "." + extension;
if (QFileInfo::exists(candidate)) {
return candidate;
}
}
return {};
}
void loadImage(const QString &basePath, QPixmap &pixmap, QSharedPointer<QMovie> &movie, const QSize &size, QWidget *parent) {
if (!parent || basePath.isEmpty()) {
return;
}
static QHash<QString, QPixmap> pixmapCache;
QString cacheKey = basePath + QString("_%1x%2").arg(size.width()).arg(size.height());
QString gifPath = basePath + ".gif";
if (QFileInfo::exists(gifPath)) {
const QString gifPath = resolveImagePath(basePath, {"gif"});
if (!gifPath.isEmpty()) {
loadGif(gifPath, movie, size, parent);
if (!pixmap.isNull()) {
pixmap = QPixmap();
@@ -84,6 +93,18 @@ void loadImage(const QString &basePath, QPixmap &pixmap, QSharedPointer<QMovie>
clearMovie(movie, parent);
const QString imagePath = resolveImagePath(basePath, {"png", "webp", "jpg", "jpeg"});
if (imagePath.isEmpty()) {
if (!pixmap.isNull()) {
pixmap = QPixmap();
parent->update();
}
return;
}
const QFileInfo imageInfo(imagePath);
const QString cacheKey = imagePath + QString("_%1x%2_%3").arg(size.width()).arg(size.height()).arg(imageInfo.lastModified().toMSecsSinceEpoch());
if (pixmapCache.contains(cacheKey)) {
QPixmap &cached = pixmapCache[cacheKey];
if (pixmap.cacheKey() != cached.cacheKey()) {
@@ -93,16 +114,7 @@ void loadImage(const QString &basePath, QPixmap &pixmap, QSharedPointer<QMovie>
return;
}
QString pngPath = basePath + ".png";
if (!QFileInfo::exists(pngPath)) {
if (!pixmap.isNull()) {
pixmap = QPixmap();
parent->update();
}
return;
}
QPixmap loadedPixmap(pngPath);
QPixmap loadedPixmap(imagePath);
if (!loadedPixmap.isNull()) {
pixmap = loadedPixmap.scaled(size, Qt::KeepAspectRatio, Qt::SmoothTransformation);
pixmapCache.insert(cacheKey, pixmap);