mirror of
https://github.com/firestar5683/StarPilot.git
synced 2026-09-01 05:33:49 +08:00
Model Manager
This commit is contained in:
@@ -284,10 +284,10 @@ class AetherListMetrics:
|
||||
panel_padding_top: int = 0
|
||||
panel_padding_bottom: int = 0
|
||||
header_height: int = 0
|
||||
section_gap: int = 28
|
||||
section_header_height: int = 87
|
||||
section_header_gap: int = 12
|
||||
row_height: int = 177
|
||||
section_gap: int = 24
|
||||
section_header_height: int = 44
|
||||
section_header_gap: int = 10
|
||||
row_height: int = 128
|
||||
utility_row_height: int = 128
|
||||
row_radius: float = 0.12
|
||||
action_width: int = 235
|
||||
@@ -1434,11 +1434,11 @@ class BreadcrumbController:
|
||||
aether_end_scissor_mode()
|
||||
PANEL_HEADER_TITLE_Y: int = 34
|
||||
PANEL_HEADER_SUBTITLE_Y: int = 78
|
||||
PANEL_HEADER_TITLE_FONT_SIZE: int = 58
|
||||
PANEL_HEADER_SUBTITLE_FONT_SIZE: int = 29
|
||||
PANEL_HEADER_TITLE_FONT_SIZE: int = 30
|
||||
PANEL_HEADER_SUBTITLE_FONT_SIZE: int = 26
|
||||
PANEL_HEADER_TITLE_FONT: FontWeight = FontWeight.SEMI_BOLD
|
||||
PANEL_HEADER_SUBTITLE_FONT: FontWeight = FontWeight.NORMAL
|
||||
PANEL_HEADER_SUBTITLE_LINE_HEIGHT: float = 33.0 # subtitle_size(29) + interline_gap(4)
|
||||
PANEL_HEADER_SUBTITLE_LINE_HEIGHT: float = 30.0 # subtitle_size(26) + interline_gap(4)
|
||||
|
||||
|
||||
def draw_settings_panel_header(header_rect: rl.Rectangle, title: str, subtitle: str | None = None,
|
||||
@@ -1590,7 +1590,7 @@ def draw_standard_toggle_row(
|
||||
pressed=pressed,
|
||||
is_last=is_last,
|
||||
show_chevron=False,
|
||||
title_size=49, subtitle_size=32,
|
||||
title_size=36, subtitle_size=26,
|
||||
style=style,
|
||||
)
|
||||
|
||||
@@ -1990,8 +1990,8 @@ def draw_section_header(
|
||||
title: str = "",
|
||||
*,
|
||||
trailing_text: str = "",
|
||||
title_size: int = 58,
|
||||
trailing_size: int = 52,
|
||||
title_size: int = 30,
|
||||
trailing_size: int = 26,
|
||||
title_color: rl.Color | None = None,
|
||||
trailing_color: rl.Color | None = None,
|
||||
style: PanelStyle = DEFAULT_PANEL_STYLE,
|
||||
@@ -2001,7 +2001,7 @@ def draw_section_header(
|
||||
trailing_reserved = min(320.0, rect.width * 0.38) if trailing_text else 0.0
|
||||
title_rect = rl.Rectangle(rect.x, rect.y + (rect.height - title_size) / 2, max(1.0, rect.width - trailing_reserved), title_size + 4)
|
||||
alignment = rl.GuiTextAlignment.TEXT_ALIGN_CENTER if align_center else rl.GuiTextAlignment.TEXT_ALIGN_LEFT
|
||||
gui_label(title_rect, title, title_size, title_color or style.subtitle_color, FontWeight.MEDIUM, alignment=alignment)
|
||||
gui_label(title_rect, title, title_size, title_color or style.subtitle_color, FontWeight.SEMI_BOLD, alignment=alignment)
|
||||
|
||||
if trailing_text:
|
||||
trailing_rect = rl.Rectangle(rect.x, rect.y + (rect.height - trailing_size) / 2, rect.width, trailing_size + 4)
|
||||
@@ -2020,10 +2020,10 @@ def draw_empty_state_card(
|
||||
title: str,
|
||||
body: str,
|
||||
*,
|
||||
title_size: int = 44,
|
||||
body_size: int = 32,
|
||||
title_size: int = 34,
|
||||
body_size: int = 26,
|
||||
body_inset_x: int = 70,
|
||||
title_gap: int = 20,
|
||||
title_gap: int = 16,
|
||||
title_top_padding: float | None = None,
|
||||
body_height: float | None = None,
|
||||
fill: rl.Color | None = None,
|
||||
@@ -2037,7 +2037,7 @@ def draw_empty_state_card(
|
||||
resolved_border = border if border is not None else style.surface_border
|
||||
draw_soft_card(card_rect, resolved_fill, resolved_border, radius=radius, segments=segments)
|
||||
|
||||
title_h = max(49.0, title_size + 12)
|
||||
title_h = max(38.0, title_size + 8)
|
||||
title_y = card_rect.y + (title_top_padding if title_top_padding is not None else max(35.0, min(61.0, card_rect.height * 0.22)))
|
||||
inset_x = min(float(body_inset_x), max(18.0, card_rect.width * 0.22))
|
||||
body_y = title_y + title_h + title_gap
|
||||
@@ -2085,9 +2085,9 @@ def draw_settings_list_row(
|
||||
pressed: bool = False,
|
||||
is_last: bool = False,
|
||||
show_chevron: bool = True,
|
||||
title_size: int = 49,
|
||||
subtitle_size: int = 38,
|
||||
value_size: int = 44,
|
||||
title_size: int = 36,
|
||||
subtitle_size: int = 26,
|
||||
value_size: int = 28,
|
||||
separator_inset: int = 24,
|
||||
title_color: rl.Color | None = None,
|
||||
subtitle_color: rl.Color | None = None,
|
||||
@@ -2131,7 +2131,7 @@ def draw_settings_list_row(
|
||||
start_y = draw_rect.y + (draw_rect.height - total_h) / 2
|
||||
|
||||
draw_text_fit_common(
|
||||
gui_app.font(FontWeight.MEDIUM), title,
|
||||
gui_app.font(FontWeight.SEMI_BOLD), title,
|
||||
rl.Vector2(text_left, start_y),
|
||||
text_width, eff_title_size,
|
||||
color=resolved_title_color,
|
||||
@@ -2143,11 +2143,12 @@ def draw_settings_list_row(
|
||||
color=resolved_subtitle_color,
|
||||
)
|
||||
else:
|
||||
title_y = draw_rect.y + (draw_rect.height - title_size) / 2
|
||||
eff_title_size = min(36, title_size)
|
||||
title_y = draw_rect.y + (draw_rect.height - eff_title_size) / 2
|
||||
draw_text_fit_common(
|
||||
gui_app.font(FontWeight.MEDIUM), title,
|
||||
gui_app.font(FontWeight.SEMI_BOLD), title,
|
||||
rl.Vector2(text_left, title_y),
|
||||
text_width, title_size,
|
||||
text_width, eff_title_size,
|
||||
color=resolved_title_color,
|
||||
)
|
||||
|
||||
@@ -2181,7 +2182,7 @@ def draw_settings_list_row(
|
||||
start_y = draw_rect.y + (draw_rect.height - total_h) / 2
|
||||
|
||||
draw_text_fit_common(
|
||||
gui_app.font(FontWeight.MEDIUM), title,
|
||||
gui_app.font(FontWeight.SEMI_BOLD), title,
|
||||
rl.Vector2(text_left, start_y),
|
||||
available_w, eff_title_size,
|
||||
color=resolved_title_color,
|
||||
@@ -2193,29 +2194,49 @@ def draw_settings_list_row(
|
||||
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
|
||||
# Proportional Side-by-Side
|
||||
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
|
||||
t_width = content_w * 0.46
|
||||
v_width = content_w * 0.52
|
||||
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
|
||||
eff_value_size = min(28, value_size) if is_narrow else min(32, value_size)
|
||||
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,
|
||||
)
|
||||
if subtitle:
|
||||
eff_title_size = min(34, 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.SEMI_BOLD), title,
|
||||
rl.Vector2(text_left, start_y),
|
||||
t_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),
|
||||
t_width, eff_sub_size,
|
||||
color=resolved_subtitle_color,
|
||||
)
|
||||
else:
|
||||
eff_title_size = min(36, title_size) if is_narrow else title_size
|
||||
title_y = draw_rect.y + (draw_rect.height - eff_title_size) / 2
|
||||
|
||||
draw_text_fit_common(
|
||||
gui_app.font(FontWeight.SEMI_BOLD), 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),
|
||||
@@ -2237,7 +2258,7 @@ def draw_settings_list_row(
|
||||
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,
|
||||
gui_app.font(FontWeight.SEMI_BOLD), title,
|
||||
rl.Vector2(text_left, start_y),
|
||||
text_width, eff_title_size,
|
||||
color=resolved_title_color,
|
||||
@@ -2249,11 +2270,12 @@ def draw_settings_list_row(
|
||||
color=resolved_subtitle_color,
|
||||
)
|
||||
else:
|
||||
title_y = draw_rect.y + (draw_rect.height - title_size) / 2
|
||||
eff_title_size = min(36, title_size)
|
||||
title_y = draw_rect.y + (draw_rect.height - eff_title_size) / 2
|
||||
draw_text_fit_common(
|
||||
gui_app.font(FontWeight.MEDIUM), title,
|
||||
gui_app.font(FontWeight.SEMI_BOLD), title,
|
||||
rl.Vector2(text_left, title_y),
|
||||
text_width, title_size,
|
||||
text_width, eff_title_size,
|
||||
color=resolved_title_color,
|
||||
)
|
||||
|
||||
@@ -2872,8 +2894,8 @@ def draw_selection_list_row(
|
||||
action_width: int = AETHER_LIST_METRICS.action_width,
|
||||
action_chip: bool = False,
|
||||
action_pill: bool = False,
|
||||
title_size: int = 44,
|
||||
subtitle_size: int = 29,
|
||||
title_size: int = 36,
|
||||
subtitle_size: int = 26,
|
||||
action_text_size: int = 26,
|
||||
action_pill_height: int = 64,
|
||||
action_pill_width: float | None = None,
|
||||
@@ -2910,7 +2932,7 @@ def draw_selection_list_row(
|
||||
|
||||
info_gap = 36 if action_pill else 42
|
||||
info_rect = rl.Rectangle(draw_rect.x + 24, draw_rect.y + 16, max(0.0, draw_rect.width - action_width - info_gap), draw_rect.height - 32)
|
||||
title_font = gui_app.font(FontWeight.MEDIUM)
|
||||
title_font = gui_app.font(FontWeight.SEMI_BOLD)
|
||||
subtitle_font = gui_app.font(FontWeight.NORMAL)
|
||||
|
||||
if subtitle:
|
||||
@@ -3332,7 +3354,7 @@ class AetherSettingsView(PanelManagerView):
|
||||
|
||||
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=46, subtitle_size=29)
|
||||
draw_settings_panel_header(text_rect, display_title, subtitle_text, title_size=30, subtitle_size=26, max_subtitle_width=1.0)
|
||||
|
||||
toggle_id = f"parent_toggle:{toggle.label}"
|
||||
tw = AETHER_LIST_METRICS.toggle_width
|
||||
@@ -3353,7 +3375,7 @@ class AetherSettingsView(PanelManagerView):
|
||||
bg_color=rl.Color(12, 10, 18, 255),
|
||||
)
|
||||
else:
|
||||
draw_settings_panel_header(rect, title, subtitle, title_size=46, subtitle_size=29)
|
||||
draw_settings_panel_header(rect, title, subtitle, title_size=30, subtitle_size=26)
|
||||
|
||||
def _active_sections(self) -> list[SettingSection]:
|
||||
if self._tab_defs and self._active_tab_key:
|
||||
@@ -3519,13 +3541,13 @@ class AetherSettingsView(PanelManagerView):
|
||||
pressed=pressed,
|
||||
is_last=is_last,
|
||||
show_chevron=row.on_click is not None,
|
||||
title_size=41, subtitle_size=28, value_size=34,
|
||||
title_size=36, subtitle_size=26, value_size=30,
|
||||
style=self._panel_style,
|
||||
)
|
||||
elif row.type == "action":
|
||||
action_fill = self._panel_style.danger_fill if row.action_danger else self._panel_style.current_fill
|
||||
action_border = self._panel_style.danger_border if row.action_danger else self._panel_style.current_border
|
||||
action_text_color = self._panel_style.danger_text if row.action_danger else AetherListColors.HEADER
|
||||
action_text_color = self._panel_style.danger_text if row.action_danger else self._panel_style.title_color
|
||||
draw_selection_list_row(
|
||||
rect,
|
||||
title=tr(row.title),
|
||||
@@ -3535,12 +3557,12 @@ class AetherSettingsView(PanelManagerView):
|
||||
pressed=pressed,
|
||||
is_last=is_last,
|
||||
action_pill=True,
|
||||
title_size=41, subtitle_size=28,
|
||||
action_pill_height=AETHER_LIST_METRICS.toggle_height, action_text_size=28,
|
||||
title_size=36, subtitle_size=26,
|
||||
action_pill_height=AETHER_LIST_METRICS.toggle_height, action_text_size=26,
|
||||
action_text_color=action_text_color,
|
||||
action_fill=action_fill,
|
||||
action_border=action_border,
|
||||
row_separator=self._panel_style.divider_color,
|
||||
row_separator=self._panel_style.divider_color,
|
||||
)
|
||||
|
||||
|
||||
@@ -3606,7 +3628,7 @@ def draw_back_button(pill_rect: rl.Rectangle, center_y: float, pressed: bool, ho
|
||||
chev_r = rl.Rectangle(back_x + (back_size - chev_size) / 2, center_y - chev_size / 2, chev_size, chev_size)
|
||||
chev_c = rl.Color(200, 200, 210, 200) if pressed else rl.Color(160, 170, 185, 180)
|
||||
draw_chevron_icon(chev_r, chev_c, thickness=2.0, direction="left")
|
||||
return btn
|
||||
return rl.Rectangle(pill_rect.x, center_y - 40, max(120.0, back_size + 24), 80)
|
||||
|
||||
|
||||
BACK_BTN = "__back__"
|
||||
|
||||
@@ -41,6 +41,8 @@ from openpilot.selfdrive.ui.layouts.settings.starpilot.aethergrid import (
|
||||
AetherListColors,
|
||||
AetherScrollbar,
|
||||
DEFAULT_PANEL_STYLE,
|
||||
aether_begin_scissor_mode,
|
||||
aether_end_scissor_mode,
|
||||
point_hits,
|
||||
draw_action_rail,
|
||||
draw_action_pill,
|
||||
@@ -48,29 +50,19 @@ from openpilot.selfdrive.ui.layouts.settings.starpilot.aethergrid import (
|
||||
draw_download_icon,
|
||||
draw_empty_state_card,
|
||||
draw_heart_icon,
|
||||
draw_list_panel_shell,
|
||||
draw_list_group_shell,
|
||||
build_list_panel_frame,
|
||||
draw_list_row_shell,
|
||||
draw_list_scroll_fades,
|
||||
draw_section_header,
|
||||
draw_settings_list_row,
|
||||
draw_settings_panel_header,
|
||||
draw_status_led,
|
||||
draw_overflow_dots,
|
||||
draw_rounded_fill,
|
||||
draw_rounded_stroke,
|
||||
init_list_panel,
|
||||
draw_interactive_rect,
|
||||
resolve_interactive_target,
|
||||
wrap_text,
|
||||
with_alpha,
|
||||
SECTION_GAP,
|
||||
SECTION_HEADER_HEIGHT,
|
||||
SECTION_HEADER_GAP,
|
||||
ROW_HEIGHT,
|
||||
)
|
||||
UTILITY_ROW_HEIGHT = AETHER_LIST_METRICS.utility_row_height
|
||||
ROW_RADIUS = AETHER_LIST_METRICS.row_radius
|
||||
ACTION_WIDTH = AETHER_LIST_METRICS.action_width
|
||||
BUTTON_HEIGHT = AETHER_LIST_METRICS.header_button_height
|
||||
@@ -79,9 +71,13 @@ DRIVING_MODEL_METRICS = replace(AETHER_LIST_METRICS, header_height=0)
|
||||
CONFIRM_TIMEOUT_SECONDS = 3.0
|
||||
TRANSITION_SECONDS = 0.24
|
||||
PANEL_STYLE = DEFAULT_PANEL_STYLE
|
||||
BANNER_HEIGHT = ROW_HEIGHT
|
||||
HEADER_BAR_HEIGHT = 107.0
|
||||
MANAGEMENT_STRIP_HEIGHT = 56.0
|
||||
BANNER_HEIGHT = 128.0
|
||||
BANNER_GAP = 14.0
|
||||
HEADER_BUTTON_HEIGHT = 80.0
|
||||
HEADER_BUTTON_GAP_Y = 14.0
|
||||
MANAGEMENT_STRIP_HEIGHT = 64.0
|
||||
MANAGEMENT_PILL_HEIGHT = 44.0
|
||||
EMPTY_STATE_HEIGHT = 240.0
|
||||
_SORT_MODES = ("alphabetical", "date", "date_oldest", "favorites", "community_picks")
|
||||
_SORT_LABELS = {
|
||||
"alphabetical": "Alphabetical",
|
||||
@@ -212,7 +208,7 @@ class DrivingModelManagerView(AetherInteractiveMixin, Widget):
|
||||
self._transition_starts.pop(key, None)
|
||||
|
||||
def _target_at(self, mouse_pos: MousePos) -> str | None:
|
||||
for prefix in ("menu:", "action:", "utility:", "row:"):
|
||||
for prefix in ("menu:", "action:", "row:"):
|
||||
for target_id, rect in self._interactive_rects.items():
|
||||
if target_id.startswith(prefix):
|
||||
pad_y = 6 if prefix == "menu:" else 0
|
||||
@@ -267,14 +263,6 @@ class DrivingModelManagerView(AetherInteractiveMixin, Widget):
|
||||
self._controller.toggle_favorite(model_key)
|
||||
return
|
||||
|
||||
if target.startswith("utility:"):
|
||||
action = target.split(":", 1)[1]
|
||||
if action == "blacklist":
|
||||
self._controller._on_blacklist_clicked()
|
||||
elif action == "ratings":
|
||||
self._controller._on_scores_clicked()
|
||||
return
|
||||
|
||||
if target.startswith("mgmt:"):
|
||||
action = target.split(":", 1)[1]
|
||||
if action == "blacklist":
|
||||
@@ -299,43 +287,38 @@ class DrivingModelManagerView(AetherInteractiveMixin, Widget):
|
||||
self._shell_rect = frame.shell
|
||||
|
||||
current_entry = self._controller.current_entry()
|
||||
use_banner = current_entry is not None
|
||||
if use_banner:
|
||||
if current_entry is not None:
|
||||
banner_rect = rl.Rectangle(scroll_rect.x, scroll_rect.y, scroll_rect.width, BANNER_HEIGHT)
|
||||
scroll_rect = rl.Rectangle(scroll_rect.x, scroll_rect.y + BANNER_HEIGHT,
|
||||
scroll_rect.width, scroll_rect.height - BANNER_HEIGHT)
|
||||
scroll_rect = rl.Rectangle(scroll_rect.x, scroll_rect.y + BANNER_HEIGHT + BANNER_GAP,
|
||||
scroll_rect.width, scroll_rect.height - BANNER_HEIGHT - BANNER_GAP)
|
||||
self._draw_current_banner(banner_rect, current_entry)
|
||||
|
||||
header_y = scroll_rect.y
|
||||
self._draw_relocated_header(scroll_rect.x, header_y, content_width)
|
||||
scroll_rect = rl.Rectangle(scroll_rect.x, scroll_rect.y + HEADER_BAR_HEIGHT,
|
||||
scroll_rect.width, scroll_rect.height - HEADER_BAR_HEIGHT)
|
||||
scroll_rect = rl.Rectangle(scroll_rect.x, scroll_rect.y + HEADER_BUTTON_HEIGHT + HEADER_BUTTON_GAP_Y,
|
||||
scroll_rect.width, scroll_rect.height - HEADER_BUTTON_HEIGHT - HEADER_BUTTON_GAP_Y)
|
||||
|
||||
randomizer_on = self._controller._params.get_bool("ModelRandomizer")
|
||||
mgmt_y = scroll_rect.y
|
||||
self._draw_sort_strip(scroll_rect.x, mgmt_y, content_width, randomizer_on)
|
||||
scroll_rect = rl.Rectangle(scroll_rect.x, scroll_rect.y + MANAGEMENT_STRIP_HEIGHT,
|
||||
scroll_rect.width, scroll_rect.height - MANAGEMENT_STRIP_HEIGHT)
|
||||
scroll_rect.width, scroll_rect.height - MANAGEMENT_STRIP_HEIGHT)
|
||||
|
||||
self._scroll_rect = scroll_rect
|
||||
|
||||
self._draw_header(frame.header)
|
||||
self._content_height = self._measure_content_height(content_width)
|
||||
self._scroll_panel.set_enabled(lambda: not self._controller._is_download_active())
|
||||
self._scroll_offset = self._scroll_panel.update(scroll_rect, max(self._content_height, scroll_rect.height))
|
||||
|
||||
rl.begin_scissor_mode(int(scroll_rect.x), int(scroll_rect.y), int(scroll_rect.width), int(scroll_rect.height))
|
||||
aether_begin_scissor_mode(int(scroll_rect.x), int(scroll_rect.y), int(scroll_rect.width), int(scroll_rect.height))
|
||||
self._draw_scroll_content(scroll_rect, content_width)
|
||||
rl.end_scissor_mode()
|
||||
aether_end_scissor_mode()
|
||||
|
||||
if self._content_height > scroll_rect.height:
|
||||
self._draw_scrollbar(scroll_rect)
|
||||
|
||||
draw_list_scroll_fades(scroll_rect, self._content_height, self._scroll_offset, AetherListColors.PANEL_BG, fade_height=FADE_HEIGHT)
|
||||
|
||||
def _draw_header(self, rect: rl.Rectangle):
|
||||
pass
|
||||
|
||||
def _draw_current_banner(self, rect: rl.Rectangle, entry: ModelCatalogEntry):
|
||||
draw_list_row_shell(
|
||||
rect,
|
||||
@@ -354,20 +337,18 @@ class DrivingModelManagerView(AetherInteractiveMixin, Widget):
|
||||
separator_inset=22,
|
||||
)
|
||||
|
||||
action_rect = draw_action_rail(rect, ACTION_WIDTH, current=True, alpha=255, fill=AetherListColors.ACTION_BG, separator=AetherListColors.ACTION_SEPARATOR, inset_y=18)
|
||||
info_rect = rl.Rectangle(rect.x + 24, rect.y + 18, rect.width - ACTION_WIDTH - 42, rect.height - 36)
|
||||
self._draw_model_info(info_rect, entry, current=True)
|
||||
|
||||
chip_rect = rl.Rectangle(rect.x + rect.width - ACTION_WIDTH + 35, rect.y + (rect.height - 61) / 2, ACTION_WIDTH - 70, 61)
|
||||
AetherChip(tr("Current"), PANEL_STYLE.current_fill, PANEL_STYLE.current_border, AetherListColors.HEADER, font_size=26).render(chip_rect)
|
||||
self._draw_current_action(action_rect)
|
||||
|
||||
def _draw_relocated_header(self, x: float, y: float, width: float):
|
||||
# Buttons placed horizontally
|
||||
btn_gap = float(AETHER_LIST_METRICS.header_button_gap)
|
||||
btn_w = (width - 16.0 - btn_gap * 2) / 3.0
|
||||
|
||||
primary_rect = rl.Rectangle(x + 8, y, btn_w, BUTTON_HEIGHT)
|
||||
secondary_rect = rl.Rectangle(x + 8 + btn_w + btn_gap, y, btn_w, BUTTON_HEIGHT)
|
||||
random_rect = rl.Rectangle(x + 8 + (btn_w + btn_gap) * 2, y, btn_w, BUTTON_HEIGHT)
|
||||
|
||||
primary_rect = rl.Rectangle(x + 8, y, btn_w, HEADER_BUTTON_HEIGHT)
|
||||
secondary_rect = rl.Rectangle(x + 8 + btn_w + btn_gap, y, btn_w, HEADER_BUTTON_HEIGHT)
|
||||
random_rect = rl.Rectangle(x + 8 + (btn_w + btn_gap) * 2, y, btn_w, HEADER_BUTTON_HEIGHT)
|
||||
|
||||
self._primary_header_button.render(primary_rect)
|
||||
self._secondary_header_button.render(secondary_rect)
|
||||
@@ -380,7 +361,7 @@ class DrivingModelManagerView(AetherInteractiveMixin, Widget):
|
||||
draw_status_led(rl.Vector2(led_x, led_y), randomizer_on)
|
||||
|
||||
def _draw_sort_strip(self, x: float, y: float, width: float, randomizer_on: bool):
|
||||
pill_h = 48.0
|
||||
pill_h = MANAGEMENT_PILL_HEIGHT
|
||||
pill_y = y + (MANAGEMENT_STRIP_HEIGHT - pill_h) / 2
|
||||
left = x + 16
|
||||
gap = 8.0
|
||||
@@ -431,120 +412,68 @@ class DrivingModelManagerView(AetherInteractiveMixin, Widget):
|
||||
draw_action_pill(seg_rect, label, fill, border, AetherListColors.HEADER, font_size=28, roundness=0.3)
|
||||
self._interactive_rects[f"sortopt:{mode}"] = seg_rect
|
||||
|
||||
def _measure_content_height(self, width: float) -> float:
|
||||
sections = self._build_sections(width)
|
||||
if not sections:
|
||||
return 260.0
|
||||
return max(sum(height for _key, height in sections) - SECTION_GAP, 0.0)
|
||||
|
||||
def _build_sections(self, width: float) -> list[tuple[str, float]]:
|
||||
sections: list[tuple[str, float]] = []
|
||||
def _get_sections(self) -> list[tuple[str, list[ModelCatalogEntry]]]:
|
||||
sort_mode = self._controller._get_sort_mode()
|
||||
|
||||
if sort_mode == "favorites":
|
||||
fav = self._controller.favorites_entries()
|
||||
if fav:
|
||||
count = len(fav)
|
||||
if self._controller.current_entry() is not None and self._controller.current_entry().user_favorite:
|
||||
count = max(count - 1, 0)
|
||||
sections.append(("favorites", SECTION_HEADER_HEIGHT + SECTION_HEADER_GAP + count * ROW_HEIGHT))
|
||||
|
||||
fav_keys = {e.key for e in fav}
|
||||
other_installed = [e for e in self._controller.installed_entries() if e.key not in fav_keys]
|
||||
if other_installed:
|
||||
count = len(other_installed)
|
||||
if self._controller.current_entry() is not None and not self._controller.current_entry().user_favorite:
|
||||
count = max(count - 1, 0)
|
||||
if count > 0:
|
||||
sections.append(("downloaded", SECTION_HEADER_HEIGHT + SECTION_HEADER_GAP + count * ROW_HEIGHT))
|
||||
elif sort_mode == "community_picks":
|
||||
community = self._controller.community_picks_entries()
|
||||
if community:
|
||||
count = len(community)
|
||||
if self._controller.current_entry() is not None and self._controller.current_entry().community_favorite:
|
||||
count = max(count - 1, 0)
|
||||
sections.append(("community_picks", SECTION_HEADER_HEIGHT + SECTION_HEADER_GAP + count * ROW_HEIGHT))
|
||||
else:
|
||||
installed = self._controller.installed_entries()
|
||||
available = self._controller.available_entries()
|
||||
if self._controller._params.get_bool("ModelRandomizer"):
|
||||
available = []
|
||||
if installed:
|
||||
installed_count = len(installed)
|
||||
if self._controller.current_entry() is not None:
|
||||
installed_count = max(installed_count - 1, 0)
|
||||
sections.append(("installed", SECTION_HEADER_HEIGHT + SECTION_HEADER_GAP + installed_count * ROW_HEIGHT))
|
||||
if available:
|
||||
sections.append(("available", SECTION_HEADER_HEIGHT + SECTION_HEADER_GAP + len(available) * ROW_HEIGHT))
|
||||
|
||||
if not sections:
|
||||
sections.append(("empty", 240))
|
||||
|
||||
return [(key, height + SECTION_GAP) for key, height in sections]
|
||||
|
||||
def _draw_scroll_content(self, rect: rl.Rectangle, width: float):
|
||||
sort_mode = self._controller._get_sort_mode()
|
||||
y = rect.y + self._scroll_offset
|
||||
|
||||
if sort_mode == "favorites":
|
||||
fav_entries = self._controller.favorites_entries()
|
||||
if self._controller.current_entry() is not None:
|
||||
fav_entries = [e for e in fav_entries if not self._controller.is_current_model(e.key)]
|
||||
|
||||
fav_entries = [e for e in self._controller.favorites_entries() if not self._controller.is_current_model(e.key)]
|
||||
fav_keys = {e.key for e in self._controller.favorites_entries()}
|
||||
other_installed = [e for e in self._controller.installed_entries() if e.key not in fav_keys]
|
||||
if self._controller.current_entry() is not None:
|
||||
other_installed = [e for e in other_installed if not self._controller.is_current_model(e.key)]
|
||||
other_installed = [e for e in self._controller.installed_entries() if e.key not in fav_keys and not self._controller.is_current_model(e.key)]
|
||||
|
||||
if fav_entries or other_installed:
|
||||
if fav_entries:
|
||||
y = self._draw_model_section(rect.x, y, width, tr("Favorites"), fav_entries)
|
||||
y += SECTION_GAP
|
||||
if other_installed:
|
||||
y = self._draw_model_section(rect.x, y, width, tr("Downloaded"), other_installed)
|
||||
y += SECTION_GAP
|
||||
else:
|
||||
self._draw_empty_state(rl.Rectangle(rect.x, y + 36, width, 200))
|
||||
return
|
||||
sections = []
|
||||
if fav_entries:
|
||||
sections.append((tr("Favorites"), fav_entries))
|
||||
if other_installed:
|
||||
sections.append((tr("Downloaded"), other_installed))
|
||||
return sections
|
||||
|
||||
if sort_mode == "community_picks":
|
||||
entries = self._controller.community_picks_entries()
|
||||
if self._controller.current_entry() is not None:
|
||||
entries = [e for e in entries if not self._controller.is_current_model(e.key)]
|
||||
entries = [e for e in self._controller.community_picks_entries() if not self._controller.is_current_model(e.key)]
|
||||
if entries:
|
||||
y = self._draw_model_section(rect.x, y, width, tr("Community Picks"), entries)
|
||||
else:
|
||||
self._draw_empty_state(rl.Rectangle(rect.x, y + 36, width, 200))
|
||||
return
|
||||
return [(tr("Community Picks"), entries)]
|
||||
return []
|
||||
|
||||
installed = self._controller.installed_entries()
|
||||
if self._controller.current_entry() is not None:
|
||||
installed = [e for e in installed if not self._controller.is_current_model(e.key)]
|
||||
available = self._controller.available_entries()
|
||||
if self._controller._params.get_bool("ModelRandomizer"):
|
||||
available = []
|
||||
|
||||
if not installed and not available:
|
||||
self._draw_empty_state(rl.Rectangle(rect.x, y + 36, width, 200))
|
||||
return
|
||||
# Standard sort modes (alphabetical, date, date_oldest)
|
||||
installed = [e for e in self._controller.installed_entries() if not self._controller.is_current_model(e.key)]
|
||||
available = [] if self._controller._params.get_bool("ModelRandomizer") else self._controller.available_entries()
|
||||
|
||||
sections = []
|
||||
if installed:
|
||||
y = self._draw_model_section(rect.x, y, width, tr("On Device"), installed)
|
||||
y += SECTION_GAP
|
||||
sections.append((tr("On Device"), installed))
|
||||
if available:
|
||||
y = self._draw_model_section(rect.x, y, width, tr("Available to Download"), available)
|
||||
y += SECTION_GAP
|
||||
sections.append((tr("Available to Download"), available))
|
||||
return sections
|
||||
|
||||
def _measure_content_height(self, width: float) -> float:
|
||||
sections = self._get_sections()
|
||||
if not sections:
|
||||
return EMPTY_STATE_HEIGHT
|
||||
total_height = sum(SECTION_HEADER_HEIGHT + SECTION_HEADER_GAP + len(entries) * ROW_HEIGHT for _title, entries in sections)
|
||||
total_height += (len(sections) - 1) * SECTION_GAP
|
||||
return float(total_height)
|
||||
|
||||
def _draw_scroll_content(self, rect: rl.Rectangle, width: float):
|
||||
sections = self._get_sections()
|
||||
if not sections:
|
||||
self._draw_empty_state(rl.Rectangle(rect.x, rect.y + self._scroll_offset + 36, width, EMPTY_STATE_HEIGHT - 40))
|
||||
return
|
||||
|
||||
y = rect.y + self._scroll_offset
|
||||
for index, (title, entries) in enumerate(sections):
|
||||
if index > 0:
|
||||
y += SECTION_GAP
|
||||
y = self._draw_model_section(rect.x, y, width, title, entries)
|
||||
|
||||
def _draw_empty_state(self, rect: rl.Rectangle):
|
||||
draw_empty_state_card(
|
||||
rl.Rectangle(rect.x, rect.y, rect.width, rect.height),
|
||||
self._controller.empty_state_title(),
|
||||
self._controller.empty_state_body(),
|
||||
title_size=46,
|
||||
body_size=36,
|
||||
title_size=34,
|
||||
body_size=26,
|
||||
body_inset_x=48,
|
||||
title_top_padding=42,
|
||||
body_height=72,
|
||||
title_top_padding=32,
|
||||
body_height=60,
|
||||
style=PANEL_STYLE,
|
||||
)
|
||||
|
||||
@@ -618,46 +547,54 @@ class DrivingModelManagerView(AetherInteractiveMixin, Widget):
|
||||
self._draw_download_action(action_rect)
|
||||
|
||||
def _draw_model_info(self, rect: rl.Rectangle, entry: ModelCatalogEntry, current: bool):
|
||||
title_size = 36
|
||||
meta_size = 26
|
||||
inter_gap = 6
|
||||
total_h = title_size + meta_size + inter_gap
|
||||
start_y = rect.y + (rect.height - total_h) / 2
|
||||
|
||||
heart_offset = 0
|
||||
if entry.user_favorite:
|
||||
heart_color = rl.Color(210, 100, 130, 230)
|
||||
heart_center = rl.Vector2(rect.x + 22, rect.y + 25)
|
||||
heart_center = rl.Vector2(rect.x + 14, start_y + title_size / 2)
|
||||
draw_heart_icon(heart_center, heart_color)
|
||||
heart_offset = 49
|
||||
title_rect = rl.Rectangle(rect.x + heart_offset, rect.y, rect.width - heart_offset, 49)
|
||||
gui_label(title_rect, entry.name, 49, AetherListColors.HEADER, FontWeight.MEDIUM)
|
||||
heart_offset = 36
|
||||
|
||||
meta_parts = [part for part in (entry.series, entry.released) if part]
|
||||
meta_rect = rl.Rectangle(rect.x, rect.y + 61, rect.width, 35)
|
||||
gui_label(meta_rect, " • ".join(meta_parts), 32, AetherListColors.SUBTEXT, FontWeight.NORMAL)
|
||||
title_rect = rl.Rectangle(rect.x + heart_offset, start_y, rect.width - heart_offset, title_size)
|
||||
gui_label(title_rect, entry.name, title_size, AetherListColors.HEADER, FontWeight.SEMI_BOLD)
|
||||
|
||||
meta_parts: list[str] = []
|
||||
if entry.series:
|
||||
meta_parts.append(entry.series)
|
||||
if entry.released:
|
||||
meta_parts.append(entry.released)
|
||||
|
||||
badge_parts: list[str] = []
|
||||
if self._controller._params.get_bool("ModelRandomizer"):
|
||||
badge_parts.append(tr("In Pool"))
|
||||
meta_parts.append(tr("In Pool"))
|
||||
elif current:
|
||||
badge_parts.append(tr("Active"))
|
||||
meta_parts.append(tr("Active"))
|
||||
elif entry.builtin:
|
||||
badge_parts.append(tr("Built-in"))
|
||||
if entry.partial:
|
||||
badge_parts.append(tr("Incomplete"))
|
||||
if entry.requires_external_gpu:
|
||||
badge_parts.append(tr("GPU required"))
|
||||
if entry.user_favorite:
|
||||
badge_parts.append(tr("Saved"))
|
||||
elif entry.community_favorite:
|
||||
badge_parts.append(tr("Popular"))
|
||||
meta_parts.append(tr("Built-in"))
|
||||
|
||||
if badge_parts:
|
||||
badge_rect = rl.Rectangle(rect.x, rect.y + 113, rect.width, 32)
|
||||
badge_color = AetherListColors.WARNING if entry.partial else AetherListColors.MUTED
|
||||
gui_label(badge_rect, " • ".join(badge_parts), 30, badge_color, FontWeight.MEDIUM)
|
||||
if entry.partial:
|
||||
meta_parts.append(tr("Incomplete"))
|
||||
if entry.requires_external_gpu:
|
||||
meta_parts.append(tr("GPU required"))
|
||||
if not entry.user_favorite and entry.community_favorite:
|
||||
meta_parts.append(tr("Popular"))
|
||||
|
||||
if meta_parts:
|
||||
meta_rect = rl.Rectangle(rect.x, start_y + title_size + inter_gap, rect.width, meta_size)
|
||||
has_warning = entry.partial or entry.requires_external_gpu
|
||||
text_color = AetherListColors.WARNING if has_warning else AetherListColors.SUBTEXT
|
||||
gui_label(meta_rect, " • ".join(meta_parts), meta_size, text_color, FontWeight.NORMAL if not has_warning else FontWeight.MEDIUM)
|
||||
|
||||
def _draw_download_action(self, rect: rl.Rectangle):
|
||||
center_x = rect.x + rect.width / 2
|
||||
center_y = rect.y + rect.height / 2 - 8
|
||||
center_y = rect.y + 42
|
||||
draw_download_icon(rl.Vector2(center_x, center_y), AetherListColors.HEADER)
|
||||
gui_label(
|
||||
rl.Rectangle(rect.x + 16, rect.y + rect.height - 58, rect.width - 32, 32),
|
||||
rl.Rectangle(rect.x + 16, rect.y + 72, rect.width - 32, 32),
|
||||
tr("Download"),
|
||||
26,
|
||||
AetherListColors.SUBTEXT,
|
||||
@@ -666,15 +603,15 @@ class DrivingModelManagerView(AetherInteractiveMixin, Widget):
|
||||
)
|
||||
|
||||
def _draw_downloading_action(self, rect: rl.Rectangle, progress_text: str):
|
||||
center = rl.Vector2(rect.x + rect.width / 2, rect.y + rect.height / 2 - 8)
|
||||
center = rl.Vector2(rect.x + rect.width / 2, rect.y + 42)
|
||||
phase = (time.monotonic() * 240.0) % 360.0
|
||||
draw_busy_ring(center, phase, PANEL_STYLE.accent)
|
||||
draw_busy_ring(center, phase, PANEL_STYLE.accent, inner_radius=14, outer_radius=20, thickness=24)
|
||||
|
||||
label = progress_text if progress_text else tr("Downloading")
|
||||
gui_label(
|
||||
rl.Rectangle(rect.x + 16, rect.y + rect.height - 58, rect.width - 32, 32),
|
||||
rl.Rectangle(rect.x + 12, rect.y + 72, rect.width - 24, 32),
|
||||
label,
|
||||
28,
|
||||
24,
|
||||
AetherListColors.SUBTEXT,
|
||||
FontWeight.MEDIUM,
|
||||
alignment=rl.GuiTextAlignment.TEXT_ALIGN_CENTER,
|
||||
@@ -682,12 +619,11 @@ class DrivingModelManagerView(AetherInteractiveMixin, Widget):
|
||||
|
||||
def _draw_menu_action(self, rect: rl.Rectangle, is_open: bool, entry: ModelCatalogEntry):
|
||||
if not is_open:
|
||||
# Three-dot menu indicator
|
||||
center_x = rect.x + rect.width / 2
|
||||
center_y = rect.y + rect.height / 2 - 10
|
||||
center_y = rect.y + 42
|
||||
draw_overflow_dots(rl.Vector2(center_x, center_y), rl.Color(AetherListColors.HEADER.r, AetherListColors.HEADER.g, AetherListColors.HEADER.b, min(AetherListColors.HEADER.a, 200)))
|
||||
gui_label(
|
||||
rl.Rectangle(rect.x + 16, rect.y + rect.height - 55, rect.width - 32, 32),
|
||||
rl.Rectangle(rect.x + 16, rect.y + 72, rect.width - 32, 32),
|
||||
tr("Options"),
|
||||
26,
|
||||
AetherListColors.SUBTEXT,
|
||||
@@ -695,66 +631,46 @@ class DrivingModelManagerView(AetherInteractiveMixin, Widget):
|
||||
alignment=rl.GuiTextAlignment.TEXT_ALIGN_CENTER,
|
||||
)
|
||||
else:
|
||||
# Expanded sub-button menu
|
||||
btn_h = 64
|
||||
gap = 12
|
||||
btn_h = 48
|
||||
gap = 8
|
||||
total_h = btn_h * 2 + gap
|
||||
start_y = rect.y + (rect.height - total_h) / 2
|
||||
btn_w = rect.width - 28
|
||||
|
||||
delete_rect = rl.Rectangle(rect.x + 14, start_y, rect.width - 20, btn_h)
|
||||
fav_rect = rl.Rectangle(rect.x + 14, start_y + btn_h + gap, rect.width - 20, btn_h)
|
||||
delete_rect = rl.Rectangle(rect.x + 14, start_y, btn_w, btn_h)
|
||||
fav_rect = rl.Rectangle(rect.x + 14, start_y + btn_h + gap, btn_w, btn_h)
|
||||
|
||||
self._interactive_rects[f"menu:{entry.key}:delete"] = delete_rect
|
||||
self._interactive_rects[f"menu:{entry.key}:favorite"] = fav_rect
|
||||
|
||||
# Delete button
|
||||
draw_action_pill(delete_rect, tr("Delete"), AetherListColors.DANGER_SOFT, rl.Color(AetherListColors.DANGER.r, AetherListColors.DANGER.g, AetherListColors.DANGER.b, min(AetherListColors.DANGER.a, 70)), AetherListColors.DANGER)
|
||||
draw_action_pill(
|
||||
delete_rect,
|
||||
tr("Delete"),
|
||||
AetherListColors.DANGER_SOFT,
|
||||
rl.Color(AetherListColors.DANGER.r, AetherListColors.DANGER.g, AetherListColors.DANGER.b, min(AetherListColors.DANGER.a, 70)),
|
||||
AetherListColors.DANGER,
|
||||
font_size=24,
|
||||
)
|
||||
|
||||
# Favorite toggle button
|
||||
is_fav = entry.user_favorite
|
||||
fav_fill = rl.Color(210, 100, 130, 44) if is_fav else rl.Color(PANEL_STYLE.accent.r, PANEL_STYLE.accent.g, PANEL_STYLE.accent.b, 26)
|
||||
fav_border = rl.Color((210 if is_fav else PANEL_STYLE.accent.r), (100 if is_fav else PANEL_STYLE.accent.g), (130 if is_fav else PANEL_STYLE.accent.b), min((255 if is_fav else PANEL_STYLE.accent.a), 70))
|
||||
fav_text_color = rl.Color(210, 100, 130, 255) if is_fav else PANEL_STYLE.accent
|
||||
fav_label = tr("Unfavorite") if is_fav else tr("Favorite")
|
||||
draw_action_pill(fav_rect, fav_label, fav_fill, fav_border, fav_text_color)
|
||||
draw_action_pill(fav_rect, fav_label, fav_fill, fav_border, fav_text_color, font_size=24)
|
||||
|
||||
def _draw_current_action(self, rect: rl.Rectangle):
|
||||
chip_rect = rl.Rectangle(rect.x + 35, rect.y + (rect.height - 61) / 2, rect.width - 70, 61)
|
||||
chip_h = 52
|
||||
chip_w = rect.width - 56
|
||||
chip_rect = rl.Rectangle(rect.x + 28, rect.y + (rect.height - chip_h) / 2, chip_w, chip_h)
|
||||
AetherChip(tr("Current"), PANEL_STYLE.current_fill, PANEL_STYLE.current_border, AetherListColors.HEADER, font_size=26).render(chip_rect)
|
||||
|
||||
def _draw_protected_action(self, rect: rl.Rectangle):
|
||||
chip_rect = rl.Rectangle(rect.x + 29, rect.y + (rect.height - 61) / 2, rect.width - 58, 61)
|
||||
chip_h = 52
|
||||
chip_w = rect.width - 56
|
||||
chip_rect = rl.Rectangle(rect.x + 28, rect.y + (rect.height - chip_h) / 2, chip_w, chip_h)
|
||||
AetherChip(tr("Protected"), rl.Color(255, 255, 255, 10), AetherListColors.MUTED, AetherListColors.SUBTEXT, font_size=26).render(chip_rect)
|
||||
|
||||
def _draw_utility_section(self, x: float, y: float, width: float, rows: list[dict]):
|
||||
content_w = width
|
||||
draw_section_header(rl.Rectangle(x, y, content_w, SECTION_HEADER_HEIGHT), tr("Automation and Tuning"), style=PANEL_STYLE)
|
||||
y += SECTION_HEADER_HEIGHT + SECTION_HEADER_GAP
|
||||
|
||||
container_rect = rl.Rectangle(x, y, content_w, len(rows) * UTILITY_ROW_HEIGHT)
|
||||
draw_list_group_shell(container_rect, style=PANEL_STYLE)
|
||||
|
||||
for index, row in enumerate(rows):
|
||||
row_rect = rl.Rectangle(x, y + index * UTILITY_ROW_HEIGHT, content_w, UTILITY_ROW_HEIGHT)
|
||||
self._draw_utility_row(row_rect, row, is_last=index == len(rows) - 1)
|
||||
|
||||
def _draw_utility_row(self, rect: rl.Rectangle, row: dict, is_last: bool):
|
||||
mouse_pos = gui_app.last_mouse_event.pos
|
||||
hovered = bool(point_hits(mouse_pos, rect, self._scroll_rect, pad_x=6, pad_y=0))
|
||||
pressed = self._pressed_target == f"utility:{row['id']}"
|
||||
self._interactive_rects[f"utility:{row['id']}"] = rect
|
||||
draw_settings_list_row(
|
||||
rect,
|
||||
title=row["title"],
|
||||
subtitle=row.get("subtitle", ""),
|
||||
value="" if row["type"] == "toggle" else row["value"],
|
||||
toggle_value=bool(row["value"]) if row["type"] == "toggle" else None,
|
||||
hovered=hovered,
|
||||
pressed=pressed,
|
||||
is_last=is_last,
|
||||
style=PANEL_STYLE,
|
||||
)
|
||||
|
||||
def _draw_scrollbar(self, rect: rl.Rectangle):
|
||||
self._scrollbar.render(rect, self._content_height, self._scroll_offset)
|
||||
|
||||
@@ -1150,31 +1066,6 @@ class StarPilotDrivingModelLayout(_SettingsPage):
|
||||
return tr("StarPilot is pulling the latest driving model list. This panel will populate automatically when the refresh completes.")
|
||||
return tr("Try refreshing the catalog once the device is offroad and connected.")
|
||||
|
||||
def utility_rows(self) -> list[dict]:
|
||||
rows = []
|
||||
|
||||
if self._params.get_bool("ModelRandomizer"):
|
||||
blacklist_count = len([m.strip() for m in (self._params.get("BlacklistedModels", encoding="utf-8") or "").split(",") if m.strip()])
|
||||
rows.extend(
|
||||
[
|
||||
{
|
||||
"id": "blacklist",
|
||||
"title": tr("Blacklist"),
|
||||
"subtitle": tr("Keep specific installed models out of the rotation."),
|
||||
"type": "value",
|
||||
"value": tr(f"{blacklist_count} blocked" if blacklist_count else "Manage"),
|
||||
},
|
||||
{
|
||||
"id": "ratings",
|
||||
"title": tr("Ratings"),
|
||||
"subtitle": tr("Review recorded drives and model score history."),
|
||||
"type": "value",
|
||||
"value": tr("View"),
|
||||
},
|
||||
]
|
||||
)
|
||||
|
||||
return rows
|
||||
|
||||
def select_model(self, model_key: str):
|
||||
selected_model = canonical_model_key(model_key)
|
||||
|
||||
Reference in New Issue
Block a user