From 6fa509cc887954eb3a3faf29d548205cf6ef2a29 Mon Sep 17 00:00:00 2001 From: firestarsdog <229254897+firestarsdog@users.noreply.github.com> Date: Wed, 15 Jul 2026 19:02:57 -0400 Subject: [PATCH] Big UI: Nuke --- .../layouts/settings/starpilot/aethergrid.py | 448 +++--------------- .../layouts/settings/starpilot/appearance.py | 9 +- .../ui/layouts/settings/starpilot/lateral.py | 1 - .../ui/layouts/settings/starpilot/panel.py | 25 +- .../ui/layouts/settings/starpilot/sounds.py | 2 +- selfdrive/ui/tests/test_aethergrid.py | 40 +- 6 files changed, 74 insertions(+), 451 deletions(-) diff --git a/selfdrive/ui/layouts/settings/starpilot/aethergrid.py b/selfdrive/ui/layouts/settings/starpilot/aethergrid.py index df3ff847e..3f0cc6d47 100644 --- a/selfdrive/ui/layouts/settings/starpilot/aethergrid.py +++ b/selfdrive/ui/layouts/settings/starpilot/aethergrid.py @@ -17,12 +17,8 @@ from openpilot.selfdrive.ui.layouts.settings.starpilot.scribble import draw_cust GEOMETRY_OFFSET = 10 PLATE_TAU = 0.060 -TILE_RADIUS = 0.08 -TILE_SEGMENTS = 12 SLIDER_BUTTON_SIZE = 87 -TILE_INSET = 1.0 TILE_RADIUS_PX = 18.0 -TILE_SIGNAL_WIDTH = 1 MIN_TILE_WIDTH = 300 TOGGLE_ROW_HEIGHT = 128 TOGGLE_MIN_HEIGHT = 80 @@ -82,9 +78,6 @@ class SPACING: tile_gap: int = 16 tile_content: int = 16 line_gap: int = 8 - section_gap: int = 24 - tab_height: int = 96 - tab_panel_gap: int = 16 def hex_to_color(hex_str: str) -> rl.Color: @@ -145,19 +138,6 @@ def mix_colors(base, accent, weight: float, alpha: int | None = None) -> rl.Colo ) -def _tone_step(color, delta: int, alpha: int | None = None) -> rl.Color: - r, g, b, a = _get_rgba(color) - return rl.Color( - max(0, min(255, r + delta)), - max(0, min(255, g + delta)), - max(0, min(255, b + delta)), - a if alpha is None else alpha, - ) - - -def _default_substrate_for(color) -> rl.Color: - return mix_colors(rl.Color(14, 17, 23, 255), color, 0.14) - def _snap(value: float) -> float: return float(round(value)) @@ -1422,37 +1402,6 @@ def draw_soft_card(rect: rl.Rectangle, fill: rl.Color, border: rl.Color, radius: draw_rounded_stroke(rect, border, radius_px=radius_px, segments=segments) -def draw_status_badges( - start_x: float, - y: float, - items: list[str], - style: PanelStyle, - *, - height: float = 64.0, - font_size: int = 40, - gap: float = 12.0, - padding_x: float = 26.0, - text_color: rl.Color = AetherListColors.HEADER, -): - badge_x = start_x - font = gui_app.font(FontWeight.BOLD) - for item in items: - text_size = measure_text_cached(font, item, font_size) - badge_w = text_size.x + padding_x - badge_rect = rl.Rectangle(badge_x, y, badge_w, height) - - fill_color = with_alpha(style.accent, 24) - border_color = with_alpha(style.accent, 80) - draw_rounded_fill(badge_rect, fill_color, radius_px=12) - draw_rounded_stroke(badge_rect, border_color, radius_px=12) - - text_x = badge_rect.x + (badge_rect.width - text_size.x) / 2 - text_y = badge_rect.y + (badge_rect.height - text_size.y) / 2 - rl.draw_text_ex(font, item, rl.Vector2(round(text_x), round(text_y)), font_size, 0, text_color) - - badge_x += badge_w + gap - - def draw_list_row_shell( rect: rl.Rectangle, *, @@ -2152,6 +2101,40 @@ def draw_settings_list_row( ) +def draw_selectable_chip(rect: rl.Rectangle, text: str, *, + current: bool, pressed: bool, + color: rl.Color, + font: rl.Font | None = None, + font_size: int = 28, + radius_px: float = 16, + text_color_active: rl.Color = AetherListColors.HEADER, + text_color_normal: rl.Color = AetherListColors.SUBTEXT, + padding_x: int = 10): + fill = rl.Color(255, 255, 255, 5) + border = rl.Color(255, 255, 255, 14) + text_color = text_color_normal + if current: + fill = mix_colors(rl.Color(18, 22, 28, 255), color, 0.22, alpha=255) + border = with_alpha(color, 72) + text_color = text_color_active + elif pressed: + fill = rl.Color(255, 255, 255, 10) + border = rl.Color(255, 255, 255, 22) + + resolved_font = font or gui_app.font(FontWeight.MEDIUM) + draw_rounded_fill(rect, fill, radius_px=radius_px) + draw_rounded_stroke(rect, border, radius_px=radius_px) + draw_text_fit_common( + resolved_font, + text, + rl.Vector2(rect.x + padding_x, rect.y + (rect.height - font_size) / 2), + max(1.0, rect.width - padding_x * 2), + font_size, + align_center=True, + color=text_color, + ) + + def format_adjustor_value(value: float, *, step: float = 1.0, unit: str = "", labels: dict[float, str] | None = None) -> str: label_map = labels or {} tolerance = max(abs(step) * 0.5, 1e-4) if step != 0 else 1e-4 @@ -2479,7 +2462,7 @@ class AetherAdjustorRow(Widget): self._set_active = set_active self._style = style self._color = color or style.accent - valid_icons = {"sound", "steering", "navigate", "system", "display", "vehicle", "road", "aicar", "first_aid", "alert_critical", "alert_state", "alert_info"} + valid_icons = {"sound", "steering", "navigate", "system", "display", "vehicle", "road", "aicar", "first_aid"} self._icon_key = icon_key if icon_key in valid_icons else None self._presets = presets or [] self._preset_applied = False @@ -2593,7 +2576,7 @@ class AetherAdjustorRow(Widget): self._set_active_state(not active) def _handle_mouse_event(self, mouse_event: MouseEvent): - del mouse_event + pass def _render_preset_chip(self, rect: rl.Rectangle, text: str, *, current: bool, pressed: bool): fill = rl.Color(255, 255, 255, 5) @@ -3074,11 +3057,6 @@ class AetherSettingsView(PanelManagerView): self._has_header = bool(header_title) or parent_toggle is not None self._tab_defs = tab_defs self._active_tab_key = tab_defs[0]["id"] if tab_defs else "" - self._scroll_panel = GuiScrollPanel2(horizontal=False) - self._scrollbar = AetherScrollbar() - self._content_height = 0.0 - self._scroll_offset = 0.0 - self._scroll_rect = rl.Rectangle(0, 0, 0, 0) def _find_row(self, target_id: str) -> SettingRow | None: for section in self._active_sections(): @@ -3438,8 +3416,6 @@ class AetherCategoryDrawer(AetherSettingsView): self._rows = rows self._slide_progress = 0.0 self._back_btn_rect = None - self._scroll_panel = GuiScrollPanel2(horizontal=False) - self._scrollbar = AetherScrollbar() # Read driving side dynamically for ergonomic layout (LHD vs RHD) try: @@ -3677,7 +3653,7 @@ class AetherTransitionManager: class AetherTile(Widget): - def __init__(self, surface_color: rl.Color | str | None = None, substrate_color: rl.Color | str | None = None, on_click: Callable | None = None): + def __init__(self, surface_color: rl.Color | str | None = None, on_click: Callable | None = None): super().__init__() if isinstance(surface_color, str): self.surface_color = hex_to_color(surface_color) @@ -3685,19 +3661,12 @@ class AetherTile(Widget): self.surface_color = surface_color else: self.surface_color = AetherListColors.PRIMARY - if isinstance(substrate_color, str): - self.substrate_color = hex_to_color(substrate_color) - else: - self.substrate_color = substrate_color or _default_substrate_for(self.surface_color) self.on_click = on_click self._plate_offset: float = 0.0 self._plate_target: float = 0.0 self._is_pressed: bool = False self._squish: float = 1.0 - def _surface_rect(self, rect: rl.Rectangle) -> rl.Rectangle: - return _inset_rect(snap_rect(rect), TILE_INSET) - @property def _hit_rect(self) -> rl.Rectangle: hit_rect = rl.Rectangle( @@ -3750,25 +3719,6 @@ class AetherTile(Widget): def _update_state(self): self._update_squish() - def _render_layers(self, rect: rl.Rectangle, radius: float = TILE_RADIUS, segments: int = TILE_SEGMENTS): - self._animate_plate(rl.get_frame_time()) - snapped_rect = snap_rect(rect) - self.set_rect(snapped_rect) - - surface_rect = self._surface_rect(snapped_rect) - base_color = mix_colors(self.substrate_color, self.surface_color, 0.10) - surface_color = mix_colors(AetherListColors.PANEL_BG, self.surface_color, 0.16 if self.enabled else 0.08) - if self._is_pressed: - surface_color = _tone_step(surface_color, -8) - border_color = mix_colors(AetherListColors.PANEL_BORDER, self.surface_color, 0.38, alpha=84 if self.enabled else 42) - - draw_rounded_fill(snapped_rect, base_color) - draw_rounded_fill(surface_rect, surface_color) - draw_rounded_stroke(surface_rect, border_color) - rl.draw_rectangle_rec(rl.Rectangle(surface_rect.x, surface_rect.y + surface_rect.height - 1, surface_rect.width, 1), with_alpha(border_color, 48)) - - return surface_rect - def _render_luxury_grid_layout( self, rect: rl.Rectangle, @@ -3807,7 +3757,7 @@ class AetherTile(Widget): font = getattr(self, "_font", gui_app.font(FontWeight.BOLD)) font_desc = getattr(self, "_font_desc", gui_app.font(FontWeight.MEDIUM)) - self._draw_text_fit(font, title_text, rl.Vector2(rx + content_pad, title_y), max_text_width, title_size, color=title_color) + draw_text_fit_common(font, title_text, rl.Vector2(rx + content_pad, title_y), max_text_width, title_size, color=title_color) if not enabled and getattr(self, "_disabled_label", ""): display_status = tr(self._disabled_label) if self._disabled_label else tr("LOCKED") @@ -3817,209 +3767,14 @@ class AetherTile(Widget): status_color = status_color_override if status_color_override is not None else accent if display_status: - self._draw_text_fit(font_desc, display_status, rl.Vector2(rx + content_pad, status_y), max_text_width, status_size, color=status_color) + draw_text_fit_common(font_desc, display_status, rl.Vector2(rx + content_pad, status_y), max_text_width, status_size, color=status_color) if right_renderer: right_renderer(rx, ry, rw, rh, content_pad, accent) - def _draw_text_fit( - self, - font: rl.Font, - text: str, - pos: rl.Vector2, - max_width: float, - font_size: float, - align_center: bool = False, - align_right: bool = False, - letter_spacing: float = 0, - uppercase: bool = False, - color: rl.Color = rl.WHITE, - shadow_alpha: int = 0, - ): - draw_text_fit_common( - font, - text, - pos, - max_width, - font_size, - align_center=align_center, - align_right=align_right, - letter_spacing=letter_spacing, - uppercase=uppercase, - color=color, - shadow_alpha=shadow_alpha, - ) - - def _centered_content( - self, face: rl.Rectangle, icon: rl.Texture2D | None, icon_scale: float, title_font_size: float, text_lines: int, line_heights: list[float] - ): - line_spacing = SPACING.line_gap - total_h = sum(line_heights) + line_spacing * (text_lines - 1) - icon_w = icon.width * icon_scale if icon else 0 - icon_h = icon.height * icon_scale if icon else 0 - if icon: - total_h += icon_h + line_spacing - group_top = face.y + (face.height - total_h) / 2 - if icon: - ix = face.x + (face.width - icon_w) / 2 - rl.draw_texture_pro(icon, rl.Rectangle(0, 0, icon.width, icon.height), rl.Rectangle(ix, group_top, icon_w, icon_h), rl.Vector2(0, 0), 0, rl.WHITE) - ty = group_top + icon_h + line_spacing - else: - ty = group_top - return face, ty - - def _wrap_text(self, font: rl.Font, text: str, max_width: float, font_size: float, max_lines: int = 2) -> list[str]: - return wrap_text(font, text, max_width, font_size, max_lines) - - def _draw_signal_edge(self, face: rl.Rectangle, color: rl.Color, width: int = 2, alpha: int = 58): - snapped_face = snap_rect(face) - signal_width = max(1, int(width)) - glow_color = with_alpha(color, max(4, alpha // 3)) - core_color = with_alpha(color, alpha) - rl.draw_rectangle_rec(rl.Rectangle(snapped_face.x, snapped_face.y, signal_width, snapped_face.height), glow_color) - inner_x = snapped_face.x + max(1, signal_width - 2) - inner_w = max(1, signal_width - 1) - rl.draw_rectangle_rec(rl.Rectangle(inner_x, snapped_face.y, inner_w, snapped_face.height), core_color) - - def _measure_tile_stack( - self, - face: rl.Rectangle, - *, - icon_height: float, - title_lines: int, - title_size: int, - primary_size: int, - desc_lines: int, - desc_size: int, - ) -> dict[str, float]: - title_block = title_lines * title_size + max(0, title_lines - 1) * SPACING.line_gap - icon_gap = SPACING.line_gap if icon_height > 0 else 0 - primary_gap = SPACING.line_gap if title_block > 0 else 0 - desc_gap = SPACING.sm if desc_lines > 0 else 0 - desc_block = desc_lines * desc_size + max(0, desc_lines - 1) * SPACING.xs - total_height = icon_height + icon_gap + title_block + primary_gap + primary_size + desc_gap + desc_block - available_height = face.height - if total_height > available_height: - overflow = total_height - available_height - reduce_icon = min(icon_height * 0.35, overflow) - icon_height -= reduce_icon - total_height -= reduce_icon - if total_height > available_height and desc_block > 0: - reduce_desc = min(desc_block * 0.5, total_height - available_height) - desc_block -= reduce_desc - total_height -= reduce_desc - if total_height > available_height: - title_block = max(title_size, title_block - (total_height - available_height)) - total_height = icon_height + icon_gap + title_block + primary_gap + primary_size + desc_gap + desc_block - top = max(face.y, face.y + (available_height - total_height) / 2) - title_y = top + icon_height + icon_gap - primary_y = title_y + title_block + primary_gap - desc_y = primary_y + primary_size + desc_gap - return { - "top": top, - "title_y": title_y, - "primary_y": primary_y, - "desc_y": desc_y, - "desc_bottom": desc_y + desc_block, - } - def _draw_custom_icon(self, key: str, x: float, y: float, s: float, color: rl.Color): draw_custom_icon(key, x, y, s, color) - def _render_tile_stack( - self, - face: rl.Rectangle, - *, - icon: rl.Texture2D | None = None, - title: str, - primary: str, - desc: str, - title_font: rl.Font, - primary_font: rl.Font, - desc_font: rl.Font, - title_size: int, - primary_size: int, - desc_size: int = 18, - custom_icon_key: str | None = None, - ): - content_pad = SPACING.tile_content - max_w = face.width - (content_pad * 2) - scale = max(0.82, min(1.12, min(face.width / 360.0, face.height / 205.0))) - title_size = max(22, int(round(title_size * scale))) - primary_size = max(18, int(round(primary_size * scale))) - desc_size = max(16, int(round(desc_size * scale))) - title_lines = self._wrap_text(title_font, title, max_w, title_size, max_lines=2) - has_icon = (icon is not None) or (custom_icon_key is not None) - icon_scale = min(0.80, max(0.56, scale * 0.72)) if has_icon else 0.0 - if custom_icon_key: - icon_height = CUSTOM_ICON_BASE_SIZE * CUSTOM_ICON_SCALE_MULT * icon_scale - elif icon: - icon_height = icon.height * icon_scale - else: - icon_height = 0.0 - desc_lines = self._wrap_text(desc_font, desc, max_w, desc_size, max_lines=3) if desc else [] - layout = self._measure_tile_stack( - face, - icon_height=icon_height, - title_lines=len(title_lines), - title_size=title_size, - primary_size=primary_size, - desc_lines=len(desc_lines), - desc_size=desc_size, - ) - - if custom_icon_key: - icon_width = CUSTOM_ICON_BASE_SIZE * CUSTOM_ICON_SCALE_MULT * icon_scale - icon_x = face.x + (face.width - icon_width) / 2 - s = icon_scale * (CUSTOM_ICON_BASE_SIZE / CUSTOM_ICON_CANVAS_SIZE) * CUSTOM_ICON_SCALE_MULT - self._draw_custom_icon(custom_icon_key, icon_x, layout["top"], s, mix_colors(rl.Color(255, 255, 255, 255), self.surface_color, 0.08)) - elif icon: - icon_width = icon.width * icon_scale - icon_x = face.x + (face.width - icon_width) / 2 - rl.draw_texture_pro( - icon, - rl.Rectangle(0, 0, icon.width, icon.height), - rl.Rectangle(icon_x, layout["top"], icon_width, icon_height), - rl.Vector2(0, 0), - 0, - rl.WHITE, - ) - - for i, line in enumerate(title_lines): - self._draw_text_fit( - title_font, - line, - rl.Vector2(face.x + content_pad, layout["title_y"] + i * (title_size + SPACING.line_gap)), - max_w, - title_size, - align_center=True, - color=AetherListColors.HEADER, - ) - - self._draw_text_fit( - primary_font, - primary, - rl.Vector2(face.x + content_pad, layout["primary_y"]), - max_w, - primary_size, - align_center=True, - color=AetherListColors.SUBTEXT if desc else AetherListColors.HEADER, - ) - - if desc_lines: - for i, line in enumerate(desc_lines): - self._draw_text_fit( - desc_font, - line, - rl.Vector2(face.x + content_pad, layout["desc_y"] + i * (desc_size + SPACING.xs)), - max_w, - desc_size, - align_center=True, - color=AetherListColors.MUTED, - ) - - return layout - def _constellation_seed(self) -> str: title = getattr(self, 'title', None) resolved = str(_resolve_value(title, '')) if title is not None else '' @@ -4122,14 +3877,14 @@ class HubTile(AetherTile): self._draw_custom_icon(self.custom_icon_key, icon_x, content_top, s, mix_colors(rl.Color(255, 255, 255, 255), accent, 0.08)) content_top += icon_h + gap - self._draw_text_fit(self._font_title, title_text, + draw_text_fit_common(self._font_title, title_text, rl.Vector2(rx + content_pad, content_top), max_w, title_size, align_center=True, color=rl.WHITE) content_top += title_size if desc_to_render: content_top += gap - self._draw_text_fit(self._font_desc, desc_to_render, + draw_text_fit_common(self._font_desc, desc_to_render, rl.Vector2(rx + content_pad, content_top), max_w, desc_size, align_center=True, color=AetherListColors.SUBTEXT) @@ -4157,7 +3912,6 @@ class ToggleTile(AetherTile): desc: str = "", is_enabled: Callable[[], bool] | None = None, disabled_label: str = "", - show_led: bool = True, ): if bg_color: super().__init__(surface_color=bg_color) @@ -4174,7 +3928,6 @@ class ToggleTile(AetherTile): self._inactive_color = rl.Color(120, 120, 120, 255) self._disabled_color = rl.Color(75, 75, 75, 255) self._disabled_label = disabled_label - self._show_led = show_led self._glow: float = 1.0 if (self.get_state() and (is_enabled or True)) else 0.0 def _update_state(self): @@ -4199,20 +3952,6 @@ class ToggleTile(AetherTile): self._plate_offset = 0.0 self._plate_target = 0.0 - if not self._show_led: - self.surface_color = self._disabled_color if not enabled else (self._active_color if active else self._inactive_color) - state_text = tr(self._disabled_label) if not enabled and self._disabled_label else (tr("LOCKED") if not enabled else (tr("ON") if active else tr("OFF"))) - signal_color = self._active_color if (enabled and active) else self.surface_color - alpha = 62 if (enabled and active) else 28 - - face = self._render_layers(rect) - self._draw_signal_edge(face, signal_color, width=TILE_SIGNAL_WIDTH, alpha=alpha) - self._render_tile_stack(face, title=self.title, primary=state_text, desc=self.desc, - title_font=self._font, primary_font=self._font, desc_font=self._font_desc, - title_size=41, primary_size=30) - return - - # --- HUD toggle path (show_led) --- if not enabled: face, accent = self._render_hud_background(rect, self._disabled_color, 0.0, bg_color=_HUD_BG_DISABLED, const_connected=False) else: @@ -4235,11 +3974,11 @@ class ToggleTile(AetherTile): curr_y = start_y for line in title_lines: - self._draw_text_fit(self._font, line, rl.Vector2(rx + content_pad, curr_y), max_w, title_size, align_center=True, color=_HUD_TEXT_DIM) + draw_text_fit_common(self._font, line, rl.Vector2(rx + content_pad, curr_y), max_w, title_size, align_center=True, color=_HUD_TEXT_DIM) curr_y += title_size + 4 curr_y += 6 for line in desc_lines: - self._draw_text_fit(self._font_desc, line, rl.Vector2(rx + content_pad, curr_y), max_w, desc_size, align_center=True, color=_HUD_TEXT_DIM) + draw_text_fit_common(self._font_desc, line, rl.Vector2(rx + content_pad, curr_y), max_w, desc_size, align_center=True, color=_HUD_TEXT_DIM) curr_y += desc_size + 2 else: title_color = rl.WHITE if active else _HUD_TEXT_DIM @@ -4255,13 +3994,13 @@ class ToggleTile(AetherTile): curr_y = title_y for line in title_lines: - self._draw_text_fit(self._font, line, rl.Vector2(rx + content_pad, curr_y), max_w, title_size, align_center=True, color=title_color) + draw_text_fit_common(self._font, line, rl.Vector2(rx + content_pad, curr_y), max_w, title_size, align_center=True, color=title_color) curr_y += title_size + 4 desc_y = title_y + len(title_lines) * (title_size + 4) + 6 curr_y = desc_y for line in desc_lines: - self._draw_text_fit(self._font_desc, line, rl.Vector2(rx + content_pad, curr_y), max_w, desc_size, align_center=True, color=rl.Color(255, 255, 255, 140)) + draw_text_fit_common(self._font_desc, line, rl.Vector2(rx + content_pad, curr_y), max_w, desc_size, align_center=True, color=rl.Color(255, 255, 255, 140)) curr_y += desc_size + 2 led_cx = rx + rw // 2 @@ -4274,7 +4013,7 @@ class ToggleTile(AetherTile): curr_y = title_y for line in title_lines: - self._draw_text_fit(self._font, line, rl.Vector2(rx + content_pad, curr_y), max_w, title_size, align_center=True, color=title_color) + draw_text_fit_common(self._font, line, rl.Vector2(rx + content_pad, curr_y), max_w, title_size, align_center=True, color=title_color) curr_y += title_size + 4 led_cx = rx + rw // 2 @@ -4305,7 +4044,6 @@ class RowToggleTile(ToggleTile): desc=desc, is_enabled=is_enabled, disabled_label=disabled_label, - show_led=True, ) def _render(self, rect: rl.Rectangle): @@ -4369,7 +4107,7 @@ class ValueTile(AetherTile): # Title title_size = max(26, int(round(32 * text_scale))) - self._draw_text_fit(self._font, self.title, + draw_text_fit_common(self._font, self.title, rl.Vector2(rx + content_pad, ry + int(rh * 0.35)), max_w, title_size, align_center=True, color=_HUD_TEXT_DIM) @@ -4377,41 +4115,11 @@ class ValueTile(AetherTile): val_text = self.get_value() val_size = max(26, int(round(35 * text_scale))) val_color = accent if enabled else _HUD_TEXT_DIM - self._draw_text_fit(self._font, val_text, + draw_text_fit_common(self._font, val_text, rl.Vector2(rx + content_pad, ry + int(rh * 0.58)), max_w, val_size, align_center=True, color=val_color) -class RowPanelTile(ValueTile): - def __init__( - self, - title: str, - on_click: Callable | None = None, - bg_color: rl.Color | str | None = None, - desc: str = "", - get_status: Callable[[], str] | None = None, - ): - super().__init__( - title=title, - get_value=get_status or (lambda: ""), - on_click=on_click, - bg_color=bg_color, - desc=desc, - ) - - def _render(self, rect: rl.Rectangle): - status_text = self.get_value() - - def draw_chevron(rx, ry, rw, rh, content_pad, accent): - chev_size = int(rh * 0.16) - cx = rx + rw - content_pad - chev_size / 2 - cy = ry + rh / 2 - chev_rect = rl.Rectangle(cx - chev_size, cy - chev_size, chev_size * 2, chev_size * 2) - draw_chevron_icon(chev_rect, rl.Color(160, 160, 175, 255), thickness=3.0, direction="right") - - self._render_luxury_grid_layout(rect, self.title, status_text, True, None, draw_chevron) - - class SliderTile(AetherTile): LONG_PRESS_THRESHOLD = 0.5 DRAG_THRESHOLD = 10 @@ -4537,7 +4245,7 @@ class SliderTile(AetherTile): # Title title_size = max(26, int(round(32 * text_scale))) - self._draw_text_fit(self._font, self.title, + draw_text_fit_common(self._font, self.title, rl.Vector2(rx + content_pad, ry + int(rh * 0.30)), max_w, title_size, align_center=True, color=_HUD_TEXT_DIM) @@ -4545,7 +4253,7 @@ class SliderTile(AetherTile): val_str = self.labels.get(current_val, f"{int(current_val)}{self.unit}") val_size = max(26, int(round(35 * text_scale))) val_color = accent if enabled else _HUD_TEXT_DIM - self._draw_text_fit(self._font, val_str, + draw_text_fit_common(self._font, val_str, rl.Vector2(rx + content_pad, ry + int(rh * 0.52)), max_w, val_size, align_center=True, color=val_color) @@ -4948,28 +4656,10 @@ class AetherSliderDialog(Widget): self._update_val_from_mouse(mouse_event.pos) def _render_preset_chip(self, rect: rl.Rectangle, text: str, *, current: bool, pressed: bool): - fill = rl.Color(255, 255, 255, 5) - border = rl.Color(255, 255, 255, 14) - text_color = AetherListColors.SUBTEXT - if current: - fill = mix_colors(rl.Color(18, 22, 28, 255), self._color, 0.22, alpha=255) - border = with_alpha(self._color, 72) - text_color = AetherListColors.HEADER - elif pressed: - fill = rl.Color(255, 255, 255, 10) - border = rl.Color(255, 255, 255, 22) - - draw_rounded_fill(rect, fill, radius_px=16) - draw_rounded_stroke(rect, border, radius_px=16) - draw_text_fit_common( - gui_app.font(FontWeight.MEDIUM), - text, - rl.Vector2(rect.x + 14, rect.y + (rect.height - 41) / 2), - max(1.0, rect.width - 28), - 41, - align_center=True, - color=text_color, - ) + draw_selectable_chip(rect, text, + current=current, pressed=pressed, + color=self._color, + font_size=41, radius_px=16, padding_x=14) def _render(self, rect: rl.Rectangle): dt = rl.get_frame_time() @@ -5252,28 +4942,10 @@ class AetherMultiSelectDialog(Widget): pass def _render_chip(self, rect: rl.Rectangle, text: str, *, current: bool, pressed: bool): - fill = rl.Color(255, 255, 255, 5) - border = rl.Color(255, 255, 255, 14) - text_color = AetherListColors.SUBTEXT - if current: - fill = mix_colors(rl.Color(18, 22, 28, 255), self._color, 0.22, alpha=255) - border = with_alpha(self._color, 72) - text_color = AetherListColors.HEADER - elif pressed: - fill = rl.Color(255, 255, 255, 10) - border = rl.Color(255, 255, 255, 22) - - draw_rounded_fill(rect, fill, radius_px=16) - draw_rounded_stroke(rect, border, radius_px=16) - draw_text_fit_common( - self._font_chip, - text, - rl.Vector2(rect.x + 10, rect.y + (rect.height - 28) / 2), - max(1.0, rect.width - 20), - 28, - align_center=True, - color=text_color, - ) + draw_selectable_chip(rect, text, + current=current, pressed=pressed, + color=self._color, font=self._font_chip, + font_size=28, radius_px=16, padding_x=10) def _render(self, rect: rl.Rectangle): dt = rl.get_frame_time() @@ -5402,7 +5074,7 @@ class AetherMultiSelectTile(AetherTile): text_scale = min(rw / 360.0, rh / 205.0) title_size = max(18, int(round(22 * text_scale))) - self._draw_text_fit(self._font, self.title, + draw_text_fit_common(self._font, self.title, rl.Vector2(rx + content_pad, ry + int(rh * 0.15)), max_w, title_size, align_center=True, color=_HUD_TEXT_DIM) @@ -5428,7 +5100,7 @@ class AetherMultiSelectTile(AetherTile): rl.draw_circle(int(led_cx), int(led_cy), 5, rl.Color(14, 16, 22, 255)) rl.draw_ring(rl.Vector2(led_cx, led_cy), 3, 4, 0, 360, 24, rl.Color(70, 78, 95, 140)) - self._draw_text_fit(self._font_desc, name, + draw_text_fit_common(self._font_desc, name, rl.Vector2(rx + content_pad + 26, y), max_w - 26, val_size, align_center=False, color=val_color) diff --git a/selfdrive/ui/layouts/settings/starpilot/appearance.py b/selfdrive/ui/layouts/settings/starpilot/appearance.py index 0f20577fb..e38beca70 100644 --- a/selfdrive/ui/layouts/settings/starpilot/appearance.py +++ b/selfdrive/ui/layouts/settings/starpilot/appearance.py @@ -2,11 +2,9 @@ from __future__ import annotations import re import math -from openpilot.system.ui.lib.application import gui_app, FontWeight, MousePos +from openpilot.system.ui.lib.application import gui_app, FontWeight from openpilot.system.ui.lib.multilang import tr, tr_noop -from openpilot.system.ui.lib.scroll_panel2 import GuiScrollPanel2 -from openpilot.system.ui.lib.text_measure import measure_text_cached -from openpilot.system.ui.widgets import DialogResult, Widget +from openpilot.system.ui.widgets import DialogResult from openpilot.system.ui.widgets.option_dialog import MultiOptionDialog from openpilot.selfdrive.ui.lib.starpilot_state import starpilot_state @@ -19,12 +17,9 @@ from openpilot.selfdrive.ui.layouts.settings.starpilot.aethergrid import ( SettingRow, SettingSection, AetherSettingsView, - AetherCategoryDrawer, TileGrid, HubTile, draw_list_group_shell, - hex_to_color, - PLATE_TAU, AetherListColors, ) from openpilot.selfdrive.ui.layouts.settings.starpilot.simple_download_manager import SimpleDownloadManager diff --git a/selfdrive/ui/layouts/settings/starpilot/lateral.py b/selfdrive/ui/layouts/settings/starpilot/lateral.py index 054b126bc..30a1d984e 100644 --- a/selfdrive/ui/layouts/settings/starpilot/lateral.py +++ b/selfdrive/ui/layouts/settings/starpilot/lateral.py @@ -1,6 +1,5 @@ from __future__ import annotations -import math import pyray as rl from openpilot.system.hardware import HARDWARE diff --git a/selfdrive/ui/layouts/settings/starpilot/panel.py b/selfdrive/ui/layouts/settings/starpilot/panel.py index 4fe2812d0..34fb32742 100644 --- a/selfdrive/ui/layouts/settings/starpilot/panel.py +++ b/selfdrive/ui/layouts/settings/starpilot/panel.py @@ -6,6 +6,7 @@ from enum import IntEnum import pyray as rl from openpilot.common.params import Params +from openpilot.starpilot.common.starpilot_variables import update_starpilot_toggles from openpilot.system.ui.lib.multilang import tr from openpilot.system.ui.lib.application import gui_app from openpilot.system.ui.widgets import DialogResult, Widget @@ -55,35 +56,29 @@ class FrameCachedParams: self._cache[cache_key] = self._params.get_float(key, **kwargs) return self._cache[cache_key] + def _notify_changed(self): + self._cache.clear() + update_starpilot_toggles() + def put(self, key, val, **kwargs): self._params.put(key, val, **kwargs) - self._cache.clear() - from openpilot.starpilot.common.starpilot_variables import update_starpilot_toggles - update_starpilot_toggles() + self._notify_changed() def put_bool(self, key, val, **kwargs): self._params.put_bool(key, val, **kwargs) - self._cache.clear() - from openpilot.starpilot.common.starpilot_variables import update_starpilot_toggles - update_starpilot_toggles() + self._notify_changed() def put_int(self, key, val, **kwargs): self._params.put_int(key, val, **kwargs) - self._cache.clear() - from openpilot.starpilot.common.starpilot_variables import update_starpilot_toggles - update_starpilot_toggles() + self._notify_changed() def put_float(self, key, val, **kwargs): self._params.put_float(key, val, **kwargs) - self._cache.clear() - from openpilot.starpilot.common.starpilot_variables import update_starpilot_toggles - update_starpilot_toggles() + self._notify_changed() def remove(self, key): self._params.remove(key) - self._cache.clear() - from openpilot.starpilot.common.starpilot_variables import update_starpilot_toggles - update_starpilot_toggles() + self._notify_changed() def __getattr__(self, name): return getattr(self._params, name) diff --git a/selfdrive/ui/layouts/settings/starpilot/sounds.py b/selfdrive/ui/layouts/settings/starpilot/sounds.py index e79b57cfb..a4400baca 100644 --- a/selfdrive/ui/layouts/settings/starpilot/sounds.py +++ b/selfdrive/ui/layouts/settings/starpilot/sounds.py @@ -433,4 +433,4 @@ while True: try: self._sound_player_process.stdin.write(f"{sound_path}|{volume}\n".encode()) self._sound_player_process.stdin.flush() - except: pass + except (BrokenPipeError, OSError): pass diff --git a/selfdrive/ui/tests/test_aethergrid.py b/selfdrive/ui/tests/test_aethergrid.py index 2642b0493..b79337e62 100644 --- a/selfdrive/ui/tests/test_aethergrid.py +++ b/selfdrive/ui/tests/test_aethergrid.py @@ -245,49 +245,12 @@ class TestAethergridContracts(unittest.TestCase): self.assertGreater(hit.height, tile._rect.height) - def test_aether_tile_uses_single_planar_face_contract(self): - mod = _import_aethergrid() - tile = mod.AetherTile(surface_color="#3B82F6") - face = tile._surface_rect(mod.rl.Rectangle(0, 0, 320, 160)) - - self.assertLess(face.width, 320) - self.assertLess(face.height, 160) - self.assertGreaterEqual(face.x, 0) - self.assertGreaterEqual(face.y, 0) - - def test_aether_tile_surface_rect_snaps_to_integer_pixels(self): - mod = _import_aethergrid() - tile = mod.AetherTile(surface_color="#3B82F6") - face = tile._surface_rect(mod.rl.Rectangle(0.5, 0.5, 320.25, 160.75)) - - self.assertEqual(face.x, round(face.x)) - self.assertEqual(face.y, round(face.y)) - self.assertEqual(face.width, round(face.width)) - self.assertEqual(face.height, round(face.height)) - - def test_aether_tile_preserves_substrate_color_attribute_for_compatibility(self): - mod = _import_aethergrid() - substrate = mod.hex_to_color("#101820") - tile = mod.AetherTile(surface_color="#3B82F6", substrate_color=substrate) - - self.assertIs(tile.substrate_color, substrate) - - def test_hub_tile_preserves_status_progress_api(self): mod = _import_aethergrid() tile = mod.HubTile("Driving Controls", "Desc", bg_color="#3B82F6", get_status=lambda: "Download 50%") self.assertEqual(tile.get_status(), "Download 50%") - def test_tile_stack_layout_keeps_full_content_block_inside_face(self): - mod = _import_aethergrid() - tile = mod.AetherTile(surface_color="#3B82F6") - face = mod.rl.Rectangle(0, 0, 320, 180) - layout = tile._measure_tile_stack(face, icon_height=60, title_lines=2, title_size=28, primary_size=30, desc_lines=2, desc_size=18) - - self.assertGreaterEqual(layout["top"], 0) - self.assertLessEqual(layout["desc_bottom"], face.height) - def test_tile_grid_reflows_to_wider_tiles_when_width_is_tight(self): mod = _import_aethergrid() grid = mod.TileGrid(columns=None, padding=mod.SPACING.tile_gap) @@ -452,13 +415,12 @@ class TestAethergridContracts(unittest.TestCase): def test_disabled_tiles_hud_mode_rendering(self): mod = _import_aethergrid() - # ToggleTile disabled, show_led=True + # ToggleTile disabled toggle = mod.ToggleTile( title="Test Loud", get_state=lambda: True, set_state=lambda s: None, is_enabled=lambda: False, - show_led=True ) # Spy on _render_hud_background orig_hud_bg = toggle._render_hud_background