mirror of
https://github.com/sunnypilot/sunnypilot.git
synced 2026-06-20 09:12:05 +08:00
init sp panels & toggle
This commit is contained in:
@@ -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)
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
Reference in New Issue
Block a user