From e91d0bef8724392044746f7d529921633758a3fc Mon Sep 17 00:00:00 2001 From: firestarsdog <229254897+firestarsdog@users.noreply.github.com> Date: Thu, 9 Apr 2026 21:44:09 -0400 Subject: [PATCH] BigUI WIP: Spacing --- .../layouts/settings/starpilot/aethergrid.py | 95 ++++--- .../layouts/settings/starpilot/main_panel.py | 4 +- .../ui/layouts/settings/starpilot/panel.py | 269 +++++++++--------- .../ui/layouts/settings/starpilot/sounds.py | 10 +- .../settings/starpilot/tabbed_panel.py | 174 +++++------ 5 files changed, 287 insertions(+), 265 deletions(-) diff --git a/selfdrive/ui/layouts/settings/starpilot/aethergrid.py b/selfdrive/ui/layouts/settings/starpilot/aethergrid.py index 47fc86488..199bf867f 100644 --- a/selfdrive/ui/layouts/settings/starpilot/aethergrid.py +++ b/selfdrive/ui/layouts/settings/starpilot/aethergrid.py @@ -16,6 +16,23 @@ TILE_PADDING = 20 SLIDER_BUTTON_SIZE = 60 +class SPACING: + xs: int = 4 + sm: int = 8 + md: int = 12 + lg: int = 16 + xl: int = 24 + xxl: int = 32 + xxxl: int = 48 + + tile_gap: int = 16 + tile_content: int = 16 + line_gap: int = 8 + section_gap: int = 24 + tab_height: int = 96 + tab_panel_gap: int = 16 + + def hex_to_color(hex_str: str) -> rl.Color: hex_str = hex_str.lstrip('#') return rl.Color(int(hex_str[0:2], 16), int(hex_str[2:4], 16), int(hex_str[4:6], 16), 255) @@ -141,7 +158,7 @@ class AetherTile(Widget): rl.draw_text_ex(font, text, rl.Vector2(round(draw_x), round(pos.y + nudge_y)), actual_font_size, spacing, rl.WHITE) def _centered_content(self, face: rl.Rectangle, icon: rl.Texture2D | None, icon_scale: float, title_font_size: float, text_lines: int, line_heights: list[float]): - line_spacing = 8 + line_spacing = SPACING.line_gap total_h = sum(line_heights) + line_spacing * (text_lines - 1) icon_w = icon.width * icon_scale if icon else 0 icon_h = icon.height * icon_scale if icon else 0 @@ -197,20 +214,21 @@ class HubTile(AetherTile): def _render(self, rect: rl.Rectangle): face = self._render_layers(rect) - max_w = face.width - 40 + content_pad = SPACING.tile_content + max_w = face.width - (content_pad * 2) lines = self._wrap_text(self._font_title, self.title, max_w, 30) line_heights = [30] * len(lines) _, ty = self._centered_content(face, self._icon, 0.75, 30, len(line_heights), line_heights) line_h = 30 - line_spacing = 8 + line_spacing = SPACING.line_gap for i, line in enumerate(lines): - self._draw_text_fit(self._font_title, line, rl.Vector2(face.x + 20, ty + i * (line_h + line_spacing)), max_w, line_h, align_center=True) + self._draw_text_fit(self._font_title, line, rl.Vector2(face.x + content_pad, ty + i * (line_h + line_spacing)), max_w, line_h, align_center=True) if self.desc: desc_lines = self._wrap_text(self._font_desc, self.desc, max_w, 18, max_lines=3) - desc_y = ty + len(lines) * (line_h + line_spacing) + 18 + desc_y = ty + len(lines) * (line_h + line_spacing) + SPACING.lg for i, line in enumerate(desc_lines): - self._draw_text_fit(self._font_desc, line, rl.Vector2(face.x + 20, desc_y + i * 20), max_w, 18, align_center=True) + self._draw_text_fit(self._font_desc, line, rl.Vector2(face.x + content_pad, desc_y + i * 20), max_w, 18, align_center=True) class ToggleTile(AetherTile): @@ -250,16 +268,17 @@ class ToggleTile(AetherTile): face = self._render_layers(rect) line_heights = [28, 30] _, ty = self._centered_content(face, self._icon, 0.75, 28, len(line_heights), line_heights) - max_w = face.width - 40 - self._draw_text_fit(self._font, self.title, rl.Vector2(face.x + 20, ty), max_w, 28, align_center=True, uppercase=True) + content_pad = SPACING.tile_content + max_w = face.width - (content_pad * 2) + self._draw_text_fit(self._font, self.title, rl.Vector2(face.x + content_pad, ty), max_w, 28, align_center=True, uppercase=True) if enabled: state_text = tr("ON") if active else tr("OFF") else: state_text = tr(self._disabled_label) if self._disabled_label else tr("LOCKED") - self._draw_text_fit(self._font, state_text, rl.Vector2(face.x + 20, ty + 28 + 8), max_w, 30, align_center=True, uppercase=True) + self._draw_text_fit(self._font, state_text, rl.Vector2(face.x + content_pad, ty + 28 + SPACING.line_gap), max_w, 30, align_center=True, uppercase=True) if self.desc: - self._draw_text_fit(self._font_desc, self.desc, rl.Vector2(face.x + 20, ty + 28 + 8 + 34), max_w, 18, align_center=True) + self._draw_text_fit(self._font_desc, self.desc, rl.Vector2(face.x + content_pad, ty + 28 + SPACING.line_gap + 34), max_w, 18, align_center=True) class ValueTile(AetherTile): @@ -285,13 +304,14 @@ class ValueTile(AetherTile): face = self._render_layers(rect) line_heights = [28, 28] _, ty = self._centered_content(face, self._icon, 0.75, 28, len(line_heights), line_heights) - max_w = face.width - 40 - self._draw_text_fit(self._font, self.title, rl.Vector2(face.x + 20, ty), max_w, 28, align_center=True, uppercase=True) + content_pad = SPACING.tile_content + max_w = face.width - (content_pad * 2) + self._draw_text_fit(self._font, self.title, rl.Vector2(face.x + content_pad, ty), max_w, 28, align_center=True, uppercase=True) val_text = self.get_value() - self._draw_text_fit(self._font, val_text, rl.Vector2(face.x + 20, ty + 28 + 8), max_w, 28, align_center=True, uppercase=True) + self._draw_text_fit(self._font, val_text, rl.Vector2(face.x + content_pad, ty + 28 + SPACING.line_gap), max_w, 28, align_center=True, uppercase=True) if self.desc: - self._draw_text_fit(self._font_desc, self.desc, rl.Vector2(face.x + 20, ty + 28 + 8 + 34), max_w, 18, align_center=True) + self._draw_text_fit(self._font_desc, self.desc, rl.Vector2(face.x + content_pad, ty + 28 + SPACING.line_gap + 34), max_w, 18, align_center=True) class AetherSlider(Widget): @@ -488,38 +508,41 @@ class AetherSliderDialog(Widget): self._cancel_offset += (self._cancel_target - self._cancel_offset) * (1 - math.exp(-dt / PLATE_TAU)) rl.draw_rectangle(0, 0, gui_app.width, gui_app.height, rl.Color(0, 0, 0, 160)) dialog_w, dialog_h = 1000, 500 + dialog_margin = SPACING.xxl + button_height = 80 + button_width = 350 dx, dy = rect.x + (rect.width - dialog_w) / 2, rect.y + (rect.height - dialog_h) / 2 - self._ok_rect = rl.Rectangle(dx + dialog_w - 450, dy + dialog_h - 120, 350, 80) - self._cancel_rect = rl.Rectangle(dx + 100, dy + dialog_h - 120, 350, 80) + self._ok_rect = rl.Rectangle(dx + dialog_w - button_width - SPACING.lg, dy + dialog_h - button_height - SPACING.lg, button_width, button_height) + self._cancel_rect = rl.Rectangle(dx + SPACING.lg, dy + dialog_h - button_height - SPACING.lg, button_width, button_height) d_rect = rl.Rectangle(dx, dy, dialog_w, dialog_h) rl.draw_rectangle_rounded(d_rect, 0.05, 10, rl.Color(30, 30, 30, 255)) rl.draw_rectangle_rounded_lines_ex(d_rect, 0.05, 10, 2, self._color) ts = measure_text_cached(self._font_title, self.title, 50) - rl.draw_text_ex(self._font_title, self.title, rl.Vector2(round(dx + (dialog_w - ts.x) / 2), round(dy + 40)), 50, 0, rl.WHITE) - slider_rect = rl.Rectangle(dx + 100, dy + 200, dialog_w - 200, 100) + rl.draw_text_ex(self._font_title, self.title, rl.Vector2(round(dx + (dialog_w - ts.x) / 2), round(dy + SPACING.xxl)), 50, 0, rl.WHITE) + slider_rect = rl.Rectangle(dx + SPACING.xxl, dy + 200, dialog_w - (SPACING.xxl * 2), 100) self._slider.render(slider_rect) c_shadow_alpha = int(255 * (1.0 - 0.9 * self._cancel_offset)) - rl.draw_rectangle_rounded(rl.Rectangle(self._cancel_rect.x + GEOMETRY_OFFSET, self._cancel_rect.y + GEOMETRY_OFFSET, 350, 80), 0.2, 10, rl.Color(30, 30, 30, c_shadow_alpha)) + rl.draw_rectangle_rounded(rl.Rectangle(self._cancel_rect.x + GEOMETRY_OFFSET, self._cancel_rect.y + GEOMETRY_OFFSET, button_width, button_height), 0.2, 10, rl.Color(30, 30, 30, c_shadow_alpha)) c_face_x = self._cancel_rect.x + GEOMETRY_OFFSET * self._cancel_offset c_face_y = self._cancel_rect.y + GEOMETRY_OFFSET * self._cancel_offset - c_face = rl.Rectangle(c_face_x, c_face_y, 350, 80) + c_face = rl.Rectangle(c_face_x, c_face_y, button_width, button_height) rl.draw_rectangle_rounded(c_face, 0.2, 10, rl.Color(80, 80, 80, 255)) rl.draw_rectangle_rounded(rl.Rectangle(c_face.x + 1, c_face.y + 1, c_face.width - 2, c_face.height - 2), 0.2, 10, rl.Color(0, 0, 0, 80)) rl.draw_rectangle_rounded(rl.Rectangle(c_face.x, c_face.y, c_face.width - 1.5, c_face.height - 1.5), 0.2, 10, rl.Color(255, 255, 255, 110)) cts = measure_text_cached(self._font_btn, tr("CANCEL"), 35) - cancel_text_pos = rl.Vector2(c_face_x + (350 - cts.x) / 2, c_face_y + (80 - cts.y) / 2) + cancel_text_pos = rl.Vector2(c_face_x + (button_width - cts.x) / 2, c_face_y + (button_height - cts.y) / 2) rl.draw_text_ex(self._font_btn, tr("CANCEL"), rl.Vector2(round(cancel_text_pos.x + 1), round(cancel_text_pos.y + 2)), 35, 0, rl.Color(0, 0, 0, 90)) rl.draw_text_ex(self._font_btn, tr("CANCEL"), rl.Vector2(round(cancel_text_pos.x), round(cancel_text_pos.y)), 35, 0, rl.WHITE) o_shadow_alpha = int(255 * (1.0 - 0.9 * self._ok_offset)) - rl.draw_rectangle_rounded(rl.Rectangle(self._ok_rect.x + GEOMETRY_OFFSET, self._ok_rect.y + GEOMETRY_OFFSET, 350, 80), 0.2, 10, rl.Color(self._color.r, self._color.g, self._color.b, int(o_shadow_alpha * 0.4))) + rl.draw_rectangle_rounded(rl.Rectangle(self._ok_rect.x + GEOMETRY_OFFSET, self._ok_rect.y + GEOMETRY_OFFSET, button_width, button_height), 0.2, 10, rl.Color(self._color.r, self._color.g, self._color.b, int(o_shadow_alpha * 0.4))) o_face_x = self._ok_rect.x + GEOMETRY_OFFSET * self._ok_offset o_face_y = self._ok_rect.y + GEOMETRY_OFFSET * self._ok_offset - o_face = rl.Rectangle(o_face_x, o_face_y, 350, 80) + o_face = rl.Rectangle(o_face_x, o_face_y, button_width, button_height) rl.draw_rectangle_rounded(o_face, 0.2, 10, self._color) rl.draw_rectangle_rounded(rl.Rectangle(o_face.x + 1, o_face.y + 1, o_face.width - 2, o_face.height - 2), 0.2, 10, rl.Color(0, 0, 0, 80)) rl.draw_rectangle_rounded(rl.Rectangle(o_face.x, o_face.y, o_face.width - 1.5, o_face.height - 1.5), 0.2, 10, rl.Color(255, 255, 255, 110)) ots = measure_text_cached(self._font_btn, tr("OK"), 35) - ok_text_pos = rl.Vector2(o_face_x + (350 - ots.x) / 2, o_face_y + (80 - ots.y) / 2) + ok_text_pos = rl.Vector2(o_face_x + (button_width - ots.x) / 2, o_face_y + (button_height - ots.y) / 2) rl.draw_text_ex(self._font_btn, tr("OK"), rl.Vector2(round(ok_text_pos.x + 1), round(ok_text_pos.y + 2)), 35, 0, rl.Color(0, 0, 0, 90)) rl.draw_text_ex(self._font_btn, tr("OK"), rl.Vector2(round(ok_text_pos.x), round(ok_text_pos.y)), 35, 0, rl.WHITE) return DialogResult.NO_ACTION @@ -568,9 +591,9 @@ class RadioTileGroup(Widget): self._option_targets.append(0.0) for i in range(len(self._option_offsets)): self._option_offsets[i] += (self._option_targets[i] - self._option_offsets[i]) * (1 - math.exp(-dt / PLATE_TAU)) - padding = 16 + gap = SPACING.lg option_w = 240 if len(self.options) <= 3 else 188 - total_width = len(self.options) * option_w + max(0, len(self.options) - 1) * padding + total_width = len(self.options) * option_w + max(0, len(self.options) - 1) * gap if self.title: title_size = measure_text_cached(self._font_title, self.title, 40) rl.draw_text_ex(self._font_title, self.title, rl.Vector2(round(rect.x), round(rect.y + (rect.height - title_size.y) / 2)), 40, 0, rl.WHITE) @@ -578,7 +601,7 @@ class RadioTileGroup(Widget): else: start_x = rect.x + (rect.width - total_width) / 2 for i, opt in enumerate(self.options): - r = rl.Rectangle(start_x + i * (option_w + padding), rect.y, option_w, rect.height) + r = rl.Rectangle(start_x + i * (option_w + gap), rect.y, option_w, rect.height) self._option_rects.append(r) is_active = i == self.current_index color = self._active_color if is_active else self._inactive_color @@ -593,7 +616,7 @@ class RadioTileGroup(Widget): rl.draw_rectangle_rounded(rl.Rectangle(face_rect.x, face_rect.y, face_rect.width - 1.5, face_rect.height - 1.5), TILE_RADIUS, 10, rl.Color(255, 255, 255, 110)) font_size = 30 spacing = round(font_size * 0.08) - max_width = r.width - 28 + max_width = r.width - (SPACING.lg + SPACING.xs) ts = measure_text_cached(self._font, opt, font_size, spacing=spacing) while font_size > 22 and ts.x > max_width: font_size -= 1 @@ -605,9 +628,11 @@ class RadioTileGroup(Widget): class TileGrid(Widget): - def __init__(self, columns: int | None = None, padding: int = 20, uniform_width: bool = False): + def __init__(self, columns: int | None = None, padding: int | None = None, uniform_width: bool = False): super().__init__() - self._columns, self.padding, self.tiles = columns, padding, [] + self._columns = columns + self._gap = padding if padding is not None else SPACING.tile_gap + self.tiles = [] self._uniform_width = uniform_width def add_tile(self, tile: Widget): self.tiles.append(tile) @@ -630,8 +655,8 @@ class TileGrid(Widget): elif count <= 6: cols = 3 else: cols = 4 rows = (count + cols - 1) // cols - tile_h = (rect.height - (self.padding * (rows - 1))) / rows - uniform_tile_w = (rect.width - (self.padding * (cols - 1))) / cols if self._uniform_width else 0 + tile_h = (rect.height - (self._gap * (rows - 1))) / rows + uniform_tile_w = (rect.width - (self._gap * (cols - 1))) / cols if self._uniform_width else 0 tile_idx = 0 for r in range(rows): remaining = count - tile_idx @@ -639,12 +664,12 @@ class TileGrid(Widget): items_in_row = min(cols, remaining) if self._uniform_width: row_tile_w = uniform_tile_w - row_width = (row_tile_w * items_in_row) + (self.padding * (items_in_row - 1)) + row_width = (row_tile_w * items_in_row) + (self._gap * (items_in_row - 1)) row_x = rect.x + (rect.width - row_width) / 2 else: - row_tile_w = (rect.width - (self.padding * (items_in_row - 1))) / items_in_row + row_tile_w = (rect.width - (self._gap * (items_in_row - 1))) / items_in_row row_x = rect.x for c in range(items_in_row): tile = tiles_to_render[tile_idx] - tile.render(rl.Rectangle(row_x + c * (row_tile_w + self.padding), rect.y + r * (tile_h + self.padding), row_tile_w, tile_h)) + tile.render(rl.Rectangle(row_x + c * (row_tile_w + self._gap), rect.y + r * (tile_h + self._gap), row_tile_w, tile_h)) tile_idx += 1 diff --git a/selfdrive/ui/layouts/settings/starpilot/main_panel.py b/selfdrive/ui/layouts/settings/starpilot/main_panel.py index ffbeabc99..0af004dbf 100644 --- a/selfdrive/ui/layouts/settings/starpilot/main_panel.py +++ b/selfdrive/ui/layouts/settings/starpilot/main_panel.py @@ -23,7 +23,7 @@ from openpilot.selfdrive.ui.layouts.settings.starpilot.themes import StarPilotTh from openpilot.selfdrive.ui.layouts.settings.starpilot.vehicle import StarPilotVehicleSettingsLayout from openpilot.selfdrive.ui.layouts.settings.starpilot.wheel import StarPilotWheelLayout -from openpilot.selfdrive.ui.layouts.settings.starpilot.aethergrid import TileGrid, HubTile, RadioTileGroup +from openpilot.selfdrive.ui.layouts.settings.starpilot.aethergrid import TileGrid, HubTile, RadioTileGroup, SPACING STARPILOT_ICONS_DIR = "toggle_icons" @@ -115,7 +115,7 @@ class StarPilotLayout(Widget): StarPilotPanelType.VEHICLE, ) - self._main_grid = TileGrid(columns=None, padding=20) + self._main_grid = TileGrid(columns=None, padding=SPACING.tile_gap) self._rebuild_grid() def set_depth_callback(self, callback: Callable): diff --git a/selfdrive/ui/layouts/settings/starpilot/panel.py b/selfdrive/ui/layouts/settings/starpilot/panel.py index e0e64f243..d3ea13316 100644 --- a/selfdrive/ui/layouts/settings/starpilot/panel.py +++ b/selfdrive/ui/layouts/settings/starpilot/panel.py @@ -8,177 +8,174 @@ import pyray as rl from openpilot.common.params import Params from openpilot.system.ui.lib.multilang import tr from openpilot.system.ui.widgets import Widget -from openpilot.selfdrive.ui.layouts.settings.starpilot.aethergrid import TileGrid +from openpilot.selfdrive.ui.layouts.settings.starpilot.aethergrid import TileGrid, HubTile, ToggleTile, ValueTile, SPACING class StarPilotPanelType(IntEnum): - MAIN = 0 - SOUNDS = 1 - DRIVING_MODEL = 2 - LONGITUDINAL = 3 - LATERAL = 4 - MAPS = 5 - NAVIGATION = 6 - DATA = 7 - DEVICE = 8 - UTILITIES = 9 - VISUALS = 10 - THEMES = 11 - VEHICLE = 12 - WHEEL = 13 - SYSTEM = 14 + MAIN = 0 + SOUNDS = 1 + DRIVING_MODEL = 2 + LONGITUDINAL = 3 + LATERAL = 4 + MAPS = 5 + NAVIGATION = 6 + DATA = 7 + DEVICE = 8 + UTILITIES = 9 + VISUALS = 10 + THEMES = 11 + VEHICLE = 12 + WHEEL = 13 + SYSTEM = 14 @dataclass class StarPilotPanelInfo: - name: str - instance: Widget - - -from openpilot.selfdrive.ui.layouts.settings.starpilot.aethergrid import TileGrid, HubTile, ToggleTile, ValueTile + name: str + instance: Widget class StarPilotPanel(Widget): - def __init__(self): - super().__init__() - self._params = Params() - self._params_memory = Params(memory=True) - self._navigate_callback: Callable | None = None - self._back_callback: Callable | None = None - self._current_sub_panel = "" - self._sub_panels: dict[str, Widget] = {} - self._scroller = None - self._tile_grid = None - self.CATEGORIES = [] + def __init__(self): + super().__init__() + self._params = Params() + self._params_memory = Params(memory=True) + self._navigate_callback: Callable | None = None + self._back_callback: Callable | None = None + self._current_sub_panel = "" + self._sub_panels: dict[str, Widget] = {} + self._scroller = None + self._tile_grid = None + self.CATEGORIES = [] - def set_navigate_callback(self, callback: Callable): - self._navigate_callback = callback + def set_navigate_callback(self, callback: Callable): + self._navigate_callback = callback - def set_back_callback(self, callback: Callable): - self._back_callback = callback + def set_back_callback(self, callback: Callable): + self._back_callback = callback - def set_current_sub_panel(self, sub_panel: str): - self._current_sub_panel = sub_panel + def set_current_sub_panel(self, sub_panel: str): + self._current_sub_panel = sub_panel - def _rebuild_grid(self): - if not self.CATEGORIES: - return + def _rebuild_grid(self): + if not self.CATEGORIES: + return - if self._tile_grid is None: - self._tile_grid = TileGrid(columns=None, padding=20) + if self._tile_grid is None: + self._tile_grid = TileGrid(columns=None, padding=SPACING.tile_gap) - self._tile_grid.clear() + self._tile_grid.clear() - for cat in self.CATEGORIES: - visible_fn = cat.get("visible") - if visible_fn is not None and not visible_fn(): - continue + for cat in self.CATEGORIES: + visible_fn = cat.get("visible") + if visible_fn is not None and not visible_fn(): + continue - tile_type = cat.get("type", "hub") - if tile_type == "hub": - on_click = cat.get("on_click") - if on_click is None: - on_click = lambda c=cat: self._navigate_to(c["panel"]) + tile_type = cat.get("type", "hub") + if tile_type == "hub": + on_click = cat.get("on_click") + if on_click is None: + on_click = lambda c=cat: self._navigate_to(c["panel"]) - tile = HubTile( - title=tr(cat["title"]), - desc=tr(cat.get("desc", "")), - icon_path=cat.get("icon"), - on_click=on_click, - starpilot_icon=cat.get("starpilot_icon", True), - bg_color=cat.get("color"), - ) - elif tile_type == "toggle": - raw_set_state = cat["set_state"] + tile = HubTile( + title=tr(cat["title"]), + desc=tr(cat.get("desc", "")), + icon_path=cat.get("icon"), + on_click=on_click, + starpilot_icon=cat.get("starpilot_icon", True), + bg_color=cat.get("color"), + ) + elif tile_type == "toggle": + raw_set_state = cat["set_state"] - def on_toggle(state: bool, setter=raw_set_state): - setter(state) - self._rebuild_grid() + def on_toggle(state: bool, setter=raw_set_state): + setter(state) + self._rebuild_grid() - tile = ToggleTile(title=tr(cat["title"]), get_state=cat["get_state"], set_state=on_toggle, icon_path=cat.get("icon"), bg_color=cat.get("color"), desc=tr(cat.get("desc", "")), is_enabled=cat.get("is_enabled"), disabled_label=cat.get("disabled_label", "")) - elif tile_type == "value": - tile = ValueTile(title=tr(cat["title"]), get_value=cat["get_value"], on_click=cat["on_click"], icon_path=cat.get("icon"), bg_color=cat.get("color"), is_enabled=cat.get("is_enabled"), desc=tr(cat.get("desc", ""))) - else: - continue + tile = ToggleTile(title=tr(cat["title"]), get_state=cat["get_state"], set_state=on_toggle, icon_path=cat.get("icon"), bg_color=cat.get("color"), desc=tr(cat.get("desc", "")), is_enabled=cat.get("is_enabled"), disabled_label=cat.get("disabled_label", "")) + elif tile_type == "value": + tile = ValueTile(title=tr(cat["title"]), get_value=cat["get_value"], on_click=cat["on_click"], icon_path=cat.get("icon"), bg_color=cat.get("color"), is_enabled=cat.get("is_enabled"), desc=tr(cat.get("desc", ""))) + else: + continue - self._tile_grid.add_tile(tile) + self._tile_grid.add_tile(tile) - def _navigate_to(self, sub_panel: str): - self._current_sub_panel = sub_panel - if self._navigate_callback: - self._navigate_callback(sub_panel) + def _navigate_to(self, sub_panel: str): + self._current_sub_panel = sub_panel + if self._navigate_callback: + self._navigate_callback(sub_panel) - def _go_back(self): - self._current_sub_panel = "" - if self._back_callback: - self._back_callback() + def _go_back(self): + self._current_sub_panel = "" + if self._back_callback: + self._back_callback() - def _render(self, rect: rl.Rectangle): - if self._current_sub_panel and self._current_sub_panel in self._sub_panels: - self._sub_panels[self._current_sub_panel].render(rect) - elif self.CATEGORIES and self._tile_grid: - self._tile_grid.render(rect) - elif self._scroller: - self._scroller.render(rect) + def _render(self, rect: rl.Rectangle): + if self._current_sub_panel and self._current_sub_panel in self._sub_panels: + self._sub_panels[self._current_sub_panel].render(rect) + elif self.CATEGORIES and self._tile_grid: + self._tile_grid.render(rect) + elif self._scroller: + self._scroller.render(rect) - def show_event(self): - super().show_event() - self._rebuild_grid() - if self._current_sub_panel and self._current_sub_panel in self._sub_panels: - self._sub_panels[self._current_sub_panel].show_event() - elif self._scroller: - self._scroller.show_event() + def show_event(self): + super().show_event() + self._rebuild_grid() + if self._current_sub_panel and self._current_sub_panel in self._sub_panels: + self._sub_panels[self._current_sub_panel].show_event() + elif self._scroller: + self._scroller.show_event() def create_tile_panel(categories: list[dict], sub_panels: dict[str, Widget] | None = None) -> StarPilotPanel: - panel = StarPilotPanel() - panel.CATEGORIES = categories - panel._sub_panels = sub_panels or {} - panel._tile_grid = TileGrid(columns=2, padding=20, uniform_width=True) + panel = StarPilotPanel() + panel.CATEGORIES = categories + panel._sub_panels = sub_panels or {} + panel._tile_grid = TileGrid(columns=2, padding=SPACING.tile_gap, uniform_width=True) - for name, child in panel._sub_panels.items(): - if hasattr(child, 'set_navigate_callback'): - child.set_navigate_callback(panel._navigate_to) - if hasattr(child, 'set_back_callback'): - child.set_back_callback(panel._go_back) + for name, child in panel._sub_panels.items(): + if hasattr(child, 'set_navigate_callback'): + child.set_navigate_callback(panel._navigate_to) + if hasattr(child, 'set_back_callback'): + child.set_back_callback(panel._go_back) - panel._rebuild_grid() - return panel + panel._rebuild_grid() + return panel def create_master_toggle_panel(toggle_specs: list[dict], sub_panels: dict[str, Widget] | None = None, - extra_categories: list[dict] | None = None) -> StarPilotPanel: - panel = create_tile_panel([], sub_panels) - categories: list[dict] = [] + extra_categories: list[dict] | None = None) -> StarPilotPanel: + panel = create_tile_panel([], sub_panels) + categories: list[dict] = [] - for spec in toggle_specs: - get_state = spec["get_state"] - visible = spec.get("visible") - manage_enabled = spec.get("manage_enabled", get_state) + for spec in toggle_specs: + get_state = spec["get_state"] + visible = spec.get("visible") + manage_enabled = spec.get("manage_enabled", get_state) - categories.append({ - "title": spec["title"], - "desc": spec.get("desc", ""), - "type": "toggle", - "get_state": get_state, - "set_state": spec["set_state"], - "icon": spec.get("icon"), - "color": spec.get("color"), - "visible": visible, - }) + categories.append({ + "title": spec["title"], + "desc": spec.get("desc", ""), + "type": "toggle", + "get_state": get_state, + "set_state": spec["set_state"], + "icon": spec.get("icon"), + "color": spec.get("color"), + "visible": visible, + }) - categories.append({ - "title": spec.get("manage_title", "Settings"), - "desc": spec.get("manage_desc", ""), - "type": "value", - "get_value": lambda enabled=get_state, active_label=spec.get("manage_label", "Manage"), inactive_label=spec.get("disabled_label", "Enable First"): tr(active_label) if enabled() else tr(inactive_label), - "on_click": lambda sub_panel=spec["panel"]: panel._navigate_to(sub_panel), - "is_enabled": manage_enabled, - "icon": spec.get("manage_icon", spec.get("icon")), - "color": spec.get("color"), - "visible": visible, - }) + categories.append({ + "title": spec.get("manage_title", "Settings"), + "desc": spec.get("manage_desc", ""), + "type": "value", + "get_value": lambda enabled=get_state, active_label=spec.get("manage_label", "Manage"), inactive_label=spec.get("disabled_label", "Enable First"): tr(active_label) if enabled() else tr(inactive_label), + "on_click": lambda sub_panel=spec["panel"]: panel._navigate_to(sub_panel), + "is_enabled": manage_enabled, + "icon": spec.get("manage_icon", spec.get("icon")), + "color": spec.get("color"), + "visible": visible, + }) - panel.CATEGORIES = categories + list(extra_categories or []) - panel._rebuild_grid() - return panel + panel.CATEGORIES = categories + list(extra_categories or []) + panel._rebuild_grid() + return panel diff --git a/selfdrive/ui/layouts/settings/starpilot/sounds.py b/selfdrive/ui/layouts/settings/starpilot/sounds.py index e5e9f2a52..8765be1e8 100644 --- a/selfdrive/ui/layouts/settings/starpilot/sounds.py +++ b/selfdrive/ui/layouts/settings/starpilot/sounds.py @@ -12,7 +12,7 @@ from openpilot.system.ui.widgets import DialogResult from openpilot.selfdrive.ui.ui_state import ui_state from openpilot.selfdrive.ui.lib.starpilot_state import starpilot_state from openpilot.selfdrive.ui.layouts.settings.starpilot.panel import StarPilotPanel -from openpilot.selfdrive.ui.layouts.settings.starpilot.aethergrid import TileGrid, ToggleTile, AetherSliderDialog, RadioTileGroup +from openpilot.selfdrive.ui.layouts.settings.starpilot.aethergrid import TileGrid, ToggleTile, AetherSliderDialog, RadioTileGroup, SPACING class StarPilotSoundsLayout(StarPilotPanel): COOLDOWN_KEY = "SwitchbackModeCooldown" @@ -98,8 +98,8 @@ class StarPilotSoundsLayout(StarPilotPanel): self._set_active_section(sub_panel) def _render(self, rect): - tab_rect = rl.Rectangle(rect.x, rect.y, rect.width, 110) - panel_rect = rl.Rectangle(rect.x, rect.y + 140, rect.width, rect.height - 140) + tab_rect = rl.Rectangle(rect.x, rect.y, rect.width, SPACING.tab_height) + panel_rect = rl.Rectangle(rect.x, rect.y + SPACING.tab_height + SPACING.tab_panel_gap, rect.width, rect.height - SPACING.tab_height - SPACING.tab_panel_gap) self._section_tabs.render(tab_rect) self._sub_panels[self._active_section].render(panel_rect) @@ -129,7 +129,7 @@ class StarPilotVolumeControlLayout(StarPilotPanel): def __init__(self): super().__init__() self._init_sound_player() - self._tile_grid = TileGrid(columns=2, padding=20, uniform_width=True) + self._tile_grid = TileGrid(columns=2, padding=SPACING.tile_gap, uniform_width=True) self.CATEGORIES = [] for key in StarPilotSoundsLayout.VOLUME_KEYS: @@ -257,7 +257,7 @@ class StarPilotCustomAlertsLayout(StarPilotPanel): def __init__(self): super().__init__() - self._tile_grid = TileGrid(columns=2, padding=20, uniform_width=True) + self._tile_grid = TileGrid(columns=2, padding=SPACING.tile_gap, uniform_width=True) self.CATEGORIES = [] for key in StarPilotSoundsLayout.CUSTOM_ALERTS_KEYS: info = self.ALERT_INFO[key] diff --git a/selfdrive/ui/layouts/settings/starpilot/tabbed_panel.py b/selfdrive/ui/layouts/settings/starpilot/tabbed_panel.py index 871cb397e..b23f9db42 100644 --- a/selfdrive/ui/layouts/settings/starpilot/tabbed_panel.py +++ b/selfdrive/ui/layouts/settings/starpilot/tabbed_panel.py @@ -8,112 +8,112 @@ import pyray as rl from openpilot.system.ui.lib.multilang import tr from openpilot.system.ui.widgets import Widget -from openpilot.selfdrive.ui.layouts.settings.starpilot.aethergrid import RadioTileGroup +from openpilot.selfdrive.ui.layouts.settings.starpilot.aethergrid import RadioTileGroup, SPACING @dataclass(frozen=True) class TabSectionSpec: - key: str - label: str - panel: Widget + key: str + label: str + panel: Widget class TabbedSectionHost(Widget): - def __init__(self, sections: list[TabSectionSpec]): - super().__init__() - if not sections: - raise ValueError("TabbedSectionHost requires at least one section") + def __init__(self, sections: list[TabSectionSpec]): + super().__init__() + if not sections: + raise ValueError("TabbedSectionHost requires at least one section") - self._sections = {spec.key: spec.panel for spec in sections} - self._section_order = [spec.key for spec in sections] - self._active_section = self._section_order[0] - self._navigate_callback: Callable | None = None - self._back_callback: Callable | None = None - self._current_sub_panel = "" - self._tab_height = 110 - self._panel_top = 140 - self._section_tabs = RadioTileGroup("", [tr(spec.label) for spec in sections], 0, self._on_tab_change) + self._sections = {spec.key: spec.panel for spec in sections} + self._section_order = [spec.key for spec in sections] + self._active_section = self._section_order[0] + self._navigate_callback: Callable | None = None + self._back_callback: Callable | None = None + self._current_sub_panel = "" + self._tab_height = SPACING.tab_height + self._panel_top = self._tab_height + SPACING.tab_panel_gap + self._section_tabs = RadioTileGroup("", [tr(spec.label) for spec in sections], 0, self._on_tab_change) - for key, panel in self._sections.items(): - if hasattr(panel, "set_navigate_callback"): - panel.set_navigate_callback(lambda sub_panel, section_key=key: self._on_child_navigate(section_key, sub_panel)) - if hasattr(panel, "set_back_callback"): - panel.set_back_callback(self._go_back) + for key, panel in self._sections.items(): + if hasattr(panel, "set_navigate_callback"): + panel.set_navigate_callback(lambda sub_panel, section_key=key: self._on_child_navigate(section_key, sub_panel)) + if hasattr(panel, "set_back_callback"): + panel.set_back_callback(self._go_back) - def set_navigate_callback(self, callback: Callable): - self._navigate_callback = callback + def set_navigate_callback(self, callback: Callable): + self._navigate_callback = callback - def set_back_callback(self, callback: Callable): - self._back_callback = callback + def set_back_callback(self, callback: Callable): + self._back_callback = callback - def set_current_sub_panel(self, sub_panel: str): - self._current_sub_panel = sub_panel - if not sub_panel: - panel = self._sections[self._active_section] - if hasattr(panel, "set_current_sub_panel"): - panel.set_current_sub_panel("") - return + def set_current_sub_panel(self, sub_panel: str): + self._current_sub_panel = sub_panel + if not sub_panel: + panel = self._sections[self._active_section] + if hasattr(panel, "set_current_sub_panel"): + panel.set_current_sub_panel("") + return - if ":" in sub_panel: - section_key, child_panel = sub_panel.split(":", 1) - self._activate_section(section_key, child_panel) - elif sub_panel in self._sections: - self._activate_section(sub_panel) - else: - panel = self._sections[self._active_section] - if hasattr(panel, "set_current_sub_panel"): - panel.set_current_sub_panel(sub_panel) + if ":" in sub_panel: + section_key, child_panel = sub_panel.split(":", 1) + self._activate_section(section_key, child_panel) + elif sub_panel in self._sections: + self._activate_section(sub_panel) + else: + panel = self._sections[self._active_section] + if hasattr(panel, "set_current_sub_panel"): + panel.set_current_sub_panel(sub_panel) - def _on_tab_change(self, index: int): - if 0 <= index < len(self._section_order): - self._current_sub_panel = "" - self._activate_section(self._section_order[index], "") - if self._navigate_callback: - self._navigate_callback("") + def _on_tab_change(self, index: int): + if 0 <= index < len(self._section_order): + self._current_sub_panel = "" + self._activate_section(self._section_order[index], "") + if self._navigate_callback: + self._navigate_callback("") - def _activate_section(self, section_key: str, child_panel: str = ""): - if section_key not in self._sections: - return + def _activate_section(self, section_key: str, child_panel: str = ""): + if section_key not in self._sections: + return - previous = self._active_section - if section_key != previous: - previous_panel = self._sections[previous] - if hasattr(previous_panel, "set_current_sub_panel"): - previous_panel.set_current_sub_panel("") - self._sections[previous].hide_event() - self._active_section = section_key - self._sections[section_key].show_event() + previous = self._active_section + if section_key != previous: + previous_panel = self._sections[previous] + if hasattr(previous_panel, "set_current_sub_panel"): + previous_panel.set_current_sub_panel("") + self._sections[previous].hide_event() + self._active_section = section_key + self._sections[section_key].show_event() - self._section_tabs.set_index(self._section_order.index(section_key)) - panel = self._sections[section_key] - if hasattr(panel, "set_current_sub_panel"): - panel.set_current_sub_panel(child_panel) + self._section_tabs.set_index(self._section_order.index(section_key)) + panel = self._sections[section_key] + if hasattr(panel, "set_current_sub_panel"): + panel.set_current_sub_panel(child_panel) - def _on_child_navigate(self, section_key: str, sub_panel: str): - self._current_sub_panel = f"{section_key}:{sub_panel}" if sub_panel else section_key - if self._navigate_callback: - self._navigate_callback(self._current_sub_panel) + def _on_child_navigate(self, section_key: str, sub_panel: str): + self._current_sub_panel = f"{section_key}:{sub_panel}" if sub_panel else section_key + if self._navigate_callback: + self._navigate_callback(self._current_sub_panel) - def _go_back(self): - self._current_sub_panel = "" - panel = self._sections[self._active_section] - if hasattr(panel, "set_current_sub_panel"): - panel.set_current_sub_panel("") - if self._back_callback: - self._back_callback() + def _go_back(self): + self._current_sub_panel = "" + panel = self._sections[self._active_section] + if hasattr(panel, "set_current_sub_panel"): + panel.set_current_sub_panel("") + if self._back_callback: + self._back_callback() - def _render(self, rect: rl.Rectangle): - tab_rect = rl.Rectangle(rect.x, rect.y, rect.width, self._tab_height) - panel_rect = rl.Rectangle(rect.x, rect.y + self._panel_top, rect.width, rect.height - self._panel_top) - self._section_tabs.render(tab_rect) - self._sections[self._active_section].render(panel_rect) + def _render(self, rect: rl.Rectangle): + tab_rect = rl.Rectangle(rect.x, rect.y, rect.width, self._tab_height) + panel_rect = rl.Rectangle(rect.x, rect.y + self._panel_top, rect.width, rect.height - self._panel_top) + self._section_tabs.render(tab_rect) + self._sections[self._active_section].render(panel_rect) - def show_event(self): - super().show_event() - self._section_tabs.show_event() - self._sections[self._active_section].show_event() + def show_event(self): + super().show_event() + self._section_tabs.show_event() + self._sections[self._active_section].show_event() - def hide_event(self): - super().hide_event() - self._section_tabs.hide_event() - self._sections[self._active_section].hide_event() + def hide_event(self): + super().hide_event() + self._section_tabs.hide_event() + self._sections[self._active_section].hide_event()