BigUI WIP: System Panel Cleanup

This commit is contained in:
firestarsdog
2026-06-29 03:53:12 -04:00
parent f26c89d6cb
commit 50630f121c
4 changed files with 164 additions and 99 deletions
-31
View File
@@ -1,31 +0,0 @@
import sys
import os
import pyray as rl
# Add selfdrive's parent to path to support openpilot imports
sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), '..')))
from selfdrive.ui.layouts.settings.starpilot.scribble import draw_custom_icon
def main():
rl.init_window(200, 200, "Icon Render")
rl.set_target_fps(60)
# Run for a few frames to make sure rendering is ready
for _ in range(10):
rl.begin_drawing()
rl.clear_background(rl.Color(15, 12, 30, 255)) # Dark background matching Driving Model tile
# Draw aicar icon centered
# x_c and y_c will be at x + 30*s, y + 30*s.
# Let's draw it at x=70, y=70, scale s=1.0. Centered at 100, 100.
draw_custom_icon("aicar", 70.0, 70.0, 1.0, rl.WHITE)
rl.end_drawing()
rl.take_screenshot("scratch/aicar_preview.png")
rl.close_window()
print("Screenshot saved to scratch/aicar_preview.png")
if __name__ == "__main__":
main()
@@ -410,6 +410,87 @@ def init_list_panel(rect: rl.Rectangle, style: PanelStyle | None = None, metrics
return frame, scroll_rect, content_width
def draw_hud_background(rect: rl.Rectangle, accent: rl.Color, glow: float = 1.0) -> tuple[rl.Rectangle, rl.Color]:
snapped = _snap_rect(rect)
rx, ry, rw, rh = int(snapped.x), int(snapped.y), int(snapped.width), int(snapped.height)
face = rl.Rectangle(rx, ry, rw, rh)
off_border = _HUD_BORDER_OFF
for i in range(4, 0, -1):
off = i * 2.5 * glow
gr = rl.Rectangle(rx - off, ry - off, rw + off * 2, rh + off * 2)
a = int(25 * (1.0 - i / 5) * glow)
_draw_rounded_fill(gr, rl.Color(accent.r, accent.g, accent.b, max(0, min(255, a))), radius_px=100)
_draw_rounded_fill(face, _HUD_BG_ON, radius_px=100)
bc = rl.Color(
max(0, min(255, int(off_border.r + (accent.r - off_border.r) * glow))),
max(0, min(255, int(off_border.g + (accent.g - off_border.g) * glow))),
max(0, min(255, int(off_border.b + (accent.b - off_border.b) * glow))),
255)
_draw_rounded_stroke(face, bc, radius_px=100)
return face, accent
def draw_constellation(face: rl.Rectangle, accent: rl.Color, glow: float, seed: str):
rng = random.Random(seed)
num = _NODE_NUM_MIN + rng.randint(0, _NODE_NUM_MAX - _NODE_NUM_MIN)
regions = [
(0.18, 0.30, 0.18, 0.30), (0.70, 0.82, 0.18, 0.30),
(0.18, 0.30, 0.70, 0.82), (0.70, 0.82, 0.70, 0.82),
(0.38, 0.62, 0.18, 0.28), (0.38, 0.62, 0.72, 0.82),
(0.18, 0.28, 0.38, 0.62), (0.72, 0.82, 0.38, 0.62),
]
ax_min, ax_max, ay_min, ay_max = regions[rng.randint(0, 7)]
ax = ax_min + rng.random() * (ax_max - ax_min)
ay = ay_min + rng.random() * (ay_max - ay_min)
nodes = []
for _ in range(num):
for _ in range(20):
a = rng.random() * 2.0 * math.pi
r = 0.05 + rng.random() * 0.11
x = max(0.04, min(0.96, ax + r * math.cos(a)))
y = max(0.04, min(0.96, ay + r * math.sin(a)))
if all(math.sqrt((x - n['x'])**2 + (y - n['y'])**2) >= 0.07 for n in nodes):
nodes.append({'x': x, 'y': y})
break
else:
nodes.append({'x': x, 'y': y})
nodes.sort(key=lambda n: -(abs(n['x'] - 0.5) + abs(n['y'] - 0.5)))
for i, n in enumerate(nodes):
n['w'] = 0 if i == 0 else 1 if i == 1 else 2
vecs = [(0, j) for j in range(1, num)]
rx, ry, rw, rh = int(face.x), int(face.y), int(face.width), int(face.height)
va = int(10 + glow * 25)
if va > 2:
vc = rl.Color(accent.r, accent.g, accent.b, min(255, va))
for i, j in vecs:
x1 = int(rx + nodes[i]['x'] * rw)
y1 = int(ry + nodes[i]['y'] * rh)
x2 = int(rx + nodes[j]['x'] * rw)
y2 = int(ry + nodes[j]['y'] * rh)
rl.draw_line_ex(rl.Vector2(x1, y1), rl.Vector2(x2, y2), 1.0, vc)
for nd in nodes:
nx = int(rx + nd['x'] * rw)
ny = int(ry + nd['y'] * rh)
if nd['w'] == 0:
core_r, diff_r, col = 3.0, 12.0, _CONST_PRIMARY
elif nd['w'] == 1:
core_r, diff_r, col = 2.0, 8.0, _CONST_SECONDARY
else:
core_r, diff_r, col = 1.2, 0.0, _CONST_TERTIARY
da = int(5 + glow * 20)
if diff_r > 0 and da > 2:
rl.draw_circle(nx, ny, int(diff_r), rl.Color(col.r, col.g, col.b, min(255, da)))
ca = int(130 + glow * 125)
rl.draw_circle(nx, ny, int(core_r), rl.Color(col.r, col.g, col.b, min(255, ca)))
def draw_interactive_rect(target_id: str, rect: rl.Rectangle, interactive_rects: dict[str, rl.Rectangle],
pressed_target: str | None, scroll_rect: rl.Rectangle | None = None,
pad_x: float = 6, pad_y: float = 0) -> tuple[bool, bool]:
@@ -556,6 +637,16 @@ class PanelManagerView(AetherInteractiveMixin, Widget):
def _draw_scroll_content(self, scroll_rect: rl.Rectangle, content_width: float) -> None:
"""Override to render visible content rows inside the scissor region."""
def _draw_static_elements(self, scroll_rect: rl.Rectangle, content_width: float) -> None:
"""Override to render fixed-position elements above the scroll area (outside scissor)."""
def _draw_bottombar(self, bottombar_rect: rl.Rectangle) -> None:
"""Override to render a fixed bottom bar below the scroll area."""
@property
def bottombar_height(self) -> float:
return 0.0
@property
def vertical_scrolling_disabled(self) -> bool:
return False
@@ -572,6 +663,10 @@ class PanelManagerView(AetherInteractiveMixin, Widget):
self._draw_header(frame.header)
btm_h = self.bottombar_height
if btm_h > 0:
scroll_rect.height = max(0.0, scroll_rect.height - btm_h)
self._content_height = self._measure_content_height(content_width)
self._scroll_panel.set_enabled(self.is_visible)
@@ -590,6 +685,8 @@ class PanelManagerView(AetherInteractiveMixin, Widget):
self._draw_scroll_content(scroll_rect, content_width)
rl.end_scissor_mode()
self._draw_static_elements(scroll_rect, content_width)
if self._content_height > scroll_rect.height and not scroll_disabled:
self._scrollbar.render(scroll_rect, self._content_height, self._scroll_offset)
@@ -597,6 +694,11 @@ class PanelManagerView(AetherInteractiveMixin, Widget):
draw_list_scroll_fades(scroll_rect, self._content_height, self._scroll_offset,
AetherListColors.PANEL_BG)
if btm_h > 0:
btm_rect = rl.Rectangle(scroll_rect.x, scroll_rect.y + scroll_rect.height,
content_width, btm_h)
self._draw_bottombar(btm_rect)
# ── shared layout helpers ─────────────────────────────────
def _uses_two_columns(self, width: float) -> bool:
@@ -3753,28 +3855,8 @@ class AetherTile(Widget):
sh = snapped.height * sq
ox = snapped.x + (snapped.width - sw) / 2
oy = snapped.y + (snapped.height - sh) / 2
rx, ry, rw, rh = int(ox), int(oy), int(sw), int(sh)
face = rl.Rectangle(rx, ry, rw, rh)
off_border = _HUD_BORDER_OFF
for i in range(4, 0, -1):
off = i * 2.5 * glow
gr = rl.Rectangle(rx - off, ry - off, rw + off * 2, rh + off * 2)
a = int(25 * (1.0 - i / 5) * glow)
_draw_rounded_fill(gr, rl.Color(accent.r, accent.g, accent.b, max(0, min(255, a))), radius_px=100)
_draw_rounded_fill(face, _HUD_BG_ON, radius_px=100)
bc = rl.Color(
max(0, min(255, int(off_border.r + (accent.r - off_border.r) * glow))),
max(0, min(255, int(off_border.g + (accent.g - off_border.g) * glow))),
max(0, min(255, int(off_border.b + (accent.b - off_border.b) * glow))),
255)
_draw_rounded_stroke(face, bc, radius_px=100)
face, accent = draw_hud_background(rl.Rectangle(ox, oy, sw, sh), accent, glow)
self._draw_constellation(face, accent, glow)
return face, accent
def _render(self, rect: rl.Rectangle):
@@ -5282,6 +5364,7 @@ class AetherSegmentedControl(Widget):
statuses: list[str | Callable[[], str] | None] | None = None,
compact: bool = False,
style: PanelStyle | None = None,
suppress_background: bool = False,
):
super().__init__()
self._options = options
@@ -5292,6 +5375,7 @@ class AetherSegmentedControl(Widget):
self._statuses += [""] * (len(self._options) - len(self._statuses))
self._compact = compact
self._style = style
self._suppress_background = suppress_background
self._font = gui_app.font(FontWeight.BOLD)
self._font_status = gui_app.font(FontWeight.NORMAL)
self._pressed_index = -1
@@ -5364,7 +5448,8 @@ class AetherSegmentedControl(Widget):
for i in range(len(self._option_offsets)):
self._option_offsets[i] += (self._option_targets[i] - self._option_offsets[i]) * (1 - math.exp(-dt / PLATE_TAU))
draw_soft_card(rect, rl.Color(255, 255, 255, 4), rl.Color(255, 255, 255, 14))
if not self._suppress_background:
draw_soft_card(rect, rl.Color(255, 255, 255, 4), rl.Color(255, 255, 255, 14))
inner_pad = 4 if self._compact else 5
gap = 4 if self._compact else 6
@@ -258,7 +258,7 @@ class StarPilotLayout(Widget):
aethergrid._draw_rounded_fill(gr, rl.Color(139, 92, 246, max(0, min(255, a))), radius_px=100)
# 0b. Dark fill — strict parity with HubTile _HUD_BG_ON
aethergrid._draw_rounded_fill(glass_rect, rl.Color(12, 10, 18, 230), radius_px=100)
aethergrid._draw_rounded_fill(glass_rect, rl.Color(12, 10, 18, 255), radius_px=100)
# 0c. Full bright purple border — strict parity
aethergrid._draw_rounded_stroke(glass_rect, rl.Color(139, 92, 246, 255), radius_px=100)
@@ -52,6 +52,7 @@ from openpilot.selfdrive.ui.layouts.settings.starpilot.aethergrid import (
_point_hits,
_draw_text_fit_common,
wrap_text,
)
from openpilot.starpilot.common.connect_server import prepare_konik_server_switch
@@ -318,8 +319,8 @@ class SystemSettingsManagerView(PanelManagerView):
[tr("Auto"), tr("Onroad"), tr("Offroad")],
self._get_drive_mode_index,
self._on_drive_mode_change,
statuses=[tr("Default"), tr("Force on"), tr("Force off")],
style=PANEL_STYLE,
suppress_background=True,
)
)
@@ -434,13 +435,60 @@ class SystemSettingsManagerView(PanelManagerView):
gui_app.push_widget(AetherBackupsCareDialog(self._controller))
def _on_frame_created(self, frame) -> None:
self._drive_mode_control.set_parent_rect(self._scroll_rect)
pass
@property
def bottombar_height(self) -> float:
return 160.0
def _draw_bottombar(self, bottombar_rect: rl.Rectangle) -> None:
draw_list_group_shell(bottombar_rect, style=self.PANEL_STYLE)
self._drive_mode_control.render(bottombar_rect)
def _draw_static_elements(self, scroll_rect: rl.Rectangle, content_width: float) -> None:
if not self._uses_two_columns(content_width):
return
column_w = self._column_width(content_width)
right_x = scroll_rect.x + column_w + self.COLUMN_GAP
btn_size = 32.0
btn_rect = rl.Rectangle(
right_x + column_w - 12 - 44 - btn_size,
scroll_rect.y + self._system_max_container_h - 12 - 10 - btn_size,
btn_size, btn_size,
)
hovered, pressed = self._interactive_state("static:first_aid", btn_rect, pad_y=6)
if pressed:
fill = rl.Color(139, 92, 246, 8)
border = rl.Color(139, 92, 246, 28)
elif hovered:
fill = rl.Color(255, 255, 255, 4)
border = rl.Color(255, 255, 255, 10)
else:
fill = rl.Color(255, 255, 255, 0)
border = rl.Color(255, 255, 255, 0)
draw_soft_card(btn_rect, fill, border)
s = btn_size / 60.0
icon_x = btn_rect.x + (btn_rect.width - 60.0 * s) / 2.0
icon_y = btn_rect.y + (btn_rect.height - 60.0 * s) / 2.0
if pressed:
icon_color = rl.Color(139, 92, 246, 190)
elif hovered:
icon_color = rl.Color(160, 170, 185, 170)
else:
icon_color = rl.Color(160, 170, 185, 80)
draw_custom_icon("first_aid", icon_x, icon_y, s, icon_color)
def _draw_header(self, rect: rl.Rectangle):
pass
def _measure_content_height(self, width: float) -> float:
RELOCATED_HEADER_HEIGHT = 80.0
display_h = self._section_block_height(self._slider_section_height(self._display_slider_keys, width))
power_h = self._section_block_height(self._slider_section_height(self._power_slider_keys, width))
@@ -456,7 +504,7 @@ class SystemSettingsManagerView(PanelManagerView):
power_container_h = self._slider_section_height(self._power_slider_keys, column_w)
left_overhead = 8.0 + 2 * (GROUP_HEADER_HEIGHT + GROUP_HEADER_GAP) + SECTION_GAP
left_natural_content_h = left_overhead + display_container_h + power_container_h + RELOCATED_HEADER_HEIGHT
left_natural_content_h = left_overhead + display_container_h + power_container_h
tiles_content_h = self.measure_page_grid_height(self._connectivity_tile_grid, column_w - 24)
right_natural_container_h = tiles_content_h + 24
@@ -473,7 +521,7 @@ class SystemSettingsManagerView(PanelManagerView):
# Scale adjustors if needed
if max_container_h < max_natural_h:
scale_f = (max_container_h - left_overhead - RELOCATED_HEADER_HEIGHT) / (left_natural_content_h - left_overhead - RELOCATED_HEADER_HEIGHT)
scale_f = max_container_h / left_natural_content_h
row_h = max(60.0, 94.0 * scale_f)
for key in self._display_slider_keys + self._power_slider_keys:
self._adjustor_rows[key].custom_row_height = row_h
@@ -487,7 +535,7 @@ class SystemSettingsManagerView(PanelManagerView):
self._adjustor_rows[key].custom_row_height = None
self._connectivity_tile_grid._tile_height = None
tiles_content_h = self.measure_page_grid_height(self._connectivity_tile_grid, width - 24)
return self._stacked_section_height([display_h, power_h, tiles_content_h + 24]) + RELOCATED_HEADER_HEIGHT
return self._stacked_section_height([display_h, power_h, tiles_content_h + 24])
def _slider_section_height(self, keys: list[str], width: float) -> float:
total = 0.0
@@ -501,47 +549,10 @@ class SystemSettingsManagerView(PanelManagerView):
self._draw_basics_tab(y, rect.x, width)
def _draw_basics_tab(self, y: float, x: float, width: float):
# Relocated Header elements drawn at the top
col_w = self._column_width(width) if self._uses_two_columns(width) else width
# 1. Draw First Aid Button
btn_w, btn_h = 68.0, 68.0
btn_x = x + col_w - btn_w - 8
btn_y = y
btn_rect = rl.Rectangle(btn_x, btn_y, btn_w, btn_h)
hovered, pressed = self._interactive_state("static:first_aid", btn_rect)
if pressed:
fill = rl.Color(255, 255, 255, 30)
border = PANEL_STYLE.accent
elif hovered:
fill = rl.Color(255, 255, 255, 18)
border = PANEL_STYLE.accent
else:
fill = rl.Color(255, 255, 255, 8)
border = rl.Color(255, 255, 255, 20)
draw_soft_card(btn_rect, fill, border)
s = 48.0 / 60.0
icon_x = btn_x + (btn_w - 60.0 * s) / 2.0
icon_y = btn_y + (btn_h - 60.0 * s) / 2.0
icon_color = PANEL_STYLE.accent if (hovered or pressed) else AetherListColors.HEADER
draw_custom_icon("first_aid", icon_x, icon_y, s, icon_color)
# 2. Draw Drive Mode Control (AetherSegmentedControl)
control_rect = rl.Rectangle(x, y, col_w - btn_w - 12.0, btn_h)
self._drive_mode_control.render(control_rect)
RELOCATED_HEADER_HEIGHT = 80.0
y += RELOCATED_HEADER_HEIGHT
if self._uses_two_columns(width):
column_w = self._column_width(width)
adj_container_h = self._system_max_container_h - RELOCATED_HEADER_HEIGHT
adj_container_h = self._system_max_container_h
# Draw unified shell for the adjustors
draw_list_group_shell(rl.Rectangle(x, y, column_w, adj_container_h), style=PANEL_STYLE)
current_y = y + 8
@@ -555,7 +566,7 @@ class SystemSettingsManagerView(PanelManagerView):
for index, key in enumerate(self._power_slider_keys):
current_y = self._draw_slider_row(rl.Rectangle(x, current_y, column_w, 0), key, is_last=index == len(self._power_slider_keys) - 1)
self._draw_two_column_tile_grid(self._connectivity_tile_grid, x + column_w + self.COLUMN_GAP, y - RELOCATED_HEADER_HEIGHT, column_w, self._system_max_container_h)
self._draw_two_column_tile_grid(self._connectivity_tile_grid, x + column_w + self.COLUMN_GAP, y, column_w, self._system_max_container_h)
return
y = self._draw_slider_section(y, x, width, tr("Display"), self._display_slider_keys)