mirror of
https://github.com/firestar5683/StarPilot.git
synced 2026-07-08 07:02:06 +08:00
BigUI WIP: System Panel Cleanup
This commit is contained in:
@@ -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)
|
||||
|
||||
Reference in New Issue
Block a user