From 7b06ec2ae67097d20bad7471518d1ddb3b26432b Mon Sep 17 00:00:00 2001 From: firestarsdog <229254897+firestarsdog@users.noreply.github.com> Date: Sat, 3 Oct 2026 15:30:50 -0400 Subject: [PATCH] Polish --- selfdrive/ui/layouts/settings/settings.py | 19 +++-- .../layouts/settings/starpilot/aethergrid.py | 9 ++- .../settings/starpilot/longitudinal.py | 20 ++--- .../ui/layouts/settings/starpilot/maps.py | 12 ++- .../starpilot/simple_download_manager.py | 75 ++++--------------- .../ui/layouts/settings/starpilot/vehicle.py | 2 +- .../ui/onroad/starpilot/navigation_card.py | 3 +- selfdrive/ui/tests/test_aethergrid.py | 31 ++++++++ .../ui/tests/test_simple_download_manager.py | 53 +++++++++++++ selfdrive/ui/translations/app.pot | 16 ++++ selfdrive/ui/translations/app_ar.po | 16 ++++ selfdrive/ui/translations/app_de.po | 16 ++++ selfdrive/ui/translations/app_en.po | 16 ++++ selfdrive/ui/translations/app_es.po | 16 ++++ selfdrive/ui/translations/app_fr.po | 16 ++++ selfdrive/ui/translations/app_ja.po | 16 ++++ selfdrive/ui/translations/app_ko.po | 16 ++++ selfdrive/ui/translations/app_pt-BR.po | 16 ++++ selfdrive/ui/translations/app_th.po | 16 ++++ selfdrive/ui/translations/app_tr.po | 16 ++++ selfdrive/ui/translations/app_uk.po | 16 ++++ selfdrive/ui/translations/app_zh-CHS.po | 16 ++++ selfdrive/ui/translations/app_zh-CHT.po | 16 ++++ selfdrive/ui/widgets/drive_stats.py | 6 +- selfdrive/ui/widgets/pairing_dialog.py | 12 +-- 25 files changed, 370 insertions(+), 96 deletions(-) create mode 100644 selfdrive/ui/tests/test_simple_download_manager.py diff --git a/selfdrive/ui/layouts/settings/settings.py b/selfdrive/ui/layouts/settings/settings.py index 22771d9fab..da1e6e1413 100644 --- a/selfdrive/ui/layouts/settings/settings.py +++ b/selfdrive/ui/layouts/settings/settings.py @@ -5,6 +5,7 @@ from collections.abc import Callable from openpilot.selfdrive.ui.layouts.settings.developer import DeveloperLayout from openpilot.selfdrive.ui.layouts.settings.device import DeviceLayout from openpilot.selfdrive.ui.layouts.settings.starpilot.main_panel import StarPilotLayout +from openpilot.selfdrive.ui.layouts.settings.starpilot.aethergrid import draw_text_fit_common from openpilot.selfdrive.ui.layouts.settings.software import SoftwareLayout from openpilot.selfdrive.ui.layouts.settings.toggles import TogglesLayout from openpilot.system.ui.lib.application import gui_app, FontWeight, MousePos @@ -24,6 +25,7 @@ CLOSE_BTN_SIZE = 200 CLOSE_ICON_SIZE = 70 NAV_BTN_HEIGHT = 110 PANEL_MARGIN = 10 +COLLAPSED_TAB_WIDTH = 64 # Colors SIDEBAR_COLOR = rl.BLACK @@ -126,7 +128,7 @@ class SettingsLayout(Widget): original_events = list(gui_app.mouse_events) if not self._sidebar_expanded: - tab_zone = rl.Rectangle(rect.x, rect.y + 581 - 70, 40, 140) + tab_zone = rl.Rectangle(rect.x, rect.y + 581 - 70, COLLAPSED_TAB_WIDTH, 140) gui_app.mouse_events[:] = [e for e in original_events if not rl.check_collision_point_rec(e.pos, tab_zone)] self._draw_current_panel(panel_rect) @@ -161,13 +163,14 @@ class SettingsLayout(Widget): # Unified Protruding Edge Tab (Expand/Collapse toggle) tab_cy = int(rect.y + 581) tab_h = 140 - tab_w = 70 - tab_x = rect.x - 30 # Leaves exactly 40px protruding onto the screen + visible_w = 40 if self._sidebar_expanded else COLLAPSED_TAB_WIDTH + tab_w = visible_w + 30 + tab_x = rect.x - 30 tab_y = tab_cy - (tab_h / 2) tab_rect = rl.Rectangle(tab_x, tab_y, tab_w, tab_h) # Hit zone is the visible portion on screen - self._collapse_btn_rect = rl.Rectangle(rect.x, tab_y, 40, tab_h) + self._collapse_btn_rect = rl.Rectangle(rect.x, tab_y, visible_w, tab_h) # Interaction state is_pressed = False @@ -192,7 +195,7 @@ class SettingsLayout(Widget): rl.draw_rectangle_rounded_lines_ex(tab_rect, 0.5, 30, 2.0, tab_border) # Chevron properly centered on the *visible* portion of the tab - chevron_x = rect.x + 20 + chevron_x = rect.x + visible_w / 2 self._draw_chevron(chevron_x, tab_cy, not self._sidebar_expanded, rl.Color(255, 255, 255, 255), size=24, bloom=True) if self._sidebar_expanded: @@ -234,8 +237,8 @@ class SettingsLayout(Widget): # Draw button text (right-aligned) panel_name = tr(panel_info.name) text_size = measure_text_cached(self._font_medium, panel_name, 65) - text_pos = rl.Vector2(button_rect.x + button_rect.width - text_size.x, button_rect.y + (button_rect.height - text_size.y) / 2) - rl.draw_text_ex(self._font_medium, panel_name, rl.Vector2(round(text_pos.x), round(text_pos.y)), 65, 0, text_color) + text_pos = rl.Vector2(button_rect.x, button_rect.y + (button_rect.height - text_size.y) / 2) + draw_text_fit_common(self._font_medium, panel_name, text_pos, button_rect.width, 65, align_right=True, color=text_color) # Store button rect for click detection panel_info.button_rect = button_rect @@ -253,7 +256,7 @@ class SettingsLayout(Widget): if not self._sidebar_expanded: # Only record swipe/tap start when touch is within the 10px left margin OR directly on the protruding tab gesture_zone = rl.Rectangle(self._rect.x, self._rect.y, 10, self._rect.height) - tab_zone = rl.Rectangle(self._rect.x, self._rect.y + 581 - 70, 40, 140) + tab_zone = rl.Rectangle(self._rect.x, self._rect.y + 581 - 70, COLLAPSED_TAB_WIDTH, 140) if rl.check_collision_point_rec(mouse_pos, gesture_zone) or rl.check_collision_point_rec(mouse_pos, tab_zone): self._swipe_start = mouse_pos else: diff --git a/selfdrive/ui/layouts/settings/starpilot/aethergrid.py b/selfdrive/ui/layouts/settings/starpilot/aethergrid.py index 6b688a9e68..418446a623 100644 --- a/selfdrive/ui/layouts/settings/starpilot/aethergrid.py +++ b/selfdrive/ui/layouts/settings/starpilot/aethergrid.py @@ -3013,7 +3013,10 @@ def draw_selection_list_row( if action_text: if action_pill: available_w = max(96.0, action_rect.width - 28) - chip_w = min(available_w, action_pill_width) if action_pill_width is not None else min(available_w, max(96.0, 42 + len(action_text) * 9)) + pill_width = action_pill_width + if pill_width is None: + pill_width = max(96.0, math.ceil(measure_text_cached(title_font, action_text, action_text_size).x) + 24) + chip_w = min(available_w, pill_width) chip_h = min(float(action_pill_height), max(36.0, action_rect.height - 28)) chip_rect = rl.Rectangle(action_rect.x + action_rect.width - chip_w - 18, action_rect.y + (action_rect.height - chip_h) / 2, chip_w, chip_h) draw_action_pill( @@ -4023,7 +4026,7 @@ class HubTile(AetherTile): icon_scale = min(0.80, max(0.56, text_scale * 0.72)) icon_h = CUSTOM_ICON_BASE_SIZE * CUSTOM_ICON_SCALE_MULT * icon_scale - total_h = icon_h + (gap if icon_h > 0 else 0) + title_size + (gap if desc_to_render else 0) + desc_size + total_h = icon_h + (gap if icon_h > 0 else 0) + (title_size + desc_size) * FONT_SCALE + (gap if desc_to_render else 0) content_top = ry + max(0, (rh - total_h) / 2) if self.custom_icon_key: @@ -4037,7 +4040,7 @@ class HubTile(AetherTile): 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 + content_top += title_size * FONT_SCALE if desc_to_render: content_top += gap diff --git a/selfdrive/ui/layouts/settings/starpilot/longitudinal.py b/selfdrive/ui/layouts/settings/starpilot/longitudinal.py index 8a674cc690..0f07bc0b23 100644 --- a/selfdrive/ui/layouts/settings/starpilot/longitudinal.py +++ b/selfdrive/ui/layouts/settings/starpilot/longitudinal.py @@ -555,23 +555,23 @@ class StarPilotLongitudinalLayout(_SettingsPage): visible=adv), SettingRow("MaxAccel", "value", tr_noop("Maximum Acceleration"), subtitle=tr_noop("Strongest acceleration openpilot is allowed to command."), - get_value=lambda: f"{self._params.get_float('MaxDesiredAcceleration'):.1f}m/s" if self._params.get_float("MaxDesiredAcceleration") is not None else "N/A", - on_click=lambda: self._show_slider("MaxDesiredAcceleration", 0.1, 4.0, step=0.1, unit="m/s", value_type="float"), + get_value=lambda: f"{self._params.get_float('MaxDesiredAcceleration'):.1f}m/s²" if self._params.get_float("MaxDesiredAcceleration") is not None else "N/A", + on_click=lambda: self._show_slider("MaxDesiredAcceleration", 0.1, 4.0, step=0.1, unit="m/s²", value_type="float"), visible=adv), SettingRow("StartAccel", "value", tr_noop("Start Acceleration"), subtitle=tr_noop("Extra acceleration when moving away from a stop."), - get_value=lambda: f"{self._params.get_float('StartAccel'):.2f}m/s", - on_click=lambda: self._show_slider("StartAccel", 0.0, 4.0, step=0.01, unit="m/s", value_type="float"), + get_value=lambda: f"{self._params.get_float('StartAccel'):.2f}m/s²", + on_click=lambda: self._show_slider("StartAccel", 0.0, 4.0, step=0.01, unit="m/s²", value_type="float"), visible=adv), SettingRow("StopAccel", "value", tr_noop("Stop Acceleration"), subtitle=tr_noop("Brake force to hold the vehicle at a complete stop."), - get_value=lambda: f"{self._params.get_float('StopAccel'):.2f}m/s", - on_click=lambda: self._show_slider("StopAccel", -4.0, 0.0, step=0.01, unit="m/s", value_type="float"), + get_value=lambda: f"{self._params.get_float('StopAccel'):.2f}m/s²", + on_click=lambda: self._show_slider("StopAccel", -4.0, 0.0, step=0.01, unit="m/s²", value_type="float"), visible=adv), SettingRow("StoppingRate", "value", tr_noop("Stopping Rate"), subtitle=tr_noop("How quickly braking ramps up to bring the car to a stop."), - get_value=lambda: f"{self._params.get_float('StoppingDecelRate'):.3f}m/s", - on_click=lambda: self._show_slider("StoppingDecelRate", 0.001, 1.0, step=0.001, unit="m/s", value_type="float"), + get_value=lambda: f"{self._params.get_float('StoppingDecelRate'):.3f}m/s²/s", + on_click=lambda: self._show_slider("StoppingDecelRate", 0.001, 1.0, step=0.001, unit="m/s²/s", value_type="float"), visible=self._show_stop_tuning_values), SettingRow("StartSpeed", "value", tr_noop("Start Speed"), subtitle=tr_noop("Speed where openpilot exits the stopped state."), @@ -674,7 +674,7 @@ class StarPilotLongitudinalLayout(_SettingsPage): self._curve_speed_controller_rows = [ SettingRow("CalibratedLatAccel", "value", tr_noop("Calibrated Lateral Accel"), subtitle=tr_noop("The learned lateral acceleration from collected driving data. Higher values allow faster cornering."), - get_value=lambda: f"{self._params.get_float('CalibratedLateralAcceleration'):.2f} m/s", + get_value=lambda: f"{self._params.get_float('CalibratedLateralAcceleration'):.2f} m/s²", on_click=None, visible=csc_on), SettingRow("CalibrationProgress", "value", tr_noop("Calibration Progress"), @@ -847,7 +847,7 @@ class StarPilotLongitudinalLayout(_SettingsPage): self._sub_panels["csc"] = AetherSettingsView( self, - [SettingSection(tr("Curve Speed Controller"), self._curve_speed_controller_rows)], + [SettingSection(title="", rows=self._curve_speed_controller_rows)], header_title=tr("Curve Speed Controller"), header_subtitle=tr("Configure speed control on curves and reset collected calibration data."), parent_toggle=pt_csc, diff --git a/selfdrive/ui/layouts/settings/starpilot/maps.py b/selfdrive/ui/layouts/settings/starpilot/maps.py index 26a8331d7a..710457ef77 100644 --- a/selfdrive/ui/layouts/settings/starpilot/maps.py +++ b/selfdrive/ui/layouts/settings/starpilot/maps.py @@ -350,7 +350,7 @@ class MapsManagerView(PanelManagerView): # Subtitle / Body Progress Description (26pt) gui_text_box( - rl.Rectangle(content_x, rect.y + 58, summary_w, 54), + rl.Rectangle(content_x, rect.y + 58, summary_w, 68), self._controller._progress_body(), 26, AetherListColors.SUBTEXT, @@ -452,9 +452,10 @@ class StarPilotMapsLayout(_SettingsPage): ) self._schedule_button = self._child( AetherButton( - lambda: tr("Update: {}").format(_localized_schedule_label(self._params.get('PreferredSchedule'))), + self._schedule_action_label, self._on_schedule, emphasized=False, + font_size=26, ) ) @@ -788,6 +789,13 @@ class StarPilotMapsLayout(_SettingsPage): else: self._on_download() + def _schedule_action_label(self) -> str: + return { + "Manually": tr("Auto: Off"), + "Weekly": tr("Auto: Weekly"), + "Monthly": tr("Auto: Monthly"), + }[schedule_label(self._params.get("PreferredSchedule"))] + def _on_schedule(self): localized_options = [(value, tr(label)) for value, label in MAP_SCHEDULE_LABELS.items()] options = [label for _, label in localized_options] diff --git a/selfdrive/ui/layouts/settings/starpilot/simple_download_manager.py b/selfdrive/ui/layouts/settings/starpilot/simple_download_manager.py index 633f8fdf82..c75957c814 100644 --- a/selfdrive/ui/layouts/settings/starpilot/simple_download_manager.py +++ b/selfdrive/ui/layouts/settings/starpilot/simple_download_manager.py @@ -10,9 +10,10 @@ from openpilot.system.ui.lib.multilang import tr 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 Widget, DialogResult +from openpilot.system.ui.widgets.confirm_dialog import ConfirmDialog from openpilot.selfdrive.ui.layouts.settings.starpilot.aethergrid import ( AetherListColors, AetherListMetrics, AetherScrollbar, - draw_hud_background, draw_soft_card, draw_action_pill, draw_selection_list_row, + draw_hud_background, draw_action_pill, draw_selection_list_row, draw_empty_state_card, draw_busy_ring, draw_list_scroll_fades, draw_rounded_fill, draw_rounded_stroke, draw_text_fit_common, draw_download_icon, @@ -167,9 +168,6 @@ class SimpleDownloadManager(Widget): self._slug_map: dict[str, str] = {} self._item_rects: dict[str, rl.Rectangle] = {} self._pill_rects: list[rl.Rectangle] = [] - self._confirm_target: str | None = None - self._confirm_yes_rect = rl.Rectangle(0, 0, 0, 0) - self._confirm_no_rect = rl.Rectangle(0, 0, 0, 0) self._close_rect = rl.Rectangle(0, 0, 0, 0) self._cancel_rect = rl.Rectangle(0, 0, 0, 0) self._info_message = "" @@ -234,7 +232,6 @@ class SimpleDownloadManager(Widget): _add_to_downloadable_list(self.params, self.downloadable_list_param, display_name) self._info_message = f"Deleted \"{display_name}\"" self._info_message_until = time.monotonic() + 2.5 - self._confirm_target = None self._refresh_list() def _start_download(self, display_name: str): @@ -266,12 +263,6 @@ class SimpleDownloadManager(Widget): for i, prect in enumerate(self._pill_rects): if rl.check_collision_point_rec(pos, prect): return f"mode:{i}" - if self._confirm_target is not None: - if rl.check_collision_point_rec(pos, self._confirm_yes_rect): - return "confirm_yes" - if rl.check_collision_point_rec(pos, self._confirm_no_rect): - return "confirm_no" - return None if self._downloading: if rl.check_collision_point_rec(pos, self._cancel_rect): return "cancel_download" @@ -301,7 +292,6 @@ class SimpleDownloadManager(Widget): if mode != self._active_mode: self._active_mode = mode self._scroll_offset = 0.0 - self._confirm_target = None self._refresh_list() elif target.startswith("item:"): idx = int(target.split(":")[1]) @@ -310,13 +300,15 @@ class SimpleDownloadManager(Widget): if self._active_mode == self.MODE_SELECT: self._select_asset(item) elif self._active_mode == self.MODE_DELETE: - self._confirm_target = item + def on_result(result: DialogResult): + if result == DialogResult.CONFIRM: + self._delete_asset(item) + + gui_app.push_widget(ConfirmDialog( + tr("Delete \"{name}\"?").format(name=item), tr("DELETE"), callback=on_result, + )) elif self._active_mode == self.MODE_DOWNLOAD: self._start_download(item) - elif target == "confirm_yes" and self._confirm_target is not None: - self._delete_asset(self._confirm_target) - elif target == "confirm_no": - self._confirm_target = None elif target == "cancel_download": self._cancel_download() @@ -450,7 +442,9 @@ class SimpleDownloadManager(Widget): # Measure content n_items = len(self._list_items) - content_height = float(n_items * self.ITEM_HEIGHT) + prog_h = 140 + progress_space = prog_h + 40 if self._downloading else 0 + content_height = float(n_items * self.ITEM_HEIGHT + progress_space) self._scroll_panel.set_enabled(True) self._scroll_offset = self._scroll_panel.update(scroll_rect, max(content_height, scroll_rect.height)) @@ -475,7 +469,6 @@ class SimpleDownloadManager(Widget): if self._downloading: # Download progress display - prog_h = 140 prog_rect = rl.Rectangle(scroll_rect.x + 20, scroll_rect.y + self._scroll_offset + 20, scroll_rect.width - 40, prog_h) draw_rounded_fill(prog_rect, with_alpha(rl.Color(255, 255, 255, 8), 255), radius_px=16) @@ -508,49 +501,7 @@ class SimpleDownloadManager(Widget): AetherListColors.HEADER, font_size=24, ) - item_y += prog_h + 40 - - if self._confirm_target is not None: - # Confirm overlay - confirm_w = min(720, scroll_rect.width - 60) - confirm_h = 220 - confirm_rect = rl.Rectangle( - scroll_rect.x + (scroll_rect.width - confirm_w) / 2, - scroll_rect.y + self._scroll_offset + (scroll_rect.height - confirm_h) / 2, - confirm_w, confirm_h, - ) - draw_soft_card(confirm_rect, rl.Color(20, 19, 25, 255), with_alpha(AetherListColors.PANEL_BORDER, 160), - radius=0.03, segments=18) - - confirm_font = gui_app.font(FontWeight.MEDIUM) - confirm_msg = tr("Delete \"{name}\"?").format(name=self._confirm_target) - draw_text_fit_common( - confirm_font, confirm_msg, - rl.Vector2(confirm_rect.x + 24, confirm_rect.y + 32), - confirm_rect.width - 48, 32, - align_center=True, color=AetherListColors.HEADER, - ) - - btn_w = (confirm_rect.width - 60) / 2 - btn_y = confirm_rect.y + confirm_rect.height - 76 - btn_h = 52 - - self._confirm_no_rect = snap_rect(rl.Rectangle(confirm_rect.x + 20, btn_y, btn_w, btn_h)) - self._confirm_yes_rect = snap_rect(rl.Rectangle(confirm_rect.x + confirm_rect.width - btn_w - 20, btn_y, btn_w, btn_h)) - - no_hovered = self._pressed_target == "confirm_no" - yes_hovered = self._pressed_target == "confirm_yes" - - draw_action_pill(self._confirm_no_rect, tr("CANCEL"), - with_alpha(rl.Color(255, 255, 255, 14 if no_hovered else 8), 255), - with_alpha(rl.Color(255, 255, 255, 36), 255), - AetherListColors.SUBTEXT, font_size=24) - draw_action_pill(self._confirm_yes_rect, tr("DELETE"), - with_alpha(AetherListColors.DANGER, 60 if yes_hovered else 36), - with_alpha(AetherListColors.DANGER, 120), - AetherListColors.HEADER, font_size=24) - - item_y += confirm_h + 20 + item_y += progress_space # Draw list items for idx, item in enumerate(self._list_items): diff --git a/selfdrive/ui/layouts/settings/starpilot/vehicle.py b/selfdrive/ui/layouts/settings/starpilot/vehicle.py index ce52f383ba..f300fdc909 100644 --- a/selfdrive/ui/layouts/settings/starpilot/vehicle.py +++ b/selfdrive/ui/layouts/settings/starpilot/vehicle.py @@ -541,7 +541,7 @@ class ButtonActionComboDialog(Widget): title_size = 64 ts = measure_text_cached(self._font_title, self._title, title_size) rl.draw_text_ex(self._font_title, self._title, - rl.Vector2(int(dx + (dialog_w - ts.x) / 2), int(dy + 87)), + rl.Vector2(int(dx + (dialog_w - ts.x) / 2), int(dy + 40)), title_size, 0, rl.WHITE) font_label = gui_app.font(FontWeight.MEDIUM) diff --git a/selfdrive/ui/onroad/starpilot/navigation_card.py b/selfdrive/ui/onroad/starpilot/navigation_card.py index 402d7d7f92..9a3585f2bb 100644 --- a/selfdrive/ui/onroad/starpilot/navigation_card.py +++ b/selfdrive/ui/onroad/starpilot/navigation_card.py @@ -10,6 +10,7 @@ import pyray as rl from openpilot.common.params import UnknownKeyName from openpilot.selfdrive.ui.ui_state import ui_state from openpilot.system.ui.lib.application import FONT_SCALE, FontWeight, gui_app +from openpilot.system.ui.lib.multilang import tr from openpilot.system.ui.lib.text_measure import measure_text_cached from openpilot.system.ui.widgets import Widget @@ -376,7 +377,7 @@ class NavigationCardRenderer(Widget): ) then_x = divider_x + 15 - then_label = "Then" + then_label = tr("Then") then_font_size = 36 then_size = measure_text_cached(self._font_medium, then_label, then_font_size) then_label_x = then_x + (then_section_width - 23 - then_size.x) / 2 diff --git a/selfdrive/ui/tests/test_aethergrid.py b/selfdrive/ui/tests/test_aethergrid.py index fed3ec2370..497d42743e 100644 --- a/selfdrive/ui/tests/test_aethergrid.py +++ b/selfdrive/ui/tests/test_aethergrid.py @@ -249,6 +249,37 @@ def test_empty_state_body_wraps_within_card(height, body_size, body_height, body assert body_labels[-1][1].endswith("...") +@pytest.mark.parametrize("action_text", ["DELETE", "DOWNLOAD", "Current", "Reset", "Set Key"]) +def test_auto_action_pill_keeps_readable_text_inside_action_rail(action_text): + mod = _import_aethergrid() + mod.FONT_SCALE = 1.242 + draws = [] + + def measure(_font, text, size, spacing=0): + return types.SimpleNamespace(x=len(text) * size * mod.FONT_SCALE * 0.65, y=size * mod.FONT_SCALE) + + with patch.object(mod, "measure_text_cached", measure), \ + patch.object(mod.rl, "draw_text_ex", lambda font, text, pos, size, spacing, color: draws.append((text, pos, size))): + pill = mod.draw_selection_list_row(mod.rl.Rectangle(0, 0, 1500, 128), title="Asset", action_text=action_text, + action_pill=True, action_text_size=26) + + text, pos, size = next(draw for draw in draws if draw[0] == action_text) + assert size == 26 + assert pill.x >= 1500 - mod.AETHER_LIST_METRICS.action_width + assert pill.x + pill.width <= 1500 + assert pos.x >= pill.x + 11 + assert pos.x + measure(None, text, size).x <= pill.x + pill.width - 11 + + +def test_action_pill_preserves_explicit_width_and_rail_limit(): + mod = _import_aethergrid() + row = mod.rl.Rectangle(0, 0, 1500, 128) + for requested_width in (120, 1000): + pill = mod.draw_selection_list_row(row, title="Asset", action_text="Download", action_pill=True, + action_pill_width=requested_width) + assert pill.width == min(requested_width, mod.AETHER_LIST_METRICS.action_width - 28) + + class TestAethergridContracts(unittest.TestCase): def test_aethergrid_module_imports_with_headless_stubs(self): mod = _import_aethergrid() diff --git a/selfdrive/ui/tests/test_simple_download_manager.py b/selfdrive/ui/tests/test_simple_download_manager.py new file mode 100644 index 0000000000..409149eecc --- /dev/null +++ b/selfdrive/ui/tests/test_simple_download_manager.py @@ -0,0 +1,53 @@ +from types import SimpleNamespace + +import pytest + +from openpilot.selfdrive.ui.layouts.settings.starpilot import simple_download_manager as downloads +from openpilot.system.ui.widgets import DialogResult + + +class FakeParams: + def __init__(self): + self.values = {} + + def get(self, key, encoding=None): + return self.values.get(key, "") + + def put(self, key, value): + self.values[key] = value + + +@pytest.mark.parametrize("result", [DialogResult.CANCEL, DialogResult.CONFIRM]) +def test_asset_deletion_requires_confirmation_and_preserves_other_assets(tmp_path, monkeypatch, result): + target = tmp_path / "boot_logo_a.png" + other = tmp_path / "boot_logo_b.png" + target.write_bytes(b"target") + other.write_bytes(b"other") + params = FakeParams() + manager = downloads.SimpleDownloadManager( + "Boot Logo", "boot logo", tmp_path, "Asset", "Download", "Downloadable", params, FakeParams(), lambda *_: None, + ) + manager._active_mode = manager.MODE_DELETE + manager._refresh_list() + dialogs = [] + monkeypatch.setattr(downloads, "ConfirmDialog", lambda text, confirm_text, callback: SimpleNamespace(text=text, callback=callback)) + monkeypatch.setattr(downloads.gui_app, "push_widget", dialogs.append) + + manager._on_target("item:0") + + assert len(dialogs) == 1 + assert "Boot Logo A" in dialogs[0].text + assert target.exists() and other.exists() + assert not params.values + + dialogs[0].callback(result) + + assert other.read_bytes() == b"other" + if result == DialogResult.CONFIRM: + assert not target.exists() + assert manager._list_items == ["Boot Logo B"] + assert params.values["Downloadable"] == "boot_logo_a" + else: + assert target.read_bytes() == b"target" + assert manager._list_items == ["Boot Logo A", "Boot Logo B"] + assert not params.values diff --git a/selfdrive/ui/translations/app.pot b/selfdrive/ui/translations/app.pot index abb6940a54..d2eb2e8cd5 100644 --- a/selfdrive/ui/translations/app.pot +++ b/selfdrive/ui/translations/app.pot @@ -1128,3 +1128,19 @@ msgstr "" #, python-format msgid "Reboot Device" msgstr "" + +#: selfdrive/ui/layouts/settings/starpilot/maps.py +msgid "Auto: Off" +msgstr "" + +#: selfdrive/ui/layouts/settings/starpilot/maps.py +msgid "Auto: Weekly" +msgstr "" + +#: selfdrive/ui/layouts/settings/starpilot/maps.py +msgid "Auto: Monthly" +msgstr "" + +#: selfdrive/ui/onroad/starpilot/navigation_card.py +msgid "Then" +msgstr "" diff --git a/selfdrive/ui/translations/app_ar.po b/selfdrive/ui/translations/app_ar.po index 608389fc07..afc72bed95 100644 --- a/selfdrive/ui/translations/app_ar.po +++ b/selfdrive/ui/translations/app_ar.po @@ -1216,3 +1216,19 @@ msgstr "✓ مشترك" #, python-format msgid "🔥 Firehose Mode 🔥" msgstr "🔥 وضع Firehose 🔥" + +#: selfdrive/ui/layouts/settings/starpilot/maps.py +msgid "Auto: Off" +msgstr "" + +#: selfdrive/ui/layouts/settings/starpilot/maps.py +msgid "Auto: Weekly" +msgstr "" + +#: selfdrive/ui/layouts/settings/starpilot/maps.py +msgid "Auto: Monthly" +msgstr "" + +#: selfdrive/ui/onroad/starpilot/navigation_card.py +msgid "Then" +msgstr "" diff --git a/selfdrive/ui/translations/app_de.po b/selfdrive/ui/translations/app_de.po index f32c27a9ef..675170b6ec 100644 --- a/selfdrive/ui/translations/app_de.po +++ b/selfdrive/ui/translations/app_de.po @@ -1219,3 +1219,19 @@ msgstr "✓ ABONNIERT" #, python-format msgid "🔥 Firehose Mode 🔥" msgstr "🔥 Firehose‑Modus 🔥" + +#: selfdrive/ui/layouts/settings/starpilot/maps.py +msgid "Auto: Off" +msgstr "" + +#: selfdrive/ui/layouts/settings/starpilot/maps.py +msgid "Auto: Weekly" +msgstr "" + +#: selfdrive/ui/layouts/settings/starpilot/maps.py +msgid "Auto: Monthly" +msgstr "" + +#: selfdrive/ui/onroad/starpilot/navigation_card.py +msgid "Then" +msgstr "Dann" diff --git a/selfdrive/ui/translations/app_en.po b/selfdrive/ui/translations/app_en.po index 6fbb537aff..762d45cc44 100644 --- a/selfdrive/ui/translations/app_en.po +++ b/selfdrive/ui/translations/app_en.po @@ -1205,3 +1205,19 @@ msgstr "✓ SUBSCRIBED" #, python-format msgid "🔥 Firehose Mode 🔥" msgstr "🔥 Firehose Mode 🔥" + +#: selfdrive/ui/layouts/settings/starpilot/maps.py +msgid "Auto: Off" +msgstr "Auto: Off" + +#: selfdrive/ui/layouts/settings/starpilot/maps.py +msgid "Auto: Weekly" +msgstr "Auto: Weekly" + +#: selfdrive/ui/layouts/settings/starpilot/maps.py +msgid "Auto: Monthly" +msgstr "Auto: Monthly" + +#: selfdrive/ui/onroad/starpilot/navigation_card.py +msgid "Then" +msgstr "Then" diff --git a/selfdrive/ui/translations/app_es.po b/selfdrive/ui/translations/app_es.po index 59b9e6dfdb..7bb80f1700 100644 --- a/selfdrive/ui/translations/app_es.po +++ b/selfdrive/ui/translations/app_es.po @@ -1223,3 +1223,19 @@ msgstr "✓ SUSCRITO" #, python-format msgid "🔥 Firehose Mode 🔥" msgstr "🔥 Modo Firehose 🔥" + +#: selfdrive/ui/layouts/settings/starpilot/maps.py +msgid "Auto: Off" +msgstr "Auto: No" + +#: selfdrive/ui/layouts/settings/starpilot/maps.py +msgid "Auto: Weekly" +msgstr "Auto: Semanal" + +#: selfdrive/ui/layouts/settings/starpilot/maps.py +msgid "Auto: Monthly" +msgstr "Auto: Mensual" + +#: selfdrive/ui/onroad/starpilot/navigation_card.py +msgid "Then" +msgstr "Luego" diff --git a/selfdrive/ui/translations/app_fr.po b/selfdrive/ui/translations/app_fr.po index f883d4d485..2fd5185964 100644 --- a/selfdrive/ui/translations/app_fr.po +++ b/selfdrive/ui/translations/app_fr.po @@ -1228,3 +1228,19 @@ msgstr "✓ ABONNÉ" #, python-format msgid "🔥 Firehose Mode 🔥" msgstr "🔥 Mode Firehose 🔥" + +#: selfdrive/ui/layouts/settings/starpilot/maps.py +msgid "Auto: Off" +msgstr "Auto : non" + +#: selfdrive/ui/layouts/settings/starpilot/maps.py +msgid "Auto: Weekly" +msgstr "Auto : hebdo" + +#: selfdrive/ui/layouts/settings/starpilot/maps.py +msgid "Auto: Monthly" +msgstr "Auto : mensuel" + +#: selfdrive/ui/onroad/starpilot/navigation_card.py +msgid "Then" +msgstr "Puis" diff --git a/selfdrive/ui/translations/app_ja.po b/selfdrive/ui/translations/app_ja.po index ca8aac1515..483abfde33 100644 --- a/selfdrive/ui/translations/app_ja.po +++ b/selfdrive/ui/translations/app_ja.po @@ -1195,3 +1195,19 @@ msgstr "✓ 登録済み" #, python-format msgid "🔥 Firehose Mode 🔥" msgstr "🔥 Firehoseモード 🔥" + +#: selfdrive/ui/layouts/settings/starpilot/maps.py +msgid "Auto: Off" +msgstr "" + +#: selfdrive/ui/layouts/settings/starpilot/maps.py +msgid "Auto: Weekly" +msgstr "" + +#: selfdrive/ui/layouts/settings/starpilot/maps.py +msgid "Auto: Monthly" +msgstr "" + +#: selfdrive/ui/onroad/starpilot/navigation_card.py +msgid "Then" +msgstr "" diff --git a/selfdrive/ui/translations/app_ko.po b/selfdrive/ui/translations/app_ko.po index f12aebaeb3..ed96b79ff2 100644 --- a/selfdrive/ui/translations/app_ko.po +++ b/selfdrive/ui/translations/app_ko.po @@ -1188,3 +1188,19 @@ msgstr "✓ 구독됨" #, python-format msgid "🔥 Firehose Mode 🔥" msgstr "🔥 파이어호스 모드 🔥" + +#: selfdrive/ui/layouts/settings/starpilot/maps.py +msgid "Auto: Off" +msgstr "" + +#: selfdrive/ui/layouts/settings/starpilot/maps.py +msgid "Auto: Weekly" +msgstr "" + +#: selfdrive/ui/layouts/settings/starpilot/maps.py +msgid "Auto: Monthly" +msgstr "" + +#: selfdrive/ui/onroad/starpilot/navigation_card.py +msgid "Then" +msgstr "" diff --git a/selfdrive/ui/translations/app_pt-BR.po b/selfdrive/ui/translations/app_pt-BR.po index 84b53c6e8d..e863abbbd1 100644 --- a/selfdrive/ui/translations/app_pt-BR.po +++ b/selfdrive/ui/translations/app_pt-BR.po @@ -1218,3 +1218,19 @@ msgstr "✓ ASSINADO" #, python-format msgid "🔥 Firehose Mode 🔥" msgstr "🔥 Modo Firehose 🔥" + +#: selfdrive/ui/layouts/settings/starpilot/maps.py +msgid "Auto: Off" +msgstr "" + +#: selfdrive/ui/layouts/settings/starpilot/maps.py +msgid "Auto: Weekly" +msgstr "" + +#: selfdrive/ui/layouts/settings/starpilot/maps.py +msgid "Auto: Monthly" +msgstr "" + +#: selfdrive/ui/onroad/starpilot/navigation_card.py +msgid "Then" +msgstr "Depois" diff --git a/selfdrive/ui/translations/app_th.po b/selfdrive/ui/translations/app_th.po index f2e56f2882..561dd11455 100644 --- a/selfdrive/ui/translations/app_th.po +++ b/selfdrive/ui/translations/app_th.po @@ -1127,3 +1127,19 @@ msgstr "" #, python-format msgid "🔥 Firehose Mode 🔥" msgstr "" + +#: selfdrive/ui/layouts/settings/starpilot/maps.py +msgid "Auto: Off" +msgstr "" + +#: selfdrive/ui/layouts/settings/starpilot/maps.py +msgid "Auto: Weekly" +msgstr "" + +#: selfdrive/ui/layouts/settings/starpilot/maps.py +msgid "Auto: Monthly" +msgstr "" + +#: selfdrive/ui/onroad/starpilot/navigation_card.py +msgid "Then" +msgstr "" diff --git a/selfdrive/ui/translations/app_tr.po b/selfdrive/ui/translations/app_tr.po index 10191234a1..a322c7ff19 100644 --- a/selfdrive/ui/translations/app_tr.po +++ b/selfdrive/ui/translations/app_tr.po @@ -1208,3 +1208,19 @@ msgstr "✓ ABONE" #, python-format msgid "🔥 Firehose Mode 🔥" msgstr "🔥 Firehose Modu 🔥" + +#: selfdrive/ui/layouts/settings/starpilot/maps.py +msgid "Auto: Off" +msgstr "" + +#: selfdrive/ui/layouts/settings/starpilot/maps.py +msgid "Auto: Weekly" +msgstr "" + +#: selfdrive/ui/layouts/settings/starpilot/maps.py +msgid "Auto: Monthly" +msgstr "" + +#: selfdrive/ui/onroad/starpilot/navigation_card.py +msgid "Then" +msgstr "Sonra" diff --git a/selfdrive/ui/translations/app_uk.po b/selfdrive/ui/translations/app_uk.po index cf78fb5a33..ad945b4028 100644 --- a/selfdrive/ui/translations/app_uk.po +++ b/selfdrive/ui/translations/app_uk.po @@ -1256,3 +1256,19 @@ msgstr "✓ ПІДПИСАНО" #, python-format msgid "🔥 Firehose Mode 🔥" msgstr "🌧️ Режим зливи 🌧️" + +#: selfdrive/ui/layouts/settings/starpilot/maps.py +msgid "Auto: Off" +msgstr "" + +#: selfdrive/ui/layouts/settings/starpilot/maps.py +msgid "Auto: Weekly" +msgstr "" + +#: selfdrive/ui/layouts/settings/starpilot/maps.py +msgid "Auto: Monthly" +msgstr "" + +#: selfdrive/ui/onroad/starpilot/navigation_card.py +msgid "Then" +msgstr "" diff --git a/selfdrive/ui/translations/app_zh-CHS.po b/selfdrive/ui/translations/app_zh-CHS.po index 16e4369476..3432b13fb1 100644 --- a/selfdrive/ui/translations/app_zh-CHS.po +++ b/selfdrive/ui/translations/app_zh-CHS.po @@ -1172,3 +1172,19 @@ msgstr "✓ 已订阅" #, python-format msgid "🔥 Firehose Mode 🔥" msgstr "🔥 Firehose 模式 🔥" + +#: selfdrive/ui/layouts/settings/starpilot/maps.py +msgid "Auto: Off" +msgstr "" + +#: selfdrive/ui/layouts/settings/starpilot/maps.py +msgid "Auto: Weekly" +msgstr "" + +#: selfdrive/ui/layouts/settings/starpilot/maps.py +msgid "Auto: Monthly" +msgstr "" + +#: selfdrive/ui/onroad/starpilot/navigation_card.py +msgid "Then" +msgstr "" diff --git a/selfdrive/ui/translations/app_zh-CHT.po b/selfdrive/ui/translations/app_zh-CHT.po index 85cfb77401..b966718366 100644 --- a/selfdrive/ui/translations/app_zh-CHT.po +++ b/selfdrive/ui/translations/app_zh-CHT.po @@ -1171,3 +1171,19 @@ msgstr "✓ 已訂閱" #, python-format msgid "🔥 Firehose Mode 🔥" msgstr "🔥 Firehose 模式 🔥" + +#: selfdrive/ui/layouts/settings/starpilot/maps.py +msgid "Auto: Off" +msgstr "" + +#: selfdrive/ui/layouts/settings/starpilot/maps.py +msgid "Auto: Weekly" +msgstr "" + +#: selfdrive/ui/layouts/settings/starpilot/maps.py +msgid "Auto: Monthly" +msgstr "" + +#: selfdrive/ui/onroad/starpilot/navigation_card.py +msgid "Then" +msgstr "" diff --git a/selfdrive/ui/widgets/drive_stats.py b/selfdrive/ui/widgets/drive_stats.py index 6b2fcbe85b..fe76dda395 100644 --- a/selfdrive/ui/widgets/drive_stats.py +++ b/selfdrive/ui/widgets/drive_stats.py @@ -702,7 +702,7 @@ class DriveStatsDashboard: max_distance = max(max_distance, 1.0) slot_width = plot.width / max(len(self._data.daily_distance), 1) bar_width = min(78.0, slot_width * 0.52) - value_headroom = 38.0 + value_headroom = 48.0 bar_area_height = max(1.0, plot.height - value_headroom) for index, day in enumerate(self._data.daily_distance): center_x = plot.x + slot_width * (index + 0.5) @@ -712,13 +712,13 @@ class DriveStatsDashboard: if day.distance > 0.0: value_text = _format_decimal(day.distance) - value_size = measure_text_cached(self._font_medium, value_text, 21) + value_size = measure_text_cached(self._font_medium, value_text, 26) value_y = max(plot.y + 4, bar_rect.y - value_size.y - 8) rl.draw_text_ex( self._font_medium, value_text, rl.Vector2(center_x - value_size.x / 2, value_y), - 21, + 26, 0, MUTED_COLOR, ) diff --git a/selfdrive/ui/widgets/pairing_dialog.py b/selfdrive/ui/widgets/pairing_dialog.py index 7077c09c40..acfbc5d297 100644 --- a/selfdrive/ui/widgets/pairing_dialog.py +++ b/selfdrive/ui/widgets/pairing_dialog.py @@ -93,9 +93,9 @@ class PairingDialog(Widget): title_font = gui_app.font(FontWeight.NORMAL) left_width = int(content_rect.width * 0.5 - 15) - title_wrapped = wrap_text(title_font, title, 75, left_width) - rl.draw_text_ex(title_font, "\n".join(title_wrapped), rl.Vector2(content_rect.x, y), 75, 0.0, rl.BLACK) - y += len(title_wrapped) * 75 + 60 + title_wrapped = "\n".join(wrap_text(title_font, title, 75, left_width)) + rl.draw_text_ex(title_font, title_wrapped, rl.Vector2(content_rect.x, y), 75, 0.0, rl.BLACK) + y += measure_text_cached(title_font, title_wrapped, 75).y + 60 # Two columns: instructions and QR code remaining_height = content_rect.height - (y - content_rect.y) @@ -128,8 +128,8 @@ class PairingDialog(Widget): text_x = rect.x + circle_radius * 2 + 40 text_width = rect.width - (circle_radius * 2 + 40) - wrapped = wrap_text(font, text, 47, int(text_width)) - text_height = len(wrapped) * 47 + wrapped = "\n".join(wrap_text(font, text, 47, int(text_width))) + text_height = measure_text_cached(font, wrapped, 47).y circle_y = y + text_height // 2 # Circle and number @@ -139,7 +139,7 @@ class PairingDialog(Widget): rl.draw_text_ex(font, number, (int(circle_x - number_size.x // 2), int(circle_y - number_size.y // 2)), 30, 0, rl.WHITE) # Text - rl.draw_text_ex(font, "\n".join(wrapped), rl.Vector2(text_x, y), 47, 0.0, rl.BLACK) + rl.draw_text_ex(font, wrapped, rl.Vector2(text_x, y), 47, 0.0, rl.BLACK) y += text_height + 50 def _render_qr_code(self, rect: rl.Rectangle) -> None: