init sp panels & toggle

This commit is contained in:
nayan
2025-06-30 19:22:15 -04:00
parent cdf990c1b1
commit 8f65d84d2f
12 changed files with 414 additions and 1 deletions
+76
View File
@@ -8,9 +8,18 @@ from openpilot.selfdrive.ui.layouts.settings.firehose import FirehoseLayout
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
from openpilot.system.ui.lib.scroller import Scroller
from openpilot.system.ui.lib.text_measure import measure_text_cached
from openpilot.selfdrive.ui.layouts.network import NetworkLayout
from openpilot.system.ui.lib.widget import Widget
from openpilot.selfdrive.ui.sunnypilot.layouts.settings.models import ModelsLayout
from openpilot.selfdrive.ui.sunnypilot.layouts.settings.sunnylink import SunnylinkLayout
from openpilot.selfdrive.ui.sunnypilot.layouts.settings.osm import OSMLayout
from openpilot.selfdrive.ui.sunnypilot.layouts.settings.trips import TripsLayout
from openpilot.selfdrive.ui.sunnypilot.layouts.settings.vehicle import VehicleLayout
from openpilot.selfdrive.ui.sunnypilot.layouts.settings.steering import SteeringLayout
from openpilot.selfdrive.ui.sunnypilot.layouts.settings.cruise import CruiseLayout
from openpilot.selfdrive.ui.sunnypilot.layouts.settings.visuals import VisualsLayout
# Import individual panels
@@ -37,6 +46,14 @@ class PanelType(IntEnum):
SOFTWARE = 3
FIREHOSE = 4
DEVELOPER = 5
SUNNYLINK = 6
MODELS = 7
STEERING = 8
CRUISE = 9
VISUALS = 10
OSM = 11
TRIPS = 12
VEHICLE = 13
@dataclass
@@ -50,13 +67,26 @@ class SettingsLayout(Widget):
def __init__(self):
super().__init__()
self._current_panel = PanelType.DEVICE
self._nav_items: list[Widget] = []
# Create sidebar scroller
self._sidebar_scroller = Scroller([], spacing=0, line_separator = False, pad_end=False)
# Panel configuration
self._panels = {
PanelType.DEVICE: PanelInfo("Device", DeviceLayout()),
PanelType.NETWORK: PanelInfo("Network", NetworkLayout()),
PanelType.SUNNYLINK: PanelInfo("sunnylink", SunnylinkLayout()),
PanelType.TOGGLES: PanelInfo("Toggles", TogglesLayout()),
PanelType.SOFTWARE: PanelInfo("Software", SoftwareLayout()),
PanelType.MODELS: PanelInfo("Models", ModelsLayout()),
PanelType.STEERING: PanelInfo("Steering", SteeringLayout()),
PanelType.CRUISE: PanelInfo("Cruise", CruiseLayout()),
PanelType.VISUALS: PanelInfo("Visuals", VisualsLayout()),
PanelType.OSM: PanelInfo("OSM", OSMLayout()),
PanelType.TRIPS: PanelInfo("Trips", TripsLayout()),
PanelType.VEHICLE: PanelInfo("Vehicle", VehicleLayout()),
PanelType.FIREHOSE: PanelInfo("Firehose", FirehoseLayout()),
PanelType.DEVELOPER: PanelInfo("Developer", DeveloperLayout()),
}
@@ -79,6 +109,31 @@ class SettingsLayout(Widget):
self._draw_sidebar(sidebar_rect)
self._draw_current_panel(panel_rect)
def _create_nav_button(self, panel_type: PanelType, panel_info: PanelInfo) -> Widget:
class NavButton(Widget):
def __init__(self, parent, p_type, p_info):
super().__init__()
self.parent = parent
self.panel_type = p_type
self.panel_info = p_info
def _render(self, rect):
is_selected = self.panel_type == self.parent._current_panel
text_color = TEXT_SELECTED if is_selected else TEXT_NORMAL
# Draw button text (right-aligned)
text_size = measure_text_cached(self.parent._font_medium, self.panel_info.name, 65)
text_pos = rl.Vector2(
rect.x + rect.width - text_size.x - 20, # 50px padding from right
rect.y + (NAV_BTN_HEIGHT - text_size.y) / 2
)
rl.draw_text_ex(self.parent._font_medium, self.panel_info.name, text_pos, 65, 0, text_color)
# Store button rect for click detection
self.panel_info.button_rect = rect
return NavButton(self, panel_type, panel_info)
def _draw_sidebar(self, rect: rl.Rectangle):
rl.draw_rectangle_rec(rect, SIDEBAR_COLOR)
@@ -102,6 +157,27 @@ class SettingsLayout(Widget):
# Store close button rect for click detection
self._close_btn_rect = close_btn_rect
# Navigation buttons with scroller
if not self._nav_items:
for panel_type, panel_info in self._panels.items():
nav_button = self._create_nav_button(panel_type, panel_info)
nav_button.rect.width = rect.width - 100 # Full width minus padding
nav_button.rect.height = NAV_BTN_HEIGHT
self._nav_items.append(nav_button)
self._sidebar_scroller.add_widget(nav_button)
# Draw navigation section with scroller
nav_rect = rl.Rectangle(
rect.x,
rect.y + 300, # Starting Y position for nav items
rect.width,
rect.height - 300 # Remaining height after close button
)
if self._nav_items:
self._sidebar_scroller.render(nav_rect)
return
# Navigation buttons
y = rect.y + 300
for panel_type, panel_info in self._panels.items():
@@ -0,0 +1,22 @@
from openpilot.system.ui.lib.list_view import ListItem, button_item, toggle_item
from openpilot.system.ui.lib.scroller import Scroller
from openpilot.system.ui.lib.widget import Widget
from openpilot.common.params import Params
class CruiseLayout(Widget):
def __init__(self):
super().__init__()
self._params = Params()
items = self._init_items()
self._scroller = Scroller(items, line_separator=True, spacing=0)
def _init_items(self):
items = [
]
return items
def _render(self, rect):
self._scroller.render(rect)
@@ -0,0 +1,26 @@
from openpilot.system.ui.lib.list_view import ListItem, button_item, toggle_item
from openpilot.system.ui.lib.scroller import Scroller
from openpilot.system.ui.lib.widget import Widget
from openpilot.common.params import Params
class ModelsLayout(Widget):
def __init__(self):
super().__init__()
self._params = Params()
items = self._init_items()
self._scroller = Scroller(items, line_separator=True, spacing=0)
def _init_items(self):
items = [
button_item("Current Model", "SELECT", callback=self._on_model_select),
toggle_item("Live Learning Steer Delay"),
]
return items
def _render(self, rect):
self._scroller.render(rect)
def _on_model_select(self):
return
@@ -0,0 +1,22 @@
from openpilot.system.ui.lib.list_view import ListItem, button_item, toggle_item
from openpilot.system.ui.lib.scroller import Scroller
from openpilot.system.ui.lib.widget import Widget
from openpilot.common.params import Params
class OSMLayout(Widget):
def __init__(self):
super().__init__()
self._params = Params()
items = self._init_items()
self._scroller = Scroller(items, line_separator=True, spacing=0)
def _init_items(self):
items = [
]
return items
def _render(self, rect):
self._scroller.render(rect)
@@ -0,0 +1,22 @@
from openpilot.system.ui.lib.list_view import ListItem, button_item, toggle_item
from openpilot.system.ui.lib.scroller import Scroller
from openpilot.system.ui.lib.widget import Widget
from openpilot.common.params import Params
class SteeringLayout(Widget):
def __init__(self):
super().__init__()
self._params = Params()
items = self._init_items()
self._scroller = Scroller(items, line_separator=True, spacing=0)
def _init_items(self):
items = [
]
return items
def _render(self, rect):
self._scroller.render(rect)
@@ -0,0 +1,22 @@
from openpilot.system.ui.lib.list_view import ListItem, button_item, toggle_item
from openpilot.system.ui.lib.scroller import Scroller
from openpilot.system.ui.lib.widget import Widget
from openpilot.common.params import Params
class SunnylinkLayout(Widget):
def __init__(self):
super().__init__()
self._params = Params()
items = self._init_items()
self._scroller = Scroller(items, line_separator=True, spacing=0)
def _init_items(self):
items = [
]
return items
def _render(self, rect):
self._scroller.render(rect)
@@ -0,0 +1,22 @@
from openpilot.system.ui.lib.list_view import ListItem, button_item, toggle_item
from openpilot.system.ui.lib.scroller import Scroller
from openpilot.system.ui.lib.widget import Widget
from openpilot.common.params import Params
class TripsLayout(Widget):
def __init__(self):
super().__init__()
self._params = Params()
items = self._init_items()
self._scroller = Scroller(items, line_separator=True, spacing=0)
def _init_items(self):
items = [
]
return items
def _render(self, rect):
self._scroller.render(rect)
@@ -0,0 +1,22 @@
from openpilot.system.ui.lib.list_view import ListItem, button_item, toggle_item
from openpilot.system.ui.lib.scroller import Scroller
from openpilot.system.ui.lib.widget import Widget
from openpilot.common.params import Params
class VehicleLayout(Widget):
def __init__(self):
super().__init__()
self._params = Params()
items = self._init_items()
self._scroller = Scroller(items, line_separator=True, spacing=0)
def _init_items(self):
items = [
]
return items
def _render(self, rect):
self._scroller.render(rect)
@@ -0,0 +1,22 @@
from openpilot.system.ui.lib.list_view import ListItem, button_item, toggle_item
from openpilot.system.ui.lib.scroller import Scroller
from openpilot.system.ui.lib.widget import Widget
from openpilot.common.params import Params
class VisualsLayout(Widget):
def __init__(self):
super().__init__()
self._params = Params()
items = self._init_items()
self._scroller = Scroller(items, line_separator=True, spacing=0)
def _init_items(self):
items = [
]
return items
def _render(self, rect):
self._scroller.render(rect)
+6 -1
View File
@@ -1,5 +1,6 @@
import pyray as rl
from openpilot.system.ui.lib.widget import Widget
from openpilot.system.ui.sunnypilot.lib.toggle import ToggleSP
ON_COLOR = rl.Color(51, 171, 76, 255)
OFF_COLOR = rl.Color(0x39, 0x39, 0x39, 255)
@@ -12,13 +13,14 @@ BG_HEIGHT = 60
ANIMATION_SPEED = 8.0
class Toggle(Widget):
class Toggle(Widget, ToggleSP):
def __init__(self, initial_state=False):
super().__init__()
self._state = initial_state
self._enabled = True
self._progress = 1.0 if initial_state else 0.0
self._target = self._progress
ToggleSP.__init__(self)
def set_rect(self, rect: rl.Rectangle):
self._rect = rl.Rectangle(rect.x, rect.y, WIDTH, HEIGHT)
@@ -52,6 +54,9 @@ class Toggle(Widget):
def _render(self, rect: rl.Rectangle):
self.update()
if ToggleSP._render(self, self._rect):
return
if self._enabled:
bg_color = self._blend_color(OFF_COLOR, ON_COLOR, self._progress)
knob_color = KNOB_COLOR
+59
View File
@@ -0,0 +1,59 @@
import pyray as rl
import openpilot.system.ui.lib.list_view as ListItem
from openpilot.system.ui.lib.widget import Widget
from openpilot.system.ui.lib.text_measure import measure_text_cached
LINE_PADDING = 40
ITEM_BASE_HEIGHT = 170
ITEM_PADDING = 20
ITEM_TEXT_FONT_SIZE = 50
ITEM_TEXT_COLOR = rl.WHITE
ITEM_DESC_TEXT_COLOR = rl.Color(128, 128, 128, 255)
ITEM_DESC_FONT_SIZE = 40
ITEM_DESC_V_OFFSET = 140
ICON_SIZE = 80
BUTTON_WIDTH = 250
BUTTON_HEIGHT = 100
BUTTON_FONT_SIZE = 35
class ListItemSP(Widget):
def __init__(self):
super().__init__()
def _render(self, rect: rl.Rectangle):
# Handle click on title/description area for toggling description
if self.description and rl.is_mouse_button_released(rl.MouseButton.MOUSE_BUTTON_LEFT):
mouse_pos = rl.get_mouse_position()
text_area_width = rect.width - self.get_action_width() - ITEM_PADDING
text_area_x = rect.x
if isinstance(self, ListItem.ToggleItem):
text_area_x = text_area_x + self.get_action_width() + ITEM_PADDING
text_area = rl.Rectangle(text_area_x, rect.y, text_area_width, rect.height)
if rl.check_collision_point_rec(mouse_pos, text_area):
self.show_desc = not self.show_desc
# Render title and description
x = rect.x + ITEM_PADDING
# Draw description if visible
if self.show_desc and self._wrapped_description:
rl.draw_text_ex(self._font, self._wrapped_description, (x, rect.y + ITEM_DESC_V_OFFSET),
ITEM_DESC_FONT_SIZE, 0, ITEM_DESC_TEXT_COLOR)
# Render action if needed
action_width = self.get_action_width()
if isinstance(self, ListItem.ToggleItem):
action_rect = rl.Rectangle(rect.x + ITEM_PADDING, rect.y, action_width, ITEM_BASE_HEIGHT)
x += action_width + ITEM_PADDING
else:
action_rect = rl.Rectangle(rect.x + rect.width - action_width, rect.y, action_width, ITEM_BASE_HEIGHT)
text_size = measure_text_cached(self._font, self.title, ITEM_TEXT_FONT_SIZE)
title_y = rect.y + (ITEM_BASE_HEIGHT - text_size.y) // 2
rl.draw_text_ex(self._font, self.title, (x, title_y), ITEM_TEXT_FONT_SIZE, 0, ITEM_TEXT_COLOR)
return action_rect
+93
View File
@@ -0,0 +1,93 @@
import pyray as rl
import openpilot.system.ui.lib.toggle as ToggleOP
ON_COLOR = rl.Color(28, 101, 186, 255)
OFF_COLOR = rl.Color(0x39, 0x39, 0x39, 255)
KNOB_COLOR = rl.WHITE
DISABLED_ON_COLOR = rl.Color(0x22, 0x77, 0x22, 255) # Dark green when disabled + on
DISABLED_OFF_COLOR = rl.Color(0x39, 0x39, 0x39, 255)
DISABLED_KNOB_COLOR = rl.Color(0x88, 0x88, 0x88, 255)
WIDTH, HEIGHT = 130, 80
BG_HEIGHT = 60
class ToggleSP:
def _render(self, rect: rl.Rectangle):
if self._enabled:
bg_color = ToggleOP.Toggle._blend_color(self, OFF_COLOR, ON_COLOR, self._progress)
knob_color = KNOB_COLOR
else:
bg_color = ToggleOP.Toggle._blend_color(self, DISABLED_OFF_COLOR, DISABLED_ON_COLOR, self._progress)
knob_color = DISABLED_KNOB_COLOR
# Draw background
bg_rect = rl.Rectangle(self._rect.x + 5, self._rect.y + 10, WIDTH - 10, BG_HEIGHT)
# Draw outline first
outline_color = ON_COLOR
if not self._enabled:
# Use a more subtle color for disabled state
outline_color = rl.Color(outline_color.r // 2, outline_color.g // 2, outline_color.b // 2, 255)
# Draw outline by drawing a slightly larger rounded rectangle behind the background
outline_rect = rl.Rectangle(bg_rect.x - 2, bg_rect.y - 2, bg_rect.width + 4, bg_rect.height + 4)
rl.draw_rectangle_rounded(outline_rect, 1.0, 10, outline_color)
# Draw actual background
rl.draw_rectangle_rounded(bg_rect, 1.0, 10, bg_color)
# Draw knob to sit inside the background
knob_padding = 5
knob_radius = BG_HEIGHT / 2 - knob_padding
left_edge = bg_rect.x + knob_padding
right_edge = bg_rect.x + bg_rect.width - knob_padding
knob_travel_distance = right_edge - left_edge - 2 * knob_radius
min_knob_x = left_edge + knob_radius
knob_x = min_knob_x + knob_travel_distance * self._progress
knob_y = self._rect.y + HEIGHT / 2
rl.draw_circle(int(knob_x), int(knob_y), knob_radius, knob_color)
symbol_size = knob_radius / 2
if self._state and (self._enabled or self._progress > 0.5):
# Draw checkmark when toggle is ON
start_x = knob_x - symbol_size * 0.8
start_y = knob_y
mid_x = knob_x - symbol_size * 0.1
mid_y = knob_y + symbol_size * 0.6
end_x = knob_x + symbol_size * 0.8
end_y = knob_y - symbol_size * 0.5
rl.draw_line_ex(
rl.Vector2(int(start_x), int(start_y)),
rl.Vector2(int(mid_x), int(mid_y)),
3,
ON_COLOR
)
rl.draw_line_ex(
rl.Vector2(int(mid_x), int(mid_y)),
rl.Vector2(int(end_x), int(end_y)),
3,
ON_COLOR
)
else:
# Draw X when toggle is OFF
x_size_factor = 0.65
x_offset = symbol_size * x_size_factor
rl.draw_line_ex(
rl.Vector2(int(knob_x - x_offset), int(knob_y - x_offset)),
rl.Vector2(int(knob_x + x_offset), int(knob_y + x_offset)),
3,
OFF_COLOR
)
rl.draw_line_ex(
rl.Vector2(int(knob_x + x_offset), int(knob_y - x_offset)),
rl.Vector2(int(knob_x - x_offset), int(knob_y + x_offset)),
3,
OFF_COLOR
)
return True