diff --git a/selfdrive/ui/layouts/settings/starpilot/aethergrid.py b/selfdrive/ui/layouts/settings/starpilot/aethergrid.py index e9841d02c5..1161c86209 100644 --- a/selfdrive/ui/layouts/settings/starpilot/aethergrid.py +++ b/selfdrive/ui/layouts/settings/starpilot/aethergrid.py @@ -2952,6 +2952,14 @@ class SettingSection: row_height: int = ROW_HEIGHT +@dataclass +class ParentToggle: + label: str + get_state: Callable[[], bool] + set_state: Callable[[bool], None] + subtitle: str = "" + + # ── AetherSettingsView — reusable list-panel ManagerView ── class AetherSettingsView(PanelManagerView): @@ -2965,6 +2973,7 @@ class AetherSettingsView(PanelManagerView): def __init__(self, controller, sections: list[SettingSection], *, header_title: str = "", header_subtitle: str = "", + parent_toggle: ParentToggle | None = None, tab_defs: list[dict] | None = None, panel_style=None, fade_height: float = AETHER_LIST_METRICS.fade_height, metrics = COMPACT_PANEL_METRICS): @@ -2976,7 +2985,8 @@ class AetherSettingsView(PanelManagerView): self._sections = sections self._header_title = header_title self._header_subtitle = header_subtitle - self._has_header = bool(header_title) + self._parent_toggle = parent_toggle + 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) @@ -2992,9 +3002,20 @@ class AetherSettingsView(PanelManagerView): return row return None + def _target_at(self, mouse_pos: MousePos) -> str | None: + if self._parent_toggle: + mid = f"parent_toggle:{self._parent_toggle.label}" + rect = self._interactive_rects.get(mid) + if rect and point_hits(mouse_pos, rect, None, pad_x=6, pad_y=6): + return mid + return super()._target_at(mouse_pos) + def _activate_target(self, target_id: str | None): if not target_id: return + if target_id.startswith("parent_toggle:") and self._parent_toggle: + self._parent_toggle.set_state(not self._parent_toggle.get_state()) + return if target_id.startswith("tab:") and self._tab_defs: self._active_tab_key = target_id[4:] return @@ -3013,6 +3034,19 @@ class AetherSettingsView(PanelManagerView): def _compute_header_height(self, content_width: float) -> float: if not self._has_header: return 0.0 + if self._parent_toggle: + h = max(42.0, 40.0) # toggle (42px at header top) vs title area (32px + 8px gap) + subtitle_text = tr(self._parent_toggle.subtitle) if self._parent_toggle.subtitle else "" + if self._header_subtitle: + subtitle_text = tr(self._header_subtitle) + if subtitle_text: + toggle_take = AETHER_LIST_METRICS.toggle_width + AETHER_LIST_METRICS.toggle_right_inset + 16 + col_w = max(100.0, content_width + AETHER_LIST_METRICS.content_right_gutter - toggle_take) + desc_font = gui_app.font(FontWeight.NORMAL) + desc_lines = wrap_text(desc_font, subtitle_text, col_w, 18, max_lines=4) + h += len(desc_lines) * 22.0 + 12.0 + h += SECTION_GAP + return h h = 40.0 # title (32px) + inner gap (8px) if self._header_subtitle: subtitle_text = tr(self._header_subtitle) @@ -3066,7 +3100,42 @@ class AetherSettingsView(PanelManagerView): def _draw_header(self, rect: rl.Rectangle): title = tr(self._header_title) if self._header_title else "" subtitle = tr(self._header_subtitle) if self._header_subtitle else "" - draw_settings_panel_header(rect, title, subtitle, title_size=32, subtitle_size=18) + + if self._parent_toggle: + toggle = self._parent_toggle + + display_title = title if title else tr(toggle.label) + subtitle_text = subtitle if subtitle else (tr(toggle.subtitle) if toggle.subtitle else "") + + toggle_take = AETHER_LIST_METRICS.toggle_width + AETHER_LIST_METRICS.toggle_right_inset + 16 + text_rect = rl.Rectangle(rect.x, rect.y, max(100.0, rect.width - toggle_take), rect.height) + draw_settings_panel_header(text_rect, display_title, subtitle_text, title_size=32, subtitle_size=18) + + toggle_id = f"parent_toggle:{toggle.label}" + tw = AETHER_LIST_METRICS.toggle_width + th = AETHER_LIST_METRICS.toggle_height + ri = AETHER_LIST_METRICS.toggle_right_inset + toggle_rect = rl.Rectangle(rect.x + rect.width - tw - ri, rect.y, tw, th) + self._interactive_rects[toggle_id] = toggle_rect + + toggle_value = toggle.get_state() + target_progress = 1.0 if toggle_value else 0.0 + current_progress = _KNOB_ANIMATION_STATES.get(toggle_id, target_progress) + dt = rl.get_frame_time() + current_progress += (target_progress - current_progress) * 12.0 * dt + if abs(current_progress - target_progress) < 0.001: + current_progress = target_progress + _KNOB_ANIMATION_STATES[toggle_id] = current_progress + + draw_toggle_switch( + rl.Rectangle(rect.x, rect.y, rect.width, th), + toggle_value, + knob_progress=current_progress, + track_color=self._panel_style.accent, + seed_id=toggle_id, + ) + else: + draw_settings_panel_header(rect, title, subtitle, title_size=32, subtitle_size=18) def _active_sections(self) -> list[SettingSection]: if self._tab_defs and self._active_tab_key: @@ -3129,12 +3198,20 @@ class AetherSettingsView(PanelManagerView): active = self._active_sections() has_visible = any(self._visible_rows(s) for s in active) if not has_visible: - draw_empty_state_card( - rl.Rectangle(rect.x, y, width, rect.height - (y - rect.y)), - tr("No settings to display"), - tr("All options in this panel are hidden or unavailable."), - style=self._panel_style, - ) + if self._parent_toggle and not self._parent_toggle.get_state(): + draw_empty_state_card( + rl.Rectangle(rect.x, y, width, rect.height - (y - rect.y)), + tr("Enable {} to configure settings.").format(tr(self._parent_toggle.label)), + "", + style=self._panel_style, + ) + elif not self._parent_toggle: + draw_empty_state_card( + rl.Rectangle(rect.x, y, width, rect.height - (y - rect.y)), + tr("No settings to display"), + tr("All options in this panel are hidden or unavailable."), + style=self._panel_style, + ) return i = 0 while i < len(active): diff --git a/selfdrive/ui/layouts/settings/starpilot/appearance.py b/selfdrive/ui/layouts/settings/starpilot/appearance.py index 6402231713..b06ba9d2fc 100644 --- a/selfdrive/ui/layouts/settings/starpilot/appearance.py +++ b/selfdrive/ui/layouts/settings/starpilot/appearance.py @@ -15,6 +15,7 @@ import pyray as rl from openpilot.selfdrive.ui.layouts.settings.starpilot.aethergrid import ( AetherSliderDialog, DEFAULT_PANEL_STYLE, + ParentToggle, SettingRow, SettingSection, AetherSettingsView, @@ -177,6 +178,14 @@ class StarPilotAppearanceLayout(_SettingsPage): super().__init__() self._build_view() + def _make_parent(self, key: str, label: str, subtitle: str = "") -> ParentToggle: + return ParentToggle( + label=label, + subtitle=subtitle, + get_state=lambda k=key: self._params.get_bool(k), + set_state=lambda s, k=key: self._params.put_bool(k, s), + ) + def _show_lead_detection_threshold_selector(self): def on_close(res, val): if res == DialogResult.CONFIRM: @@ -197,127 +206,146 @@ class StarPilotAppearanceLayout(_SettingsPage): po = lambda: self._params.get_bool("PedalsOnUI") ol = lambda: starpilot_state.car_state.hasOpenpilotLongitudinal bsm = lambda: starpilot_state.car_state.hasBSM + model_on = lambda: self._params.get_bool("ModelUI") + hud_on = lambda: self._params.get_bool("CustomUI") # ═══ 1. Model & Path Visualization ═══ self._model_rows = [ - SettingRow("ModelUI", "toggle", tr_noop("Model UI"), - subtitle=tr_noop("Display the driving model path, lanes, and road edges."), - get_state=lambda: self._params.get_bool("ModelUI"), - set_state=lambda s: self._params.put_bool("ModelUI", s)), SettingRow("DynamicPathWidth", "toggle", tr_noop("Dynamic Path"), subtitle="", get_state=lambda: self._params.get_bool("DynamicPathWidth"), - set_state=lambda s: self._params.put_bool("DynamicPathWidth", s)), + set_state=lambda s: self._params.put_bool("DynamicPathWidth", s), + visible=model_on), SettingRow("LaneLinesWidth", "value", tr_noop("Lane Line Width"), subtitle="", get_value=self._get_lane_lines_display, - on_click=lambda: self._show_int_selector("LaneLinesWidth", 0, 24, self._get_lane_lines_unit())), + on_click=lambda: self._show_int_selector("LaneLinesWidth", 0, 24, self._get_lane_lines_unit()), + visible=model_on), SettingRow("LaneLinesColor", "value", tr_noop("Lane Line Color"), subtitle="", get_value=lambda: self._get_color_display("LaneLinesColor"), - on_click=lambda: self._show_color_selector("LaneLinesColor")), + on_click=lambda: self._show_color_selector("LaneLinesColor"), + visible=model_on), SettingRow("PathWidth", "value", tr_noop("Path Width"), subtitle="", get_value=self._get_path_width_display, - on_click=self._show_path_width_selector), + on_click=self._show_path_width_selector, + visible=model_on), SettingRow("PathEdgeWidth", "value", tr_noop("Path Edge Width"), subtitle="", get_value=lambda: f"{self._params.get_int('PathEdgeWidth')}%", - on_click=lambda: self._show_int_selector("PathEdgeWidth", 0, 100, "%")), + on_click=lambda: self._show_int_selector("PathEdgeWidth", 0, 100, "%"), + visible=model_on), SettingRow("PathEdgesColor", "value", tr_noop("Path Edge Color"), subtitle="", get_value=lambda: self._get_color_display("PathEdgesColor"), - on_click=lambda: self._show_color_selector("PathEdgesColor")), + on_click=lambda: self._show_color_selector("PathEdgesColor"), + visible=model_on), SettingRow("PathColor", "value", tr_noop("Path Color"), subtitle="", get_value=lambda: self._get_color_display("PathColor"), - on_click=lambda: self._show_color_selector("PathColor")), + on_click=lambda: self._show_color_selector("PathColor"), + visible=model_on), SettingRow("RoadEdgesWidth", "value", tr_noop("Road Edge Width"), subtitle="", get_value=self._get_road_edges_display, - on_click=lambda: self._show_int_selector("RoadEdgesWidth", 0, 24, self._get_road_edges_unit())), + on_click=lambda: self._show_int_selector("RoadEdgesWidth", 0, 24, self._get_road_edges_unit()), + visible=model_on), SettingRow("RainbowPath", "toggle", tr_noop("Rainbow Path"), subtitle="", get_state=lambda: self._params.get_bool("RainbowPath"), - set_state=lambda s: self._params.put_bool("RainbowPath", s)), + set_state=lambda s: self._params.put_bool("RainbowPath", s), + visible=model_on), SettingRow("AccelerationPath", "toggle", tr_noop("Acceleration Path"), subtitle="", get_state=lambda: self._params.get_bool("AccelerationPath"), set_state=lambda s: self._params.put_bool("AccelerationPath", s), - enabled=ol), + enabled=ol, + visible=model_on), SettingRow("AdjacentPath", "toggle", tr_noop("Adjacent Lanes"), subtitle="", get_state=lambda: self._params.get_bool("AdjacentPath"), - set_state=lambda s: self._params.put_bool("AdjacentPath", s)), + set_state=lambda s: self._params.put_bool("AdjacentPath", s), + visible=model_on), SettingRow("AdjacentPathMetrics", "toggle", tr_noop("Adjacent Lane Metrics"), subtitle="", get_state=lambda: self._params.get_bool("AdjacentPathMetrics"), - set_state=lambda s: self._params.put_bool("AdjacentPathMetrics", s)), + set_state=lambda s: self._params.put_bool("AdjacentPathMetrics", s), + visible=model_on), SettingRow("BlindSpotPath", "toggle", tr_noop("Blind Spot Path"), subtitle="", get_state=lambda: self._params.get_bool("BlindSpotPath"), set_state=lambda s: self._params.put_bool("BlindSpotPath", s), - enabled=bsm), + enabled=bsm, + visible=model_on), ] # ═══ 2. Driving Widgets & HUD ═══ self._hud_rows = [ - SettingRow("CustomUI", "toggle", tr_noop("Driving Screen Widgets"), - subtitle=tr_noop("Show interactive indicators on the driving screen."), - get_state=lambda: self._params.get_bool("CustomUI"), - set_state=lambda s: self._params.put_bool("CustomUI", s)), SettingRow("Compass", "toggle", tr_noop("Compass"), subtitle="", get_state=lambda: self._params.get_bool("Compass"), - set_state=lambda s: self._params.put_bool("Compass", s)), + set_state=lambda s: self._params.put_bool("Compass", s), + visible=hud_on), SettingRow("OnroadDistanceButton", "toggle", tr_noop("Personality Button"), subtitle="", get_state=lambda: self._params.get_bool("OnroadDistanceButton"), - set_state=lambda s: self._params.put_bool("OnroadDistanceButton", s)), + set_state=lambda s: self._params.put_bool("OnroadDistanceButton", s), + visible=hud_on), SettingRow("RotatingWheel", "toggle", tr_noop("Rotating Wheel"), subtitle="", get_state=lambda: self._params.get_bool("RotatingWheel"), - set_state=lambda s: self._params.put_bool("RotatingWheel", s)), + set_state=lambda s: self._params.put_bool("RotatingWheel", s), + visible=hud_on), SettingRow("ShowSteering", "toggle", tr_noop("Steering Torque Indicator"), subtitle="", get_state=lambda: self._params.get_bool("ShowSteering"), - set_state=lambda s: self._params.put_bool("ShowSteering", s)), + set_state=lambda s: self._params.put_bool("ShowSteering", s), + visible=hud_on), SettingRow("SignalMetrics", "toggle", tr_noop("Turn Signal Borders"), subtitle="", get_state=lambda: self._params.get_bool("SignalMetrics"), - set_state=lambda s: self._params.put_bool("SignalMetrics", s)), + set_state=lambda s: self._params.put_bool("SignalMetrics", s), + visible=hud_on), SettingRow("BlindSpotMetrics", "toggle", tr_noop("Blind Spot Borders"), subtitle="", get_state=lambda: self._params.get_bool("BlindSpotMetrics"), set_state=lambda s: self._params.put_bool("BlindSpotMetrics", s), - enabled=bsm), + enabled=bsm, + visible=hud_on), SettingRow("WheelSpeed", "toggle", tr_noop("Wheel Speed"), subtitle="", get_state=lambda: self._params.get_bool("WheelSpeed"), - set_state=lambda s: self._params.put_bool("WheelSpeed", s)), + set_state=lambda s: self._params.put_bool("WheelSpeed", s), + visible=hud_on), SettingRow("BorderWidth", "value", tr_noop("Border Width"), subtitle="", get_value=lambda: f"{int(round(self._params.get_float('BorderWidth')))}%", - on_click=lambda: self._show_float_selector("BorderWidth", 25, 250, 5, "%")), + on_click=lambda: self._show_float_selector("BorderWidth", 25, 250, 5, "%"), + visible=hud_on), SettingRow("PedalsOnUI", "toggle", tr_noop("Pedal Indicators"), subtitle="", get_state=lambda: self._params.get_bool("PedalsOnUI"), set_state=lambda s: self._params.put_bool("PedalsOnUI", s), - enabled=ol), + enabled=ol, + visible=hud_on), SettingRow("DynamicPedalsOnUI", "toggle", tr_noop("Dynamic Pedals"), subtitle="", get_state=lambda: self._params.get_bool("DynamicPedalsOnUI"), set_state=lambda s: self._set_exclusive_pedal("DynamicPedalsOnUI", "StaticPedalsOnUI", s), - enabled=lambda: po() and ol()), + enabled=lambda: po() and ol(), + visible=hud_on), SettingRow("StaticPedalsOnUI", "toggle", tr_noop("Static Pedals"), subtitle="", get_state=lambda: self._params.get_bool("StaticPedalsOnUI"), set_state=lambda s: self._set_exclusive_pedal("StaticPedalsOnUI", "DynamicPedalsOnUI", s), - enabled=lambda: po() and ol()), + enabled=lambda: po() and ol(), + visible=hud_on), SettingRow("StoppedTimer", "toggle", tr_noop("Stopped Timer"), subtitle="", get_state=lambda: self._params.get_bool("StoppedTimer"), - set_state=lambda s: self._params.put_bool("StoppedTimer", s)), + set_state=lambda s: self._params.put_bool("StoppedTimer", s), + visible=hud_on), ] # ═══ 3. Screen Declutter & Visibility ═══ @@ -461,12 +489,18 @@ class StarPilotAppearanceLayout(_SettingsPage): panel_style=PANEL_STYLE, ) + pt_model = self._make_parent("ModelUI", "Model UI", + "Display the driving model path, lanes, and road edges.") + pt_hud = self._make_parent("CustomUI", "Driving Screen Widgets", + "Show interactive indicators on the driving screen.") + # Register subpanels for Level 2 slide transitions self._sub_panels["model"] = AetherSettingsView( self, [SettingSection(title="", rows=self._model_rows)], header_title=tr_noop("Model & Path Visualization"), header_subtitle=tr_noop("Customize dynamic lane paths, road edges, and colors."), + parent_toggle=pt_model, panel_style=PANEL_STYLE, ) self._sub_panels["hud"] = AetherSettingsView( @@ -474,6 +508,7 @@ class StarPilotAppearanceLayout(_SettingsPage): [SettingSection(title="", rows=self._hud_rows)], header_title=tr_noop("Driving Widgets & HUD"), header_subtitle=tr_noop("Configure compass, dynamic pedals, signals, and screen borders."), + parent_toggle=pt_hud, panel_style=PANEL_STYLE, ) self._sub_panels["declutter"] = AetherSettingsView( diff --git a/selfdrive/ui/layouts/settings/starpilot/lateral.py b/selfdrive/ui/layouts/settings/starpilot/lateral.py index d3b39055a3..841f68a9ef 100644 --- a/selfdrive/ui/layouts/settings/starpilot/lateral.py +++ b/selfdrive/ui/layouts/settings/starpilot/lateral.py @@ -13,6 +13,7 @@ from openpilot.selfdrive.ui.layouts.settings.starpilot.panel import _SettingsPag from openpilot.selfdrive.ui.layouts.settings.starpilot.aethergrid import ( DEFAULT_PANEL_STYLE, AetherSettingsView, + ParentToggle, SettingRow, SettingSection, TileGrid, @@ -140,6 +141,14 @@ class StarPilotLateralLayout(_SettingsPage): super().__init__() self._build_panels() + def _make_parent(self, key: str, label: str, subtitle: str = "") -> ParentToggle: + return ParentToggle( + label=label, + subtitle=subtitle, + get_state=lambda k=key: self._params.get_bool(k), + set_state=lambda s, k=key: self._params.put_bool(k, s), + ) + def _build_panels(self): p = self._params cs = starpilot_state.car_state @@ -161,12 +170,6 @@ class StarPilotLateralLayout(_SettingsPage): # ── 1. Steering Behavior ── self._behavior_rows = [ - SettingRow( - "AlwaysOnLateral", "toggle", tr_noop("Always On Lateral"), - subtitle=tr_noop("Steering stays active when ACC is off."), - get_state=lambda: p.get_bool("AlwaysOnLateral"), - set_state=lambda s: _confirm_reboot_toggle(p, "AlwaysOnLateral", s) if s else p.put_bool("AlwaysOnLateral", False), - ), SettingRow( "PauseAOLOnBrake", "value", tr_noop("Pause AOL On Brake"), subtitle=tr_noop("Pause AOL below this speed while brake is pressed."), @@ -203,12 +206,6 @@ class StarPilotLateralLayout(_SettingsPage): # ── 2. Lane Changes ── self._lane_change_rows = [ - SettingRow( - "LaneChanges", "toggle", tr_noop("Lane Changes"), - subtitle=tr_noop("Allow openpilot to change lanes."), - get_state=lambda: p.get_bool("LaneChanges"), - set_state=lambda s: p.put_bool("LaneChanges", s), - ), SettingRow( "NudgelessLaneChange", "toggle", tr_noop("Auto Lane Changes"), subtitle=tr_noop("Signal triggers automatic lane change."), @@ -255,12 +252,6 @@ class StarPilotLateralLayout(_SettingsPage): # ── 3. Advanced Lateral Tuning ── self._advanced_rows = [ - SettingRow( - "AdvancedLateralTune", "toggle", tr_noop("Advanced Lateral Tuning"), - subtitle=tr_noop("Fine-tune steering response and auto-tuning."), - get_state=lambda: p.get_bool("AdvancedLateralTune"), - set_state=lambda s: p.put_bool("AdvancedLateralTune", s), - ), SettingRow( "NNFF", "toggle", tr_noop("NNFF"), subtitle=tr_noop("Neural net feedforward steering controller."), @@ -365,12 +356,25 @@ class StarPilotLateralLayout(_SettingsPage): header_subtitle=tr_noop("Configure steering behavior and lane changes."), ) + p = self._params + pt_behavior = ParentToggle( + label="Always On Lateral", + subtitle="Steering stays active when ACC is off.", + get_state=lambda: p.get_bool("AlwaysOnLateral"), + set_state=lambda s: _confirm_reboot_toggle(p, "AlwaysOnLateral", s) if s else p.put_bool("AlwaysOnLateral", False), + ) + pt_lane_changes = self._make_parent("LaneChanges", "Lane Changes", + "Allow openpilot to change lanes.") + pt_advanced = self._make_parent("AdvancedLateralTune", "Advanced Lateral Tuning", + "Fine-tune steering response and auto-tuning.") + # Register subpanels for Level 2 slide transitions self._sub_panels["behavior"] = AetherSettingsView( self, [SettingSection(title="", rows=self._behavior_rows)], header_title=tr_noop("Steering Behavior"), header_subtitle=tr_noop("Configure Always On Lateral (AOL), pause speed thresholds, and turn signal behaviors."), + parent_toggle=pt_behavior, panel_style=PANEL_STYLE, ) self._sub_panels["lane_changes"] = AetherSettingsView( @@ -378,6 +382,7 @@ class StarPilotLateralLayout(_SettingsPage): [SettingSection(title="", rows=self._lane_change_rows)], header_title=tr_noop("Lane Changes"), header_subtitle=tr_noop("Configure automatic lane changes, speed/width thresholds, and smoothing parameters."), + parent_toggle=pt_lane_changes, panel_style=PANEL_STYLE, ) self._sub_panels["advanced"] = AetherSettingsView( @@ -385,6 +390,7 @@ class StarPilotLateralLayout(_SettingsPage): [SettingSection(title="", rows=self._advanced_rows)], header_title=tr_noop("Advanced Lateral Tuning"), header_subtitle=tr_noop("Adjust actuator delay, steer ratio, Kp, friction, and neural network feedforward controllers."), + parent_toggle=pt_advanced, panel_style=PANEL_STYLE, ) self._wire_sub_panels() diff --git a/selfdrive/ui/layouts/settings/starpilot/longitudinal.py b/selfdrive/ui/layouts/settings/starpilot/longitudinal.py index 0204f14fa4..6f9862e4e2 100644 --- a/selfdrive/ui/layouts/settings/starpilot/longitudinal.py +++ b/selfdrive/ui/layouts/settings/starpilot/longitudinal.py @@ -19,6 +19,7 @@ from openpilot.selfdrive.ui.layouts.settings.starpilot.aethergrid import ( AETHER_LIST_METRICS, AetherSliderDialog, DEFAULT_PANEL_STYLE, + ParentToggle, SettingRow, SettingSection, AetherSettingsView, @@ -249,6 +250,14 @@ class StarPilotLongitudinalLayout(_SettingsPage): def _show_stop_tuning_values(self) -> bool: return self._advanced_enabled() and not (starpilot_state.car_state.isToyota and self._params.get_bool("FrogsGoMoosTweak")) + def _make_parent(self, key: str, label: str, subtitle: str = "") -> ParentToggle: + return ParentToggle( + label=label, + subtitle=subtitle, + get_state=lambda k=key: self._params.get_bool(k), + set_state=lambda s, k=key: self._params.put_bool(k, s), + ) + def _build_view(self): ol = lambda: starpilot_state.car_state.hasOpenpilotLongitudinal ce_on = lambda: self._params.get_bool("ConditionalExperimental") @@ -258,10 +267,6 @@ class StarPilotLongitudinalLayout(_SettingsPage): # ── 1. Longitudinal Tuning Rows ── self._tune_rows = [ - SettingRow("LongitudinalTune", "toggle", tr_noop("Longitudinal Tuning"), - subtitle=tr_noop("Acceleration and braking control changes to fine-tune how openpilot drives."), - get_state=lambda: self._params.get_bool("LongitudinalTune"), - set_state=lambda s: self._params.put_bool("LongitudinalTune", s)), SettingRow("AccelProfile", "value", tr_noop("Acceleration Profile"), subtitle=tr_noop("Choose how quickly openpilot speeds up."), get_value=self._get_acceleration_profile_label, @@ -297,10 +302,6 @@ class StarPilotLongitudinalLayout(_SettingsPage): # ── 2. Advanced Actuators Rows ── adv = self._advanced_enabled self._advanced_rows = [ - SettingRow("AdvancedLongitudinalTune", "toggle", tr_noop("Advanced Longitudinal Tuning"), - subtitle=tr_noop("Advanced acceleration and braking changes for refining launch, stopping, and actuator response."), - get_state=lambda: self._params.get_bool("AdvancedLongitudinalTune"), - set_state=lambda s: self._params.put_bool("AdvancedLongitudinalTune", s)), SettingRow("EVTuning", "toggle", tr_noop("EV Tuning"), subtitle=tr_noop("Acceleration tuning for EV and direct-drive vehicles."), get_state=lambda: self._params.get_bool("EVTuning"), @@ -359,10 +360,6 @@ class StarPilotLongitudinalLayout(_SettingsPage): # ── 3. Speed Limit Controller (SLC) Rows ── self._slc_rows = [ - SettingRow("SLC", "toggle", tr_noop("Speed Limit Controller"), - subtitle=tr_noop("Limit the car's maximum speed to the current speed limit."), - get_state=lambda: self._params.get_bool("SpeedLimitController"), - set_state=lambda s: self._params.put_bool("SpeedLimitController", s)), SettingRow("SLCFallback", "value", tr_noop("Fallback Speed"), subtitle="", get_value=lambda: self._params.get("SLCFallback", encoding="utf-8") or "Set Speed", @@ -436,10 +433,6 @@ class StarPilotLongitudinalLayout(_SettingsPage): # ── 4. Adaptive Speed Controls Rows (CES + CSC) ── self._conditional_experimental_rows = [ - SettingRow("ConditionalExperimental", "toggle", tr_noop("Conditional Experimental Mode"), - subtitle="", - get_state=lambda: self._params.get_bool("ConditionalExperimental"), - set_state=lambda s: self._params.put_bool("ConditionalExperimental", s)), SettingRow("PersistExp", "toggle", tr_noop("Persist Experimental State"), subtitle=tr_noop("Keep override through reboots until manually cleared."), get_state=lambda: self._params.get_bool("PersistExperimentalState"), @@ -508,10 +501,6 @@ class StarPilotLongitudinalLayout(_SettingsPage): ] self._curve_speed_controller_rows = [ - SettingRow("CurveSpeed", "toggle", tr_noop("Curve Speed Controller"), - subtitle="", - get_state=lambda: self._params.get_bool("CurveSpeedController"), - set_state=lambda s: self._params.put_bool("CurveSpeedController", s)), SettingRow("ShowCSCStatus", "toggle", tr_noop("Status Widget"), subtitle=tr_noop("Show the Curve Speed Controller ambient effect on the driving screen."), get_state=lambda: self._params.get_bool("ShowCSCStatus"), @@ -537,10 +526,6 @@ class StarPilotLongitudinalLayout(_SettingsPage): # ── 5. Driving Personalities Rows ── self._personality_rows = [ - SettingRow("PersonalitiesToggle", "toggle", tr_noop("Driving Personalities"), - subtitle="", - get_state=lambda: self._params.get_bool("CustomPersonalities"), - set_state=lambda s: self._params.put_bool("CustomPersonalities", s)), SettingRow("Traffic", "value", tr_noop("Traffic"), subtitle=tr_noop("Configure follow distance, smoothness, and response for traffic conditions."), get_value=lambda: tr_noop("Configure"), @@ -561,10 +546,6 @@ class StarPilotLongitudinalLayout(_SettingsPage): # ── 6. Daily QOL & Weather Rows ── self._daily_rows = [ - SettingRow("QOLToggle", "toggle", tr_noop("Quality of Life"), - subtitle="", - get_state=lambda: self._params.get_bool("QOLLongitudinal"), - set_state=lambda s: self._params.put_bool("QOLLongitudinal", s)), SettingRow("CustomCruise", "value", tr_noop("Cruise Interval"), subtitle="", get_value=lambda: f"{max(1, self._params.get_int('CustomCruise'))} mph", @@ -661,6 +642,19 @@ class StarPilotLongitudinalLayout(_SettingsPage): panel_style=PANEL_STYLE, ) + pt_tune = self._make_parent("LongitudinalTune", "Longitudinal Tuning", + "Acceleration and braking control changes to fine-tune how openpilot drives.") + pt_advanced = self._make_parent("AdvancedLongitudinalTune", "Advanced Longitudinal Tuning", + "Advanced acceleration and braking changes for refining launch, stopping, and actuator response.") + pt_personality = self._make_parent("CustomPersonalities", "Driving Personalities") + pt_daily = self._make_parent("QOLLongitudinal", "Quality of Life") + pt_slc = self._make_parent("SpeedLimitController", "Speed Limit Controller", + "Limit the car's maximum speed to the current speed limit.") + pt_ce = self._make_parent("ConditionalExperimental", "Conditional Experimental", + "Configure triggers and threshold speeds for automated Experimental Mode switching.") + pt_csc = self._make_parent("CurveSpeedController", "Curve Speed Controller", + "Configure speed control on curves and reset collected calibration data.") + ce_rows = self._conditional_experimental_rows self._sub_panels["ce"] = AetherSettingsView( self, @@ -670,6 +664,7 @@ class StarPilotLongitudinalLayout(_SettingsPage): ], header_title=tr("Conditional Experimental"), header_subtitle=tr("Configure triggers and threshold speeds for automated Experimental Mode switching."), + parent_toggle=pt_ce, panel_style=PANEL_STYLE, ) @@ -682,6 +677,7 @@ class StarPilotLongitudinalLayout(_SettingsPage): ], header_title=tr("Curve Speed Controller"), header_subtitle=tr("Configure speed control on curves and reset collected calibration data."), + parent_toggle=pt_csc, panel_style=PANEL_STYLE, ) @@ -693,6 +689,7 @@ class StarPilotLongitudinalLayout(_SettingsPage): [SettingSection(title="", rows=self._tune_rows)], header_title=tr_noop("Longitudinal Tuning"), header_subtitle=tr_noop("Configure acceleration profiles, smooth following, lane changes, and route speed control."), + parent_toggle=pt_tune, panel_style=PANEL_STYLE, ) self._sub_panels["advanced"] = AetherSettingsView( @@ -700,6 +697,7 @@ class StarPilotLongitudinalLayout(_SettingsPage): [SettingSection(title="", rows=self._advanced_rows)], header_title=tr_noop("Advanced Actuators"), header_subtitle=tr_noop("Adjust actuator delay, EV/Truck tuning, and launch/stop speeds/rates."), + parent_toggle=pt_advanced, panel_style=PANEL_STYLE, ) self._sub_panels["slc"] = AetherSettingsView( @@ -707,6 +705,7 @@ class StarPilotLongitudinalLayout(_SettingsPage): [SettingSection(title="", rows=self._slc_rows)], header_title=tr_noop("Speed Limit Controller"), header_subtitle=tr_noop("Manage auto speed matching, confirmation, offsets, and source priority."), + parent_toggle=pt_slc, panel_style=PANEL_STYLE, ) self._sub_panels["personality"] = AetherSettingsView( @@ -714,6 +713,7 @@ class StarPilotLongitudinalLayout(_SettingsPage): [SettingSection(title="", rows=self._personality_rows)], header_title=tr_noop("Driving Personalities"), header_subtitle=tr_noop("Customize follow distance and jerk/response metrics for each personality profile."), + parent_toggle=pt_personality, panel_style=PANEL_STYLE, ) self._sub_panels["daily"] = AetherSettingsView( @@ -721,6 +721,7 @@ class StarPilotLongitudinalLayout(_SettingsPage): [SettingSection(title="", rows=self._daily_rows)], header_title=tr_noop("Daily QOL & Weather"), header_subtitle=tr_noop("Configure cruise intervals, standstill behaviors, gear mapping, and weather presets."), + parent_toggle=pt_daily, panel_style=PANEL_STYLE, ) self._wire_sub_panels()