From 944f0f087da8a9a0a946a996346b41c6b97151b4 Mon Sep 17 00:00:00 2001 From: firestarsdog <229254897+firestarsdog@users.noreply.github.com> Date: Tue, 18 Aug 2026 01:57:20 -0400 Subject: [PATCH] Domi's Olo Biscuits --- .../ui/onroad/starpilot/slc_speed_limit.py | 75 ++++++++++++------- .../onroad/starpilot/source_bubble_layout.py | 9 ++- selfdrive/ui/tests/test_slc_sources_bubble.py | 22 +++--- 3 files changed, 64 insertions(+), 42 deletions(-) diff --git a/selfdrive/ui/onroad/starpilot/slc_speed_limit.py b/selfdrive/ui/onroad/starpilot/slc_speed_limit.py index 755f5aa73..9b9b5c969 100644 --- a/selfdrive/ui/onroad/starpilot/slc_speed_limit.py +++ b/selfdrive/ui/onroad/starpilot/slc_speed_limit.py @@ -427,8 +427,8 @@ _SOURCE_PANEL_BG = rl.Color(0, 0, 0, 175) _SOURCE_PANEL_BORDER = rl.Color(196, 205, 208, 80) _SOURCE_DIVIDER = rl.Color(196, 205, 208, 100) _SOURCE_ACTIVE_BAR = rl.Color(CONTROL_BORDER.r, CONTROL_BORDER.g, CONTROL_BORDER.b, 230) -_SOURCE_ICON_MUTED = rl.Color(196, 205, 208, 220) -_SOURCE_LABEL = rl.Color(255, 255, 255, 235) +_SOURCE_ICON_MUTED = rl.Color(160, 170, 175, 200) +_SOURCE_LABEL_MUTED = rl.Color(166, 166, 166, 255) _SOURCE_ACTIVE_BAR_WIDTH = 6.0 _SOURCE_ACTIVE_BAR_HEIGHT = 36.0 _SOURCE_ACTIVE_BAR_X = 2.0 @@ -540,6 +540,23 @@ def _draw_source_icon(icon_key: str, x: float, y: float, size: float, color: rl. rl.draw_circle_v(pivot, max(2.0, size * 0.06 * dashboard_scale), color) +def _draw_sources_bubble_empty_state(panel_rect: rl.Rectangle) -> None: + """Draw the 3-line centered empty state when no sources are available.""" + font = _get_semi_bold() + font_size = 30 + line_gap = 6.0 + lines = (tr("NO"), tr("SOURCES"), tr("AVAILABLE")) + + line_sizes = [measure_text_cached(font, line, font_size) for line in lines] + total_h = sum(sz.y for sz in line_sizes) + line_gap * (len(lines) - 1) + curr_y = round(panel_rect.y + (panel_rect.height - total_h) / 2) + + for line, sz in zip(lines, line_sizes): + pos_x = round(panel_rect.x + (panel_rect.width - sz.x) / 2) + rl.draw_text_ex(font, line, rl.Vector2(pos_x, curr_y), font_size, 0, _WHITE) + curr_y += round(sz.y + line_gap) + + def _draw_sources_bubble(state: dict, sign_rect: rl.Rectangle): """Draw the expanded source list attached to the SLC card.""" font_semi = _get_semi_bold() @@ -549,6 +566,17 @@ def _draw_sources_bubble(state: dict, sign_rect: rl.Rectangle): active_only = state.get('slc_active_sources_only', False) abbreviated = state.get('slc_abbreviated_sources', False) + panel_rect = rl.Rectangle( + sign_rect.x + sign_rect.width + _SOURCE_PANEL_GAP, + sign_rect.y, + _SOURCE_PANEL_WIDTH, + sign_rect.height, + ) + rl.draw_rectangle_rounded(panel_rect, CONTROL_ROUNDNESS, CONTROL_SEGMENTS, _SOURCE_PANEL_BG) + rl.draw_rectangle_rounded_lines_ex( + panel_rect, CONTROL_ROUNDNESS, CONTROL_SEGMENTS, 1, _SOURCE_PANEL_BORDER, + ) + rows = [ ( panel_label, @@ -563,19 +591,9 @@ def _draw_sources_bubble(state: dict, sign_rect: rl.Rectangle): ] if not rows: + _draw_sources_bubble_empty_state(panel_rect) return - panel_rect = rl.Rectangle( - sign_rect.x + sign_rect.width + _SOURCE_PANEL_GAP, - sign_rect.y, - _SOURCE_PANEL_WIDTH, - sign_rect.height, - ) - rl.draw_rectangle_rounded(panel_rect, CONTROL_ROUNDNESS, CONTROL_SEGMENTS, _SOURCE_PANEL_BG) - rl.draw_rectangle_rounded_lines_ex( - panel_rect, CONTROL_ROUNDNESS, CONTROL_SEGMENTS, 1, _SOURCE_PANEL_BORDER, - ) - row_h = (panel_rect.height - 2 * _SOURCE_PANEL_PAD_Y) / len(rows) content_left = panel_rect.x + _SOURCE_PANEL_PAD_X content_right = panel_rect.x + panel_rect.width - _SOURCE_PANEL_PAD_X @@ -588,7 +606,7 @@ def _draw_sources_bubble(state: dict, sign_rect: rl.Rectangle): for index, (panel_label, compact_label, icon_key, value, is_active) in enumerate(rows): row_y = panel_rect.y + _SOURCE_PANEL_PAD_Y + index * row_h if index: - divider_y = row_y + divider_y = round(row_y) rl.draw_line_ex( rl.Vector2(content_left, divider_y), rl.Vector2(content_right, divider_y), @@ -603,13 +621,15 @@ def _draw_sources_bubble(state: dict, sign_rect: rl.Rectangle): ) active_bar_rect = rl.Rectangle( panel_rect.x + _SOURCE_ACTIVE_BAR_X, - row_y + (row_h - active_bar_height) / 2, + round(row_y + (row_h - active_bar_height) / 2), _SOURCE_ACTIVE_BAR_WIDTH, active_bar_height, ) rl.draw_rectangle_rounded(active_bar_rect, 0.5, 4, _SOURCE_ACTIVE_BAR) value_text = source_value_text(value) + text_color = _WHITE if is_active else _SOURCE_LABEL_MUTED + if abbreviated: text_font = font_bold if is_active else font_semi label_text = fit_source_label( @@ -619,19 +639,19 @@ def _draw_sources_bubble(state: dict, sign_rect: rl.Rectangle): lambda text: measure_text_cached(text_font, text, font_size).x, ) label_size = measure_text_cached(text_font, label_text, font_size) - baseline_y = row_y + (row_h - label_size.y) / 2 + text_y = round(row_y + (row_h - label_size.y) / 2) rl.draw_text_ex( text_font, label_text, - rl.Vector2(label_left, baseline_y), + rl.Vector2(label_left, text_y), font_size, 0, - _WHITE if is_active else _SOURCE_LABEL, + text_color, ) continue compact_label = tr(compact_label) - full_label = compact_label + full_label = tr(panel_label) value_size = measure_text_cached(font_bold, value_text, font_size) max_label_width = max( 0.0, @@ -645,18 +665,16 @@ def _draw_sources_bubble(state: dict, sign_rect: rl.Rectangle): ) label_size = measure_text_cached(font_semi, label_text, font_size) text_height = max(label_size.y, value_size.y) - baseline_y = row_y + (row_h - text_height) / 2 - icon_y = row_y + (row_h - icon_size) / 2 + text_y = round(row_y + (row_h - text_height) / 2) + icon_y = round(row_y + (row_h - icon_size) / 2) icon_color = _WHITE if is_active else _SOURCE_ICON_MUTED _draw_source_icon(icon_key, content_left, icon_y, icon_size, icon_color) - label_pos = rl.Vector2(label_left, baseline_y) - value_pos = rl.Vector2(content_right - value_size.x, baseline_y) - label_color = _WHITE if is_active else _SOURCE_LABEL - value_color = _WHITE if is_active else _SOURCE_LABEL - rl.draw_text_ex(font_semi, label_text, label_pos, font_size, 0, label_color) - rl.draw_text_ex(font_bold, value_text, value_pos, font_size, 0, value_color) + label_pos = rl.Vector2(label_left, text_y) + value_pos = rl.Vector2(round(content_right - value_size.x), text_y) + rl.draw_text_ex(font_semi, label_text, label_pos, font_size, 0, text_color) + rl.draw_text_ex(font_bold, value_text, value_pos, font_size, 0, text_color) # ── Public API ──────────────────────────────────────────────────────── @@ -674,8 +692,7 @@ def render_speed_limit_at(state: dict, rect: rl.Rectangle, expanded: bool = Fals use_vienna = state['use_vienna'] visual_rect = rl.Rectangle(rect.x, rect.y, EU_SIGN_SIZE, EU_SIGN_SIZE) if use_vienna else rect - source = state.get('speed_limit_source') - if expanded and source and source != "None" and source != "": + if expanded: _draw_sources_bubble(state, visual_rect) return visual_rect diff --git a/selfdrive/ui/onroad/starpilot/source_bubble_layout.py b/selfdrive/ui/onroad/starpilot/source_bubble_layout.py index 0aba43118..ef4e0d760 100644 --- a/selfdrive/ui/onroad/starpilot/source_bubble_layout.py +++ b/selfdrive/ui/onroad/starpilot/source_bubble_layout.py @@ -59,22 +59,23 @@ def visible_source_rows( values: Mapping[str, float], active_source: str, enabled_sources: Iterable[str], - active_only: bool, + active_only: bool = True, ) -> list[tuple[str, str, float, bool]]: - """Return enabled source rows, optionally excluding empty readings.""" + """Return enabled source rows that possess a valid positive speed reading.""" enabled = set(enabled_sources) rows = [] for title, _abbrev, value_key, panel_label, icon_key in source_defs: if title not in enabled: continue value = values[value_key] - if active_only and (not math.isfinite(value) or value <= 0): + has_reading = math.isfinite(value) and value > 0 and round(value) > 0 + if not has_reading: continue rows.append(( panel_label, icon_key, value, - active_source == title and math.isfinite(value) and value > 0, + active_source == title, )) return rows diff --git a/selfdrive/ui/tests/test_slc_sources_bubble.py b/selfdrive/ui/tests/test_slc_sources_bubble.py index 55cdf06e2..41e90f9ff 100644 --- a/selfdrive/ui/tests/test_slc_sources_bubble.py +++ b/selfdrive/ui/tests/test_slc_sources_bubble.py @@ -59,22 +59,26 @@ def test_visible_source_rows_honor_active_only_and_source_order(): ] values = {"dashboard": 45.0, "map": 0.0, "vision": 50.0, "mapbox": 30.0, "next": 20.0} + # Map Data has value 0.0, so it is omitted; Dashboard (45.0) is active assert visible_source_rows( - source_defs, values, "Dashboard", ("Dashboard", "Map Data"), False, - ) == [ - ("Dashboard", "dashboard", 45.0, True), - ("Map Data", "map", 0.0, False), - ] - assert visible_source_rows( - source_defs, values, "Dashboard", ("Dashboard", "Map Data"), True, + source_defs, values, "Dashboard", ("Dashboard", "Map Data"), ) == [ ("Dashboard", "dashboard", 45.0, True), ] + # When Map Data is the active target but has 0.0 reading, Dashboard is inactive (available standby) assert visible_source_rows( - source_defs, values, "Map Data", ("Dashboard", "Map Data"), True, + source_defs, values, "Map Data", ("Dashboard", "Map Data"), ) == [ ("Dashboard", "dashboard", 45.0, False), ] + # Multiple available sources with readings appear in canonical order assert visible_source_rows( - source_defs, {key: 0.0 for key in values}, "Map Data", ("Map Data",), True, + source_defs, values, "Vision", ("Dashboard", "Map Data", "Vision"), + ) == [ + ("Dashboard", "dashboard", 45.0, False), + ("Vision", "camera", 50.0, True), + ] + # When no sources have a valid speed reading (> 0), returns empty list (triggers empty state) + assert visible_source_rows( + source_defs, {key: 0.0 for key in values}, "Map Data", ("Map Data",), ) == []