From e1458d4723bfb6e261d4980a3f2876f77ef2c27d Mon Sep 17 00:00:00 2001 From: firestarsdog <229254897+firestarsdog@users.noreply.github.com> Date: Sun, 23 Aug 2026 03:38:38 -0400 Subject: [PATCH] Consistency --- .../layouts/settings/starpilot/aethergrid.py | 174 +++++++++++++----- .../ui/layouts/settings/starpilot/sounds.py | 11 +- .../settings/starpilot/system_settings.py | 15 +- .../ui/layouts/settings/starpilot/vehicle.py | 47 +++-- 4 files changed, 168 insertions(+), 79 deletions(-) diff --git a/selfdrive/ui/layouts/settings/starpilot/aethergrid.py b/selfdrive/ui/layouts/settings/starpilot/aethergrid.py index b853446b3..ff875e42a 100644 --- a/selfdrive/ui/layouts/settings/starpilot/aethergrid.py +++ b/selfdrive/ui/layouts/settings/starpilot/aethergrid.py @@ -1968,10 +1968,12 @@ def draw_metric_strip( ) +GROUP_TOP_INSET = 16.0 GROUP_HEADER_HEIGHT = 30.0 -GROUP_HEADER_GAP = 1.0 -GROUP_HEADER_LINE_GAP = 1.0 -GROUP_OVERHEAD = 8.0 + GROUP_HEADER_HEIGHT + GROUP_HEADER_LINE_GAP + GROUP_HEADER_GAP +GROUP_HEADER_GAP = 10.0 +GROUP_HEADER_LINE_GAP = 4.0 +GROUP_HEADER_TOTAL_HEIGHT = GROUP_HEADER_HEIGHT + GROUP_HEADER_LINE_GAP + GROUP_HEADER_GAP +GROUP_OVERHEAD = GROUP_TOP_INSET + GROUP_HEADER_TOTAL_HEIGHT GROUP_HAIRLINE_COLOR = rl.Color(255, 255, 255, 30) GROUP_HEADER_COLOR = AetherListColors.HEADER @@ -1979,7 +1981,7 @@ GROUP_HEADER_COLOR = AetherListColors.HEADER def draw_group_header(x: float, y: float, width: float, label: str) -> float: gui_label(rl.Rectangle(x, y, max(1.0, width), GROUP_HEADER_HEIGHT), label, 30, GROUP_HEADER_COLOR, FontWeight.MEDIUM) y += GROUP_HEADER_HEIGHT + GROUP_HEADER_LINE_GAP - rl.draw_line(int(x), int(y), int(x + width), int(y), GROUP_HAIRLINE_COLOR) + rl.draw_line(int(round(x)), int(round(y)), int(round(x + width)), int(round(y)), GROUP_HAIRLINE_COLOR) return y + GROUP_HEADER_GAP @@ -2086,7 +2088,7 @@ def draw_settings_list_row( title_size: int = 49, subtitle_size: int = 38, value_size: int = 44, - separator_inset: int = 22, + separator_inset: int = 24, title_color: rl.Color | None = None, subtitle_color: rl.Color | None = None, value_color: rl.Color | None = None, @@ -2096,7 +2098,12 @@ def draw_settings_list_row( resolved_title_color = title_color or (style.title_color if enabled else style.muted_color) resolved_subtitle_color = subtitle_color or (style.subtitle_color if enabled else style.muted_color) resolved_value_color = value_color or (style.title_color if enabled else style.muted_color) - chevron_rect = rl.Rectangle(draw_rect.x + draw_rect.width - AETHER_LIST_METRICS.utility_chevron_right, draw_rect.y + 18, 26, 26) + + is_narrow = draw_rect.width < 1000 + chevron_right_inset = 28 if is_narrow else AETHER_LIST_METRICS.utility_chevron_right + chevron_y = draw_rect.y + (draw_rect.height - 26) / 2 + chevron_rect = rl.Rectangle(draw_rect.x + draw_rect.width - chevron_right_inset, chevron_y, 26, 26) + draw_list_row_shell( draw_rect, hovered=hovered and enabled, @@ -2110,32 +2117,40 @@ def draw_settings_list_row( separator_inset=separator_inset, ) - # Compute text width based on right-side elements — no truncation, font-size reduces gracefully text_left = draw_rect.x + 24 - if toggle_value is not None: - text_right = draw_rect.x + draw_rect.width - AETHER_LIST_METRICS.toggle_width - AETHER_LIST_METRICS.toggle_right_inset - 12 - elif value: - text_right = draw_rect.x + draw_rect.width - AETHER_LIST_METRICS.utility_value_right - 12 - elif show_chevron: - text_right = draw_rect.x + draw_rect.width - AETHER_LIST_METRICS.utility_chevron_right - 26 - 12 - else: - text_right = draw_rect.x + draw_rect.width - 24 - text_width = max(100.0, text_right - text_left) - draw_text_fit_common( - gui_app.font(FontWeight.MEDIUM), title, - rl.Vector2(text_left, draw_rect.y + 16), - text_width, title_size, - color=resolved_title_color, - ) - if subtitle: - draw_text_fit_common( - gui_app.font(FontWeight.NORMAL), subtitle, - rl.Vector2(text_left, draw_rect.y + 64), - text_width, subtitle_size, - color=resolved_subtitle_color, - ) if toggle_value is not None: + toggle_right_inset = 28 if is_narrow else AETHER_LIST_METRICS.toggle_right_inset + text_right = draw_rect.x + draw_rect.width - AETHER_LIST_METRICS.toggle_width - toggle_right_inset - 12 + text_width = max(100.0, text_right - text_left) + + if subtitle: + eff_title_size = min(36, title_size) + eff_sub_size = min(26, subtitle_size) + total_h = eff_title_size + eff_sub_size + 4 + start_y = draw_rect.y + (draw_rect.height - total_h) / 2 + + draw_text_fit_common( + gui_app.font(FontWeight.MEDIUM), title, + rl.Vector2(text_left, start_y), + text_width, eff_title_size, + color=resolved_title_color, + ) + draw_text_fit_common( + gui_app.font(FontWeight.NORMAL), subtitle, + rl.Vector2(text_left, start_y + eff_title_size + 4), + text_width, eff_sub_size, + color=resolved_subtitle_color, + ) + else: + title_y = draw_rect.y + (draw_rect.height - title_size) / 2 + draw_text_fit_common( + gui_app.font(FontWeight.MEDIUM), title, + rl.Vector2(text_left, title_y), + text_width, title_size, + color=resolved_title_color, + ) + target = 1.0 if toggle_value else 0.0 current_progress = _KNOB_ANIMATION_STATES.get(title, target) dt = rl.get_frame_time() @@ -2151,26 +2166,99 @@ def draw_settings_list_row( is_enabled=enabled, track_color=style.accent, seed_id=title, + right_inset=toggle_right_inset, ) return if value: - value_left = draw_rect.x + draw_rect.width - AETHER_LIST_METRICS.utility_value_right - value_right = chevron_rect.x - 16 if show_chevron else draw_rect.x + draw_rect.width - 24 - value_rect = rl.Rectangle(value_left, draw_rect.y + 20, max(48.0, value_right - value_left), 34) - gui_label( - value_rect, - value, - value_size, - resolved_value_color, - FontWeight.MEDIUM, - alignment=rl.GuiTextAlignment.TEXT_ALIGN_RIGHT, + if is_narrow and draw_rect.height >= 86 and not subtitle: + # Adaptive Two-Line Stacked Layout: Title on top, Value spanning full width below + eff_title_size = min(34, title_size) + eff_value_size = min(28, value_size) + available_w = max(100.0, draw_rect.width - 48 - (32 if show_chevron else 0)) + + total_h = eff_title_size + eff_value_size + 6 + start_y = draw_rect.y + (draw_rect.height - total_h) / 2 + + draw_text_fit_common( + gui_app.font(FontWeight.MEDIUM), title, + rl.Vector2(text_left, start_y), + available_w, eff_title_size, + color=resolved_title_color, + ) + draw_text_fit_common( + gui_app.font(FontWeight.MEDIUM), value, + rl.Vector2(text_left, start_y + eff_title_size + 6), + available_w, eff_value_size, + color=resolved_subtitle_color if resolved_value_color == resolved_title_color else resolved_value_color, + ) + else: + # Proportional Side-by-Side: 42% Title on left, 56% Value right-aligned before chevron + if is_narrow: + content_w = max(100.0, draw_rect.width - 48 - (32 if show_chevron else 0)) + t_width = content_w * 0.42 + v_width = content_w * 0.56 + v_right = draw_rect.x + 24 + content_w + else: + v_width = max(100.0, float(AETHER_LIST_METRICS.utility_value_right - (AETHER_LIST_METRICS.utility_chevron_right if show_chevron else 24) - 16)) + t_width = max(100.0, draw_rect.width - 48 - v_width - (32 if show_chevron else 0)) + v_right = chevron_rect.x - 16 if show_chevron else draw_rect.x + draw_rect.width - 24 + + eff_title_size = min(36, title_size) if is_narrow else title_size + eff_value_size = min(30, value_size) if is_narrow else value_size + + title_y = draw_rect.y + (draw_rect.height - eff_title_size) / 2 + value_y = draw_rect.y + (draw_rect.height - eff_value_size) / 2 + + draw_text_fit_common( + gui_app.font(FontWeight.MEDIUM), title, + rl.Vector2(text_left, title_y), + t_width, eff_title_size, + color=resolved_title_color, + ) + draw_text_fit_common( + gui_app.font(FontWeight.MEDIUM), value, + rl.Vector2(v_right - v_width, value_y), + v_width, eff_value_size, + align_right=True, + color=resolved_value_color, + ) + + if show_chevron: + draw_chevron_icon(chevron_rect, style.muted_color) + return + + # Generic row without value or toggle + text_right = chevron_rect.x - 12 if show_chevron else draw_rect.x + draw_rect.width - 24 + text_width = max(100.0, text_right - text_left) + if subtitle: + eff_title_size = min(36, title_size) + eff_sub_size = min(26, subtitle_size) + total_h = eff_title_size + eff_sub_size + 4 + start_y = draw_rect.y + (draw_rect.height - total_h) / 2 + draw_text_fit_common( + gui_app.font(FontWeight.MEDIUM), title, + rl.Vector2(text_left, start_y), + text_width, eff_title_size, + color=resolved_title_color, ) + draw_text_fit_common( + gui_app.font(FontWeight.NORMAL), subtitle, + rl.Vector2(text_left, start_y + eff_title_size + 4), + text_width, eff_sub_size, + color=resolved_subtitle_color, + ) + else: + title_y = draw_rect.y + (draw_rect.height - title_size) / 2 + draw_text_fit_common( + gui_app.font(FontWeight.MEDIUM), title, + rl.Vector2(text_left, title_y), + text_width, title_size, + color=resolved_title_color, + ) + if show_chevron: - draw_chevron_icon( - chevron_rect, - style.muted_color, - ) + draw_chevron_icon(chevron_rect, style.muted_color) def draw_selectable_chip(rect: rl.Rectangle, text: str, *, diff --git a/selfdrive/ui/layouts/settings/starpilot/sounds.py b/selfdrive/ui/layouts/settings/starpilot/sounds.py index e682a4806..7fcb08eaa 100644 --- a/selfdrive/ui/layouts/settings/starpilot/sounds.py +++ b/selfdrive/ui/layouts/settings/starpilot/sounds.py @@ -34,6 +34,8 @@ from openpilot.selfdrive.ui.layouts.settings.starpilot.aethergrid import ( GROUP_HEADER_HEIGHT, GROUP_HEADER_GAP, GROUP_HEADER_LINE_GAP, + GROUP_HEADER_TOTAL_HEIGHT, + GROUP_TOP_INSET, draw_group_header, ) @@ -199,8 +201,7 @@ class SoundsManagerView(AdjustorTogglesPanelView): self._adjustor_rows[key].custom_row_height = None self._adjustor_rows[self._controller.COOLDOWN_KEY].custom_row_height = None - hdr_h = GROUP_HEADER_HEIGHT + GROUP_HEADER_GAP + GROUP_HEADER_LINE_GAP - vol_overhead = 4 + 24 + 4 # top pad + "Reset All" label + gap + vol_overhead = GROUP_TOP_INSET + 28 # top pad + "Reset All" label available_h = max(72.0, (self._scroll_rect.height if self._scroll_rect else 0.0) - 6.0) rows_available = max(72.0 * (len(self._controller.VOLUME_KEYS) + 1), available_h - vol_overhead) @@ -210,7 +211,7 @@ class SoundsManagerView(AdjustorTogglesPanelView): self._adjustor_rows[self._controller.COOLDOWN_KEY].custom_row_height = ROW_HEIGHT left_content_h = (len(self._controller.VOLUME_KEYS) + 1) * ROW_HEIGHT + vol_overhead - tiles_needed_h = self.measure_page_grid_height(self._toggle_grid, col_width - 24) + 24 + 4 + hdr_h + tiles_needed_h = self.measure_page_grid_height(self._toggle_grid, col_width - 24) + 24 + GROUP_TOP_INSET + GROUP_HEADER_TOTAL_HEIGHT max_content_h = max(left_content_h, tiles_needed_h) self._left_container_h = max_content_h @@ -236,7 +237,7 @@ class SoundsManagerView(AdjustorTogglesPanelView): style=PANEL_STYLE ) - current_y = y + 4 + current_y = y + GROUP_TOP_INSET label_rect = rl.Rectangle(x + 24, current_y, width - 48, 28) gui_label(label_rect, tr("Reset All"), 28, AetherListColors.SUBTEXT, FontWeight.MEDIUM, @@ -255,7 +256,7 @@ class SoundsManagerView(AdjustorTogglesPanelView): def _draw_utility_column(self, y: float, x: float, width: float): draw_list_group_shell(rl.Rectangle(x, y, width, self._tiles_container_h), style=PANEL_STYLE) - header_y = draw_group_header(x + 24, y + 4, width - 48, tr("Alerts")) + header_y = draw_group_header(x + 24, y + GROUP_TOP_INSET, width - 48, tr("Alerts")) avail_h = self._tiles_container_h - (header_y - y) self._render_page_grid(self._toggle_grid, rl.Rectangle(x + 12, header_y, width - 24, max(0.0, avail_h - 12))) diff --git a/selfdrive/ui/layouts/settings/starpilot/system_settings.py b/selfdrive/ui/layouts/settings/starpilot/system_settings.py index ca01747a8..0786351fe 100644 --- a/selfdrive/ui/layouts/settings/starpilot/system_settings.py +++ b/selfdrive/ui/layouts/settings/starpilot/system_settings.py @@ -39,6 +39,8 @@ from openpilot.selfdrive.ui.layouts.settings.starpilot.aethergrid import ( GROUP_HEADER_GAP, GROUP_HEADER_LINE_GAP, GROUP_HEADER_HEIGHT, + GROUP_HEADER_TOTAL_HEIGHT, + GROUP_TOP_INSET, draw_group_header, AetherSliderDialog, mix_colors, @@ -485,9 +487,8 @@ class SystemSettingsManagerView(PanelManagerView): draw_custom_icon("first_aid", icon_x, icon_y, s, icon_color) def _measure_content_height(self, width: float) -> float: - hdr_h = GROUP_HEADER_HEIGHT + GROUP_HEADER_LINE_GAP + GROUP_HEADER_GAP - display_h = self._slider_section_height(self._display_slider_keys, width) + 4 + hdr_h - power_h = self._slider_section_height(self._power_slider_keys, width) + 4 + hdr_h + display_h = self._slider_section_height(self._display_slider_keys, width) + GROUP_TOP_INSET + GROUP_HEADER_TOTAL_HEIGHT + power_h = self._slider_section_height(self._power_slider_keys, width) + GROUP_TOP_INSET + GROUP_HEADER_TOTAL_HEIGHT if self._uses_two_columns(width): column_w = self._column_width(width) @@ -499,7 +500,7 @@ class SystemSettingsManagerView(PanelManagerView): display_container_h = self._slider_section_height(self._display_slider_keys, column_w) power_container_h = self._slider_section_height(self._power_slider_keys, column_w) - left_overhead = 4.0 + 2 * (GROUP_HEADER_HEIGHT + GROUP_HEADER_LINE_GAP + GROUP_HEADER_GAP) + SECTION_GAP + left_overhead = GROUP_TOP_INSET + 2 * GROUP_HEADER_TOTAL_HEIGHT + SECTION_GAP left_natural_content_h = left_overhead + display_container_h + power_container_h tiles_content_h = self.measure_page_grid_height(self._connectivity_tile_grid, column_w - 24) @@ -550,7 +551,7 @@ class SystemSettingsManagerView(PanelManagerView): draw_list_group_shell(rl.Rectangle(x, y, column_w, adj_container_h), style=PANEL_STYLE) - current_y = y + 4 + current_y = y + GROUP_TOP_INSET current_y = draw_group_header(x + 24, current_y, column_w - 48, tr("Display")) for index, key in enumerate(self._display_slider_keys): current_y = self._draw_slider_row(rl.Rectangle(x, current_y, column_w, 0), key, is_last=index == len(self._display_slider_keys) - 1) @@ -580,10 +581,10 @@ class SystemSettingsManagerView(PanelManagerView): self._render_page_grid(self._connectivity_tile_grid, rl.Rectangle(x + 12, y + 12, width - 24, tiles_content_h)) def _draw_slider_section(self, y: float, x: float, width: float, title: str, keys: list[str]) -> float: - group_h = self._slider_section_height(keys, width) + 4 + GROUP_HEADER_HEIGHT + GROUP_HEADER_LINE_GAP + GROUP_HEADER_GAP + group_h = self._slider_section_height(keys, width) + GROUP_TOP_INSET + GROUP_HEADER_TOTAL_HEIGHT group_rect = rl.Rectangle(x, y, width, group_h) draw_list_group_shell(group_rect, style=PANEL_STYLE) - current_y = group_rect.y + 4 + current_y = group_rect.y + GROUP_TOP_INSET current_y = draw_group_header(x + 24, current_y, width - 48, title) for index, key in enumerate(keys): current_y = self._draw_slider_row(rl.Rectangle(group_rect.x, current_y, group_rect.width, 0), key, is_last=index == len(keys) - 1) diff --git a/selfdrive/ui/layouts/settings/starpilot/vehicle.py b/selfdrive/ui/layouts/settings/starpilot/vehicle.py index 99c4e2cad..fc1268a99 100644 --- a/selfdrive/ui/layouts/settings/starpilot/vehicle.py +++ b/selfdrive/ui/layouts/settings/starpilot/vehicle.py @@ -29,6 +29,8 @@ from openpilot.selfdrive.ui.layouts.settings.starpilot.aethergrid import ( GROUP_HEADER_GAP, GROUP_HEADER_HEIGHT, GROUP_HEADER_LINE_GAP, + GROUP_HEADER_TOTAL_HEIGHT, + GROUP_TOP_INSET, draw_group_header, draw_list_group_shell, draw_rounded_fill, @@ -192,17 +194,17 @@ class VehicleSettingsManagerView(PanelManagerView): row_h = rect.height if row_h < 90: - title_size = 36 - subtitle_size = 26 - value_size = 32 + title_size = 30 + subtitle_size = 24 + value_size = 26 elif row_h < 105: - title_size = 42 - subtitle_size = 32 - value_size = 38 + title_size = 34 + subtitle_size = 26 + value_size = 28 else: - title_size = 49 - subtitle_size = 38 - value_size = 44 + title_size = 36 + subtitle_size = 28 + value_size = 30 if row.type == "value" or row.id.startswith("combo:"): value_text = row.get_value() if row.get_value else "" @@ -222,10 +224,9 @@ class VehicleSettingsManagerView(PanelManagerView): style=PANEL_STYLE) def _draw_section(self, y: float, x: float, width: float, title: str, rows: list[SettingRow], row_height: float = ROW_HEIGHT) -> float: - hdr_oh = GROUP_HEADER_HEIGHT + GROUP_HEADER_LINE_GAP + GROUP_HEADER_GAP - group_h = len(rows) * row_height + hdr_oh + 4 + group_h = len(rows) * row_height + GROUP_HEADER_TOTAL_HEIGHT + GROUP_TOP_INSET draw_list_group_shell(rl.Rectangle(x, y, width, group_h), style=PANEL_STYLE) - current_y = y + 4 + current_y = y + GROUP_TOP_INSET current_y = draw_group_header(x + 24, current_y, width - 48, tr(title)) for i, row in enumerate(rows): self._draw_row(rl.Rectangle(x, current_y, width, row_height), row, i == len(rows) - 1) @@ -242,7 +243,7 @@ class VehicleSettingsManagerView(PanelManagerView): rx = rect.x + col_w + self.COLUMN_GAP draw_list_group_shell(rl.Rectangle(rect.x, y, col_w, self._container_h), style=PANEL_STYLE) - row_y = y + 4 + row_y = y + GROUP_TOP_INSET row_y = draw_group_header(rect.x + 24, row_y, col_w - 48, tr("Vehicle Identity")) for i, row in enumerate(identity_rows): self._draw_row(rl.Rectangle(rect.x, row_y, col_w, self._left_row_height), @@ -250,6 +251,7 @@ class VehicleSettingsManagerView(PanelManagerView): row_y += self._left_row_height if steering_rows: + row_y += GROUP_TOP_INSET row_y = draw_group_header(rect.x + 24, row_y, col_w - 48, tr("Steering Controls")) for i, row in enumerate(steering_rows): self._draw_row(rl.Rectangle(rect.x, row_y, col_w, self._left_row_height), @@ -258,7 +260,7 @@ class VehicleSettingsManagerView(PanelManagerView): if self._toggle_grid.tiles: draw_list_group_shell(rl.Rectangle(rx, y, col_w, self._container_h), style=PANEL_STYLE) - tile_y = y + 4 + tile_y = y + GROUP_TOP_INSET tile_y = draw_group_header(rx + 24, tile_y, col_w - 48, tr("Features")) avail_h = self._container_h - (tile_y - y) self._render_page_grid(self._toggle_grid, rl.Rectangle(rx + 12, tile_y, col_w - 24, max(0.0, avail_h - 12))) @@ -273,10 +275,9 @@ class VehicleSettingsManagerView(PanelManagerView): self._toggle_grid._columns = 3 avail = width - 24 th = self.measure_page_grid_height(self._toggle_grid, avail) - hdr_oh = GROUP_HEADER_HEIGHT + GROUP_HEADER_LINE_GAP + GROUP_HEADER_GAP - group_h = th + 24 + 4 + hdr_oh + group_h = th + 24 + GROUP_TOP_INSET + GROUP_HEADER_TOTAL_HEIGHT draw_list_group_shell(rl.Rectangle(rect.x, y, width, group_h), style=PANEL_STYLE) - features_y = y + 4 + features_y = y + GROUP_TOP_INSET features_y = draw_group_header(rect.x + 24, features_y, width - 48, tr("Features")) self._render_page_grid(self._toggle_grid, rl.Rectangle(rect.x + 12, features_y, avail, max(0.0, group_h - (features_y - y) - 12))) @@ -297,10 +298,9 @@ class VehicleSettingsManagerView(PanelManagerView): tiles_h = self.measure_page_grid_height(self._toggle_grid, width - 24) if self._uses_two_columns(width): - hdr_oh = GROUP_HEADER_HEIGHT + GROUP_HEADER_LINE_GAP + GROUP_HEADER_GAP - subsection_overhead = hdr_oh + 4 + subsection_overhead = GROUP_HEADER_TOTAL_HEIGHT + GROUP_TOP_INSET if steering_rows: - subsection_overhead += hdr_oh + subsection_overhead += GROUP_HEADER_TOTAL_HEIGHT + GROUP_TOP_INSET avail_h = self._scroll_rect.height if self._scroll_rect else 760.0 avail_for_rows = max(100.0, avail_h - subsection_overhead) @@ -316,11 +316,10 @@ class VehicleSettingsManagerView(PanelManagerView): self._container_h = total_h return total_h - hdr_oh = GROUP_HEADER_HEIGHT + GROUP_HEADER_LINE_GAP + GROUP_HEADER_GAP - identity_natural_h = hdr_oh + 4 + len(identity_rows) * ROW_HEIGHT - steering_natural_h = hdr_oh + 4 + len(steering_rows) * ROW_HEIGHT + identity_natural_h = GROUP_HEADER_TOTAL_HEIGHT + GROUP_TOP_INSET + len(identity_rows) * ROW_HEIGHT + steering_natural_h = GROUP_HEADER_TOTAL_HEIGHT + GROUP_TOP_INSET + len(steering_rows) * ROW_HEIGHT left_natural_h = identity_natural_h + SECTION_GAP + steering_natural_h - tiles_overhead = 4 + hdr_oh + 24 if tiles_h else 0 + tiles_overhead = GROUP_TOP_INSET + GROUP_HEADER_TOTAL_HEIGHT + 24 if tiles_h else 0 return left_natural_h + tiles_h + tiles_overhead def _build_driving_toggles(self) -> list[dict]: