Files
onepilot/selfdrive/ui/layouts/settings/settings.py
T
firestar5683 d0e1db6766 StarPilot
2026-03-22 03:15:05 -05:00

205 lines
7.5 KiB
Python

import pyray as rl
from dataclasses import dataclass
from enum import IntEnum
from collections.abc import Callable
from openpilot.selfdrive.ui.layouts.settings.developer import DeveloperLayout
from openpilot.selfdrive.ui.layouts.settings.device import DeviceLayout
from openpilot.selfdrive.ui.layouts.settings.firehose import FirehoseLayout
from openpilot.selfdrive.ui.layouts.settings.starpilot.main_panel import StarPilotLayout
from openpilot.selfdrive.ui.layouts.settings.software import SoftwareLayout
from openpilot.selfdrive.ui.layouts.settings.toggles import TogglesLayout
from openpilot.system.ui.lib.application import gui_app, FontWeight, MousePos
from openpilot.system.ui.lib.multilang import tr, tr_noop
from openpilot.system.ui.lib.text_measure import measure_text_cached
from openpilot.system.ui.lib.wifi_manager import WifiManager
from openpilot.system.ui.widgets import Widget
from openpilot.system.ui.widgets.network import NetworkUI
from openpilot.common.params import Params
# Constants
SIDEBAR_WIDTH = 500
CLOSE_BTN_SIZE = 200
CLOSE_ICON_SIZE = 70
NAV_BTN_HEIGHT = 110
PANEL_MARGIN = 50
# Colors
SIDEBAR_COLOR = rl.BLACK
PANEL_COLOR = rl.Color(41, 41, 41, 255)
CLOSE_BTN_COLOR = rl.Color(41, 41, 41, 255)
CLOSE_BTN_PRESSED = rl.Color(59, 59, 59, 255)
TEXT_NORMAL = rl.Color(128, 128, 128, 255)
TEXT_SELECTED = rl.WHITE
class PanelType(IntEnum):
DEVICE = 0
NETWORK = 1
TOGGLES = 2
SOFTWARE = 3
FIREHOSE = 4
DEVELOPER = 5
STARPILOT = 6
@dataclass
class PanelInfo:
name: str
instance: Widget
button_rect: rl.Rectangle = rl.Rectangle(0, 0, 0, 0)
class SettingsLayout(Widget):
def __init__(self):
super().__init__()
self._params = Params()
self._current_panel = PanelType.DEVICE
# Panel depth tracking for hierarchical back navigation
# 0 = top level (settings main), 1+ = sub-panels (FrogPilot categories)
self._panel_depth = 0
# Panel configuration
wifi_manager = WifiManager()
wifi_manager.set_active(False)
self._panels = {
PanelType.DEVICE: PanelInfo(tr_noop("Device"), DeviceLayout()),
PanelType.NETWORK: PanelInfo(tr_noop("Network"), NetworkUI(wifi_manager)),
PanelType.TOGGLES: PanelInfo(tr_noop("Toggles"), TogglesLayout()),
PanelType.SOFTWARE: PanelInfo(tr_noop("Software"), SoftwareLayout()),
PanelType.FIREHOSE: PanelInfo(tr_noop("Firehose"), FirehoseLayout()),
PanelType.DEVELOPER: PanelInfo(tr_noop("Developer"), DeveloperLayout()),
PanelType.STARPILOT: PanelInfo(tr_noop("StarPilot"), StarPilotLayout()),
}
# Connect StarPilot depth callback for hierarchical back navigation
self._panels[PanelType.STARPILOT].instance.set_depth_callback(self.set_panel_depth)
self._panels[PanelType.STARPILOT].instance.set_settings_layout(self)
self._font_medium = gui_app.font(FontWeight.MEDIUM)
self._close_icon = gui_app.texture("icons/backspace.png", CLOSE_ICON_SIZE, CLOSE_ICON_SIZE)
# Callbacks
self._close_callback: Callable | None = None
def set_callbacks(self, on_close: Callable):
self._close_callback = on_close
def set_panel_depth(self, depth: int):
self._panel_depth = depth
def refresh_developer_visibility(self):
pass
def get_panel_depth(self) -> int:
return self._panel_depth
def _handle_back_navigation(self):
if self._panel_depth > 0:
self._panel_depth -= 1
if hasattr(self._panels[PanelType.STARPILOT].instance, 'navigate_back'):
self._panels[PanelType.STARPILOT].instance.navigate_back()
else:
if self._close_callback:
self._close_callback()
def _render(self, rect: rl.Rectangle):
# Calculate layout
sidebar_rect = rl.Rectangle(rect.x, rect.y, SIDEBAR_WIDTH, rect.height)
panel_rect = rl.Rectangle(rect.x + SIDEBAR_WIDTH, rect.y, rect.width - SIDEBAR_WIDTH, rect.height)
# Draw components
self._draw_sidebar(sidebar_rect)
self._draw_current_panel(panel_rect)
def _draw_sidebar(self, rect: rl.Rectangle):
rl.draw_rectangle_rec(rect, SIDEBAR_COLOR)
# Back/Close button - hierarchical navigation
back_btn_rect = rl.Rectangle(rect.x + (rect.width - CLOSE_BTN_SIZE) / 2, rect.y + 60, CLOSE_BTN_SIZE, CLOSE_BTN_SIZE)
pressed = rl.is_mouse_button_down(rl.MouseButton.MOUSE_BUTTON_LEFT) and rl.check_collision_point_rec(rl.get_mouse_position(), back_btn_rect)
close_color = CLOSE_BTN_PRESSED if pressed else CLOSE_BTN_COLOR
rl.draw_rectangle_rounded(back_btn_rect, 1.0, 20, close_color)
icon_color = rl.Color(255, 255, 255, 255) if not pressed else rl.Color(220, 220, 220, 255)
icon_dest = rl.Rectangle(
back_btn_rect.x + (back_btn_rect.width - self._close_icon.width) / 2,
back_btn_rect.y + (back_btn_rect.height - self._close_icon.height) / 2,
self._close_icon.width,
self._close_icon.height,
)
rl.draw_texture_pro(
self._close_icon,
rl.Rectangle(0, 0, self._close_icon.width, self._close_icon.height),
icon_dest,
rl.Vector2(0, 0),
0,
icon_color,
)
# Store back button rect for click detection
self._back_btn_rect = back_btn_rect
# Get tuning level to control Developer panel visibility
tuning_level_str = self._params.get("TuningLevel", return_default=True, default="1")
tuning_level = int(tuning_level_str) if tuning_level_str else 1
# Navigation buttons
y = rect.y + 300
for panel_type, panel_info in self._panels.items():
# Hide Developer panel unless tuning level >= 3
if panel_type == PanelType.DEVELOPER and tuning_level < 3:
continue
button_rect = rl.Rectangle(rect.x + 50, y, rect.width - 150, NAV_BTN_HEIGHT)
# Button styling
is_selected = panel_type == self._current_panel
text_color = TEXT_SELECTED if is_selected else TEXT_NORMAL
# Draw button text (right-aligned)
panel_name = tr(panel_info.name)
text_size = measure_text_cached(self._font_medium, panel_name, 65)
text_pos = rl.Vector2(button_rect.x + button_rect.width - text_size.x, button_rect.y + (button_rect.height - text_size.y) / 2)
rl.draw_text_ex(self._font_medium, panel_name, text_pos, 65, 0, text_color)
# Store button rect for click detection
panel_info.button_rect = button_rect
y += NAV_BTN_HEIGHT
def _draw_current_panel(self, rect: rl.Rectangle):
rl.draw_rectangle_rounded(rl.Rectangle(rect.x + 10, rect.y + 10, rect.width - 20, rect.height - 20), 0.04, 30, PANEL_COLOR)
content_rect = rl.Rectangle(rect.x + PANEL_MARGIN, rect.y + 25, rect.width - (PANEL_MARGIN * 2), rect.height - 50)
# rl.draw_rectangle_rounded(content_rect, 0.03, 30, PANEL_COLOR)
panel = self._panels[self._current_panel]
if panel.instance:
panel.instance.render(content_rect)
def _handle_mouse_release(self, mouse_pos: MousePos) -> None:
# Check back/close button - hierarchical navigation
if rl.check_collision_point_rec(mouse_pos, self._back_btn_rect):
self._handle_back_navigation()
return
# Check navigation buttons
for panel_type, panel_info in self._panels.items():
if rl.check_collision_point_rec(mouse_pos, panel_info.button_rect):
self.set_current_panel(panel_type)
return
def set_current_panel(self, panel_type: PanelType):
if panel_type != self._current_panel:
self._panels[self._current_panel].instance.hide_event()
self._current_panel = panel_type
self._panels[self._current_panel].instance.show_event()
def show_event(self):
super().show_event()
self._panels[self._current_panel].instance.show_event()
def hide_event(self):
super().hide_event()
self._panels[self._current_panel].instance.hide_event()