mirror of
https://github.com/firestar5683/StarPilot.git
synced 2026-07-01 19:42:07 +08:00
BigUI WIP: Refinement
BigUI WIP: Sound Panel Touch Sliders BigUI WIP: Sound Elements -> Aethergrid Extraction BigUI WIP: More push_widget
This commit is contained in:
-2972
File diff suppressed because it is too large
Load Diff
@@ -4,7 +4,7 @@ import math
|
||||
import time
|
||||
import pyray as rl
|
||||
from collections.abc import Callable
|
||||
from openpilot.system.ui.lib.application import gui_app, FontWeight, MousePos
|
||||
from openpilot.system.ui.lib.application import gui_app, FontWeight, MousePos, MouseEvent
|
||||
from openpilot.system.ui.lib.multilang import tr
|
||||
from openpilot.system.ui.lib.text_measure import measure_text_cached
|
||||
from openpilot.system.ui.widgets import Widget, DialogResult
|
||||
@@ -1352,3 +1352,117 @@ class TileGrid(Widget):
|
||||
tile = tiles_to_render[tile_idx]
|
||||
tile.render(rl.Rectangle(row_x + c * (row_tile_w + self._gap), rect.y + r * (tile_h + self._gap), row_tile_w, tile_h))
|
||||
tile_idx += 1
|
||||
|
||||
class AetherContinuousSlider(Widget):
|
||||
def __init__(self, min_val: float, max_val: float, step: float, current_val: float, on_change, title: str = "", unit: str = "", labels: dict | None = None, color: rl.Color | None = None):
|
||||
super().__init__()
|
||||
self.min_val = min_val
|
||||
self.max_val = max_val
|
||||
self.base_step = step
|
||||
self.current_val = current_val
|
||||
self.on_change = on_change
|
||||
self.title = title
|
||||
self.unit = unit
|
||||
self.labels = labels or {}
|
||||
self.color = color or rl.Color(54, 77, 239, 255)
|
||||
|
||||
self._is_dragging = False
|
||||
self._last_mouse_x = 0.0
|
||||
self._smooth_value = current_val
|
||||
self._font = gui_app.font(FontWeight.BOLD)
|
||||
|
||||
def _handle_mouse_press(self, mouse_pos: MousePos):
|
||||
if rl.check_collision_point_rec(mouse_pos, self._rect):
|
||||
self._is_dragging = True
|
||||
self._last_mouse_x = mouse_pos.x
|
||||
self._update_val_from_absolute(mouse_pos.x, self.base_step)
|
||||
|
||||
def _handle_mouse_release(self, mouse_pos: MousePos):
|
||||
if self._is_dragging:
|
||||
self._is_dragging = False
|
||||
|
||||
def _handle_mouse_event(self, mouse_event: MouseEvent):
|
||||
if self._is_dragging:
|
||||
dt = rl.get_frame_time()
|
||||
dx = mouse_event.pos.x - self._last_mouse_x
|
||||
self._last_mouse_x = mouse_event.pos.x
|
||||
|
||||
velocity = abs(dx / max(dt, 0.001))
|
||||
|
||||
if velocity > 1500:
|
||||
step = self.base_step * 10
|
||||
elif velocity > 500:
|
||||
step = self.base_step * 5
|
||||
else:
|
||||
step = self.base_step
|
||||
|
||||
self._update_val_from_absolute(mouse_event.pos.x, step)
|
||||
|
||||
def _update_val_from_absolute(self, mouse_x: float, step: float):
|
||||
track_w = self._rect.width
|
||||
if track_w <= 0: return
|
||||
rel_x = max(0.0, min(1.0, (mouse_x - self._rect.x) / track_w))
|
||||
val = self.min_val + rel_x * (self.max_val - self.min_val)
|
||||
self._set_snapped_val(val, step)
|
||||
|
||||
def _set_snapped_val(self, val: float, step: float):
|
||||
snapped = round((val - self.min_val) / step) * step + self.min_val
|
||||
snapped = max(self.min_val, min(self.max_val, snapped))
|
||||
if snapped != self.current_val:
|
||||
self.current_val = snapped
|
||||
self.on_change(self.current_val)
|
||||
|
||||
def _render(self, rect: rl.Rectangle):
|
||||
self.set_rect(rect)
|
||||
dt = rl.get_frame_time()
|
||||
|
||||
self._smooth_value += (self.current_val - self._smooth_value) * (1 - math.exp(-dt / 0.060))
|
||||
|
||||
rl.draw_rectangle_rounded(rect, 0.3, 16, rl.Color(35, 35, 40, 255))
|
||||
|
||||
frac = max(0.0, min(1.0, (self._smooth_value - self.min_val) / (self.max_val - self.min_val)))
|
||||
fill_w = frac * rect.width
|
||||
if fill_w > 0:
|
||||
fill_rect = rl.Rectangle(rect.x, rect.y, fill_w, rect.height)
|
||||
rl.draw_rectangle_rounded(fill_rect, 0.3, 16, self.color)
|
||||
|
||||
if fill_w > 16:
|
||||
rl.draw_rectangle_rounded(rl.Rectangle(fill_rect.x, fill_rect.y, fill_rect.width - 2, fill_rect.height - 2), 0.3, 16, rl.Color(255, 255, 255, 30))
|
||||
|
||||
title_y = rect.y + (rect.height - 24) / 2
|
||||
rl.draw_text_ex(self._font, self.title, rl.Vector2(round(rect.x + 24), round(title_y)), 24, 0, rl.WHITE)
|
||||
|
||||
val_str = self.labels.get(self.current_val, f"{int(self.current_val)}{self.unit}")
|
||||
ts = measure_text_cached(self._font, val_str, 24)
|
||||
|
||||
text_color = rl.WHITE if frac < 0.85 else rl.Color(0, 0, 0, 180)
|
||||
text_x = rect.x + rect.width - ts.x - 24
|
||||
text_y = rect.y + (rect.height - ts.y) / 2
|
||||
rl.draw_text_ex(self._font, val_str, rl.Vector2(round(text_x), round(text_y)), 24, 0, text_color)
|
||||
|
||||
|
||||
def draw_toggle_pill(rect: rl.Rectangle, is_on: bool, is_enabled: bool, title: str, status_str: str, hovered: bool, pressed: bool):
|
||||
if not is_enabled:
|
||||
bg_color = rl.Color(35, 35, 40, 150)
|
||||
elif is_on:
|
||||
bg_color = AetherListColors.PRIMARY
|
||||
else:
|
||||
bg_color = rl.Color(35, 35, 40, 255)
|
||||
|
||||
rl.draw_rectangle_rounded(rect, 0.3, 16, bg_color)
|
||||
|
||||
if (hovered or pressed) and is_enabled:
|
||||
overlay = rl.Color(255, 255, 255, 14 if pressed else 8)
|
||||
rl.draw_rectangle_rounded(rect, 0.3, 16, overlay)
|
||||
|
||||
if is_on and is_enabled:
|
||||
rl.draw_rectangle_rounded(rl.Rectangle(rect.x, rect.y, rect.width - 2, rect.height - 2), 0.3, 16, rl.Color(255, 255, 255, 30))
|
||||
|
||||
font = gui_app.font(FontWeight.BOLD)
|
||||
title_y = rect.y + (rect.height - 24) / 2
|
||||
text_color = rl.WHITE if is_enabled else AetherListColors.MUTED
|
||||
rl.draw_text_ex(font, title, rl.Vector2(round(rect.x + 24), round(title_y)), 24, 0, text_color)
|
||||
|
||||
ts = measure_text_cached(font, status_str, 24)
|
||||
status_x = rect.x + rect.width - ts.x - 24
|
||||
rl.draw_text_ex(font, status_str, rl.Vector2(round(status_x), round(title_y)), 24, 0, text_color)
|
||||
|
||||
@@ -1,258 +0,0 @@
|
||||
from __future__ import annotations
|
||||
import os
|
||||
import shutil
|
||||
import threading
|
||||
import subprocess
|
||||
from datetime import datetime
|
||||
from pathlib import Path
|
||||
|
||||
from openpilot.system.ui.lib.application import gui_app
|
||||
from openpilot.system.ui.lib.multilang import tr, tr_noop
|
||||
from openpilot.system.ui.widgets import DialogResult
|
||||
from openpilot.system.ui.widgets.confirm_dialog import ConfirmDialog, alert_dialog
|
||||
from openpilot.system.ui.widgets.keyboard import Keyboard
|
||||
from openpilot.system.ui.widgets.option_dialog import MultiOptionDialog
|
||||
from openpilot.selfdrive.ui.layouts.settings.starpilot.panel import StarPilotPanel
|
||||
from openpilot.selfdrive.ui.layouts.settings.starpilot.aethergrid import TileGrid
|
||||
|
||||
LEGACY_STARPILOT_PARAM_RENAMES = {
|
||||
"FrogPilotApiToken": "StarPilotApiToken",
|
||||
"FrogPilotCarParams": "StarPilotCarParams",
|
||||
"FrogPilotCarParamsPersistent": "StarPilotCarParamsPersistent",
|
||||
"FrogPilotDongleId": "StarPilotDongleId",
|
||||
"FrogPilotStats": "StarPilotStats",
|
||||
}
|
||||
|
||||
|
||||
class StarPilotDataLayout(StarPilotPanel):
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
self._keyboard = Keyboard(min_text_size=0)
|
||||
self.CATEGORIES = [
|
||||
{"title": tr_noop("Manage Backups"), "panel": "backups", "icon": "toggle_icons/icon_system.png", "color": "#D43D8A"},
|
||||
{"title": tr_noop("Toggle Backups"), "panel": "toggle_backups", "icon": "toggle_icons/icon_system.png", "color": "#D43D8A"},
|
||||
{"title": tr_noop("Driving Data Storage"), "type": "value", "get_value": self._get_storage, "on_click": lambda: None, "icon": "toggle_icons/icon_system.png", "color": "#D43D8A", "is_enabled": lambda: False},
|
||||
{
|
||||
"title": tr_noop("Delete Driving Data"),
|
||||
"type": "hub",
|
||||
"on_click": self._on_delete_driving_data,
|
||||
"icon": "toggle_icons/icon_system.png",
|
||||
"color": "#D43D8A",
|
||||
},
|
||||
{
|
||||
"title": tr_noop("Delete Error Logs"),
|
||||
"type": "hub",
|
||||
"on_click": self._on_delete_error_logs,
|
||||
"icon": "toggle_icons/icon_system.png",
|
||||
"color": "#D43D8A",
|
||||
},
|
||||
]
|
||||
|
||||
self._sub_panels = {
|
||||
"backups": StarPilotBackupsLayout(),
|
||||
"toggle_backups": StarPilotToggleBackupsLayout(),
|
||||
}
|
||||
self._tile_grid = TileGrid(columns=2, padding=20, uniform_width=True)
|
||||
|
||||
for name, panel in self._sub_panels.items():
|
||||
if hasattr(panel, 'set_navigate_callback'):
|
||||
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_delete_driving_data(self):
|
||||
def _do_delete(res):
|
||||
if res == DialogResult.CONFIRM:
|
||||
|
||||
def _task():
|
||||
drive_paths = ["/data/media/0/realdata/", "/data/media/0/realdata_HD/", "/data/media/0/realdata_konik/"]
|
||||
for path in drive_paths:
|
||||
p = Path(path)
|
||||
if p.exists():
|
||||
for entry in p.iterdir():
|
||||
if entry.is_dir():
|
||||
shutil.rmtree(entry, ignore_errors=True)
|
||||
|
||||
threading.Thread(target=_task, daemon=True).start()
|
||||
gui_app.set_modal_overlay(alert_dialog(tr("Driving data deletion started.")))
|
||||
|
||||
gui_app.set_modal_overlay(ConfirmDialog(tr("Delete all driving data and footage?"), tr("Delete"), on_close=_do_delete))
|
||||
|
||||
def _on_delete_error_logs(self):
|
||||
def _do_delete(res):
|
||||
if res == DialogResult.CONFIRM:
|
||||
shutil.rmtree("/data/error_logs", ignore_errors=True)
|
||||
os.makedirs("/data/error_logs", exist_ok=True)
|
||||
gui_app.set_modal_overlay(alert_dialog(tr("Error logs deleted.")))
|
||||
|
||||
gui_app.set_modal_overlay(ConfirmDialog(tr("Delete all error logs?"), tr("Delete"), on_close=_do_delete))
|
||||
|
||||
def _get_storage(self):
|
||||
paths = ["/data/media/0/osm/offline", "/data/media/0/realdata", "/data/backups"]
|
||||
total = 0
|
||||
for p in paths:
|
||||
pp = Path(p)
|
||||
if pp.exists():
|
||||
total += sum(f.stat().st_size for f in pp.rglob('*') if f.is_file())
|
||||
mb = total / (1024 * 1024)
|
||||
if mb > 1024:
|
||||
return f"{(mb / 1024):.2f} GB"
|
||||
return f"{mb:.2f} MB"
|
||||
|
||||
|
||||
class StarPilotBackupsLayout(StarPilotPanel):
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
self._tile_grid = TileGrid(columns=2, padding=20, uniform_width=True)
|
||||
self.CATEGORIES = [
|
||||
{"title": tr_noop("Create Backup"), "type": "hub", "on_click": self._on_create_backup, "color": "#D43D8A"},
|
||||
{"title": tr_noop("Restore Backup"), "type": "hub", "on_click": self._on_restore_backup, "color": "#D43D8A"},
|
||||
{"title": tr_noop("Delete Backup"), "type": "hub", "on_click": self._on_delete_backup, "color": "#D43D8A"},
|
||||
]
|
||||
self._rebuild_grid()
|
||||
|
||||
def _get_backups(self):
|
||||
b_dir = Path("/data/backups")
|
||||
if not b_dir.exists():
|
||||
return []
|
||||
return [f.name for f in b_dir.glob("*.tar.zst") if "in_progress" not in f.name]
|
||||
|
||||
def _on_create_backup(self):
|
||||
def on_name(res, name):
|
||||
if res == DialogResult.CONFIRM:
|
||||
safe_name = name.replace(" ", "_") if name else f"backup_{datetime.now().strftime('%Y%m%d_%H%M%S')}"
|
||||
backup_path = f"/data/backups/{safe_name}.tar.zst"
|
||||
if Path(backup_path).exists():
|
||||
gui_app.set_modal_overlay(alert_dialog(tr("A backup with this name already exists.")))
|
||||
return
|
||||
gui_app.set_modal_overlay(alert_dialog(tr("Backup creation started.")))
|
||||
|
||||
def _task():
|
||||
os.makedirs("/data/backups", exist_ok=True)
|
||||
subprocess.run(["tar", "--use-compress-program=zstd", "-cf", backup_path, "/data/openpilot"])
|
||||
|
||||
threading.Thread(target=_task, daemon=True).start()
|
||||
|
||||
self._keyboard.reset(min_text_size=0)
|
||||
self._keyboard.set_title(tr("Name your backup"), "")
|
||||
self._keyboard.set_text("")
|
||||
self._keyboard.set_callback(lambda result: on_name(result, self._keyboard.text))
|
||||
gui_app.push_widget(self._keyboard)
|
||||
|
||||
def _on_restore_backup(self):
|
||||
backups = self._get_backups()
|
||||
if not backups:
|
||||
gui_app.set_modal_overlay(alert_dialog(tr("No backups found.")))
|
||||
return
|
||||
dialog = MultiOptionDialog(tr("Select Backup"), backups)
|
||||
|
||||
def _on_select(res):
|
||||
if res == DialogResult.CONFIRM and dialog.selection:
|
||||
gui_app.set_modal_overlay(alert_dialog(tr("Restoring... device will reboot.")))
|
||||
|
||||
def _task():
|
||||
subprocess.run(["rm", "-rf", "/data/openpilot/*"])
|
||||
subprocess.run(["tar", "--use-compress-program=zstd", "-xf", f"/data/backups/{dialog.selection}", "-C", "/"])
|
||||
os.system("reboot")
|
||||
|
||||
threading.Thread(target=_task, daemon=True).start()
|
||||
|
||||
gui_app.set_modal_overlay(dialog, callback=_on_select)
|
||||
|
||||
def _on_delete_backup(self):
|
||||
backups = self._get_backups()
|
||||
if not backups:
|
||||
gui_app.set_modal_overlay(alert_dialog(tr("No backups found.")))
|
||||
return
|
||||
dialog = MultiOptionDialog(tr("Delete Backup"), backups)
|
||||
|
||||
def _on_select(res):
|
||||
if res == DialogResult.CONFIRM and dialog.selection:
|
||||
os.remove(f"/data/backups/{dialog.selection}")
|
||||
self._rebuild_grid()
|
||||
|
||||
gui_app.set_modal_overlay(dialog, callback=_on_select)
|
||||
|
||||
|
||||
class StarPilotToggleBackupsLayout(StarPilotPanel):
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
self._keyboard = Keyboard(min_text_size=0)
|
||||
self._tile_grid = TileGrid(columns=2, padding=20, uniform_width=True)
|
||||
self.CATEGORIES = [
|
||||
{"title": tr_noop("Create Toggle Backup"), "type": "hub", "on_click": self._on_create, "color": "#D43D8A"},
|
||||
{"title": tr_noop("Restore Toggle Backup"), "type": "hub", "on_click": self._on_restore, "color": "#D43D8A"},
|
||||
{"title": tr_noop("Delete Toggle Backup"), "type": "hub", "on_click": self._on_delete, "color": "#D43D8A"},
|
||||
]
|
||||
self._rebuild_grid()
|
||||
|
||||
def _get_backups(self):
|
||||
b_dir = Path("/data/toggle_backups")
|
||||
if not b_dir.exists():
|
||||
return []
|
||||
return [d.name for d in b_dir.iterdir() if d.is_dir() and "in_progress" not in d.name]
|
||||
|
||||
def _on_create(self):
|
||||
def on_name(res, name):
|
||||
if res == DialogResult.CONFIRM:
|
||||
safe_name = name.replace(" ", "_") if name else f"toggle_backup_{datetime.now().strftime('%Y%m%d_%H%M%S')}"
|
||||
backup_path = Path(f"/data/toggle_backups/{safe_name}")
|
||||
if backup_path.exists():
|
||||
gui_app.set_modal_overlay(alert_dialog(tr("A toggle backup with this name already exists.")))
|
||||
return
|
||||
os.makedirs(backup_path, exist_ok=True)
|
||||
shutil.copytree("/data/params/d", str(backup_path), dirs_exist_ok=True)
|
||||
gui_app.set_modal_overlay(alert_dialog(tr("Toggle backup created.")))
|
||||
self._rebuild_grid()
|
||||
|
||||
self._keyboard.reset(min_text_size=0)
|
||||
self._keyboard.set_title(tr("Name your toggle backup"), "")
|
||||
self._keyboard.set_text("")
|
||||
self._keyboard.set_callback(lambda result: on_name(result, self._keyboard.text))
|
||||
gui_app.push_widget(self._keyboard)
|
||||
|
||||
def _on_restore(self):
|
||||
backups = self._get_backups()
|
||||
if not backups:
|
||||
gui_app.set_modal_overlay(alert_dialog(tr("No toggle backups found.")))
|
||||
return
|
||||
dialog = MultiOptionDialog(tr("Select Toggle Backup"), backups)
|
||||
|
||||
def _on_select(res):
|
||||
if res == DialogResult.CONFIRM and dialog.selection:
|
||||
|
||||
def on_confirm(r2):
|
||||
if r2 == DialogResult.CONFIRM:
|
||||
src = Path(f"/data/toggle_backups/{dialog.selection}")
|
||||
params_dir = Path("/data/params/d")
|
||||
for old_key, new_key in LEGACY_STARPILOT_PARAM_RENAMES.items():
|
||||
if (src / old_key).exists():
|
||||
(params_dir / new_key).unlink(missing_ok=True)
|
||||
shutil.copytree(str(src), "/data/params/d", dirs_exist_ok=True)
|
||||
for old_key, new_key in LEGACY_STARPILOT_PARAM_RENAMES.items():
|
||||
old_path = params_dir / old_key
|
||||
new_path = params_dir / new_key
|
||||
if old_path.exists():
|
||||
old_path.replace(new_path)
|
||||
gui_app.set_modal_overlay(alert_dialog(tr("Toggles restored.")))
|
||||
self._rebuild_grid()
|
||||
|
||||
gui_app.set_modal_overlay(ConfirmDialog(tr("This will overwrite your current toggles."), tr("Restore"), on_close=on_confirm))
|
||||
|
||||
gui_app.set_modal_overlay(dialog, callback=_on_select)
|
||||
|
||||
def _on_delete(self):
|
||||
backups = self._get_backups()
|
||||
if not backups:
|
||||
gui_app.set_modal_overlay(alert_dialog(tr("No toggle backups found.")))
|
||||
return
|
||||
dialog = MultiOptionDialog(tr("Delete Toggle Backup"), backups)
|
||||
|
||||
def _on_select(res):
|
||||
if res == DialogResult.CONFIRM and dialog.selection:
|
||||
shutil.rmtree(f"/data/toggle_backups/{dialog.selection}", ignore_errors=True)
|
||||
self._rebuild_grid()
|
||||
|
||||
gui_app.set_modal_overlay(dialog, callback=_on_select)
|
||||
@@ -1,292 +0,0 @@
|
||||
from __future__ import annotations
|
||||
from pathlib import Path
|
||||
|
||||
from openpilot.selfdrive.ui.ui_state import ui_state
|
||||
from openpilot.system.hardware import HARDWARE
|
||||
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.confirm_dialog import ConfirmDialog
|
||||
from openpilot.selfdrive.ui.layouts.settings.starpilot.panel import StarPilotPanel
|
||||
from openpilot.selfdrive.ui.layouts.settings.starpilot.aethergrid import AetherSliderDialog, TileGrid
|
||||
|
||||
|
||||
class StarPilotDeviceLayout(StarPilotPanel):
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
self.CATEGORIES = [
|
||||
{"title": tr_noop("Screen Settings"), "panel": "screen", "icon": "toggle_icons/icon_light.png", "color": "#D43D8A"},
|
||||
{"title": tr_noop("Device Settings"), "panel": "device_settings", "icon": "toggle_icons/icon_device.png", "color": "#D43D8A"},
|
||||
{
|
||||
"title": tr_noop("Device Shutdown"),
|
||||
"type": "value",
|
||||
"get_value": self._get_shutdown_timer,
|
||||
"on_click": self._show_shutdown_selector,
|
||||
"color": "#D43D8A",
|
||||
},
|
||||
{
|
||||
"title": tr_noop("Disable Logging"),
|
||||
"type": "toggle",
|
||||
"get_state": lambda: self._params.get_bool("NoLogging"),
|
||||
"set_state": lambda s: self._params.put_bool("NoLogging", s),
|
||||
"color": "#D43D8A",
|
||||
},
|
||||
{
|
||||
"title": tr_noop("Disable Uploads"),
|
||||
"type": "toggle",
|
||||
"get_state": lambda: self._params.get_bool("NoUploads"),
|
||||
"set_state": lambda s: self._params.put_bool("NoUploads", s),
|
||||
"color": "#D43D8A",
|
||||
},
|
||||
{
|
||||
"title": tr_noop("Disable Onroad Uploads"),
|
||||
"type": "toggle",
|
||||
"param": "DisableOnroadUploads",
|
||||
"get_state": lambda: self._params.get_bool("DisableOnroadUploads"),
|
||||
"set_state": lambda s: self._params.put_bool("DisableOnroadUploads", s),
|
||||
"color": "#D43D8A",
|
||||
},
|
||||
{
|
||||
"title": tr_noop("High-Quality Recording"),
|
||||
"type": "toggle",
|
||||
"param": "HigherBitrate",
|
||||
"get_state": lambda: self._params.get_bool("HigherBitrate"),
|
||||
"set_state": lambda s: self._on_higher_bitrate_toggle(s),
|
||||
"color": "#D43D8A",
|
||||
},
|
||||
]
|
||||
|
||||
self._sub_panels = {
|
||||
"screen": StarPilotScreenLayout(),
|
||||
"device_settings": StarPilotDeviceManagementLayout(),
|
||||
}
|
||||
self._tile_grid = TileGrid(columns=2, padding=20, uniform_width=True)
|
||||
|
||||
for name, panel in self._sub_panels.items():
|
||||
if hasattr(panel, 'set_navigate_callback'):
|
||||
panel.set_navigate_callback(self._navigate_to)
|
||||
if hasattr(panel, 'set_back_callback'):
|
||||
panel.set_back_callback(self._go_back)
|
||||
|
||||
self._rebuild_grid()
|
||||
|
||||
def _rebuild_grid(self):
|
||||
no_uploads = self._params.get_bool("NoUploads")
|
||||
disable_onroad = self._params.get_bool("DisableOnroadUploads")
|
||||
filtered = []
|
||||
for cat in self.CATEGORIES:
|
||||
param = cat.get("param")
|
||||
if param == "DisableOnroadUploads" and not no_uploads:
|
||||
continue
|
||||
if param == "HigherBitrate" and (not no_uploads or disable_onroad):
|
||||
continue
|
||||
filtered.append(cat)
|
||||
original = self.CATEGORIES
|
||||
self.CATEGORIES = filtered
|
||||
super()._rebuild_grid()
|
||||
self.CATEGORIES = original
|
||||
|
||||
def _on_higher_bitrate_toggle(self, state):
|
||||
self._params.put_bool("HigherBitrate", state)
|
||||
cache_path = Path("/cache/use_HD")
|
||||
if state:
|
||||
cache_path.parent.mkdir(parents=True, exist_ok=True)
|
||||
cache_path.touch()
|
||||
else:
|
||||
if cache_path.exists():
|
||||
cache_path.unlink()
|
||||
if ui_state.started:
|
||||
gui_app.set_modal_overlay(
|
||||
ConfirmDialog(
|
||||
tr("Reboot required. Reboot now?"), tr("Reboot"), tr("Cancel"), on_close=lambda res: HARDWARE.reboot() if res == DialogResult.CONFIRM else None
|
||||
)
|
||||
)
|
||||
|
||||
def _get_shutdown_timer(self):
|
||||
v = self._params.get_int("DeviceShutdown")
|
||||
if v == 0:
|
||||
return tr("5 mins")
|
||||
if v <= 3:
|
||||
return f"{v * 15} mins"
|
||||
return f"{v - 3} " + (tr("hour") if v == 4 else tr("hours"))
|
||||
|
||||
def _show_shutdown_selector(self):
|
||||
def on_close(res, val):
|
||||
if res == DialogResult.CONFIRM:
|
||||
self._params.put_int("DeviceShutdown", int(val))
|
||||
self._rebuild_grid()
|
||||
|
||||
labels = {0: tr("5 mins")}
|
||||
for i in range(1, 4):
|
||||
labels[i] = f"{i * 15} mins"
|
||||
for i in range(4, 34):
|
||||
labels[i] = f"{i - 3} " + (tr("hour") if i == 4 else tr("hours"))
|
||||
|
||||
gui_app.set_modal_overlay(AetherSliderDialog(tr("Device Shutdown"), 0, 33, 1, self._params.get_int("DeviceShutdown"), on_close, labels=labels, color="#D43D8A"))
|
||||
|
||||
|
||||
class StarPilotScreenLayout(StarPilotPanel):
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
self._tile_grid = TileGrid(columns=2, padding=20, uniform_width=True)
|
||||
self.CATEGORIES = [
|
||||
{
|
||||
"title": tr_noop("Screen Settings"),
|
||||
"type": "toggle",
|
||||
"get_state": lambda: self._params.get_bool("ScreenManagement"),
|
||||
"set_state": lambda s: self._params.put_bool("ScreenManagement", s),
|
||||
"icon": "toggle_icons/icon_light.png",
|
||||
"color": "#D43D8A",
|
||||
},
|
||||
{
|
||||
"title": tr_noop("Brightness (Offroad)"),
|
||||
"type": "value",
|
||||
"get_value": lambda: self._get_brightness("ScreenBrightness"),
|
||||
"on_click": lambda: self._show_brightness_selector("ScreenBrightness"),
|
||||
"color": "#D43D8A",
|
||||
"visible": lambda: self._params.get_bool("ScreenManagement"),
|
||||
},
|
||||
{
|
||||
"title": tr_noop("Brightness (Onroad)"),
|
||||
"type": "value",
|
||||
"get_value": lambda: self._get_brightness("ScreenBrightnessOnroad"),
|
||||
"on_click": lambda: self._show_brightness_selector("ScreenBrightnessOnroad"),
|
||||
"color": "#D43D8A",
|
||||
"visible": lambda: self._params.get_bool("ScreenManagement"),
|
||||
},
|
||||
{
|
||||
"title": tr_noop("Timeout (Offroad)"),
|
||||
"type": "value",
|
||||
"get_value": lambda: f"{self._params.get_int('ScreenTimeout')}s",
|
||||
"on_click": lambda: self._show_timeout_selector("ScreenTimeout"),
|
||||
"color": "#D43D8A",
|
||||
"visible": lambda: self._params.get_bool("ScreenManagement"),
|
||||
},
|
||||
{
|
||||
"title": tr_noop("Timeout (Onroad)"),
|
||||
"type": "value",
|
||||
"get_value": lambda: f"{self._params.get_int('ScreenTimeoutOnroad')}s",
|
||||
"on_click": lambda: self._show_timeout_selector("ScreenTimeoutOnroad"),
|
||||
"color": "#D43D8A",
|
||||
"visible": lambda: self._params.get_bool("ScreenManagement"),
|
||||
},
|
||||
{
|
||||
"title": tr_noop("Standby Mode"),
|
||||
"type": "toggle",
|
||||
"get_state": lambda: self._params.get_bool("StandbyMode"),
|
||||
"set_state": lambda s: self._params.put_bool("StandbyMode", s),
|
||||
"color": "#D43D8A",
|
||||
"visible": lambda: self._params.get_bool("ScreenManagement"),
|
||||
},
|
||||
]
|
||||
self._rebuild_grid()
|
||||
|
||||
def _get_brightness(self, key):
|
||||
v = self._params.get_int(key)
|
||||
if key == "ScreenBrightnessOnroad" and v == 0:
|
||||
v = 1
|
||||
if v == 0:
|
||||
return tr("Off")
|
||||
if v == 101:
|
||||
return tr("Auto")
|
||||
return f"{v}%"
|
||||
|
||||
def _show_brightness_selector(self, key):
|
||||
def on_close(res, val):
|
||||
if res == DialogResult.CONFIRM:
|
||||
new_v = int(val)
|
||||
if key == "ScreenBrightnessOnroad":
|
||||
new_v = max(new_v, 1)
|
||||
self._params.put_int(key, new_v)
|
||||
HARDWARE.set_brightness(new_v)
|
||||
self._rebuild_grid()
|
||||
|
||||
min_value = 1 if key == "ScreenBrightnessOnroad" else 0
|
||||
current_value = max(self._params.get_int(key), min_value)
|
||||
labels = {101: tr("Auto")}
|
||||
if min_value == 0:
|
||||
labels[0] = tr("Off")
|
||||
|
||||
gui_app.set_modal_overlay(
|
||||
AetherSliderDialog(tr(key), min_value, 101, 1, current_value, on_close, unit="%", labels=labels, color="#D43D8A")
|
||||
)
|
||||
|
||||
def _show_timeout_selector(self, key):
|
||||
def on_close(res, val):
|
||||
if res == DialogResult.CONFIRM:
|
||||
self._params.put_int(key, int(val))
|
||||
self._rebuild_grid()
|
||||
|
||||
gui_app.set_modal_overlay(AetherSliderDialog(tr(key), 5, 60, 5, self._params.get_int(key), on_close, unit="s", color="#D43D8A"))
|
||||
|
||||
|
||||
class StarPilotDeviceManagementLayout(StarPilotPanel):
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
self._tile_grid = TileGrid(columns=2, padding=20, uniform_width=True)
|
||||
self.CATEGORIES = [
|
||||
{
|
||||
"title": tr_noop("Device Settings"),
|
||||
"type": "toggle",
|
||||
"get_state": lambda: self._params.get_bool("DeviceManagement"),
|
||||
"set_state": lambda s: self._params.put_bool("DeviceManagement", s),
|
||||
"icon": "toggle_icons/icon_device.png",
|
||||
"color": "#D43D8A",
|
||||
},
|
||||
{
|
||||
"title": tr_noop("Low-Voltage Cutoff"),
|
||||
"type": "value",
|
||||
"get_value": lambda: f"{self._params.get_float('LowVoltageShutdown'):.1f}V",
|
||||
"on_click": self._show_voltage_selector,
|
||||
"color": "#D43D8A",
|
||||
"visible": lambda: self._params.get_bool("DeviceManagement"),
|
||||
},
|
||||
{
|
||||
"title": tr_noop("Raise Temp Limits"),
|
||||
"type": "toggle",
|
||||
"get_state": lambda: self._params.get_bool("IncreaseThermalLimits"),
|
||||
"set_state": lambda s: self._params.put_bool("IncreaseThermalLimits", s),
|
||||
"color": "#D43D8A",
|
||||
"visible": lambda: self._params.get_bool("DeviceManagement"),
|
||||
},
|
||||
{
|
||||
"title": tr_noop("Use Konik Server"),
|
||||
"type": "toggle",
|
||||
"get_state": lambda: self._get_konik_state(),
|
||||
"set_state": lambda s: self._on_konik_toggle(s),
|
||||
"color": "#D43D8A",
|
||||
"visible": lambda: self._params.get_bool("DeviceManagement"),
|
||||
},
|
||||
]
|
||||
self._rebuild_grid()
|
||||
|
||||
def _get_konik_state(self):
|
||||
if Path("/data/not_vetted").exists():
|
||||
return True
|
||||
return self._params.get_bool("UseKonikServer")
|
||||
|
||||
def _on_konik_toggle(self, state):
|
||||
self._params.put_bool("UseKonikServer", state)
|
||||
cache_path = Path("/cache/use_konik")
|
||||
if state:
|
||||
cache_path.parent.mkdir(parents=True, exist_ok=True)
|
||||
cache_path.touch()
|
||||
else:
|
||||
if cache_path.exists():
|
||||
cache_path.unlink()
|
||||
if ui_state.started:
|
||||
gui_app.set_modal_overlay(
|
||||
ConfirmDialog(
|
||||
tr("Reboot required. Reboot now?"), tr("Reboot"), tr("Cancel"), on_close=lambda res: HARDWARE.reboot() if res == DialogResult.CONFIRM else None
|
||||
)
|
||||
)
|
||||
|
||||
def _show_voltage_selector(self):
|
||||
def on_close(res, val):
|
||||
if res == DialogResult.CONFIRM:
|
||||
self._params.put_float("LowVoltageShutdown", float(val))
|
||||
self._rebuild_grid()
|
||||
|
||||
gui_app.set_modal_overlay(
|
||||
AetherSliderDialog(tr("Low-Voltage Cutoff"), 11.8, 12.5, 0.1, self._params.get_float("LowVoltageShutdown"), on_close, unit="V", color="#D43D8A")
|
||||
)
|
||||
@@ -105,14 +105,14 @@ class StarPilotAdvancedLateralLayout(StarPilotPanel):
|
||||
if res == DialogResult.CONFIRM:
|
||||
self._params.put_float(key, float(val))
|
||||
self._rebuild_grid()
|
||||
gui_app.set_modal_overlay(AetherSliderDialog(tr(key), min_v, max_v, step, self._params.get_float(key), on_close, unit=unit, color="#597497"))
|
||||
gui_app.push_widget(AetherSliderDialog(tr(key), min_v, max_v, step, self._params.get_float(key), on_close, unit=unit, color="#597497"))
|
||||
|
||||
def _on_reboot_toggle(self, key, state):
|
||||
self._params.put_bool(key, state)
|
||||
from openpilot.selfdrive.ui.ui_state import ui_state
|
||||
if ui_state.started:
|
||||
dialog = ConfirmDialog(tr("Reboot required. Reboot now?"), tr("Reboot"), tr("Cancel"), on_close=lambda res: HARDWARE.reboot() if res == DialogResult.CONFIRM else None)
|
||||
gui_app.set_modal_overlay(dialog)
|
||||
gui_app.push_widget(dialog)
|
||||
|
||||
class StarPilotAlwaysOnLateralLayout(StarPilotPanel):
|
||||
def __init__(self):
|
||||
@@ -129,13 +129,13 @@ class StarPilotAlwaysOnLateralLayout(StarPilotPanel):
|
||||
if res == DialogResult.CONFIRM:
|
||||
self._params.put_int(key, int(val))
|
||||
self._rebuild_grid()
|
||||
gui_app.set_modal_overlay(AetherSliderDialog(tr(key), 0, 100, 1, self._params.get_int(key), on_close, unit=" mph", color="#597497"))
|
||||
gui_app.push_widget(AetherSliderDialog(tr(key), 0, 100, 1, self._params.get_int(key), on_close, unit=" mph", color="#597497"))
|
||||
|
||||
def _on_reboot_toggle(self, key, state):
|
||||
self._params.put_bool(key, state)
|
||||
from openpilot.selfdrive.ui.ui_state import ui_state
|
||||
if ui_state.started:
|
||||
gui_app.set_modal_overlay(ConfirmDialog(tr("Reboot required. Reboot now?"), tr("Reboot"), tr("Cancel"), on_close=lambda res: HARDWARE.reboot() if res == DialogResult.CONFIRM else None))
|
||||
gui_app.push_widget(ConfirmDialog(tr("Reboot required. Reboot now?"), tr("Reboot"), tr("Cancel"), on_close=lambda res: HARDWARE.reboot() if res == DialogResult.CONFIRM else None))
|
||||
|
||||
class StarPilotLaneChangesLayout(StarPilotPanel):
|
||||
def __init__(self):
|
||||
@@ -155,14 +155,14 @@ class StarPilotLaneChangesLayout(StarPilotPanel):
|
||||
if res == DialogResult.CONFIRM:
|
||||
self._params.put_int(key, int(val))
|
||||
self._rebuild_grid()
|
||||
gui_app.set_modal_overlay(AetherSliderDialog(tr(key), 0, 100, 1, self._params.get_int(key), on_close, unit=" mph", color="#597497"))
|
||||
gui_app.push_widget(AetherSliderDialog(tr(key), 0, 100, 1, self._params.get_int(key), on_close, unit=" mph", color="#597497"))
|
||||
|
||||
def _show_float_selector(self, key, min_v, max_v, step, unit=""):
|
||||
def on_close(res, val):
|
||||
if res == DialogResult.CONFIRM:
|
||||
self._params.put_float(key, float(val))
|
||||
self._rebuild_grid()
|
||||
gui_app.set_modal_overlay(AetherSliderDialog(tr(key), min_v, max_v, step, self._params.get_float(key), on_close, unit=unit, color="#597497"))
|
||||
gui_app.push_widget(AetherSliderDialog(tr(key), min_v, max_v, step, self._params.get_float(key), on_close, unit=unit, color="#597497"))
|
||||
|
||||
class StarPilotLateralTuneLayout(StarPilotPanel):
|
||||
def __init__(self):
|
||||
@@ -192,7 +192,7 @@ class StarPilotLateralTuneLayout(StarPilotPanel):
|
||||
self._params.put_bool(key, state)
|
||||
from openpilot.selfdrive.ui.ui_state import ui_state
|
||||
if ui_state.started:
|
||||
gui_app.set_modal_overlay(ConfirmDialog(tr("Reboot required. Reboot now?"), tr("Reboot"), tr("Cancel"), on_close=lambda res: HARDWARE.reboot() if res == DialogResult.CONFIRM else None))
|
||||
gui_app.push_widget(ConfirmDialog(tr("Reboot required. Reboot now?"), tr("Reboot"), tr("Cancel"), on_close=lambda res: HARDWARE.reboot() if res == DialogResult.CONFIRM else None))
|
||||
|
||||
class StarPilotLateralQOLLayout(StarPilotPanel):
|
||||
def __init__(self):
|
||||
@@ -208,7 +208,7 @@ class StarPilotLateralQOLLayout(StarPilotPanel):
|
||||
if res == DialogResult.CONFIRM:
|
||||
self._params.put_int(key, int(val))
|
||||
self._rebuild_grid()
|
||||
gui_app.set_modal_overlay(AetherSliderDialog(tr(key), 0, 100, 1, self._params.get_int(key), on_close, unit=" mph", color="#597497"))
|
||||
gui_app.push_widget(AetherSliderDialog(tr(key), 0, 100, 1, self._params.get_int(key), on_close, unit=" mph", color="#597497"))
|
||||
|
||||
class StarPilotLateralLayout(StarPilotPanel):
|
||||
def __init__(self):
|
||||
|
||||
@@ -240,7 +240,7 @@ class StarPilotAdvancedLongitudinalLayout(StarPilotPanel):
|
||||
self._params.put_float(key, float(val))
|
||||
self._rebuild_grid()
|
||||
|
||||
gui_app.set_modal_overlay(AetherSliderDialog(tr(key), min_v, max_v, step, self._params.get_float(key), on_close, unit=unit, color="#597497"))
|
||||
gui_app.push_widget(AetherSliderDialog(tr(key), min_v, max_v, step, self._params.get_float(key), on_close, unit=unit, color="#597497"))
|
||||
|
||||
|
||||
class StarPilotConditionalExperimentalLayout(StarPilotPanel):
|
||||
@@ -372,7 +372,7 @@ class StarPilotConditionalExperimentalLayout(StarPilotPanel):
|
||||
self._params.put_int(key, int(val))
|
||||
self._rebuild_grid()
|
||||
|
||||
gui_app.set_modal_overlay(AetherSliderDialog(tr(key), 0, 100, 1, self._params.get_int(key), on_close, unit=" mph", color="#597497"))
|
||||
gui_app.push_widget(AetherSliderDialog(tr(key), 0, 100, 1, self._params.get_int(key), on_close, unit=" mph", color="#597497"))
|
||||
|
||||
def _show_int_selector(self, key, min_v, max_v, unit=""):
|
||||
def on_close(res, val):
|
||||
@@ -380,7 +380,7 @@ class StarPilotConditionalExperimentalLayout(StarPilotPanel):
|
||||
self._params.put_int(key, int(val))
|
||||
self._rebuild_grid()
|
||||
|
||||
gui_app.set_modal_overlay(AetherSliderDialog(tr(key), min_v, max_v, 1, self._params.get_int(key), on_close, unit=unit, color="#597497"))
|
||||
gui_app.push_widget(AetherSliderDialog(tr(key), min_v, max_v, 1, self._params.get_int(key), on_close, unit=unit, color="#597497"))
|
||||
|
||||
|
||||
class StarPilotCurveSpeedLayout(StarPilotPanel):
|
||||
@@ -441,7 +441,7 @@ class StarPilotCurveSpeedLayout(StarPilotPanel):
|
||||
self._params.remove("CurvatureData")
|
||||
self._rebuild_grid()
|
||||
|
||||
gui_app.set_modal_overlay(ConfirmDialog(tr("Reset Curve Data?"), tr("Confirm"), on_close=on_close))
|
||||
gui_app.push_widget(ConfirmDialog(tr("Reset Curve Data?"), tr("Confirm"), on_close=on_close))
|
||||
|
||||
|
||||
class StarPilotPersonalitiesLayout(StarPilotPanel):
|
||||
@@ -537,7 +537,7 @@ class StarPilotPersonalityProfileLayout(StarPilotPanel):
|
||||
self._params.remove(self._profile + key)
|
||||
self._rebuild_grid()
|
||||
|
||||
gui_app.set_modal_overlay(ConfirmDialog(tr("Reset to Defaults?"), tr("Confirm"), on_close=on_close))
|
||||
gui_app.push_widget(ConfirmDialog(tr("Reset to Defaults?"), tr("Confirm"), on_close=on_close))
|
||||
|
||||
def _show_float_selector(self, key, min_v, max_v, step, unit=""):
|
||||
def on_close(res, val):
|
||||
@@ -545,7 +545,7 @@ class StarPilotPersonalityProfileLayout(StarPilotPanel):
|
||||
self._params.put_float(key, float(val))
|
||||
self._rebuild_grid()
|
||||
|
||||
gui_app.set_modal_overlay(AetherSliderDialog(tr(key), min_v, max_v, step, self._params.get_float(key), on_close, unit=unit, color="#597497"))
|
||||
gui_app.push_widget(AetherSliderDialog(tr(key), min_v, max_v, step, self._params.get_float(key), on_close, unit=unit, color="#597497"))
|
||||
|
||||
def _show_int_selector(self, key, min_v, max_v, unit=""):
|
||||
def on_close(res, val):
|
||||
@@ -553,7 +553,7 @@ class StarPilotPersonalityProfileLayout(StarPilotPanel):
|
||||
self._params.put_int(key, int(val))
|
||||
self._rebuild_grid()
|
||||
|
||||
gui_app.set_modal_overlay(AetherSliderDialog(tr(key), min_v, max_v, 5, self._params.get_int(key), on_close, unit=unit, color="#597497"))
|
||||
gui_app.push_widget(AetherSliderDialog(tr(key), min_v, max_v, 5, self._params.get_int(key), on_close, unit=unit, color="#597497"))
|
||||
|
||||
|
||||
class StarPilotLongitudinalTuneLayout(StarPilotPanel):
|
||||
@@ -669,7 +669,7 @@ class StarPilotLongitudinalTuneLayout(StarPilotPanel):
|
||||
self._params.put_int(key, label_to_value[dialog.selection])
|
||||
self._rebuild_grid()
|
||||
|
||||
gui_app.set_modal_overlay(dialog, callback=on_select)
|
||||
gui_app.push_widget(dialog, callback=on_select)
|
||||
|
||||
def _show_int_selector(self, key, min_v, max_v, unit=""):
|
||||
def on_close(res, val):
|
||||
@@ -677,7 +677,7 @@ class StarPilotLongitudinalTuneLayout(StarPilotPanel):
|
||||
self._params.put_int(key, int(val))
|
||||
self._rebuild_grid()
|
||||
|
||||
gui_app.set_modal_overlay(AetherSliderDialog(tr(key), min_v, max_v, 1, self._params.get_int(key), on_close, unit=unit, color="#597497"))
|
||||
gui_app.push_widget(AetherSliderDialog(tr(key), min_v, max_v, 1, self._params.get_int(key), on_close, unit=unit, color="#597497"))
|
||||
|
||||
|
||||
class StarPilotLongitudinalQOLLayout(StarPilotPanel):
|
||||
@@ -782,7 +782,7 @@ class StarPilotLongitudinalQOLLayout(StarPilotPanel):
|
||||
self._rebuild_grid()
|
||||
|
||||
current = max(1, self._params.get_int(key))
|
||||
gui_app.set_modal_overlay(AetherSliderDialog(tr(key), 1, 100, 1, current, on_close, unit=" mph", color="#597497"))
|
||||
gui_app.push_widget(AetherSliderDialog(tr(key), 1, 100, 1, current, on_close, unit=" mph", color="#597497"))
|
||||
|
||||
def _show_int_selector(self, key, min_v, max_v, unit=""):
|
||||
def on_close(res, val):
|
||||
@@ -790,7 +790,7 @@ class StarPilotLongitudinalQOLLayout(StarPilotPanel):
|
||||
self._params.put_int(key, int(val))
|
||||
self._rebuild_grid()
|
||||
|
||||
gui_app.set_modal_overlay(AetherSliderDialog(tr(key), min_v, max_v, 1, self._params.get_int(key), on_close, unit=unit, color="#597497"))
|
||||
gui_app.push_widget(AetherSliderDialog(tr(key), min_v, max_v, 1, self._params.get_int(key), on_close, unit=unit, color="#597497"))
|
||||
|
||||
|
||||
class StarPilotSpeedLimitControllerLayout(StarPilotPanel):
|
||||
@@ -867,7 +867,7 @@ class StarPilotSpeedLimitControllerLayout(StarPilotPanel):
|
||||
secondary_options = ["None"] + [option for option in ("Dashboard", "Map Data", "Vision") if option != primary]
|
||||
selected_secondary = current_secondary if current_secondary in secondary_options else "None"
|
||||
secondary_dialog = MultiOptionDialog(tr("SLC Secondary Priority"), secondary_options, selected_secondary)
|
||||
gui_app.set_modal_overlay(secondary_dialog, callback=lambda res: on_secondary_select(primary, secondary_dialog, res))
|
||||
gui_app.push_widget(secondary_dialog, callback=lambda res: on_secondary_select(primary, secondary_dialog, res))
|
||||
|
||||
primary_dialog = MultiOptionDialog(tr("SLC Primary Priority"), primary_options, current_primary)
|
||||
|
||||
@@ -881,7 +881,7 @@ class StarPilotSpeedLimitControllerLayout(StarPilotPanel):
|
||||
return
|
||||
show_secondary_dialog(primary_dialog.selection)
|
||||
|
||||
gui_app.set_modal_overlay(primary_dialog, callback=on_primary_select)
|
||||
gui_app.push_widget(primary_dialog, callback=on_primary_select)
|
||||
|
||||
def _show_selection(self, key, options):
|
||||
current = self._params.get(key, encoding='utf-8') or "None"
|
||||
@@ -892,7 +892,7 @@ class StarPilotSpeedLimitControllerLayout(StarPilotPanel):
|
||||
self._params.put(key, dialog.selection)
|
||||
self._rebuild_grid()
|
||||
|
||||
gui_app.set_modal_overlay(dialog, callback=on_select)
|
||||
gui_app.push_widget(dialog, callback=on_select)
|
||||
|
||||
|
||||
class StarPilotSLCOffsetsLayout(StarPilotPanel):
|
||||
@@ -928,7 +928,7 @@ class StarPilotSLCOffsetsLayout(StarPilotPanel):
|
||||
self._rebuild_grid()
|
||||
|
||||
min_value, max_value = self._speed_range()
|
||||
gui_app.set_modal_overlay(
|
||||
gui_app.push_widget(
|
||||
AetherSliderDialog(tr(key), min_value, max_value, 1, self._params.get_int(key), on_close, unit=self._speed_unit(), color="#597497")
|
||||
)
|
||||
|
||||
@@ -997,7 +997,7 @@ class StarPilotSLCQOLLayout(StarPilotPanel):
|
||||
self._params.put_int(key, int(val))
|
||||
self._rebuild_grid()
|
||||
|
||||
gui_app.set_modal_overlay(AetherSliderDialog(tr(key), min_v, max_v, 1, self._params.get_int(key), on_close, unit=unit, color="#597497"))
|
||||
gui_app.push_widget(AetherSliderDialog(tr(key), min_v, max_v, 1, self._params.get_int(key), on_close, unit=unit, color="#597497"))
|
||||
|
||||
|
||||
class StarPilotSLCVisualsLayout(StarPilotPanel):
|
||||
@@ -1065,9 +1065,9 @@ class StarPilotWeatherLayout(StarPilotPanel):
|
||||
self._params.remove("WeatherAPIKey")
|
||||
self._rebuild_grid()
|
||||
|
||||
gui_app.set_modal_overlay(ConfirmDialog(tr("Remove API Key?"), tr("Confirm"), on_close=on_confirm))
|
||||
gui_app.push_widget(ConfirmDialog(tr("Remove API Key?"), tr("Confirm"), on_close=on_confirm))
|
||||
|
||||
gui_app.set_modal_overlay(dialog, callback=on_select)
|
||||
gui_app.push_widget(dialog, callback=on_select)
|
||||
|
||||
|
||||
class StarPilotWeatherBase(StarPilotPanel):
|
||||
@@ -1117,4 +1117,4 @@ class StarPilotWeatherBase(StarPilotPanel):
|
||||
self._rebuild_grid()
|
||||
|
||||
curr = self._params.get_int(key)
|
||||
gui_app.set_modal_overlay(AetherSliderDialog(tr(key), min_v, max_v, step, curr, on_close, unit=unit, color="#597497"))
|
||||
gui_app.push_widget(AetherSliderDialog(tr(key), min_v, max_v, step, curr, on_close, unit=unit, color="#597497"))
|
||||
|
||||
@@ -14,10 +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.navigation import StarPilotNavigationLayout
|
||||
from openpilot.selfdrive.ui.layouts.settings.starpilot.data import StarPilotDataLayout
|
||||
from openpilot.selfdrive.ui.layouts.settings.starpilot.device import StarPilotDeviceLayout
|
||||
from openpilot.selfdrive.ui.layouts.settings.starpilot.system_settings import StarPilotSystemLayout
|
||||
from openpilot.selfdrive.ui.layouts.settings.starpilot.utilities import StarPilotUtilitiesLayout
|
||||
from openpilot.selfdrive.ui.layouts.settings.starpilot.visuals import StarPilotVisualsLayout
|
||||
from openpilot.selfdrive.ui.layouts.settings.starpilot.themes import StarPilotThemesLayout
|
||||
from openpilot.selfdrive.ui.layouts.settings.starpilot.vehicle import StarPilotVehicleSettingsLayout
|
||||
@@ -94,9 +91,6 @@ class StarPilotLayout(Widget):
|
||||
StarPilotPanelType.LATERAL: StarPilotPanelInfo(tr_noop("Steering"), StarPilotLateralLayout()),
|
||||
StarPilotPanelType.MAPS: StarPilotPanelInfo(tr_noop("Map Data"), StarPilotMapsLayout()),
|
||||
StarPilotPanelType.NAVIGATION: StarPilotPanelInfo(tr_noop("Navigation"), StarPilotNavigationLayout()),
|
||||
StarPilotPanelType.DATA: StarPilotPanelInfo(tr_noop("Data Management"), StarPilotDataLayout()),
|
||||
StarPilotPanelType.DEVICE: StarPilotPanelInfo(tr_noop("Device Controls"), StarPilotDeviceLayout()),
|
||||
StarPilotPanelType.UTILITIES: StarPilotPanelInfo(tr_noop("Utilities"), StarPilotUtilitiesLayout()),
|
||||
StarPilotPanelType.VISUALS: StarPilotPanelInfo(tr_noop("Appearance"), StarPilotVisualsLayout()),
|
||||
StarPilotPanelType.THEMES: StarPilotPanelInfo(tr_noop("Themes"), StarPilotThemesLayout()),
|
||||
StarPilotPanelType.VEHICLE: StarPilotPanelInfo(tr_noop("Vehicle Settings"), StarPilotVehicleSettingsLayout()),
|
||||
@@ -201,9 +195,6 @@ class StarPilotLayout(Widget):
|
||||
"LATERAL": StarPilotPanelType.LATERAL,
|
||||
"MAPS": StarPilotPanelType.MAPS,
|
||||
"NAVIGATION": StarPilotPanelType.NAVIGATION,
|
||||
"DATA": StarPilotPanelType.DATA,
|
||||
"DEVICE": StarPilotPanelType.DEVICE,
|
||||
"UTILITIES": StarPilotPanelType.UTILITIES,
|
||||
"VISUALS": StarPilotPanelType.VISUALS,
|
||||
"THEMES": StarPilotPanelType.THEMES,
|
||||
"VEHICLE": StarPilotPanelType.VEHICLE,
|
||||
|
||||
@@ -124,7 +124,7 @@ class StarPilotMapsLayout(StarPilotPanel):
|
||||
self._params.put("PreferredSchedule", schedule_param_value(dialog.selection))
|
||||
self._rebuild_grid()
|
||||
|
||||
gui_app.set_modal_overlay(dialog, callback=on_select)
|
||||
gui_app.push_widget(dialog, callback=on_select)
|
||||
|
||||
def _on_download(self):
|
||||
current_selected = self._params.get("MapsSelected", encoding="utf-8") or ""
|
||||
@@ -133,15 +133,15 @@ class StarPilotMapsLayout(StarPilotPanel):
|
||||
self._params.put("MapsSelected", selected_raw)
|
||||
selected = [k.strip() for k in selected_raw.split(",") if k.strip()]
|
||||
if not selected:
|
||||
gui_app.set_modal_overlay(alert_dialog(tr("Please select at least one region or state first!")))
|
||||
gui_app.push_widget(alert_dialog(tr("Please select at least one region or state first!")))
|
||||
return
|
||||
|
||||
def on_confirm(res):
|
||||
if res == DialogResult.CONFIRM:
|
||||
self._params_memory.put_bool("DownloadMaps", True)
|
||||
gui_app.set_modal_overlay(alert_dialog(tr("Map download started in background.")))
|
||||
gui_app.push_widget(alert_dialog(tr("Map download started in background.")))
|
||||
|
||||
gui_app.set_modal_overlay(ConfirmDialog(tr("Start downloading maps for selected regions?"), tr("Download"), on_close=on_confirm))
|
||||
gui_app.push_widget(ConfirmDialog(tr("Start downloading maps for selected regions?"), tr("Download"), on_close=on_confirm))
|
||||
|
||||
def _on_remove(self):
|
||||
def on_confirm(res):
|
||||
@@ -149,7 +149,7 @@ class StarPilotMapsLayout(StarPilotPanel):
|
||||
maps_path = Path("/data/media/0/osm/offline")
|
||||
if maps_path.exists():
|
||||
shutil.rmtree(maps_path, ignore_errors=True)
|
||||
gui_app.set_modal_overlay(alert_dialog(tr("Maps removed.")))
|
||||
gui_app.push_widget(alert_dialog(tr("Maps removed.")))
|
||||
self._rebuild_grid()
|
||||
|
||||
gui_app.set_modal_overlay(ConfirmDialog(tr("Delete all downloaded map data?"), tr("Remove"), on_close=on_confirm))
|
||||
gui_app.push_widget(ConfirmDialog(tr("Delete all downloaded map data?"), tr("Remove"), on_close=on_confirm))
|
||||
|
||||
@@ -51,7 +51,7 @@ class StarPilotNavigationLayout(StarPilotPanel):
|
||||
self._rebuild_grid()
|
||||
|
||||
def _on_setup(self):
|
||||
gui_app.set_modal_overlay(
|
||||
gui_app.push_widget(
|
||||
alert_dialog(tr("Mapbox Setup:\n1. Create account at mapbox.com\n2. Generate Public/Secret keys\n3. Add keys in 'Mapbox Credentials'"))
|
||||
)
|
||||
|
||||
@@ -134,7 +134,7 @@ class StarPilotMapboxLayout(StarPilotPanel):
|
||||
self._params.remove(key)
|
||||
self._rebuild_grid()
|
||||
|
||||
gui_app.set_modal_overlay(ConfirmDialog(tr(f"Remove your {key.replace('Mapbox', '')} key?"), tr("Remove"), on_close=on_remove))
|
||||
gui_app.push_widget(ConfirmDialog(tr(f"Remove your {key.replace('Mapbox', '')} key?"), tr("Remove"), on_close=on_remove))
|
||||
else:
|
||||
|
||||
def on_close(res, text):
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
from __future__ import annotations
|
||||
import math
|
||||
import subprocess
|
||||
import time
|
||||
from pathlib import Path
|
||||
@@ -7,14 +8,213 @@ import pyray as rl
|
||||
|
||||
from openpilot.common.basedir import BASEDIR
|
||||
from openpilot.starpilot.common.starpilot_variables import ACTIVE_THEME_PATH
|
||||
from openpilot.system.ui.lib.application import gui_app
|
||||
from openpilot.system.ui.lib.application import gui_app, FontWeight, MouseEvent, MousePos
|
||||
from openpilot.system.ui.lib.multilang import tr, tr_noop
|
||||
from openpilot.system.ui.widgets import DialogResult
|
||||
from openpilot.system.ui.lib.text_measure import measure_text_cached
|
||||
from openpilot.system.ui.widgets import Widget
|
||||
from openpilot.system.ui.widgets.label import gui_label
|
||||
from openpilot.selfdrive.ui.ui_state import ui_state
|
||||
from openpilot.selfdrive.ui.lib.starpilot_state import starpilot_state
|
||||
from openpilot.selfdrive.ui.layouts.settings.starpilot.panel import StarPilotPanel
|
||||
from openpilot.selfdrive.ui.layouts.settings.starpilot.tabbed_panel import TabSectionSpec, TabbedSectionHost
|
||||
from openpilot.selfdrive.ui.layouts.settings.starpilot.aethergrid import TileGrid, ToggleTile, SPACING
|
||||
from openpilot.selfdrive.ui.layouts.settings.starpilot.aethergrid import (
|
||||
AETHER_LIST_METRICS,
|
||||
AetherListColors,
|
||||
build_list_panel_frame,
|
||||
draw_list_panel_shell,
|
||||
AetherContinuousSlider,
|
||||
draw_toggle_pill,
|
||||
)
|
||||
|
||||
MODEL_PANEL_BG = AetherListColors.PANEL_BG
|
||||
MODEL_HEADER_TEXT = AetherListColors.HEADER
|
||||
MODEL_SUBTEXT = AetherListColors.SUBTEXT
|
||||
MODEL_MUTED = AetherListColors.MUTED
|
||||
|
||||
SECTION_GAP = AETHER_LIST_METRICS.section_gap
|
||||
|
||||
|
||||
|
||||
class SoundsManagerView(Widget):
|
||||
def __init__(self, controller: "StarPilotSoundsLayout"):
|
||||
super().__init__()
|
||||
self._controller = controller
|
||||
self._pressed_target: str | None = None
|
||||
|
||||
self._sliders: dict[str, AetherContinuousSlider] = {}
|
||||
self._slider_was_dragging: dict[str, bool] = {}
|
||||
self._toggle_rects: dict[str, rl.Rectangle] = {}
|
||||
self._font = gui_app.font(FontWeight.BOLD)
|
||||
|
||||
self._init_sliders()
|
||||
|
||||
def _init_sliders(self):
|
||||
for key in self._controller.VOLUME_KEYS:
|
||||
val = self._controller._params.get_int(key, return_default=True, default=100)
|
||||
|
||||
def on_change(v, k=key):
|
||||
new_v = int(v)
|
||||
if new_v != 101 and new_v < self._controller.VOLUME_INFO[k]["min"]:
|
||||
new_v = self._controller.VOLUME_INFO[k]["min"]
|
||||
self._controller._params.put_int(k, new_v)
|
||||
|
||||
slider = AetherContinuousSlider(
|
||||
min_val=0.0,
|
||||
max_val=101.0,
|
||||
step=1.0,
|
||||
current_val=float(val),
|
||||
on_change=on_change,
|
||||
title=tr(self._controller.VOLUME_INFO[key]["title"]),
|
||||
unit="%",
|
||||
labels={0.0: tr("Muted"), 101.0: tr("Auto")},
|
||||
color=AetherListColors.PRIMARY
|
||||
)
|
||||
self._sliders[key] = slider
|
||||
self._slider_was_dragging[key] = False
|
||||
|
||||
cd_val = self._controller._params.get_int(self._controller.COOLDOWN_KEY, return_default=True, default=0)
|
||||
def on_cd_change(v):
|
||||
self._controller._params.put_int(self._controller.COOLDOWN_KEY, int(v))
|
||||
|
||||
cd_slider = AetherContinuousSlider(
|
||||
min_val=0.0,
|
||||
max_val=float(self._controller.COOLDOWN_INFO["max"]),
|
||||
step=1.0,
|
||||
current_val=float(cd_val),
|
||||
on_change=on_cd_change,
|
||||
title=tr(self._controller.COOLDOWN_INFO["title"]),
|
||||
unit=" " + tr("min"),
|
||||
labels={0.0: tr("Off"), 1.0: tr("1 minute")},
|
||||
color=AetherListColors.PRIMARY
|
||||
)
|
||||
self._sliders[self._controller.COOLDOWN_KEY] = cd_slider
|
||||
self._slider_was_dragging[self._controller.COOLDOWN_KEY] = False
|
||||
|
||||
def _handle_mouse_press(self, mouse_pos: MousePos):
|
||||
self._pressed_target = self._target_at(mouse_pos)
|
||||
for slider in self._sliders.values():
|
||||
slider._handle_mouse_press(mouse_pos)
|
||||
|
||||
def _handle_mouse_release(self, mouse_pos: MousePos):
|
||||
for slider in self._sliders.values():
|
||||
slider._handle_mouse_release(mouse_pos)
|
||||
|
||||
target = self._target_at(mouse_pos)
|
||||
if self._pressed_target is not None and self._pressed_target == target:
|
||||
self._activate_target(target)
|
||||
self._pressed_target = None
|
||||
|
||||
def _handle_mouse_event(self, mouse_event: MouseEvent):
|
||||
for slider in self._sliders.values():
|
||||
slider._handle_mouse_event(mouse_event)
|
||||
|
||||
def _target_at(self, mouse_pos: MousePos) -> str | None:
|
||||
for key, rect in self._toggle_rects.items():
|
||||
if rl.check_collision_point_rec(mouse_pos, rect):
|
||||
return f"toggle:{key}"
|
||||
return None
|
||||
|
||||
def _activate_target(self, target: str):
|
||||
if target.startswith("toggle:"):
|
||||
key = target.split(":", 1)[1]
|
||||
info = self._controller.ALERT_INFO.get(key)
|
||||
if info and info.get("is_enabled", lambda: True)():
|
||||
current = self._controller._params.get_bool(key)
|
||||
self._controller._params.put_bool(key, not current)
|
||||
|
||||
def _render(self, rect: rl.Rectangle):
|
||||
self.set_rect(rect)
|
||||
self._toggle_rects.clear()
|
||||
|
||||
frame = build_list_panel_frame(rect)
|
||||
draw_list_panel_shell(frame)
|
||||
|
||||
header_rect = frame.header
|
||||
self._draw_header(header_rect)
|
||||
|
||||
# Reclaim the dead space! The global header allocates 210px, but our text only uses ~100px.
|
||||
metrics = AETHER_LIST_METRICS
|
||||
actual_header_height = 100
|
||||
content_y = header_rect.y + actual_header_height
|
||||
content_h = (frame.shell.y + frame.shell.height) - content_y - metrics.panel_padding_bottom
|
||||
|
||||
content_rect = rl.Rectangle(
|
||||
frame.scroll.x,
|
||||
content_y,
|
||||
frame.scroll.width,
|
||||
content_h
|
||||
)
|
||||
|
||||
col_width = (content_rect.width - SECTION_GAP) / 2
|
||||
left_col = rl.Rectangle(content_rect.x, content_rect.y, col_width, content_rect.height)
|
||||
right_col = rl.Rectangle(content_rect.x + col_width + SECTION_GAP, content_rect.y, col_width, content_rect.height)
|
||||
|
||||
self._draw_volume_section(left_col)
|
||||
self._draw_utility_section(right_col)
|
||||
|
||||
for key, slider in self._sliders.items():
|
||||
is_dragging = slider._is_dragging
|
||||
if self._slider_was_dragging[key] and not is_dragging:
|
||||
if key in self._controller.VOLUME_KEYS:
|
||||
self._controller._test_sound(key)
|
||||
self._slider_was_dragging[key] = is_dragging
|
||||
|
||||
def _draw_header(self, rect: rl.Rectangle):
|
||||
title_rect = rl.Rectangle(rect.x, rect.y + 4, rect.width * 0.55, 40)
|
||||
gui_label(title_rect, tr("Sounds & Alerts"), 40, MODEL_HEADER_TEXT, FontWeight.SEMI_BOLD)
|
||||
|
||||
subtitle_rect = rl.Rectangle(rect.x, rect.y + 48, rect.width * 0.58, 36)
|
||||
gui_label(subtitle_rect, tr("Manage system volumes and custom alert toggles."), 24, MODEL_SUBTEXT, FontWeight.NORMAL)
|
||||
|
||||
def _draw_volume_section(self, rect: rl.Rectangle):
|
||||
num_volumes = len(self._controller.VOLUME_KEYS)
|
||||
vol_row_h = rect.height / num_volumes
|
||||
|
||||
for index, key in enumerate(self._controller.VOLUME_KEYS):
|
||||
row_rect = rl.Rectangle(rect.x, rect.y + index * vol_row_h, rect.width, vol_row_h)
|
||||
self._draw_slider_row(row_rect, key, self._controller.VOLUME_INFO[key])
|
||||
|
||||
def _draw_utility_section(self, rect: rl.Rectangle):
|
||||
total_elements = 7 # 1 cooldown + 6 alerts
|
||||
row_h = rect.height / total_elements
|
||||
|
||||
# Cooldown Slider (Index 0)
|
||||
cd_row_rect = rl.Rectangle(rect.x, rect.y, rect.width, row_h)
|
||||
self._draw_slider_row(cd_row_rect, self._controller.COOLDOWN_KEY, self._controller.COOLDOWN_INFO)
|
||||
|
||||
# Custom Alert Toggle Pills (Indices 1 to 6)
|
||||
for index, key in enumerate(self._controller.CUSTOM_ALERTS_KEYS):
|
||||
row_rect = rl.Rectangle(rect.x, rect.y + (index + 1) * row_h, rect.width, row_h)
|
||||
self._draw_toggle_row(row_rect, key, self._controller.ALERT_INFO[key])
|
||||
|
||||
def _draw_slider_row(self, rect: rl.Rectangle, key: str, info: dict):
|
||||
slider = self._sliders[key]
|
||||
|
||||
padded_rect = rl.Rectangle(rect.x, rect.y + 4, rect.width - 12, rect.height - 8)
|
||||
|
||||
if not slider._is_dragging:
|
||||
current_param = self._controller._params.get_int(key, return_default=True, default=100 if key != self._controller.COOLDOWN_KEY else 0)
|
||||
if slider.current_val != current_param:
|
||||
slider.current_val = float(current_param)
|
||||
|
||||
slider.render(padded_rect)
|
||||
|
||||
def _draw_toggle_row(self, rect: rl.Rectangle, key: str, info: dict):
|
||||
padded_rect = rl.Rectangle(rect.x, rect.y + 4, rect.width - 12, rect.height - 8)
|
||||
|
||||
current_val = self._controller._params.get_bool(key)
|
||||
is_enabled = info.get("is_enabled", lambda: True)()
|
||||
|
||||
mouse_pos = gui_app.last_mouse_event.pos
|
||||
hovered = rl.check_collision_point_rec(mouse_pos, padded_rect)
|
||||
pressed = self._pressed_target == f"toggle:{key}"
|
||||
|
||||
status_str = tr("ON") if current_val else tr("OFF")
|
||||
if not is_enabled: status_str = tr(info.get("disabled_label", "UNAVAILABLE"))
|
||||
|
||||
draw_toggle_pill(padded_rect, current_val, is_enabled, tr(info["title"]), status_str, hovered, pressed)
|
||||
|
||||
self._toggle_rects[key] = padded_rect
|
||||
|
||||
|
||||
class StarPilotSoundsLayout(StarPilotPanel):
|
||||
COOLDOWN_KEY = "SwitchbackModeCooldown"
|
||||
@@ -37,42 +237,16 @@ class StarPilotSoundsLayout(StarPilotPanel):
|
||||
"SpeedLimitChangedAlert",
|
||||
]
|
||||
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
self._section_tabs = TabbedSectionHost([
|
||||
TabSectionSpec("volume_control", "Volumes", StarPilotVolumeControlLayout()),
|
||||
TabSectionSpec("custom_alerts", "Alerts", StarPilotCustomAlertsLayout()),
|
||||
])
|
||||
|
||||
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 StarPilotVolumeControlLayout(StarPilotPanel):
|
||||
COOLDOWN_INFO = {"title": tr_noop("Switchback Mode Cooldown"), "icon": "toggle_icons/icon_mute.png", "min": 0, "max": 30}
|
||||
COOLDOWN_INFO = {"title": tr_noop("Switchback Mode Cooldown"), "min": 0, "max": 30}
|
||||
VOLUME_INFO = {
|
||||
"BelowSteerSpeedVolume": {"title": tr_noop("Min Steer Speed Alert"), "icon": "toggle_icons/icon_mute.png", "min": 0},
|
||||
"DisengageVolume": {"title": tr_noop("Disengage Volume"), "icon": "toggle_icons/icon_mute.png", "min": 0},
|
||||
"EngageVolume": {"title": tr_noop("Engage Volume"), "icon": "toggle_icons/icon_green_light.png", "min": 0},
|
||||
"PromptVolume": {"title": tr_noop("Prompt Volume"), "icon": "toggle_icons/icon_message.png", "min": 0},
|
||||
"PromptDistractedVolume": {"title": tr_noop("Distracted Volume"), "icon": "toggle_icons/icon_display.png", "min": 0},
|
||||
"RefuseVolume": {"title": tr_noop("Refuse Volume"), "icon": "toggle_icons/icon_mute.png", "min": 0},
|
||||
"WarningSoftVolume": {"title": tr_noop("Warning Soft"), "icon": "toggle_icons/icon_conditional.png", "min": 25},
|
||||
"WarningImmediateVolume": {"title": tr_noop("Warning Immediate"), "icon": "toggle_icons/icon_conditional.png", "min": 25},
|
||||
"BelowSteerSpeedVolume": {"title": tr_noop("Min Steer Speed Alert"), "min": 0},
|
||||
"DisengageVolume": {"title": tr_noop("Disengage Volume"), "min": 0},
|
||||
"EngageVolume": {"title": tr_noop("Engage Volume"), "min": 0},
|
||||
"PromptVolume": {"title": tr_noop("Prompt Volume"), "min": 0},
|
||||
"PromptDistractedVolume": {"title": tr_noop("Distracted Volume"), "min": 0},
|
||||
"RefuseVolume": {"title": tr_noop("Refuse Volume"), "min": 0},
|
||||
"WarningSoftVolume": {"title": tr_noop("Warning Soft"), "min": 25},
|
||||
"WarningImmediateVolume": {"title": tr_noop("Warning Immediate"), "min": 25},
|
||||
}
|
||||
|
||||
_sound_player_process = None
|
||||
@@ -81,71 +255,35 @@ class StarPilotVolumeControlLayout(StarPilotPanel):
|
||||
super().__init__()
|
||||
self._init_sound_player()
|
||||
|
||||
self.SECTIONS = [
|
||||
{
|
||||
"title": tr_noop("Volume Levels"),
|
||||
"categories": self._build_volume_categories(),
|
||||
self.ALERT_INFO = {
|
||||
"GoatScream": {"title": tr_noop("Goat Scream")},
|
||||
"GoatScreamCriticalAlerts": {"title": tr_noop("Goat Critical")},
|
||||
"GreenLightAlert": {"title": tr_noop("Green Light")},
|
||||
"LeadDepartingAlert": {"title": tr_noop("Lead Departure")},
|
||||
"LoudBlindspotAlert": {
|
||||
"title": tr_noop("Loud Blindspot"),
|
||||
"is_enabled": lambda: starpilot_state.car_state.hasBSM,
|
||||
"disabled_label": tr_noop("Needs BSM")
|
||||
},
|
||||
{
|
||||
"title": tr_noop("Safety & Cooldown"),
|
||||
"categories": self._build_safety_categories(),
|
||||
}
|
||||
]
|
||||
self._rebuild_grid()
|
||||
"SpeedLimitChangedAlert": {
|
||||
"title": tr_noop("Speed Limit"),
|
||||
"is_enabled": lambda: self._params.get_bool("ShowSpeedLimits") or (
|
||||
starpilot_state.car_state.hasOpenpilotLongitudinal and self._params.get_bool("SpeedLimitController")
|
||||
),
|
||||
"disabled_label": tr_noop("Needs Speed Limits")
|
||||
},
|
||||
}
|
||||
|
||||
def _build_volume_categories(self):
|
||||
cats = []
|
||||
for key in StarPilotSoundsLayout.VOLUME_KEYS:
|
||||
info = self.VOLUME_INFO[key]
|
||||
self._manager_view = SoundsManagerView(self)
|
||||
|
||||
def get_val(k=key):
|
||||
return float(self._params.get_int(k, return_default=True, default=100))
|
||||
def _render(self, rect: rl.Rectangle):
|
||||
self._manager_view.render(rect)
|
||||
|
||||
def set_val(val, k=key):
|
||||
new_v = int(val)
|
||||
if new_v != 101 and new_v < self.VOLUME_INFO[k]["min"]:
|
||||
new_v = self.VOLUME_INFO[k]["min"]
|
||||
self._params.put_int(k, new_v)
|
||||
def show_event(self):
|
||||
super().show_event()
|
||||
|
||||
def test_cb(k=key):
|
||||
self._test_sound(k)
|
||||
|
||||
cats.append({
|
||||
"title": info["title"],
|
||||
"type": "slider",
|
||||
"get_value": get_val,
|
||||
"set_value": set_val,
|
||||
"on_test": test_cb,
|
||||
"min_val": 0,
|
||||
"max_val": 101,
|
||||
"step": 1,
|
||||
"unit": "%",
|
||||
"labels": {0: tr("Muted"), 101: tr("Auto")},
|
||||
"icon": info["icon"],
|
||||
"color": "#3B82F6",
|
||||
})
|
||||
return cats
|
||||
|
||||
def _build_safety_categories(self):
|
||||
def get_cooldown_val():
|
||||
return float(self._params.get_int(StarPilotSoundsLayout.COOLDOWN_KEY, return_default=True, default=0))
|
||||
|
||||
def set_cooldown_val(val):
|
||||
self._params.put_int(StarPilotSoundsLayout.COOLDOWN_KEY, int(val))
|
||||
|
||||
return [{
|
||||
"title": self.COOLDOWN_INFO["title"],
|
||||
"type": "slider",
|
||||
"get_value": get_cooldown_val,
|
||||
"set_value": set_cooldown_val,
|
||||
"min_val": 0,
|
||||
"max_val": float(self.COOLDOWN_INFO["max"]),
|
||||
"step": 1,
|
||||
"unit": " " + tr("min"),
|
||||
"labels": {0: tr("Off"), 1: tr("1 minute")},
|
||||
"icon": self.COOLDOWN_INFO["icon"],
|
||||
"color": "#3B82F6",
|
||||
}]
|
||||
def hide_event(self):
|
||||
super().hide_event()
|
||||
|
||||
@classmethod
|
||||
def _init_sound_player(cls):
|
||||
@@ -194,60 +332,3 @@ while True:
|
||||
self._sound_player_process.stdin.write(f"{sound_path}|{volume}\n".encode())
|
||||
self._sound_player_process.stdin.flush()
|
||||
except: pass
|
||||
|
||||
class StarPilotCustomAlertsLayout(StarPilotPanel):
|
||||
ALERT_INFO = {
|
||||
"GoatScream": {"title": tr_noop("Goat Scream"), "icon": "toggle_icons/icon_sound.png"},
|
||||
"GoatScreamCriticalAlerts": {"title": tr_noop("Goat Critical"), "icon": "toggle_icons/icon_sound.png"},
|
||||
"GreenLightAlert": {"title": tr_noop("Green Light"), "icon": "toggle_icons/icon_green_light.png"},
|
||||
"LeadDepartingAlert": {"title": tr_noop("Lead Departure"), "icon": "toggle_icons/icon_steering.png"},
|
||||
"LoudBlindspotAlert": {"title": tr_noop("Loud Blindspot"), "icon": "toggle_icons/icon_display.png"},
|
||||
"SpeedLimitChangedAlert": {"title": tr_noop("Speed Limit"), "icon": "toggle_icons/icon_speed_limit.png"},
|
||||
}
|
||||
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
self._tile_grid = TileGrid(columns=2, padding=SPACING.tile_gap, uniform_width=True)
|
||||
self.CATEGORIES = []
|
||||
for key in StarPilotSoundsLayout.CUSTOM_ALERTS_KEYS:
|
||||
info = self.ALERT_INFO[key]
|
||||
self.CATEGORIES.append({
|
||||
"title": info["title"],
|
||||
"type": "toggle",
|
||||
"get_state": lambda k=key: self._params.get_bool(k),
|
||||
"set_state": lambda s, k=key: self._params.put_bool(k, s),
|
||||
"icon": info["icon"],
|
||||
"color": "#3B82F6",
|
||||
"key": key # Store for visibility check
|
||||
})
|
||||
self._rebuild_grid()
|
||||
|
||||
def _rebuild_grid(self):
|
||||
if not self.CATEGORIES:
|
||||
return
|
||||
self._tile_grid.clear()
|
||||
|
||||
for cat in self.CATEGORIES:
|
||||
key = cat.get("key")
|
||||
is_enabled = lambda: True
|
||||
disabled_label = ""
|
||||
|
||||
if key == "LoudBlindspotAlert":
|
||||
is_enabled = lambda: starpilot_state.car_state.hasBSM
|
||||
disabled_label = tr_noop("Needs BSM")
|
||||
elif key == "SpeedLimitChangedAlert":
|
||||
is_enabled = lambda: self._params.get_bool("ShowSpeedLimits") or (
|
||||
starpilot_state.car_state.hasOpenpilotLongitudinal and self._params.get_bool("SpeedLimitController")
|
||||
)
|
||||
disabled_label = tr_noop("Needs Speed Limits")
|
||||
|
||||
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"),
|
||||
is_enabled=is_enabled,
|
||||
disabled_label=tr(disabled_label) if disabled_label else "",
|
||||
)
|
||||
self._tile_grid.add_tile(tile)
|
||||
|
||||
@@ -1,92 +1,700 @@
|
||||
from __future__ import annotations
|
||||
import json
|
||||
import os
|
||||
import shutil
|
||||
import subprocess
|
||||
import threading
|
||||
import time
|
||||
from datetime import datetime
|
||||
from pathlib import Path
|
||||
from dataclasses import replace
|
||||
|
||||
import pyray as rl
|
||||
|
||||
from openpilot.system.hardware import HARDWARE
|
||||
from openpilot.system.ui.lib.application import gui_app, FontWeight, MouseEvent, MousePos
|
||||
from openpilot.system.ui.lib.multilang import tr
|
||||
from openpilot.system.ui.lib.scroll_panel2 import GuiScrollPanel2
|
||||
from openpilot.system.ui.widgets import DialogResult, Widget
|
||||
from openpilot.system.ui.widgets.confirm_dialog import ConfirmDialog, alert_dialog
|
||||
from openpilot.system.ui.widgets.keyboard import Keyboard
|
||||
from openpilot.system.ui.widgets.option_dialog import MultiOptionDialog
|
||||
from openpilot.system.ui.widgets.label import gui_label
|
||||
|
||||
from openpilot.selfdrive.ui.ui_state import ui_state
|
||||
from openpilot.selfdrive.ui.layouts.settings.starpilot.panel import StarPilotPanel
|
||||
from openpilot.selfdrive.ui.layouts.settings.starpilot.aethergrid import RadioTileGroup
|
||||
from openpilot.selfdrive.ui.layouts.settings.starpilot.data import StarPilotDataLayout
|
||||
from openpilot.selfdrive.ui.layouts.settings.starpilot.device import StarPilotDeviceLayout
|
||||
from openpilot.selfdrive.ui.layouts.settings.starpilot.utilities import StarPilotUtilitiesLayout
|
||||
from openpilot.selfdrive.ui.layouts.settings.starpilot.aethergrid import (
|
||||
AETHER_LIST_METRICS,
|
||||
AetherListColors,
|
||||
AetherScrollbar,
|
||||
AetherContinuousSlider,
|
||||
build_list_panel_frame,
|
||||
draw_list_panel_shell,
|
||||
draw_list_scroll_fades,
|
||||
draw_toggle_pill,
|
||||
)
|
||||
|
||||
LEGACY_STARPILOT_PARAM_RENAMES = {
|
||||
"FrogPilotApiToken": "StarPilotApiToken",
|
||||
"FrogPilotCarParams": "StarPilotCarParams",
|
||||
"FrogPilotCarParamsPersistent": "StarPilotCarParamsPersistent",
|
||||
"FrogPilotDongleId": "StarPilotDongleId",
|
||||
"FrogPilotStats": "StarPilotStats",
|
||||
}
|
||||
|
||||
EXCLUDED_KEYS = {
|
||||
"AvailableModels",
|
||||
"AvailableModelNames",
|
||||
"StarPilotStats",
|
||||
"GithubSshKeys",
|
||||
"GithubUsername",
|
||||
"MapBoxRequests",
|
||||
"ModelDrivesAndScores",
|
||||
"OverpassRequests",
|
||||
"SpeedLimits",
|
||||
"SpeedLimitsFiltered",
|
||||
"UpdaterAvailableBranches",
|
||||
}
|
||||
|
||||
REPORT_CATEGORIES = [
|
||||
"Acceleration feels harsh or jerky",
|
||||
"An alert was unclear and I'm not sure what it meant",
|
||||
"Braking is too sudden or uncomfortable",
|
||||
"I'm not sure if this is normal or a bug:",
|
||||
"My steering wheel buttons aren't working",
|
||||
"openpilot disengages when I don't expect it",
|
||||
"openpilot feels sluggish or slow to respond",
|
||||
"Something else (please describe)",
|
||||
]
|
||||
|
||||
class SystemSettingsManagerView(Widget):
|
||||
def __init__(self, controller: "StarPilotSystemLayout"):
|
||||
super().__init__()
|
||||
self._controller = controller
|
||||
self._scroll_panel = GuiScrollPanel2(horizontal=False)
|
||||
self._scrollbar = AetherScrollbar()
|
||||
self._content_height = 0.0
|
||||
self._scroll_offset = 0.0
|
||||
self._pressed_target: str | None = None
|
||||
self._can_click = True
|
||||
|
||||
self._action_rects: dict[str, rl.Rectangle] = {}
|
||||
self._toggle_rects: dict[str, rl.Rectangle] = {}
|
||||
self._shell_rect = rl.Rectangle(0, 0, 0, 0)
|
||||
self._scroll_rect = rl.Rectangle(0, 0, 0, 0)
|
||||
|
||||
shutdown_labels = {0: tr("5 mins")}
|
||||
for i in range(1, 4): shutdown_labels[i] = f"{i * 15} mins"
|
||||
for i in range(4, 34): shutdown_labels[i] = f"{i - 3} " + (tr("hour") if i == 4 else tr("hours"))
|
||||
|
||||
brightness_labels = {101: tr("Auto"), 0: tr("Off")}
|
||||
|
||||
self._sliders = {
|
||||
"ScreenBrightness": AetherContinuousSlider(0, 101, 1, self._controller._params.get_int("ScreenBrightness"), lambda v: self._controller._set_brightness("ScreenBrightness", v), title=tr("Brightness (Offroad)"), unit="%", labels=brightness_labels, color=AetherListColors.PRIMARY),
|
||||
"ScreenBrightnessOnroad": AetherContinuousSlider(1, 101, 1, max(1, self._controller._params.get_int("ScreenBrightnessOnroad")), lambda v: self._controller._set_brightness("ScreenBrightnessOnroad", max(1, int(v))), title=tr("Brightness (Onroad)"), unit="%", labels=brightness_labels, color=AetherListColors.PRIMARY),
|
||||
"ScreenTimeout": AetherContinuousSlider(5, 60, 5, self._controller._params.get_int("ScreenTimeout"), lambda v: self._controller._params.put_int("ScreenTimeout", int(v)), title=tr("Timeout (Offroad)"), unit="s", color=AetherListColors.PRIMARY),
|
||||
"ScreenTimeoutOnroad": AetherContinuousSlider(5, 60, 5, self._controller._params.get_int("ScreenTimeoutOnroad"), lambda v: self._controller._params.put_int("ScreenTimeoutOnroad", int(v)), title=tr("Timeout (Onroad)"), unit="s", color=AetherListColors.PRIMARY),
|
||||
"DeviceShutdown": AetherContinuousSlider(0, 33, 1, self._controller._params.get_int("DeviceShutdown"), lambda v: self._controller._params.put_int("DeviceShutdown", int(v)), title=tr("Device Shutdown"), labels=shutdown_labels, color=AetherListColors.PRIMARY),
|
||||
"LowVoltageShutdown": AetherContinuousSlider(11.8, 12.5, 0.1, self._controller._params.get_float("LowVoltageShutdown"), lambda v: self._controller._params.put_float("LowVoltageShutdown", float(v)), title=tr("Low-Voltage Cutoff"), unit="V", color=AetherListColors.PRIMARY),
|
||||
}
|
||||
|
||||
def _clear_ephemeral_state(self):
|
||||
self._pressed_target = None
|
||||
self._can_click = True
|
||||
|
||||
def show_event(self):
|
||||
super().show_event()
|
||||
self._clear_ephemeral_state()
|
||||
|
||||
def hide_event(self):
|
||||
super().hide_event()
|
||||
self._clear_ephemeral_state()
|
||||
|
||||
def _handle_mouse_press(self, mouse_pos: MousePos):
|
||||
self._pressed_target = None
|
||||
self._can_click = True
|
||||
|
||||
for action_id, rect in self._action_rects.items():
|
||||
visible_rect = rl.get_collision_rec(rect, self._scroll_rect)
|
||||
if visible_rect.width > 0 and visible_rect.height > 0 and rl.check_collision_point_rec(mouse_pos, visible_rect):
|
||||
self._pressed_target = f"action:{action_id}"
|
||||
return
|
||||
|
||||
for toggle_id, rect in self._toggle_rects.items():
|
||||
visible_rect = rl.get_collision_rec(rect, self._scroll_rect)
|
||||
if visible_rect.width > 0 and visible_rect.height > 0 and rl.check_collision_point_rec(mouse_pos, visible_rect):
|
||||
self._pressed_target = f"toggle:{toggle_id}"
|
||||
return
|
||||
|
||||
for slider in self._sliders.values():
|
||||
slider._handle_mouse_press(mouse_pos)
|
||||
|
||||
def _handle_mouse_event(self, mouse_event: MouseEvent):
|
||||
if not self._scroll_panel.is_touch_valid():
|
||||
self._can_click = False
|
||||
for slider in self._sliders.values():
|
||||
slider._handle_mouse_event(mouse_event)
|
||||
|
||||
def _handle_mouse_release(self, mouse_pos: MousePos):
|
||||
target = self._pressed_target
|
||||
self._pressed_target = None
|
||||
|
||||
if target and self._can_click:
|
||||
if target.startswith("action:"):
|
||||
action_id = target.split(":", 1)[1]
|
||||
rect = self._action_rects.get(action_id)
|
||||
elif target.startswith("toggle:"):
|
||||
toggle_id = target.split(":", 1)[1]
|
||||
rect = self._toggle_rects.get(toggle_id)
|
||||
|
||||
if rect:
|
||||
visible_rect = rl.get_collision_rec(rect, self._scroll_rect)
|
||||
if visible_rect.width > 0 and visible_rect.height > 0 and rl.check_collision_point_rec(mouse_pos, visible_rect):
|
||||
self._activate_target(target)
|
||||
|
||||
for slider in self._sliders.values():
|
||||
slider._handle_mouse_release(mouse_pos)
|
||||
|
||||
def _activate_target(self, target: str):
|
||||
action_id = target.split(":", 1)[1]
|
||||
self._controller.handle_action(action_id)
|
||||
|
||||
def _render(self, rect: rl.Rectangle):
|
||||
self.set_rect(rect)
|
||||
self._action_rects.clear()
|
||||
self._toggle_rects.clear()
|
||||
|
||||
metrics = replace(AETHER_LIST_METRICS, header_height=110)
|
||||
frame = build_list_panel_frame(rect, metrics)
|
||||
self._shell_rect = frame.shell
|
||||
draw_list_panel_shell(frame)
|
||||
|
||||
header_rect = frame.header
|
||||
self._draw_header(header_rect)
|
||||
|
||||
scroll_rect = frame.scroll
|
||||
self._scroll_rect = scroll_rect
|
||||
|
||||
content_width = scroll_rect.width - AETHER_LIST_METRICS.content_right_gutter
|
||||
self._content_height = self._measure_content_height()
|
||||
self._scroll_offset = self._scroll_panel.update(scroll_rect, max(self._content_height, scroll_rect.height))
|
||||
|
||||
rl.begin_scissor_mode(int(scroll_rect.x), int(scroll_rect.y), int(scroll_rect.width), int(scroll_rect.height))
|
||||
self._draw_scroll_content(scroll_rect, content_width)
|
||||
rl.end_scissor_mode()
|
||||
|
||||
if self._content_height > scroll_rect.height:
|
||||
self._draw_scrollbar(scroll_rect)
|
||||
|
||||
draw_list_scroll_fades(scroll_rect, self._content_height, self._scroll_offset, AetherListColors.PANEL_BG, fade_height=AETHER_LIST_METRICS.fade_height)
|
||||
|
||||
def _draw_header(self, rect: rl.Rectangle):
|
||||
title_rect = rl.Rectangle(rect.x, rect.y + 4, rect.width * 0.55, 40)
|
||||
gui_label(title_rect, tr("System Settings"), 40, AetherListColors.HEADER, FontWeight.SEMI_BOLD)
|
||||
subtitle_rect = rl.Rectangle(rect.x, rect.y + 48, rect.width * 0.58, 36)
|
||||
gui_label(subtitle_rect, tr("Manage device behavior, power, and storage seamlessly."), 24, AetherListColors.SUBTEXT, FontWeight.NORMAL)
|
||||
|
||||
def _measure_column_height(self, sections: list[dict]) -> float:
|
||||
total_height = 0
|
||||
for section in sections:
|
||||
total_height += AETHER_LIST_METRICS.section_header_height + AETHER_LIST_METRICS.section_header_gap
|
||||
for row in section["rows"]:
|
||||
if row["type"] == "slider":
|
||||
total_height += 100 + 16
|
||||
elif row["type"] in ["toggle", "toggle_row"]:
|
||||
total_height += 90 + 16
|
||||
elif row["type"] == "action_group":
|
||||
total_height += 110 + 16
|
||||
total_height += AETHER_LIST_METRICS.section_gap
|
||||
return max(total_height - AETHER_LIST_METRICS.section_gap, 0.0)
|
||||
|
||||
def _measure_content_height(self) -> float:
|
||||
cols = self._controller.utility_columns()
|
||||
return max(self._measure_column_height(cols["left"]), self._measure_column_height(cols["right"]), 0.0)
|
||||
|
||||
def _draw_scroll_content(self, rect: rl.Rectangle, width: float):
|
||||
cols = self._controller.utility_columns()
|
||||
col_w = (width - AETHER_LIST_METRICS.section_gap) / 2
|
||||
left_x = rect.x
|
||||
right_x = rect.x + col_w + AETHER_LIST_METRICS.section_gap
|
||||
|
||||
self._draw_column(rl.Rectangle(left_x, rect.y + self._scroll_offset, col_w, rect.height), cols["left"])
|
||||
self._draw_column(rl.Rectangle(right_x, rect.y + self._scroll_offset, col_w, rect.height), cols["right"])
|
||||
|
||||
def _draw_column(self, rect: rl.Rectangle, sections: list[dict]):
|
||||
y = rect.y
|
||||
mouse_pos = gui_app.last_mouse_event.pos
|
||||
|
||||
for section in sections:
|
||||
title_rect = rl.Rectangle(rect.x, y, rect.width, AETHER_LIST_METRICS.section_header_height)
|
||||
gui_label(title_rect, section["title"], 26, AetherListColors.SUBTEXT, FontWeight.MEDIUM)
|
||||
y += AETHER_LIST_METRICS.section_header_height + AETHER_LIST_METRICS.section_header_gap
|
||||
|
||||
for row in section["rows"]:
|
||||
if row["type"] == "slider":
|
||||
slider = self._sliders[row["id"]]
|
||||
slider.render(rl.Rectangle(rect.x, y, rect.width, 100))
|
||||
y += 100 + 16
|
||||
elif row["type"] == "toggle_row":
|
||||
items = row["items"]
|
||||
item_w = (rect.width - 16 * (len(items) - 1)) / len(items)
|
||||
for i, item in enumerate(items):
|
||||
enabled = item.get("enabled", True)
|
||||
toggle_rect = rl.Rectangle(rect.x + i * (item_w + 16), y, item_w, 90)
|
||||
if enabled:
|
||||
self._toggle_rects[item["id"]] = toggle_rect
|
||||
hovered = rl.check_collision_point_rec(mouse_pos, toggle_rect)
|
||||
pressed = self._pressed_target == f"toggle:{item['id']}"
|
||||
draw_toggle_pill(toggle_rect, item["value"], enabled, item["title"], tr("ON") if item["value"] else tr("OFF"), hovered, pressed)
|
||||
y += 90 + 16
|
||||
elif row["type"] == "toggle":
|
||||
enabled = row.get("enabled", True)
|
||||
toggle_rect = rl.Rectangle(rect.x, y, rect.width, 90)
|
||||
if enabled:
|
||||
self._toggle_rects[row["id"]] = toggle_rect
|
||||
hovered = rl.check_collision_point_rec(mouse_pos, toggle_rect)
|
||||
pressed = self._pressed_target == f"toggle:{row['id']}"
|
||||
draw_toggle_pill(toggle_rect, row["value"], enabled, row["title"], tr("ON") if row["value"] else tr("OFF"), hovered, pressed)
|
||||
y += 90 + 16
|
||||
elif row["type"] == "action_group":
|
||||
group_rect = rl.Rectangle(rect.x, y, rect.width, 110)
|
||||
self._draw_action_group(group_rect, row, mouse_pos)
|
||||
y += 110 + 16
|
||||
|
||||
y += AETHER_LIST_METRICS.section_gap
|
||||
|
||||
def _draw_action_group(self, rect: rl.Rectangle, row: dict, mouse_pos: MousePos):
|
||||
rl.draw_rectangle_rounded(rect, 0.3, 16, rl.Color(35, 35, 40, 255))
|
||||
|
||||
title_y = rect.y + (rect.height - 24) / 2
|
||||
gui_label(rl.Rectangle(rect.x + 24, title_y, rect.width * 0.4, 24), row["title"], 24, rl.WHITE, FontWeight.BOLD)
|
||||
|
||||
actions = row["actions"]
|
||||
btn_gap = 12
|
||||
available_w = rect.width * 0.6 - 40
|
||||
btn_w = (available_w - (len(actions) - 1) * btn_gap) / len(actions)
|
||||
start_x = rect.x + rect.width - available_w - 16
|
||||
|
||||
for i, action in enumerate(actions):
|
||||
btn_rect = rl.Rectangle(start_x + i * (btn_w + btn_gap), rect.y + 12, btn_w, rect.height - 24)
|
||||
self._action_rects[action["id"]] = btn_rect
|
||||
|
||||
hovered = rl.check_collision_point_rec(mouse_pos, btn_rect)
|
||||
pressed = self._pressed_target == f"action:{action['id']}"
|
||||
|
||||
active = action.get("active", False)
|
||||
color = AetherListColors.PRIMARY if active else rl.Color(60, 60, 65, 255)
|
||||
if action.get("danger"):
|
||||
color = AetherListColors.DANGER
|
||||
|
||||
if hovered: color = rl.Color(min(color.r + 20, 255), min(color.g + 20, 255), min(color.b + 20, 255), 255)
|
||||
if pressed: color = rl.Color(max(color.r - 20, 0), max(color.g - 20, 0), max(color.b - 20, 0), 255)
|
||||
|
||||
rl.draw_rectangle_rounded(btn_rect, 0.4, 16, color)
|
||||
gui_label(btn_rect, action["label"], 24, rl.WHITE, FontWeight.BOLD, alignment=rl.GuiTextAlignment.TEXT_ALIGN_CENTER)
|
||||
|
||||
def _draw_scrollbar(self, rect: rl.Rectangle):
|
||||
self._scrollbar.render(rect, self._content_height, self._scroll_offset)
|
||||
|
||||
|
||||
class StarPilotSystemLayout(StarPilotPanel):
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
|
||||
self._section_names = ["device", "data_and_backups", "utilities"]
|
||||
self._active_section = self._section_names[0]
|
||||
self._sub_panels = {
|
||||
"device": StarPilotDeviceLayout(),
|
||||
"data_and_backups": StarPilotDataLayout(),
|
||||
"utilities": StarPilotUtilitiesLayout(),
|
||||
}
|
||||
|
||||
self._section_tabs = RadioTileGroup(
|
||||
"",
|
||||
[tr("Device"), tr("Data"), tr("Utilities")],
|
||||
0,
|
||||
self._on_section_change,
|
||||
)
|
||||
|
||||
for name, panel in self._sub_panels.items():
|
||||
if hasattr(panel, 'set_navigate_callback'):
|
||||
panel.set_navigate_callback(lambda sub_panel, section_name=name: self._navigate_to_child(section_name, sub_panel))
|
||||
if hasattr(panel, 'set_back_callback'):
|
||||
panel.set_back_callback(self._go_back)
|
||||
|
||||
def _on_section_change(self, index: int):
|
||||
if 0 <= index < len(self._section_names):
|
||||
previous_panel = self._sub_panels[self._active_section]
|
||||
if hasattr(previous_panel, 'set_current_sub_panel'):
|
||||
previous_panel.set_current_sub_panel("")
|
||||
self._current_sub_panel = ""
|
||||
self._set_active_section(self._section_names[index], "")
|
||||
if self._navigate_callback:
|
||||
self._navigate_callback("")
|
||||
|
||||
def _set_active_section(self, section_name: str, child_panel: str = ""):
|
||||
if section_name not in self._sub_panels:
|
||||
return
|
||||
|
||||
if section_name != self._active_section:
|
||||
self._sub_panels[self._active_section].hide_event()
|
||||
self._active_section = section_name
|
||||
self._sub_panels[self._active_section].show_event()
|
||||
|
||||
self._section_tabs.set_index(self._section_names.index(section_name))
|
||||
panel = self._sub_panels[section_name]
|
||||
if hasattr(panel, 'set_current_sub_panel'):
|
||||
panel.set_current_sub_panel(child_panel)
|
||||
|
||||
def _navigate_to_child(self, section_name: str, child_panel: str):
|
||||
self._set_active_section(section_name, child_panel)
|
||||
if self._navigate_callback:
|
||||
self._navigate_callback(f"{section_name}:{child_panel}")
|
||||
|
||||
def set_current_sub_panel(self, sub_panel: str):
|
||||
super().set_current_sub_panel(sub_panel)
|
||||
if not sub_panel:
|
||||
self._set_active_section(self._active_section, "")
|
||||
return
|
||||
|
||||
if ":" in sub_panel:
|
||||
section_name, child_panel = sub_panel.split(":", 1)
|
||||
self._set_active_section(section_name, child_panel)
|
||||
elif sub_panel in self._section_names:
|
||||
self._set_active_section(sub_panel)
|
||||
|
||||
def _render(self, rect):
|
||||
tab_rect = rl.Rectangle(rect.x, rect.y, rect.width, 110)
|
||||
panel_rect = rl.Rectangle(rect.x, rect.y + 140, rect.width, rect.height - 140)
|
||||
self._section_tabs.render(tab_rect)
|
||||
self._sub_panels[self._active_section].render(panel_rect)
|
||||
self._keyboard = Keyboard(min_text_size=0)
|
||||
self._manager_view = SystemSettingsManagerView(self)
|
||||
|
||||
def show_event(self):
|
||||
super().show_event()
|
||||
self._sub_panels[self._active_section].show_event()
|
||||
self._manager_view.show_event()
|
||||
|
||||
def hide_event(self):
|
||||
super().hide_event()
|
||||
self._sub_panels[self._active_section].hide_event()
|
||||
self._manager_view.hide_event()
|
||||
|
||||
def _render(self, rect: rl.Rectangle):
|
||||
self._manager_view.render(rect)
|
||||
|
||||
def utility_columns(self) -> dict[str, list[dict]]:
|
||||
state = self._get_force_drive_state()
|
||||
no_uploads = self._params.get_bool("NoUploads")
|
||||
disable_onroad = self._params.get_bool("DisableOnroadUploads")
|
||||
|
||||
screen_management = self._params.get_bool("ScreenManagement")
|
||||
screen_rows = [
|
||||
{"id": "ScreenManagement", "type": "toggle", "title": tr("Screen Management"), "value": screen_management},
|
||||
{"id": "StandbyMode", "type": "toggle", "title": tr("Standby Mode"), "value": self._params.get_bool("StandbyMode"), "enabled": screen_management},
|
||||
]
|
||||
if screen_management:
|
||||
screen_rows.extend([
|
||||
{"id": "ScreenBrightness", "type": "slider"},
|
||||
{"id": "ScreenBrightnessOnroad", "type": "slider"},
|
||||
{"id": "ScreenTimeout", "type": "slider"},
|
||||
{"id": "ScreenTimeoutOnroad", "type": "slider"},
|
||||
])
|
||||
|
||||
device_management = self._params.get_bool("DeviceManagement")
|
||||
device_rows = [
|
||||
{"id": "DeviceManagement", "type": "toggle", "title": tr("Device Management"), "value": device_management},
|
||||
{"id": "IncreaseThermalLimits", "type": "toggle", "title": tr("Raise Thermal Limits"), "value": self._params.get_bool("IncreaseThermalLimits"), "enabled": device_management},
|
||||
]
|
||||
if device_management:
|
||||
device_rows.extend([
|
||||
{"id": "DeviceShutdown", "type": "slider"},
|
||||
{"id": "LowVoltageShutdown", "type": "slider"},
|
||||
])
|
||||
|
||||
network_rows = [
|
||||
{"type": "toggle_row", "items": [
|
||||
{"id": "NoUploads", "title": tr("Disable Uploads"), "value": no_uploads},
|
||||
{"id": "UseKonikServer", "title": tr("Use Konik Server"), "value": self._get_konik_state()}
|
||||
]},
|
||||
{"type": "toggle_row", "items": [
|
||||
{"id": "DisableOnroadUploads", "title": tr("Disable Onroad Uploads"), "value": disable_onroad, "enabled": not no_uploads},
|
||||
{"id": "NoLogging", "title": tr("Disable Logging"), "value": self._params.get_bool("NoLogging")}
|
||||
]},
|
||||
{"id": "HigherBitrate", "type": "toggle", "title": tr("High-Quality Recording"), "value": self._params.get_bool("HigherBitrate"), "enabled": not disable_onroad and not no_uploads}
|
||||
]
|
||||
|
||||
data_rows = [
|
||||
{"id": "Storage", "type": "action_group", "title": tr("Storage & Logs"), "actions": [
|
||||
{"id": "Storage", "label": f"{tr('Clear Data')} ({self._get_storage()})", "danger": True},
|
||||
{"id": "ErrorLogs", "label": tr("Clear Logs"), "danger": True}
|
||||
]},
|
||||
{"id": "SystemBackups", "type": "action_group", "title": tr("System Backups"), "actions": [
|
||||
{"id": "CreateBackup", "label": tr("Create")},
|
||||
{"id": "RestoreBackup", "label": tr("Restore")},
|
||||
{"id": "DeleteBackup", "label": tr("Delete"), "danger": True}
|
||||
]},
|
||||
{"id": "ToggleBackups", "type": "action_group", "title": tr("Toggle Backups"), "actions": [
|
||||
{"id": "CreateToggleBackup", "label": tr("Create")},
|
||||
{"id": "RestoreToggleBackup", "label": tr("Restore")},
|
||||
{"id": "DeleteToggleBackup", "label": tr("Delete"), "danger": True}
|
||||
]},
|
||||
]
|
||||
|
||||
util_rows = [
|
||||
{"type": "toggle_row", "items": [{"id": "DebugMode", "type": "toggle", "title": tr("Debug Mode"), "value": self._params.get_bool("DebugMode")}]},
|
||||
{"id": "ForceDriveState", "type": "action_group", "title": tr("Force Drive State"), "actions": [
|
||||
{"id": "DriveDefault", "label": tr("Auto"), "active": state == tr("Default")},
|
||||
{"id": "DriveOnroad", "label": tr("Onroad"), "active": state == tr("Onroad")},
|
||||
{"id": "DriveOffroad", "label": tr("Offroad"), "active": state == tr("Offroad")}
|
||||
]},
|
||||
{"id": "QuickActions", "type": "action_group", "title": tr("Quick Actions"), "actions": [
|
||||
{"id": "FlashPanda", "label": tr("Flash Panda")},
|
||||
{"id": "ReportIssue", "label": tr("Report Issue")}
|
||||
]},
|
||||
{"id": "FactoryReset", "type": "action_group", "title": tr("Factory Reset"), "actions": [
|
||||
{"id": "ResetDefaults", "label": tr("Toggles"), "danger": True},
|
||||
{"id": "ResetStock", "label": tr("Stock OP"), "danger": True}
|
||||
]},
|
||||
]
|
||||
|
||||
return {
|
||||
"left": [
|
||||
{"title": tr("Display Configuration"), "rows": screen_rows},
|
||||
{"title": tr("Developer & Maintenance"), "rows": util_rows},
|
||||
],
|
||||
"right": [
|
||||
{"title": tr("Power & Thermals"), "rows": device_rows},
|
||||
{"title": tr("Networking & Data"), "rows": network_rows},
|
||||
{"title": tr("Data & Backups"), "rows": data_rows},
|
||||
]
|
||||
}
|
||||
|
||||
def handle_action(self, action_id: str):
|
||||
if action_id == "ScreenManagement":
|
||||
self._params.put_bool("ScreenManagement", not self._params.get_bool("ScreenManagement"))
|
||||
elif action_id == "StandbyMode":
|
||||
self._params.put_bool("StandbyMode", not self._params.get_bool("StandbyMode"))
|
||||
elif action_id == "DeviceManagement":
|
||||
self._params.put_bool("DeviceManagement", not self._params.get_bool("DeviceManagement"))
|
||||
elif action_id == "IncreaseThermalLimits":
|
||||
self._params.put_bool("IncreaseThermalLimits", not self._params.get_bool("IncreaseThermalLimits"))
|
||||
elif action_id == "UseKonikServer":
|
||||
self._on_konik_toggle(not self._get_konik_state())
|
||||
elif action_id == "NoLogging":
|
||||
self._params.put_bool("NoLogging", not self._params.get_bool("NoLogging"))
|
||||
elif action_id == "NoUploads":
|
||||
self._params.put_bool("NoUploads", not self._params.get_bool("NoUploads"))
|
||||
elif action_id == "DisableOnroadUploads":
|
||||
self._params.put_bool("DisableOnroadUploads", not self._params.get_bool("DisableOnroadUploads"))
|
||||
elif action_id == "HigherBitrate":
|
||||
self._on_higher_bitrate_toggle(not self._params.get_bool("HigherBitrate"))
|
||||
elif action_id == "Storage":
|
||||
self._on_delete_driving_data()
|
||||
elif action_id == "ErrorLogs":
|
||||
self._on_delete_error_logs()
|
||||
elif action_id == "CreateBackup":
|
||||
self._on_create_backup()
|
||||
elif action_id == "RestoreBackup":
|
||||
self._on_restore_backup()
|
||||
elif action_id == "DeleteBackup":
|
||||
self._on_delete_backup()
|
||||
elif action_id == "CreateToggleBackup":
|
||||
self._on_create_toggle_backup()
|
||||
elif action_id == "RestoreToggleBackup":
|
||||
self._on_restore_toggle_backup()
|
||||
elif action_id == "DeleteToggleBackup":
|
||||
self._on_delete_toggle_backup()
|
||||
elif action_id == "DebugMode":
|
||||
self._params.put_bool("DebugMode", not self._params.get_bool("DebugMode"))
|
||||
elif action_id == "DriveDefault":
|
||||
self._params.put_bool("ForceOffroad", False)
|
||||
self._params.put_bool("ForceOnroad", False)
|
||||
elif action_id == "DriveOnroad":
|
||||
self._params.put_bool("ForceOnroad", True)
|
||||
self._params.put_bool("ForceOffroad", False)
|
||||
elif action_id == "DriveOffroad":
|
||||
self._params.put_bool("ForceOffroad", True)
|
||||
self._params.put_bool("ForceOnroad", False)
|
||||
elif action_id == "FlashPanda":
|
||||
self._on_flash_panda()
|
||||
elif action_id == "ReportIssue":
|
||||
self._on_report_issue()
|
||||
elif action_id == "ResetDefaults":
|
||||
self._on_reset_defaults()
|
||||
elif action_id == "ResetStock":
|
||||
self._on_reset_stock()
|
||||
|
||||
def _set_brightness(self, key, val):
|
||||
self._params.put_int(key, int(val))
|
||||
if key == "ScreenBrightnessOnroad" or key == "ScreenBrightness":
|
||||
HARDWARE.set_brightness(int(val))
|
||||
|
||||
def _get_konik_state(self):
|
||||
if Path("/data/not_vetted").exists():
|
||||
return True
|
||||
return self._params.get_bool("UseKonikServer")
|
||||
|
||||
def _on_konik_toggle(self, state):
|
||||
self._params.put_bool("UseKonikServer", state)
|
||||
cache_path = Path("/cache/use_konik")
|
||||
if state:
|
||||
cache_path.parent.mkdir(parents=True, exist_ok=True)
|
||||
cache_path.touch()
|
||||
else:
|
||||
if cache_path.exists():
|
||||
cache_path.unlink()
|
||||
if ui_state.started:
|
||||
gui_app.push_widget(
|
||||
ConfirmDialog(
|
||||
tr("Reboot required. Reboot now?"), tr("Reboot"), tr("Cancel"), on_close=lambda res: HARDWARE.reboot() if res == DialogResult.CONFIRM else None
|
||||
)
|
||||
)
|
||||
|
||||
def _on_higher_bitrate_toggle(self, state):
|
||||
self._params.put_bool("HigherBitrate", state)
|
||||
cache_path = Path("/cache/use_HD")
|
||||
if state:
|
||||
cache_path.parent.mkdir(parents=True, exist_ok=True)
|
||||
cache_path.touch()
|
||||
else:
|
||||
if cache_path.exists():
|
||||
cache_path.unlink()
|
||||
if ui_state.started:
|
||||
gui_app.push_widget(
|
||||
ConfirmDialog(
|
||||
tr("Reboot required. Reboot now?"), tr("Reboot"), tr("Cancel"), on_close=lambda res: HARDWARE.reboot() if res == DialogResult.CONFIRM else None
|
||||
)
|
||||
)
|
||||
|
||||
def _get_storage(self):
|
||||
paths = ["/data/media/0/osm/offline", "/data/media/0/realdata", "/data/backups"]
|
||||
total = 0
|
||||
for p in paths:
|
||||
pp = Path(p)
|
||||
if pp.exists():
|
||||
total += sum(f.stat().st_size for f in pp.rglob('*') if f.is_file())
|
||||
mb = total / (1024 * 1024)
|
||||
if mb > 1024:
|
||||
return f"{(mb / 1024):.2f} GB"
|
||||
return f"{mb:.2f} MB"
|
||||
|
||||
def _on_delete_driving_data(self):
|
||||
def _do_delete(res):
|
||||
if res == DialogResult.CONFIRM:
|
||||
def _task():
|
||||
drive_paths = ["/data/media/0/realdata/", "/data/media/0/realdata_HD/", "/data/media/0/realdata_konik/"]
|
||||
for path in drive_paths:
|
||||
p = Path(path)
|
||||
if p.exists():
|
||||
for entry in p.iterdir():
|
||||
if entry.is_dir():
|
||||
shutil.rmtree(entry, ignore_errors=True)
|
||||
threading.Thread(target=_task, daemon=True).start()
|
||||
gui_app.push_widget(alert_dialog(tr("Driving data deletion started.")))
|
||||
gui_app.push_widget(ConfirmDialog(tr("Delete all driving data and footage?"), tr("Delete"), on_close=_do_delete))
|
||||
|
||||
def _on_delete_error_logs(self):
|
||||
def _do_delete(res):
|
||||
if res == DialogResult.CONFIRM:
|
||||
shutil.rmtree("/data/error_logs", ignore_errors=True)
|
||||
os.makedirs("/data/error_logs", exist_ok=True)
|
||||
gui_app.push_widget(alert_dialog(tr("Error logs deleted.")))
|
||||
gui_app.push_widget(ConfirmDialog(tr("Delete all error logs?"), tr("Delete"), on_close=_do_delete))
|
||||
|
||||
def _get_backups(self, folder="backups"):
|
||||
b_dir = Path(f"/data/{folder}")
|
||||
if not b_dir.exists():
|
||||
return []
|
||||
if folder == "backups":
|
||||
return [f.name for f in b_dir.glob("*.tar.zst") if "in_progress" not in f.name]
|
||||
return [d.name for d in b_dir.iterdir() if d.is_dir() and "in_progress" not in d.name]
|
||||
|
||||
def _on_create_backup(self):
|
||||
def on_name(res, name):
|
||||
if res == DialogResult.CONFIRM:
|
||||
safe_name = name.replace(" ", "_") if name else f"backup_{datetime.now().strftime('%Y%m%d_%H%M%S')}"
|
||||
backup_path = f"/data/backups/{safe_name}.tar.zst"
|
||||
if Path(backup_path).exists():
|
||||
gui_app.push_widget(alert_dialog(tr("A backup with this name already exists.")))
|
||||
return
|
||||
gui_app.push_widget(alert_dialog(tr("Backup creation started.")))
|
||||
def _task():
|
||||
os.makedirs("/data/backups", exist_ok=True)
|
||||
subprocess.run(["tar", "--use-compress-program=zstd", "-cf", backup_path, "/data/openpilot"])
|
||||
threading.Thread(target=_task, daemon=True).start()
|
||||
self._keyboard.reset(min_text_size=0)
|
||||
self._keyboard.set_title(tr("Name your backup"), "")
|
||||
self._keyboard.set_text("")
|
||||
self._keyboard.set_callback(lambda result: on_name(result, self._keyboard.text))
|
||||
gui_app.push_widget(self._keyboard)
|
||||
|
||||
def _on_restore_backup(self):
|
||||
backups = self._get_backups("backups")
|
||||
if not backups:
|
||||
gui_app.push_widget(alert_dialog(tr("No backups found.")))
|
||||
return
|
||||
dialog = MultiOptionDialog(tr("Select Backup"), backups)
|
||||
def _on_select(res):
|
||||
if res == DialogResult.CONFIRM and dialog.selection:
|
||||
gui_app.push_widget(alert_dialog(tr("Restoring... device will reboot.")))
|
||||
def _task():
|
||||
subprocess.run(["rm", "-rf", "/data/openpilot/*"])
|
||||
subprocess.run(["tar", "--use-compress-program=zstd", "-xf", f"/data/backups/{dialog.selection}", "-C", "/"])
|
||||
os.system("reboot")
|
||||
threading.Thread(target=_task, daemon=True).start()
|
||||
gui_app.push_widget(dialog, callback=_on_select)
|
||||
|
||||
def _on_delete_backup(self):
|
||||
backups = self._get_backups("backups")
|
||||
if not backups:
|
||||
gui_app.push_widget(alert_dialog(tr("No backups found.")))
|
||||
return
|
||||
dialog = MultiOptionDialog(tr("Delete Backup"), backups)
|
||||
def _on_select(res):
|
||||
if res == DialogResult.CONFIRM and dialog.selection:
|
||||
os.remove(f"/data/backups/{dialog.selection}")
|
||||
gui_app.push_widget(dialog, callback=_on_select)
|
||||
|
||||
def _on_create_toggle_backup(self):
|
||||
def on_name(res, name):
|
||||
if res == DialogResult.CONFIRM:
|
||||
safe_name = name.replace(" ", "_") if name else f"toggle_backup_{datetime.now().strftime('%Y%m%d_%H%M%S')}"
|
||||
backup_path = Path(f"/data/toggle_backups/{safe_name}")
|
||||
if backup_path.exists():
|
||||
gui_app.push_widget(alert_dialog(tr("A toggle backup with this name already exists.")))
|
||||
return
|
||||
os.makedirs(backup_path, exist_ok=True)
|
||||
shutil.copytree("/data/params/d", str(backup_path), dirs_exist_ok=True)
|
||||
gui_app.push_widget(alert_dialog(tr("Toggle backup created.")))
|
||||
self._keyboard.reset(min_text_size=0)
|
||||
self._keyboard.set_title(tr("Name your toggle backup"), "")
|
||||
self._keyboard.set_text("")
|
||||
self._keyboard.set_callback(lambda result: on_name(result, self._keyboard.text))
|
||||
gui_app.push_widget(self._keyboard)
|
||||
|
||||
def _on_restore_toggle_backup(self):
|
||||
backups = self._get_backups("toggle_backups")
|
||||
if not backups:
|
||||
gui_app.push_widget(alert_dialog(tr("No toggle backups found.")))
|
||||
return
|
||||
dialog = MultiOptionDialog(tr("Select Toggle Backup"), backups)
|
||||
def _on_select(res):
|
||||
if res == DialogResult.CONFIRM and dialog.selection:
|
||||
def on_confirm(r2):
|
||||
if r2 == DialogResult.CONFIRM:
|
||||
src = Path(f"/data/toggle_backups/{dialog.selection}")
|
||||
params_dir = Path("/data/params/d")
|
||||
for old_key, new_key in LEGACY_STARPILOT_PARAM_RENAMES.items():
|
||||
if (src / old_key).exists():
|
||||
(params_dir / new_key).unlink(missing_ok=True)
|
||||
shutil.copytree(str(src), "/data/params/d", dirs_exist_ok=True)
|
||||
for old_key, new_key in LEGACY_STARPILOT_PARAM_RENAMES.items():
|
||||
old_path = params_dir / old_key
|
||||
new_path = params_dir / new_key
|
||||
if old_path.exists():
|
||||
old_path.replace(new_path)
|
||||
gui_app.push_widget(alert_dialog(tr("Toggles restored.")))
|
||||
gui_app.push_widget(ConfirmDialog(tr("This will overwrite your current toggles."), tr("Restore"), on_close=on_confirm))
|
||||
gui_app.push_widget(dialog, callback=_on_select)
|
||||
|
||||
def _on_delete_toggle_backup(self):
|
||||
backups = self._get_backups("toggle_backups")
|
||||
if not backups:
|
||||
gui_app.push_widget(alert_dialog(tr("No toggle backups found.")))
|
||||
return
|
||||
dialog = MultiOptionDialog(tr("Delete Toggle Backup"), backups)
|
||||
def _on_select(res):
|
||||
if res == DialogResult.CONFIRM and dialog.selection:
|
||||
shutil.rmtree(f"/data/toggle_backups/{dialog.selection}", ignore_errors=True)
|
||||
gui_app.push_widget(dialog, callback=_on_select)
|
||||
|
||||
def _get_force_drive_state(self):
|
||||
if self._params.get_bool("ForceOnroad"):
|
||||
return tr("Onroad")
|
||||
if self._params.get_bool("ForceOffroad"):
|
||||
return tr("Offroad")
|
||||
return tr("Default")
|
||||
|
||||
def _on_flash_panda(self):
|
||||
def _do_flash(res):
|
||||
if res == DialogResult.CONFIRM:
|
||||
self._params_memory.put_bool("FlashPanda", True)
|
||||
gui_app.push_widget(alert_dialog(tr("Panda flashing started. Device will reboot when finished.")))
|
||||
gui_app.push_widget(ConfirmDialog(tr("Flash Panda firmware?"), tr("Flash"), callback=_do_flash))
|
||||
|
||||
def _on_report_issue(self):
|
||||
def on_category(res):
|
||||
if res != DialogResult.CONFIRM or not dialog.selection:
|
||||
return
|
||||
discord_user = self._params.get("DiscordUsername", encoding='utf-8') or ""
|
||||
def on_discord(res2, username):
|
||||
if res2 == DialogResult.CONFIRM and username:
|
||||
self._params.put("DiscordUsername", username)
|
||||
report = json.dumps({"DiscordUser": username, "Issue": dialog.selection})
|
||||
self._params_memory.put("IssueReported", report)
|
||||
gui_app.push_widget(alert_dialog(tr("Issue reported. Thank you!")))
|
||||
self._keyboard.reset(min_text_size=1)
|
||||
self._keyboard.set_title(tr("Discord Username"), "")
|
||||
self._keyboard.set_text(discord_user or "")
|
||||
self._keyboard.set_callback(lambda result: on_discord(result, self._keyboard.text))
|
||||
gui_app.push_widget(self._keyboard)
|
||||
dialog = MultiOptionDialog(tr("Select Issue"), REPORT_CATEGORIES, callback=on_category)
|
||||
gui_app.push_widget(dialog)
|
||||
|
||||
def _on_reset_defaults(self):
|
||||
def _do_reset(res):
|
||||
if res == DialogResult.CONFIRM:
|
||||
all_keys = self._params.all_keys()
|
||||
for k in all_keys:
|
||||
if k in EXCLUDED_KEYS:
|
||||
continue
|
||||
default = self._params.get_default_value(k)
|
||||
if default is not None:
|
||||
self._params.put(k, default)
|
||||
gui_app.push_widget(alert_dialog(tr("Toggles reset to defaults.")))
|
||||
gui_app.push_widget(ConfirmDialog(tr("Reset all toggles to defaults?"), tr("Reset"), callback=_do_reset))
|
||||
|
||||
def _on_reset_stock(self):
|
||||
def _do_reset(res):
|
||||
if res == DialogResult.CONFIRM:
|
||||
all_keys = self._params.all_keys()
|
||||
for k in all_keys:
|
||||
if k in EXCLUDED_KEYS:
|
||||
continue
|
||||
stock = self._params.get_stock_value(k)
|
||||
if stock is not None:
|
||||
self._params.put(k, stock)
|
||||
gui_app.push_widget(alert_dialog(tr("Toggles reset to stock openpilot.")))
|
||||
gui_app.push_widget(ConfirmDialog(tr("Reset all toggles to stock openpilot?"), tr("Reset"), callback=_do_reset))
|
||||
|
||||
@@ -175,7 +175,7 @@ class StarPilotThemesLayout(StarPilotPanel):
|
||||
self._params.remove("StartupMessageTop")
|
||||
self._params.remove("StartupMessageBottom")
|
||||
|
||||
gui_app.set_modal_overlay(dialog, callback=on_select)
|
||||
gui_app.push_widget(dialog, callback=on_select)
|
||||
|
||||
|
||||
class StarPilotPersonalizeLayout(StarPilotPanel):
|
||||
@@ -329,4 +329,4 @@ class StarPilotPersonalizeLayout(StarPilotPanel):
|
||||
self._params.put(key, selected_slug)
|
||||
self._rebuild_grid()
|
||||
|
||||
gui_app.set_modal_overlay(dialog, callback=on_select)
|
||||
gui_app.push_widget(dialog, callback=on_select)
|
||||
|
||||
@@ -1,150 +0,0 @@
|
||||
from __future__ import annotations
|
||||
import json
|
||||
from openpilot.system.hardware import HARDWARE
|
||||
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.confirm_dialog import ConfirmDialog, alert_dialog
|
||||
from openpilot.system.ui.widgets.keyboard import Keyboard
|
||||
from openpilot.system.ui.widgets.option_dialog import MultiOptionDialog
|
||||
from openpilot.selfdrive.ui.layouts.settings.starpilot.panel import StarPilotPanel
|
||||
from openpilot.selfdrive.ui.layouts.settings.starpilot.aethergrid import TileGrid
|
||||
|
||||
EXCLUDED_KEYS = {
|
||||
"AvailableModels",
|
||||
"AvailableModelNames",
|
||||
"StarPilotStats",
|
||||
"GithubSshKeys",
|
||||
"GithubUsername",
|
||||
"MapBoxRequests",
|
||||
"ModelDrivesAndScores",
|
||||
"OverpassRequests",
|
||||
"SpeedLimits",
|
||||
"SpeedLimitsFiltered",
|
||||
"UpdaterAvailableBranches",
|
||||
}
|
||||
|
||||
REPORT_CATEGORIES = [
|
||||
"Acceleration feels harsh or jerky",
|
||||
"An alert was unclear and I'm not sure what it meant",
|
||||
"Braking is too sudden or uncomfortable",
|
||||
"I'm not sure if this is normal or a bug:",
|
||||
"My steering wheel buttons aren't working",
|
||||
"openpilot disengages when I don't expect it",
|
||||
"openpilot feels sluggish or slow to respond",
|
||||
"Something else (please describe)",
|
||||
]
|
||||
|
||||
|
||||
class StarPilotUtilitiesLayout(StarPilotPanel):
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
self._keyboard = Keyboard(min_text_size=1)
|
||||
self._tile_grid = TileGrid(columns=2, padding=20, uniform_width=True)
|
||||
self.CATEGORIES = [
|
||||
{
|
||||
"title": tr_noop("Debug Mode"),
|
||||
"type": "toggle",
|
||||
"get_state": lambda: self._params.get_bool("DebugMode"),
|
||||
"set_state": lambda s: self._params.put_bool("DebugMode", s),
|
||||
"color": "#D43D8A",
|
||||
},
|
||||
{"title": tr_noop("Flash Panda"), "type": "hub", "on_click": self._on_flash_panda, "color": "#D43D8A"},
|
||||
{
|
||||
"title": tr_noop("Force Drive State"),
|
||||
"type": "value",
|
||||
"get_value": self._get_force_drive_state,
|
||||
"on_click": self._on_force_drive_state,
|
||||
"color": "#D43D8A",
|
||||
},
|
||||
{"title": tr_noop("Report Issue"), "type": "hub", "on_click": self._on_report_issue, "color": "#D43D8A"},
|
||||
{"title": tr_noop("Reset to Defaults"), "type": "hub", "on_click": self._on_reset_defaults, "color": "#D43D8A"},
|
||||
{"title": tr_noop("Reset to Stock"), "type": "hub", "on_click": self._on_reset_stock, "color": "#D43D8A"},
|
||||
]
|
||||
self._rebuild_grid()
|
||||
|
||||
def _get_force_drive_state(self):
|
||||
if self._params.get_bool("ForceOnroad"):
|
||||
return tr("Onroad")
|
||||
if self._params.get_bool("ForceOffroad"):
|
||||
return tr("Offroad")
|
||||
return tr("Default")
|
||||
|
||||
def _on_flash_panda(self):
|
||||
def _do_flash(res):
|
||||
if res == DialogResult.CONFIRM:
|
||||
self._params_memory.put_bool("FlashPanda", True)
|
||||
gui_app.push_widget(alert_dialog(tr("Panda flashing started. Device will reboot when finished.")))
|
||||
|
||||
gui_app.push_widget(ConfirmDialog(tr("Flash Panda firmware?"), tr("Flash"), callback=_do_flash))
|
||||
|
||||
def _on_force_drive_state(self):
|
||||
options = [tr("Offroad"), tr("Onroad"), tr("Default")]
|
||||
current = self._get_force_drive_state()
|
||||
def on_select(res):
|
||||
if res == DialogResult.CONFIRM and dialog.selection:
|
||||
if dialog.selection == tr("Offroad"):
|
||||
self._params.put_bool("ForceOffroad", True)
|
||||
self._params.put_bool("ForceOnroad", False)
|
||||
elif dialog.selection == tr("Onroad"):
|
||||
self._params.put_bool("ForceOnroad", True)
|
||||
self._params.put_bool("ForceOffroad", False)
|
||||
else:
|
||||
self._params.put_bool("ForceOffroad", False)
|
||||
self._params.put_bool("ForceOnroad", False)
|
||||
self._rebuild_grid()
|
||||
|
||||
dialog = MultiOptionDialog(tr("Force Drive State"), options, current, callback=on_select)
|
||||
gui_app.push_widget(dialog)
|
||||
|
||||
def _on_report_issue(self):
|
||||
def on_category(res):
|
||||
if res != DialogResult.CONFIRM or not dialog.selection:
|
||||
return
|
||||
discord_user = self._params.get("DiscordUsername", encoding='utf-8') or ""
|
||||
|
||||
def on_discord(res2, username):
|
||||
if res2 == DialogResult.CONFIRM and username:
|
||||
self._params.put("DiscordUsername", username)
|
||||
report = json.dumps({"DiscordUser": username, "Issue": dialog.selection})
|
||||
self._params_memory.put("IssueReported", report)
|
||||
gui_app.push_widget(alert_dialog(tr("Issue reported. Thank you!")))
|
||||
|
||||
self._keyboard.reset(min_text_size=1)
|
||||
self._keyboard.set_title(tr("Discord Username"), "")
|
||||
self._keyboard.set_text(discord_user or "")
|
||||
self._keyboard.set_callback(lambda result: on_discord(result, self._keyboard.text))
|
||||
gui_app.push_widget(self._keyboard)
|
||||
|
||||
dialog = MultiOptionDialog(tr("Select Issue"), REPORT_CATEGORIES, callback=on_category)
|
||||
gui_app.push_widget(dialog)
|
||||
|
||||
def _on_reset_defaults(self):
|
||||
def _do_reset(res):
|
||||
if res == DialogResult.CONFIRM:
|
||||
all_keys = self._params.all_keys()
|
||||
for k in all_keys:
|
||||
if k in EXCLUDED_KEYS:
|
||||
continue
|
||||
default = self._params.get_default_value(k)
|
||||
if default is not None:
|
||||
self._params.put(k, default)
|
||||
gui_app.push_widget(alert_dialog(tr("Toggles reset to defaults.")))
|
||||
self._rebuild_grid()
|
||||
|
||||
gui_app.push_widget(ConfirmDialog(tr("Reset all toggles to defaults?"), tr("Reset"), callback=_do_reset))
|
||||
|
||||
def _on_reset_stock(self):
|
||||
def _do_reset(res):
|
||||
if res == DialogResult.CONFIRM:
|
||||
all_keys = self._params.all_keys()
|
||||
for k in all_keys:
|
||||
if k in EXCLUDED_KEYS:
|
||||
continue
|
||||
stock = self._params.get_stock_value(k)
|
||||
if stock is not None:
|
||||
self._params.put(k, stock)
|
||||
gui_app.push_widget(alert_dialog(tr("Toggles reset to stock openpilot.")))
|
||||
self._rebuild_grid()
|
||||
|
||||
gui_app.push_widget(ConfirmDialog(tr("Reset all toggles to stock openpilot?"), tr("Reset"), callback=_do_reset))
|
||||
@@ -167,7 +167,7 @@ class StarPilotVehicleSettingsLayout(StarPilotPanel):
|
||||
def _on_select_make(self):
|
||||
makes = list(self._make_options)
|
||||
if not makes:
|
||||
gui_app.set_modal_overlay(ConfirmDialog(tr("No fingerprint list available."), tr("OK"), on_close=lambda r: None))
|
||||
gui_app.push_widget(ConfirmDialog(tr("No fingerprint list available."), tr("OK"), on_close=lambda r: None))
|
||||
return
|
||||
|
||||
current_make = self._params.get("CarMake", encoding='utf-8') or ""
|
||||
@@ -185,17 +185,17 @@ class StarPilotVehicleSettingsLayout(StarPilotPanel):
|
||||
self._params.remove("CarModelName")
|
||||
self._rebuild_grid()
|
||||
|
||||
gui_app.set_modal_overlay(dialog, callback=on_select)
|
||||
gui_app.push_widget(dialog, callback=on_select)
|
||||
|
||||
def _on_select_model(self):
|
||||
make = self._params.get("CarMake", encoding='utf-8') or ""
|
||||
if not make:
|
||||
gui_app.set_modal_overlay(ConfirmDialog(tr("Please select a Car Make first!"), tr("OK"), on_close=lambda r: None))
|
||||
gui_app.push_widget(ConfirmDialog(tr("Please select a Car Make first!"), tr("OK"), on_close=lambda r: None))
|
||||
return
|
||||
|
||||
model_options = self._models_by_make.get(make, ())
|
||||
if not model_options:
|
||||
gui_app.set_modal_overlay(ConfirmDialog(tr("No models available for this make."), tr("OK"), on_close=lambda r: None))
|
||||
gui_app.push_widget(ConfirmDialog(tr("No models available for this make."), tr("OK"), on_close=lambda r: None))
|
||||
return
|
||||
|
||||
option_labels = [option.option_label for option in model_options]
|
||||
@@ -216,7 +216,7 @@ class StarPilotVehicleSettingsLayout(StarPilotPanel):
|
||||
self._params.put("CarMake", make)
|
||||
self._rebuild_grid()
|
||||
|
||||
gui_app.set_modal_overlay(dialog, callback=on_select)
|
||||
gui_app.push_widget(dialog, callback=on_select)
|
||||
|
||||
def _on_disable_long(self, state):
|
||||
if state:
|
||||
@@ -230,7 +230,7 @@ class StarPilotVehicleSettingsLayout(StarPilotPanel):
|
||||
HARDWARE.reboot()
|
||||
self._rebuild_grid()
|
||||
|
||||
gui_app.set_modal_overlay(ConfirmDialog(tr("Disable openpilot longitudinal control?"), tr("Disable"), on_close=on_confirm))
|
||||
gui_app.push_widget(ConfirmDialog(tr("Disable openpilot longitudinal control?"), tr("Disable"), on_close=on_confirm))
|
||||
else:
|
||||
self._params.put_bool("DisableOpenpilotLongitudinal", False)
|
||||
self._rebuild_grid()
|
||||
@@ -447,7 +447,7 @@ class StarPilotToyotaVehicleLayout(StarPilotPanel):
|
||||
self._params.put_int("LockDoorsTimer", int(val))
|
||||
self._rebuild_grid()
|
||||
|
||||
gui_app.set_modal_overlay(
|
||||
gui_app.push_widget(
|
||||
AetherSliderDialog(tr("Lock Doors Timer"), 0, 300, 5, self._params.get_int("LockDoorsTimer"), on_close, labels=_lock_doors_timer_labels(), color="#64748B")
|
||||
)
|
||||
|
||||
@@ -457,7 +457,7 @@ class StarPilotToyotaVehicleLayout(StarPilotPanel):
|
||||
self._params.put_float("ClusterOffset", float(val))
|
||||
self._rebuild_grid()
|
||||
|
||||
gui_app.set_modal_overlay(
|
||||
gui_app.push_widget(
|
||||
AetherSliderDialog(tr("Dashboard Speed Offset"), 1.000, 1.050, 0.001, self._params.get_float("ClusterOffset"), on_close, unit="x", color="#64748B")
|
||||
)
|
||||
|
||||
|
||||
@@ -439,7 +439,7 @@ class StarPilotModelUILayout(StarPilotPanel):
|
||||
self._params.put_int(key, int(val))
|
||||
self._rebuild_grid()
|
||||
|
||||
gui_app.set_modal_overlay(AetherSliderDialog(tr(key), min_v, max_v, 1, self._params.get_int(key), on_close, unit=unit, color="#8B5CF6"))
|
||||
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)
|
||||
@@ -454,7 +454,7 @@ class StarPilotModelUILayout(StarPilotPanel):
|
||||
self._params.put_float(key, v)
|
||||
self._rebuild_grid()
|
||||
|
||||
gui_app.set_modal_overlay(AetherSliderDialog(tr(key), min_v, max_v, step, current, on_close, unit=unit, color="#8B5CF6"))
|
||||
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 ""
|
||||
@@ -476,7 +476,7 @@ class StarPilotModelUILayout(StarPilotPanel):
|
||||
self._params.put(key, dialog.selection)
|
||||
self._rebuild_grid()
|
||||
|
||||
gui_app.set_modal_overlay(dialog, callback=on_select)
|
||||
gui_app.push_widget(dialog, callback=on_select)
|
||||
|
||||
|
||||
class StarPilotNavigationVisualsLayout(StarPilotPanel):
|
||||
@@ -577,4 +577,4 @@ class StarPilotVisualQOLLayout(StarPilotPanel):
|
||||
self._params.put_int("CameraView", idx)
|
||||
self._rebuild_grid()
|
||||
|
||||
gui_app.set_modal_overlay(dialog, callback=on_select)
|
||||
gui_app.push_widget(dialog, callback=on_select)
|
||||
|
||||
@@ -115,7 +115,7 @@ class StarPilotWheelLayout(StarPilotPanel):
|
||||
self._params_memory.put_bool("StarPilotTogglesUpdated", True)
|
||||
self._rebuild_grid()
|
||||
|
||||
gui_app.set_modal_overlay(dialog, callback=on_select)
|
||||
gui_app.push_widget(dialog, callback=on_select)
|
||||
|
||||
def _rebuild_grid(self):
|
||||
if not self.CATEGORIES:
|
||||
|
||||
Reference in New Issue
Block a user