Switcharoo

This commit is contained in:
firestarsdog
2026-08-12 20:32:29 -04:00
parent 8c41d92e89
commit cbf7f35c85
3 changed files with 367 additions and 128 deletions
@@ -1161,31 +1161,36 @@ class BreadcrumbController:
if not layout:
return
if target == "action:home":
while len(gui_app._nav_stack) > 1:
gui_app.pop_widget()
layout._panel_stack.clear()
layout._update_sub_panel_visibility()
layout._current_category_idx = None
layout._set_current_panel(StarPilotPanelType.MAIN)
elif target == "action:category":
while len(gui_app._nav_stack) > 1:
gui_app.pop_widget()
layout._panel_stack.clear()
nav_stack = getattr(gui_app, "_nav_stack", [])
cat = layout.CATEGORIES[layout._current_category_idx]
if "buttons" in cat:
layout._set_current_panel(StarPilotPanelType.MAIN)
else:
if target == "action:home":
while len(nav_stack) > 1:
gui_app.pop_widget()
layout.reset_to_root()
elif target.startswith("action:hub:"):
target_depth = int(target.split(":")[-1])
while len(nav_stack) > 1:
gui_app.pop_widget()
layout.navigate_to_hub_depth(target_depth)
elif target == "action:category":
# Compatibility with the former single-category breadcrumb action.
while len(nav_stack) > 1:
gui_app.pop_widget()
if getattr(layout, "_hub_path", None):
layout.navigate_to_hub_depth(1)
elif layout._current_panel != StarPilotPanelType.MAIN:
layout._panel_stack.clear()
layout._commit_navigation()
else:
layout.reset_to_root()
elif target == "action:panel":
while len(gui_app._nav_stack) > 1:
while len(nav_stack) > 1:
gui_app.pop_widget()
layout._panel_stack.clear()
layout._commit_navigation()
elif target.startswith("action:nav_stack:"):
target_idx = int(target.split(":")[-1])
while len(gui_app._nav_stack) > target_idx + 1:
while len(nav_stack) > target_idx + 1:
gui_app.pop_widget()
elif target.startswith("action:panel_stack:"):
target_idx = int(target.split(":")[-1])
@@ -1203,22 +1208,20 @@ class BreadcrumbController:
if not layout:
return path
pushed_widgets = gui_app._nav_stack[1:]
cat_title = ""
is_folder = False
if layout._current_category_idx is not None:
cat = layout.CATEGORIES[layout._current_category_idx]
cat_title = cat["title"]
is_folder = "buttons" in cat
path.append((cat_title, "action:category"))
hub_path = getattr(layout, "_hub_path", [])
for i, folder in enumerate(hub_path, start=1):
path.append((tr(folder["title"]), f"action:hub:{i}"))
if layout._current_panel != StarPilotPanelType.MAIN:
panel_info = layout._panels[layout._current_panel]
if panel_info.name:
if is_folder or layout._current_category_idx is None:
panel_title = panel_info.name
path.append((panel_title, "action:panel"))
selected_leaf = getattr(layout, "_selected_leaf", None)
if selected_leaf is not None:
panel_title = selected_leaf["title"]
else:
panel_title = layout._panels[layout._current_panel].name
if panel_title:
path.append((tr(panel_title), "action:panel"))
pushed_widgets = getattr(gui_app, "_nav_stack", [])[1:]
for i, widget in enumerate(pushed_widgets):
if hasattr(widget, '_header_title') and widget._header_title:
@@ -5473,6 +5476,3 @@ class TileGrid(Widget):
tile.render(snap_rect(rl.Rectangle(row_x + c * (row_tile_w + self._gap), rect.y + y_offset + r * (tile_h + self._gap), row_tile_w, tile_h)))
tile_idx += 1
@@ -27,14 +27,25 @@ class StarPilotLayout(Widget):
"panel": "SOUNDS",
},
{
"title": "Driving Controls",
"icon": "steering",
"buttons": [("Driving Model", "DRIVING_MODEL", "aicar"), ("Gas / Brake", "LONGITUDINAL", "road"), ("Steering", "LATERAL", "steering")],
"title": "Driving Model",
"icon": "aicar",
"panel": "DRIVING_MODEL",
},
{
"title": "Navigation & Maps",
"icon": "navigate",
"buttons": [("Map Data", "MAPS", "navigate"), ("Navigation", "NAVIGATION", "road")],
"title": "Driving Controls",
"icon": "steering",
"children": [
{
"title": "Navigation & Maps",
"icon": "navigate",
"children": [
{"title": "Map Data", "panel": "MAPS", "icon": "navigate"},
{"title": "Navigation", "panel": "NAVIGATION", "icon": "road"},
],
},
{"title": "Gas / Brake", "panel": "LONGITUDINAL", "icon": "road"},
{"title": "Steering", "panel": "LATERAL", "icon": "steering"},
],
},
{
"title": "System",
@@ -53,11 +64,27 @@ class StarPilotLayout(Widget):
},
]
PANEL_TYPE_MAP = {
"SOUNDS": StarPilotPanelType.SOUNDS,
"SYSTEM": StarPilotPanelType.SYSTEM,
"DRIVING_MODEL": StarPilotPanelType.DRIVING_MODEL,
"LONGITUDINAL": StarPilotPanelType.LONGITUDINAL,
"LATERAL": StarPilotPanelType.LATERAL,
"MAPS": StarPilotPanelType.MAPS,
"NAVIGATION": StarPilotPanelType.NAVIGATION,
"VISUALS": StarPilotPanelType.VISUALS,
"VEHICLE": StarPilotPanelType.VEHICLE,
}
def __init__(self):
super().__init__()
self._params = FrameCachedParams()
self._current_panel = StarPilotPanelType.MAIN
self._hub_path: list[dict] = []
self._selected_leaf: dict | None = None
# Kept as a compatibility alias for callers that only need the top-level
# folder index. Nested hub navigation is represented by _hub_path.
self._current_category_idx: int | None = None
self._depth_callback: Callable | None = None
self._settings_layout = None
@@ -101,38 +128,69 @@ class StarPilotLayout(Widget):
def set_settings_layout(self, settings_layout):
self._settings_layout = settings_layout
@property
def hub_path(self) -> tuple[dict, ...]:
return tuple(self._hub_path)
def navigate_back(self):
if self._panel_stack:
self._panel_stack.pop()
self._commit_navigation()
elif self._current_panel != StarPilotPanelType.MAIN:
if self._current_category_idx is not None:
cat_info = self.CATEGORIES[self._current_category_idx]
if "buttons" in cat_info:
self._set_current_panel(StarPilotPanelType.MAIN)
else:
self._current_category_idx = None
self._set_current_panel(StarPilotPanelType.MAIN)
else:
self._set_current_panel(StarPilotPanelType.MAIN)
elif self._current_category_idx is not None:
self._current_category_idx = None
# A panel always returns to the folder that launched it.
self._set_current_panel(StarPilotPanelType.MAIN)
elif self._hub_path:
# Once the grid is visible, each back step removes one hub folder.
self._hub_path.pop()
self._selected_leaf = None
self._sync_legacy_category_idx()
self._rebuild_grid()
self._commit_navigation()
def reset_to_root(self):
"""Close nested content and restore the primary six-tile hub."""
self._hub_path.clear()
self._selected_leaf = None
self._sync_legacy_category_idx()
self._set_current_panel(StarPilotPanelType.MAIN)
def navigate_to_hub_depth(self, depth: int):
"""Jump to a folder in the current hub path from a breadcrumb."""
depth = max(0, min(depth, len(self._hub_path)))
self._hub_path = self._hub_path[:depth]
self._selected_leaf = None
self._sync_legacy_category_idx()
self._set_current_panel(StarPilotPanelType.MAIN)
def _sync_legacy_category_idx(self):
if self._hub_path:
self._current_category_idx = self.CATEGORIES.index(self._hub_path[0])
else:
self._current_category_idx = None
def _open_folder(self, folder: dict):
if "children" not in folder:
return
self._hub_path.append(folder)
self._selected_leaf = None
self._sync_legacy_category_idx()
self._set_current_panel(StarPilotPanelType.MAIN)
def _open_leaf(self, leaf: dict):
panel_key = leaf.get("panel")
if panel_key is None:
return
self._selected_leaf = leaf
self._set_current_panel(self.PANEL_TYPE_MAP[panel_key])
def _update_depth(self):
depth = 0
# Root = 0, each visible hub folder = 1, and an open backend panel adds
# one more level. Existing panel sub-pages remain below that panel.
depth = len(self._hub_path)
if self._current_panel != StarPilotPanelType.MAIN:
if self._current_category_idx is not None:
cat_info = self.CATEGORIES[self._current_category_idx]
depth = 2 if "buttons" in cat_info else 1
else:
depth = 1
# Deep nesting check
if self._panel_stack:
depth += len(self._panel_stack)
elif self._current_category_idx is not None:
depth = 1
depth += 1
depth += len(self._panel_stack)
if self._depth_callback:
self._depth_callback(depth)
@@ -166,70 +224,28 @@ class StarPilotLayout(Widget):
panel.set_navigate_callback(self._push_sub_panel)
def _rebuild_grid(self):
state = (self._current_category_idx,)
state = tuple(id(folder) for folder in self._hub_path)
if getattr(self, "_last_grid_state", None) == state:
return
self._last_grid_state = state
self._main_grid.clear()
panel_type_map = {
"SOUNDS": StarPilotPanelType.SOUNDS,
"SYSTEM": StarPilotPanelType.SYSTEM,
"DRIVING_MODEL": StarPilotPanelType.DRIVING_MODEL,
"LONGITUDINAL": StarPilotPanelType.LONGITUDINAL,
"LATERAL": StarPilotPanelType.LATERAL,
"MAPS": StarPilotPanelType.MAPS,
"NAVIGATION": StarPilotPanelType.NAVIGATION,
"VISUALS": StarPilotPanelType.VISUALS,
"VEHICLE": StarPilotPanelType.VEHICLE,
}
if self._current_category_idx is None:
# Main Categories Grid
for i, cat in enumerate(self.CATEGORIES):
def on_click(idx=i):
cat_info = self.CATEGORIES[idx]
self._current_category_idx = idx
panel_key = cat_info.get("panel")
if panel_key is not None:
self._set_current_panel(panel_type_map[panel_key])
else:
self._rebuild_grid()
if self._depth_callback:
self._depth_callback(1)
tile = HubTile(
title=tr(cat["title"]),
desc=tr(cat.get("desc", "")),
icon_key=cat["icon"],
on_click=on_click,
bg_color=cat.get("color")
)
self._main_grid.add_tile(tile)
else:
# Sub-buttons Grid for selected Category
cat = self.CATEGORIES[self._current_category_idx]
visible_buttons = cat["buttons"]
for button_info in visible_buttons:
if len(button_info) == 3:
label, panel_key, btn_icon = button_info
visible_nodes = self.CATEGORIES if not self._hub_path else self._hub_path[-1]["children"]
for node in visible_nodes:
def on_click(item=node):
if "children" in item:
self._open_folder(item)
else:
label, panel_key = button_info
btn_icon = cat["icon"]
p_type = panel_type_map[panel_key]
def on_btn_click(p=p_type):
self._set_current_panel(p)
self._open_leaf(item)
tile = HubTile(
title=tr(label),
desc="",
icon_key=btn_icon,
on_click=on_btn_click,
bg_color=cat.get("color")
)
self._main_grid.add_tile(tile)
tile = HubTile(
title=tr(node["title"]),
desc=tr(node.get("desc", "")),
icon_key=node["icon"],
on_click=on_click,
bg_color=node.get("color")
)
self._main_grid.add_tile(tile)
def _set_current_panel(self, panel_type: StarPilotPanelType):
if panel_type != self._current_panel:
@@ -244,8 +260,10 @@ class StarPilotLayout(Widget):
if panel_type != StarPilotPanelType.MAIN:
self._panels[panel_type].instance.show_event()
else:
self._selected_leaf = None
self._rebuild_grid()
elif panel_type == StarPilotPanelType.MAIN:
self._selected_leaf = None
self._rebuild_grid()
self._panel_stack.clear()
@@ -1,20 +1,241 @@
from types import SimpleNamespace
from openpilot.selfdrive.ui.layouts.settings.starpilot.main_panel import StarPilotLayout
from openpilot.selfdrive.ui.layouts.settings.starpilot.navigation import StarPilotNavigationLayout
from openpilot.selfdrive.ui.layouts.settings.starpilot.panel import StarPilotPanelType
from openpilot.selfdrive.ui.layouts.settings.starpilot.aethergrid import BreadcrumbController, gui_app
def test_navigation_and_maps_is_the_parent_folder_for_map_data_and_navigation():
category = next(item for item in StarPilotLayout.CATEGORIES if item["title"] == "Navigation & Maps")
assert "panel" not in category
assert category["buttons"] == [
("Map Data", "MAPS", "navigate"),
("Navigation", "NAVIGATION", "road"),
def test_root_hub_contains_the_six_categories_in_order():
assert [item["title"] for item in StarPilotLayout.CATEGORIES] == [
"Sounds & Alerts",
"Driving Model",
"Driving Controls",
"System",
"Appearance",
"Vehicle Settings",
]
assert all(item["title"] != "Map Data" for item in StarPilotLayout.CATEGORIES)
assert len(StarPilotLayout.CATEGORIES) == 6
assert all(item["title"] != "Navigation & Maps" for item in StarPilotLayout.CATEGORIES)
def test_driving_controls_contains_nested_navigation_folder_and_leaf_routes():
controls = next(item for item in StarPilotLayout.CATEGORIES if item["title"] == "Driving Controls")
assert "panel" not in controls
assert [item["title"] for item in controls["children"]] == [
"Navigation & Maps",
"Gas / Brake",
"Steering",
]
navigation_maps = controls["children"][0]
assert "panel" not in navigation_maps
assert navigation_maps["children"] == [
{"title": "Map Data", "panel": "MAPS", "icon": "navigate"},
{"title": "Navigation", "panel": "NAVIGATION", "icon": "road"},
]
assert controls["children"][1]["panel"] == "LONGITUDINAL"
assert controls["children"][2]["panel"] == "LATERAL"
def test_driving_model_is_a_root_leaf_and_existing_panel_routes_are_preserved():
driving_model = next(item for item in StarPilotLayout.CATEGORIES if item["title"] == "Driving Model")
assert driving_model["panel"] == "DRIVING_MODEL"
assert driving_model["icon"] == "aicar"
assert "children" not in driving_model
assert StarPilotLayout.PANEL_TYPE_MAP["DRIVING_MODEL"] == StarPilotPanelType.DRIVING_MODEL
assert StarPilotLayout.PANEL_TYPE_MAP["MAPS"] == StarPilotPanelType.MAPS
assert StarPilotLayout.PANEL_TYPE_MAP["NAVIGATION"] == StarPilotPanelType.NAVIGATION
assert StarPilotPanelType.NAVIGATION.value == 13
class _FakeHubTile:
def __init__(self, title, desc, icon_key, on_click, bg_color=None):
self.title = title
self.desc = desc
self.icon_key = icon_key
self.on_click = on_click
self.bg_color = bg_color
class _FakeGrid:
def __init__(self):
self.tiles = []
def clear(self):
self.tiles.clear()
def add_tile(self, tile):
self.tiles.append(tile)
class _PanelSpy:
def __init__(self, name):
self.name = name
self.show_count = 0
self.hide_count = 0
self.current_sub_panel = ""
def show_event(self):
self.show_count += 1
def hide_event(self):
self.hide_count += 1
def set_current_sub_panel(self, sub_panel):
self.current_sub_panel = sub_panel
def _make_layout(monkeypatch):
import openpilot.selfdrive.ui.layouts.settings.starpilot.main_panel as main_panel
monkeypatch.setattr(main_panel, "HubTile", _FakeHubTile)
layout = object.__new__(StarPilotLayout)
layout._current_panel = StarPilotPanelType.MAIN
layout._hub_path = []
layout._selected_leaf = None
layout._current_category_idx = None
layout._panel_stack = []
layout._depth_callback = None
layout._main_grid = _FakeGrid()
layout._panels = {}
for panel_type in StarPilotPanelType:
layout._panels[panel_type] = SimpleNamespace(
name=panel_type.name,
instance=None if panel_type == StarPilotPanelType.MAIN else _PanelSpy(panel_type.name),
)
depths = []
layout.set_depth_callback(depths.append)
StarPilotLayout.active_instance = layout
layout._rebuild_grid()
return layout, depths
def _click_title(layout, title):
tile = next(tile for tile in layout._main_grid.tiles if tile.title == title)
tile.on_click()
def test_nested_hub_navigation_back_and_depth_values(monkeypatch):
layout, depths = _make_layout(monkeypatch)
assert len(layout._main_grid.tiles) == 6
assert depths == []
_click_title(layout, "Driving Controls")
assert [folder["title"] for folder in layout._hub_path] == ["Driving Controls"]
assert layout._current_panel == StarPilotPanelType.MAIN
layout._update_depth()
assert depths[-1] == 1
_click_title(layout, "Navigation & Maps")
assert [folder["title"] for folder in layout._hub_path] == ["Driving Controls", "Navigation & Maps"]
layout._update_depth()
assert depths[-1] == 2
_click_title(layout, "Map Data")
assert layout._current_panel == StarPilotPanelType.MAPS
assert layout._selected_leaf["title"] == "Map Data"
assert depths[-1] == 3
maps_panel = layout._panels[StarPilotPanelType.MAPS].instance
assert (maps_panel.show_count, maps_panel.hide_count) == (1, 0)
layout.navigate_back()
assert layout._current_panel == StarPilotPanelType.MAIN
assert [folder["title"] for folder in layout._hub_path] == ["Driving Controls", "Navigation & Maps"]
assert depths[-1] == 2
assert (maps_panel.show_count, maps_panel.hide_count) == (1, 1)
layout.navigate_back()
assert [folder["title"] for folder in layout._hub_path] == ["Driving Controls"]
assert depths[-1] == 1
layout.navigate_back()
assert layout._hub_path == []
assert depths[-1] == 0
def test_root_driving_model_opens_directly_and_sub_panel_depth_is_additive(monkeypatch):
layout, depths = _make_layout(monkeypatch)
_click_title(layout, "Driving Model")
assert layout._hub_path == []
assert layout._current_panel == StarPilotPanelType.DRIVING_MODEL
assert depths[-1] == 1
layout._panel_stack.append((StarPilotPanelType.DRIVING_MODEL, "details"))
layout._commit_navigation()
assert depths[-1] == 2
layout.navigate_back()
assert layout._current_panel == StarPilotPanelType.DRIVING_MODEL
assert layout._panel_stack == []
assert depths[-1] == 1
layout.navigate_back()
assert layout._current_panel == StarPilotPanelType.MAIN
assert depths[-1] == 0
def test_breadcrumb_paths_and_folder_jump_back(monkeypatch):
layout, _ = _make_layout(monkeypatch)
monkeypatch.setattr(gui_app, "_nav_stack", [layout], raising=False)
assert BreadcrumbController.build_path() == [("StarPilot", "action:home")]
_click_title(layout, "Driving Controls")
assert BreadcrumbController.build_path() == [
("StarPilot", "action:home"),
("Driving Controls", "action:hub:1"),
]
_click_title(layout, "Navigation & Maps")
assert BreadcrumbController.build_path() == [
("StarPilot", "action:home"),
("Driving Controls", "action:hub:1"),
("Navigation & Maps", "action:hub:2"),
]
_click_title(layout, "Map Data")
assert BreadcrumbController.build_path()[-1] == ("Map Data", "action:panel")
nav_stack = [layout, object()]
monkeypatch.setattr(gui_app, "_nav_stack", nav_stack, raising=False)
monkeypatch.setattr(gui_app, "pop_widget", lambda: nav_stack.pop(), raising=False)
BreadcrumbController().handle_click("action:hub:1")
assert nav_stack == [layout]
assert layout._current_panel == StarPilotPanelType.MAIN
assert [folder["title"] for folder in layout._hub_path] == ["Driving Controls"]
assert BreadcrumbController.build_path()[-1] == ("Driving Controls", "action:hub:1")
def test_home_breadcrumb_clears_hub_path_panel_stack_and_active_panel(monkeypatch):
layout, _ = _make_layout(monkeypatch)
_click_title(layout, "Driving Controls")
_click_title(layout, "Navigation & Maps")
_click_title(layout, "Map Data")
layout._panel_stack.append((StarPilotPanelType.MAPS, "details"))
nav_stack = [layout, object(), object()]
monkeypatch.setattr(gui_app, "_nav_stack", nav_stack, raising=False)
monkeypatch.setattr(gui_app, "pop_widget", lambda: nav_stack.pop(), raising=False)
BreadcrumbController().handle_click("action:home")
maps_panel = layout._panels[StarPilotPanelType.MAPS].instance
assert nav_stack == [layout]
assert layout._hub_path == []
assert layout._selected_leaf is None
assert layout._panel_stack == []
assert layout._current_panel == StarPilotPanelType.MAIN
assert maps_panel.hide_count == 1
assert BreadcrumbController.build_path() == [("StarPilot", "action:home")]
def test_navigation_start_is_the_summary_action_not_a_duplicate_rail_target():
layout = object.__new__(StarPilotNavigationLayout)
layout._draft_destination = {