BigUI WIP: Extraction

This commit is contained in:
firestarsdog
2026-04-30 01:49:24 -04:00
parent 3adc8592da
commit d0c19745c3
3 changed files with 245 additions and 98 deletions
@@ -232,6 +232,7 @@ class AetherListFrame:
AETHER_LIST_METRICS = AetherListMetrics()
AETHER_COMPACT_ROW_HEIGHT = AETHER_LIST_METRICS.utility_row_height
@dataclass(frozen=True, slots=True)
@@ -582,6 +583,155 @@ def draw_metric_strip(
)
def draw_section_header(
rect: rl.Rectangle,
title: str = "",
*,
trailing_text: str = "",
title_size: int = 26,
trailing_size: int = 20,
title_color: rl.Color | None = None,
trailing_color: rl.Color | None = None,
style: PanelStyle = DEFAULT_PANEL_STYLE,
):
if title:
trailing_reserved = min(320.0, rect.width * 0.38) if trailing_text else 0.0
title_rect = rl.Rectangle(rect.x, rect.y + (rect.height - title_size) / 2, max(1.0, rect.width - trailing_reserved), title_size + 4)
gui_label(title_rect, title, title_size, title_color or style.subtitle_color, FontWeight.MEDIUM)
if trailing_text:
trailing_rect = rl.Rectangle(rect.x, rect.y + (rect.height - trailing_size) / 2, rect.width, trailing_size + 4)
gui_label(
trailing_rect,
trailing_text,
trailing_size,
trailing_color or style.subtitle_color,
FontWeight.NORMAL,
alignment=rl.GuiTextAlignment.TEXT_ALIGN_RIGHT,
)
def draw_empty_state_card(
rect: rl.Rectangle,
title: str,
body: str,
*,
title_size: int = 30,
body_size: int = 22,
body_inset_x: int = 48,
title_gap: int = 14,
title_top_padding: float | None = None,
body_height: float | None = None,
fill: rl.Color | None = None,
border: rl.Color | None = None,
radius: float = 0.08,
segments: int = 18,
style: PanelStyle = DEFAULT_PANEL_STYLE,
):
card_rect = _snap_rect(rect)
resolved_fill = fill if fill is not None else style.surface_fill
resolved_border = border if border is not None else style.surface_border
draw_soft_card(card_rect, resolved_fill, resolved_border, radius=radius, segments=segments)
title_h = max(34.0, title_size + 8)
title_y = card_rect.y + (title_top_padding if title_top_padding is not None else max(24.0, min(42.0, card_rect.height * 0.22)))
inset_x = min(float(body_inset_x), max(18.0, card_rect.width * 0.22))
body_y = title_y + title_h + title_gap
resolved_body_h = body_height if body_height is not None else max(40.0, card_rect.height - (body_y - card_rect.y) - 24.0)
gui_label(
rl.Rectangle(card_rect.x, title_y, card_rect.width, title_h),
title,
title_size,
style.title_color,
FontWeight.MEDIUM,
alignment=rl.GuiTextAlignment.TEXT_ALIGN_CENTER,
)
gui_label(
rl.Rectangle(card_rect.x + inset_x, body_y, max(1.0, card_rect.width - inset_x * 2), resolved_body_h),
body,
body_size,
style.subtitle_color,
FontWeight.NORMAL,
alignment=rl.GuiTextAlignment.TEXT_ALIGN_CENTER,
)
def draw_list_group_shell(
rect: rl.Rectangle,
*,
fill: rl.Color | None = None,
border: rl.Color | None = None,
radius: float = 0.055,
segments: int = 18,
style: PanelStyle = DEFAULT_PANEL_STYLE,
):
draw_soft_card(rect, fill if fill is not None else style.surface_fill, border if border is not None else style.surface_border, radius=radius, segments=segments)
def draw_settings_list_row(
rect: rl.Rectangle,
*,
title: str,
subtitle: str = "",
value: str = "",
toggle_value: bool | None = None,
hovered: bool = False,
pressed: bool = False,
is_last: bool = False,
show_chevron: bool = True,
title_size: int = 28,
subtitle_size: int = 20,
value_size: int = 24,
separator_inset: int = 22,
value_color: rl.Color | None = None,
style: PanelStyle = DEFAULT_PANEL_STYLE,
):
draw_rect = _snap_rect(rect)
draw_list_row_shell(
draw_rect,
hovered=hovered,
pressed=pressed,
is_last=is_last,
row_bg=rl.Color(255, 255, 255, 0),
row_border=rl.Color(255, 255, 255, 0),
row_separator=style.divider_color,
current_bg=rl.Color(255, 255, 255, 0),
current_border=rl.Color(255, 255, 255, 0),
separator_inset=separator_inset,
)
label_rect = rl.Rectangle(draw_rect.x + 24, draw_rect.y + 14, draw_rect.width * 0.55, 28)
subtitle_rect = rl.Rectangle(draw_rect.x + 24, draw_rect.y + 46, draw_rect.width * 0.60, 24)
gui_label(label_rect, title, title_size, style.title_color, FontWeight.MEDIUM)
if subtitle:
gui_label(subtitle_rect, subtitle, subtitle_size, style.subtitle_color, FontWeight.NORMAL)
if toggle_value is not None:
draw_toggle_switch(draw_rect, bool(toggle_value), track_color=style.accent)
return
if value:
value_rect = rl.Rectangle(draw_rect.x + draw_rect.width - AETHER_LIST_METRICS.utility_value_right, draw_rect.y + 20, AETHER_LIST_METRICS.utility_value_width, 28)
gui_label(
value_rect,
value,
value_size,
value_color or style.title_color,
FontWeight.MEDIUM,
alignment=rl.GuiTextAlignment.TEXT_ALIGN_RIGHT,
)
if show_chevron:
gui_label(
rl.Rectangle(draw_rect.x + draw_rect.width - AETHER_LIST_METRICS.utility_chevron_right, draw_rect.y + 18, 26, 26),
"",
32,
style.muted_color,
FontWeight.MEDIUM,
alignment=rl.GuiTextAlignment.TEXT_ALIGN_CENTER,
)
def draw_selection_list_row(
rect: rl.Rectangle,
*,
@@ -606,9 +756,28 @@ def draw_selection_list_row(
action_fill: rl.Color = AetherListColors.CURRENT_BG,
action_border: rl.Color = AetherListColors.CURRENT_BORDER,
action_text_color: rl.Color = AetherListColors.HEADER,
row_bg: rl.Color = AetherListColors.ROW_BG,
row_border: rl.Color = AetherListColors.ROW_BORDER,
row_separator: rl.Color = AetherListColors.ROW_SEPARATOR,
row_hover: rl.Color = AetherListColors.ROW_HOVER,
current_bg: rl.Color = AetherListColors.CURRENT_BG,
current_border: rl.Color = AetherListColors.CURRENT_BORDER,
):
draw_rect = _snap_rect(rect)
draw_list_row_shell(draw_rect, current=current, hovered=hovered, pressed=pressed, is_last=is_last, alpha=alpha)
draw_list_row_shell(
draw_rect,
current=current,
hovered=hovered,
pressed=pressed,
is_last=is_last,
alpha=alpha,
row_bg=row_bg,
row_border=row_border,
row_separator=row_separator,
row_hover=row_hover,
current_bg=current_bg,
current_border=current_border,
)
action_rect = rl.Rectangle(draw_rect.x + draw_rect.width - action_width, draw_rect.y, action_width, draw_rect.height)
if not action_pill:
action_rect = draw_action_rail(draw_rect, action_width, current=current, alpha=alpha)
@@ -38,18 +38,21 @@ from openpilot.selfdrive.ui.layouts.settings.starpilot.aethergrid import (
AetherListColors,
AetherScrollbar,
AetherSliderDialog,
_point_hits,
draw_action_rail,
draw_action_pill,
draw_busy_ring,
draw_download_icon,
draw_empty_state_card,
draw_heart_icon,
draw_list_panel_shell,
draw_list_group_shell,
build_list_panel_frame,
draw_list_row_shell,
draw_list_scroll_fades,
draw_soft_card,
draw_section_header,
draw_settings_list_row,
draw_status_led,
draw_toggle_switch,
draw_overflow_dots,
)
@@ -224,8 +227,7 @@ class DrivingModelManagerView(Widget):
def _target_at(self, mouse_pos: MousePos) -> str | None:
for sub_key, rect in self._menu_sub_rects.items():
visible_rect = rl.get_collision_rec(rect, self._scroll_rect)
if visible_rect.width > 0 and visible_rect.height > 0 and rl.check_collision_point_rec(mouse_pos, visible_rect):
if _point_hits(mouse_pos, rect, self._scroll_rect, pad_x=6, pad_y=6):
return f"menu:{sub_key}"
for key, rect in self._action_rects.items():
@@ -411,32 +413,25 @@ class DrivingModelManagerView(Widget):
self._draw_utility_section(rect.x, y, width, utility_rows)
def _draw_empty_state(self, rect: rl.Rectangle):
state_rect = rl.Rectangle(rect.x, rect.y, rect.width - 18, rect.height)
draw_soft_card(state_rect, rl.Color(255, 255, 255, 5), rl.Color(255, 255, 255, 14), radius=0.08, segments=18)
gui_label(
rl.Rectangle(state_rect.x, state_rect.y + 42, state_rect.width, 40),
draw_empty_state_card(
rl.Rectangle(rect.x, rect.y, rect.width - AETHER_LIST_METRICS.content_right_gutter, rect.height),
self._controller.empty_state_title(),
32,
MODEL_HEADER_TEXT,
FontWeight.MEDIUM,
alignment=rl.GuiTextAlignment.TEXT_ALIGN_CENTER,
)
gui_label(
rl.Rectangle(state_rect.x + 48, state_rect.y + 88, state_rect.width - 96, 72),
self._controller.empty_state_body(),
24,
MODEL_SUBTEXT,
FontWeight.NORMAL,
alignment=rl.GuiTextAlignment.TEXT_ALIGN_CENTER,
title_size=32,
body_size=24,
body_inset_x=48,
title_top_padding=42,
body_height=72,
fill=rl.Color(255, 255, 255, 5),
border=rl.Color(255, 255, 255, 14),
)
def _draw_model_section(self, x: float, y: float, width: float, title: str, entries: list[ModelCatalogEntry]) -> float:
title_rect = rl.Rectangle(x, y, width - 18, SECTION_HEADER_HEIGHT)
gui_label(title_rect, title, 26, MODEL_SUBTEXT, FontWeight.MEDIUM)
draw_section_header(rl.Rectangle(x, y, width - AETHER_LIST_METRICS.content_right_gutter, SECTION_HEADER_HEIGHT), title)
y += SECTION_HEADER_HEIGHT + SECTION_HEADER_GAP
for index, entry in enumerate(entries):
row_rect = rl.Rectangle(x, y + index * ROW_HEIGHT, width - 18, ROW_HEIGHT)
row_rect = rl.Rectangle(x, y + index * ROW_HEIGHT, width - AETHER_LIST_METRICS.content_right_gutter, ROW_HEIGHT)
self._draw_model_row(row_rect, entry, is_last=index == len(entries) - 1)
return y + len(entries) * ROW_HEIGHT
@@ -571,7 +566,7 @@ class DrivingModelManagerView(Widget):
)
else:
# Expanded sub-button menu
btn_h = 40
btn_h = 44
gap = 8
total_h = btn_h * 2 + gap
start_y = rect.y + (rect.height - total_h) / 2
@@ -602,46 +597,32 @@ class DrivingModelManagerView(Widget):
AetherChip(tr("Protected"), rl.Color(255, 255, 255, 10), MODEL_MUTED, MODEL_SUBTEXT, font_size=18).render(chip_rect)
def _draw_utility_section(self, x: float, y: float, width: float, rows: list[dict]):
title_rect = rl.Rectangle(x, y, width - 18, SECTION_HEADER_HEIGHT)
gui_label(title_rect, tr("Automation and Tuning"), 26, MODEL_SUBTEXT, FontWeight.MEDIUM)
content_w = width - AETHER_LIST_METRICS.content_right_gutter
draw_section_header(rl.Rectangle(x, y, content_w, SECTION_HEADER_HEIGHT), tr("Automation and Tuning"))
y += SECTION_HEADER_HEIGHT + SECTION_HEADER_GAP
container_rect = rl.Rectangle(x, y, width - 18, len(rows) * UTILITY_ROW_HEIGHT)
draw_soft_card(container_rect, rl.Color(255, 255, 255, 4), rl.Color(255, 255, 255, 15), radius=0.055, segments=18)
container_rect = rl.Rectangle(x, y, content_w, len(rows) * UTILITY_ROW_HEIGHT)
draw_list_group_shell(container_rect, fill=rl.Color(255, 255, 255, 4), border=rl.Color(255, 255, 255, 15))
for index, row in enumerate(rows):
row_rect = rl.Rectangle(x, y + index * UTILITY_ROW_HEIGHT, width - 18, UTILITY_ROW_HEIGHT)
row_rect = rl.Rectangle(x, y + index * UTILITY_ROW_HEIGHT, content_w, UTILITY_ROW_HEIGHT)
self._draw_utility_row(row_rect, row, is_last=index == len(rows) - 1)
def _draw_utility_row(self, rect: rl.Rectangle, row: dict, is_last: bool):
mouse_pos = gui_app.last_mouse_event.pos
hovered = rl.check_collision_point_rec(mouse_pos, rect)
pressed = self._pressed_target == f"utility:{row['id']}"
bg = rl.Color(255, 255, 255, 8 if hovered else 0)
if pressed:
bg = rl.Color(255, 255, 255, 14)
if bg.a:
rl.draw_rectangle_rounded(rect, ROW_RADIUS, 18, bg)
if not is_last:
line_y = int(rect.y + rect.height - 1)
rl.draw_line(int(rect.x + 22), line_y, int(rect.x + rect.width - 22), line_y, MODEL_ROW_SEPARATOR)
self._utility_rects[row["id"]] = rect
label_rect = rl.Rectangle(rect.x + 24, rect.y + 14, rect.width * 0.55, 28)
subtitle_rect = rl.Rectangle(rect.x + 24, rect.y + 46, rect.width * 0.60, 24)
gui_label(label_rect, row["title"], 28, MODEL_HEADER_TEXT, FontWeight.MEDIUM)
if row.get("subtitle"):
gui_label(subtitle_rect, row["subtitle"], 20, MODEL_SUBTEXT, FontWeight.NORMAL)
if row["type"] == "toggle":
draw_toggle_switch(rect, bool(row["value"]))
else:
value_rect = rl.Rectangle(rect.x + rect.width - AETHER_LIST_METRICS.utility_value_right, rect.y + 20, AETHER_LIST_METRICS.utility_value_width, 28)
gui_label(value_rect, row["value"], 24, MODEL_HEADER_TEXT, FontWeight.MEDIUM, alignment=rl.GuiTextAlignment.TEXT_ALIGN_RIGHT)
gui_label(
rl.Rectangle(rect.x + rect.width - AETHER_LIST_METRICS.utility_chevron_right, rect.y + 18, 26, 26), "", 32, MODEL_MUTED, FontWeight.MEDIUM, alignment=rl.GuiTextAlignment.TEXT_ALIGN_CENTER
)
draw_settings_list_row(
rect,
title=row["title"],
subtitle=row.get("subtitle", ""),
value="" if row["type"] == "toggle" else row["value"],
toggle_value=bool(row["value"]) if row["type"] == "toggle" else None,
hovered=hovered,
pressed=pressed,
is_last=is_last,
)
def _draw_scrollbar(self, rect: rl.Rectangle):
self._scrollbar.render(rect, self._content_height, self._scroll_offset)
+42 -45
View File
@@ -20,16 +20,19 @@ from openpilot.system.ui.widgets.label import gui_label, gui_text_box
from openpilot.system.ui.widgets.option_dialog import MultiOptionDialog
from openpilot.selfdrive.ui.layouts.settings.starpilot.aethergrid import (
AETHER_COMPACT_ROW_HEIGHT,
AETHER_LIST_METRICS,
AetherButton,
AetherChip,
AetherListColors,
AetherScrollbar,
DEFAULT_PANEL_STYLE,
build_list_panel_frame,
draw_action_pill,
draw_busy_ring,
draw_empty_state_card,
draw_list_panel_shell,
draw_metric_strip,
draw_section_header,
draw_tab_card,
draw_selection_list_row,
draw_list_scroll_fades,
@@ -62,17 +65,16 @@ NETWORK_TYPE_LABELS = {
OFFLINE_MAPS_PATH = Path("/data/media/0/osm/offline")
CANCEL_REQUEST_TIMEOUT = 3.0
HEADER_TOP_OFFSET = 10
HEADER_TOP_OFFSET = 4
HEADER_TITLE_HEIGHT = 40
HEADER_SUBTITLE_HEIGHT = 28
HEADER_SUBTITLE_HEIGHT = 24
HEADER_BOTTOM_GAP = 12
BROWSER_TOOLBAR_HEIGHT = 64
BROWSER_SECTION_HEADER_HEIGHT = 34
BROWSER_INSET = 18
BROWSER_TAB_GAP = 12
BROWSER_CONTEXT_TAB_GAP = 10
BROWSER_SECTION_HEADER_HEIGHT = AETHER_LIST_METRICS.section_header_height
BROWSER_SECTION_HEADER_GAP = AETHER_LIST_METRICS.section_header_gap
BROWSER_INSET = AETHER_LIST_METRICS.content_right_gutter
BROWSER_TAB_GAP = AETHER_LIST_METRICS.section_header_gap
BROWSER_CONTEXT_TAB_HEIGHT = 52
BROWSER_REGION_ROW_HEIGHT = 88
BROWSER_REGION_ROW_HEIGHT = AETHER_COMPACT_ROW_HEIGHT
BROWSER_EMPTY_STATE_HEIGHT = 128
STATUS_CARD_INSET = BROWSER_INSET
STATUS_BUTTON_HEIGHT = 52
@@ -81,7 +83,13 @@ STATUS_REMOVE_HEIGHT = 40
STATUS_METRIC_GAP = 18
STATUS_SELECTION_CHIP_HEIGHT = 30
MAPS_TILE_GREEN = rl.Color(16, 185, 129, 255)
MAPS_PANEL_STYLE = replace(DEFAULT_PANEL_STYLE, accent=MAPS_TILE_GREEN)
MAPS_PANEL_STYLE = replace(
DEFAULT_PANEL_STYLE,
accent=MAPS_TILE_GREEN,
current_fill=rl.Color(16, 185, 129, 16),
current_border=rl.Color(16, 185, 129, 42),
underline_color=rl.Color(16, 185, 129, 150),
)
COUNTRIES_SECTION = next(section for section in MAPS_CATALOG if section["key"] == "countries")
STATES_SECTION = next(section for section in MAPS_CATALOG if section["key"] == "states")
@@ -389,23 +397,17 @@ class MapBrowserCard(Widget):
)
def _render_empty_state(self, rect: rl.Rectangle, title: str, body: str):
state_rect = rl.Rectangle(rect.x, rect.y, rect.width, rect.height)
draw_soft_card(state_rect, MAPS_PANEL_STYLE.surface_fill, rl.Color(255, 255, 255, 10))
gui_label(
rl.Rectangle(state_rect.x, state_rect.y + 24, state_rect.width, 32),
draw_empty_state_card(
rect,
title,
26,
AetherListColors.HEADER,
FontWeight.MEDIUM,
alignment=rl.GuiTextAlignment.TEXT_ALIGN_CENTER,
)
gui_label(
rl.Rectangle(state_rect.x + 40, state_rect.y + 60, state_rect.width - 80, 44),
body,
19,
AetherListColors.SUBTEXT,
FontWeight.NORMAL,
alignment=rl.GuiTextAlignment.TEXT_ALIGN_CENTER,
title_size=26,
body_size=19,
body_inset_x=40,
title_top_padding=24,
body_height=44,
border=rl.Color(255, 255, 255, 10),
style=MAPS_PANEL_STYLE,
)
def _render_region_rows(self, rect: rl.Rectangle, regions: list[dict]):
@@ -436,29 +438,23 @@ class MapBrowserCard(Widget):
action_width=142,
action_pill=True,
action_text_size=15,
action_pill_height=36,
action_pill_height=40,
action_pill_width=112 if selected else 84,
title_size=26,
subtitle_size=17,
row_separator=MAPS_PANEL_STYLE.divider_color,
current_bg=MAPS_PANEL_STYLE.current_fill,
current_border=MAPS_PANEL_STYLE.current_border,
action_fill=rl.Color(94, 168, 130, 18) if selected else rl.Color(255, 255, 255, 8),
action_border=rl.Color(94, 168, 130, 38) if selected else rl.Color(255, 255, 255, 24),
action_text_color=AetherListColors.SUBTEXT if selected else AetherListColors.HEADER,
action_text_color=AetherListColors.HEADER,
)
def _active_browse_regions(self) -> list[dict]:
return self._controller._browse_regions_for_active_group()
def _render_section_header(self, rect: rl.Rectangle, title: str, *, count_text: str | None = None):
del title
if count_text:
gui_label(
rl.Rectangle(rect.x, rect.y + (rect.height - 22) / 2, rect.width, 22),
count_text,
20,
AetherListColors.SUBTEXT,
FontWeight.NORMAL,
alignment=rl.GuiTextAlignment.TEXT_ALIGN_RIGHT,
)
draw_section_header(rect, title, trailing_text=count_text or "", title_size=24, trailing_size=20, style=MAPS_PANEL_STYLE)
def _measure_height(self, width: float) -> float:
if self._controller._showing_source_picker():
@@ -466,8 +462,8 @@ class MapBrowserCard(Widget):
return BROWSER_CONTEXT_TAB_HEIGHT + 20
total = 10.0
total += self._measure_context_tabs_height(width) + 12
total += BROWSER_SECTION_HEADER_HEIGHT + 2
total += self._measure_context_tabs_height(width) + BROWSER_SECTION_HEADER_GAP
total += BROWSER_SECTION_HEADER_HEIGHT + BROWSER_SECTION_HEADER_GAP
region_count = len(self._active_browse_regions())
total += self._row_height(region_count, BROWSER_REGION_ROW_HEIGHT) if region_count else BROWSER_EMPTY_STATE_HEIGHT
return total + 10
@@ -491,14 +487,14 @@ class MapBrowserCard(Widget):
context_tabs_h = self._measure_context_tabs_height(content_w)
self._render_context_tabs(rl.Rectangle(content_x, y, content_w, context_tabs_h))
y += context_tabs_h + 12
y += context_tabs_h + BROWSER_SECTION_HEADER_GAP
self._render_section_header(
rl.Rectangle(content_x, y, content_w, BROWSER_SECTION_HEADER_HEIGHT),
"",
tr("Regions"),
count_text=self._controller._active_group_count_text(),
)
y += BROWSER_SECTION_HEADER_HEIGHT + 2
y += BROWSER_SECTION_HEADER_HEIGHT + BROWSER_SECTION_HEADER_GAP
regions = self._active_browse_regions()
region_h = self._row_height(len(regions), BROWSER_REGION_ROW_HEIGHT) if regions else BROWSER_EMPTY_STATE_HEIGHT
@@ -1130,15 +1126,16 @@ class StarPilotMapsLayout(StarPilotPanel):
hdr = frame.header
title_y = hdr.y + HEADER_TOP_OFFSET
subtitle_y = hdr.y + 48
gui_label(rl.Rectangle(hdr.x, title_y, hdr.width, HEADER_TITLE_HEIGHT), tr("Map Data"), 40, AetherListColors.HEADER, FontWeight.SEMI_BOLD)
gui_label(rl.Rectangle(hdr.x, title_y + 40, hdr.width * 0.72, HEADER_SUBTITLE_HEIGHT), tr("Use offline maps for speed-limit control and keep only the regions you need."), 22, AetherListColors.SUBTEXT, FontWeight.NORMAL)
gui_label(rl.Rectangle(hdr.x, subtitle_y, hdr.width * 0.60, HEADER_SUBTITLE_HEIGHT), tr("Use offline maps for speed-limit control and keep only the regions you need."), 22, AetherListColors.SUBTEXT, FontWeight.NORMAL)
header_status_y = title_y + HEADER_TITLE_HEIGHT + HEADER_SUBTITLE_HEIGHT + 8
header_status_y = subtitle_y + HEADER_SUBTITLE_HEIGHT + 12
header_status_rect = rl.Rectangle(hdr.x, header_status_y, hdr.width, hdr.y + hdr.height - header_status_y - HEADER_BOTTOM_GAP)
self._status_card.render(header_status_rect)
scroll_rect = frame.scroll
content_width = scroll_rect.width - 18
content_width = scroll_rect.width - AETHER_LIST_METRICS.content_right_gutter
scroll_content_rect = rl.Rectangle(scroll_rect.x, scroll_rect.y, scroll_rect.width, scroll_rect.height)
self._content_height = self._measure_content_height(content_width)
self._scroll_panel.set_enabled(self.is_visible)