mirror of
https://github.com/firestar5683/StarPilot.git
synced 2026-07-07 06:32:08 +08:00
BigUI WIP: Some panel standarization
This commit is contained in:
@@ -450,6 +450,108 @@ class AetherInteractiveMixin:
|
||||
self._can_click = True
|
||||
|
||||
|
||||
class PanelManagerView(AetherInteractiveMixin, Widget):
|
||||
"""Shared base for list-panel ManagerViews used by some panels.
|
||||
|
||||
Encapsulates scroll infrastructure, the standard ``_render`` pipeline,
|
||||
shared two-column layout helpers, and a convenience toggle-tile factory.
|
||||
Subclasses declare panel-specific metrics via ``METRICS`` and override
|
||||
``_draw_header``, ``_measure_content_height``, and
|
||||
``_draw_scroll_content`` (at minimum).
|
||||
"""
|
||||
|
||||
METRICS: AetherListMetrics = AETHER_LIST_METRICS
|
||||
PANEL_STYLE: PanelStyle = DEFAULT_PANEL_STYLE
|
||||
TWO_COLUMN_BREAKPOINT: int = 1180
|
||||
COLUMN_GAP: float = 22.0
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
self._scroll_panel = GuiScrollPanel2(horizontal=False)
|
||||
self._scrollbar = AetherScrollbar()
|
||||
self._content_height = 0.0
|
||||
self._scroll_offset = 0.0
|
||||
self._scroll_rect = rl.Rectangle(0, 0, 0, 0)
|
||||
|
||||
# ── hooks ─────────────────────────────────────────────────
|
||||
|
||||
def _on_frame_created(self, frame: AetherListFrame) -> None:
|
||||
"""Called after ``init_list_panel`` builds the panel frame.
|
||||
Override to store ``frame.shell`` or perform per-frame setup."""
|
||||
|
||||
def _draw_header(self, header_rect: rl.Rectangle) -> None:
|
||||
"""Override to render the panel header."""
|
||||
|
||||
def _measure_content_height(self, content_width: float) -> float:
|
||||
"""Override to compute total scrollable content height."""
|
||||
|
||||
def _draw_scroll_content(self, scroll_rect: rl.Rectangle, content_width: float) -> None:
|
||||
"""Override to render visible content rows inside the scissor region."""
|
||||
|
||||
# ── render pipeline ───────────────────────────────────────
|
||||
|
||||
def _render(self, rect: rl.Rectangle) -> None:
|
||||
self._interactive_rects.clear()
|
||||
self.set_rect(rect)
|
||||
|
||||
frame, scroll_rect, content_width = init_list_panel(rect, self.PANEL_STYLE, self.METRICS)
|
||||
self._scroll_rect = scroll_rect
|
||||
self._on_frame_created(frame)
|
||||
|
||||
self._draw_header(frame.header)
|
||||
|
||||
self._content_height = self._measure_content_height(content_width)
|
||||
self._scroll_panel.set_enabled(self.is_visible)
|
||||
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))
|
||||
self._draw_scroll_content(scroll_rect, content_width)
|
||||
rl.end_scissor_mode()
|
||||
|
||||
if self._content_height > scroll_rect.height:
|
||||
self._scrollbar.render(scroll_rect, self._content_height, self._scroll_offset)
|
||||
|
||||
draw_list_scroll_fades(scroll_rect, self._content_height, self._scroll_offset,
|
||||
AetherListColors.PANEL_BG)
|
||||
|
||||
# ── shared layout helpers ─────────────────────────────────
|
||||
|
||||
def _uses_two_columns(self, width: float) -> bool:
|
||||
return width >= self.TWO_COLUMN_BREAKPOINT
|
||||
|
||||
def _column_width(self, width: float) -> float:
|
||||
return (width - self.COLUMN_GAP) / 2 if self._uses_two_columns(width) else width
|
||||
|
||||
def _section_height(self, count: int, row_height: float) -> float:
|
||||
return 0.0 if count <= 0 else count * row_height
|
||||
|
||||
def _section_block_height(self, content_height: float) -> float:
|
||||
if content_height <= 0:
|
||||
return 0.0
|
||||
return self.METRICS.section_header_height + self.METRICS.section_header_gap + content_height
|
||||
|
||||
def _stacked_section_height(self, sections: list[float]) -> float:
|
||||
if not sections:
|
||||
return 0.0
|
||||
return sum(sections) + self.METRICS.section_gap * (len(sections) - 1)
|
||||
|
||||
# ── convenience builders ──────────────────────────────────
|
||||
|
||||
def _make_toggle_tile(self, d: dict) -> ToggleTile:
|
||||
return ToggleTile(
|
||||
title=d["title"],
|
||||
get_state=d.get("get_state", d.get("get")),
|
||||
set_state=d.get("set_state", d.get("set")),
|
||||
bg_color=self.PANEL_STYLE.accent,
|
||||
desc=d.get("subtitle", d.get("desc", "")),
|
||||
is_enabled=d.get("is_enabled"),
|
||||
disabled_label=d.get("disabled_label", ""),
|
||||
)
|
||||
|
||||
|
||||
PANEL_HEADER_TITLE_Y: int = 4
|
||||
PANEL_HEADER_SUBTITLE_Y: int = 48
|
||||
PANEL_HEADER_TITLE_FONT_SIZE: int = 40
|
||||
|
||||
@@ -9,7 +9,6 @@ from openpilot.common.basedir import BASEDIR
|
||||
from openpilot.starpilot.common.starpilot_variables import ACTIVE_THEME_PATH
|
||||
from openpilot.system.ui.lib.application import gui_app, FontWeight, MouseEvent, MousePos
|
||||
from openpilot.system.ui.lib.multilang import tr, tr_noop
|
||||
from openpilot.system.ui.lib.scroll_panel2 import GuiScrollPanel2
|
||||
from openpilot.system.ui.widgets import Widget, DialogResult
|
||||
from openpilot.system.ui.widgets.label import gui_label
|
||||
from openpilot.selfdrive.ui.ui_state import ui_state
|
||||
@@ -20,17 +19,15 @@ from openpilot.selfdrive.ui.layouts.settings.starpilot.aethergrid import (
|
||||
COMPACT_PANEL_METRICS,
|
||||
AetherAdjustorRow,
|
||||
AetherListColors,
|
||||
AetherScrollbar,
|
||||
PanelManagerView,
|
||||
TileGrid,
|
||||
ToggleTile,
|
||||
DEFAULT_PANEL_STYLE,
|
||||
_point_hits,
|
||||
draw_action_pill,
|
||||
draw_list_group_shell,
|
||||
draw_list_scroll_fades,
|
||||
draw_section_header,
|
||||
draw_settings_panel_header,
|
||||
init_list_panel,
|
||||
AetherSliderDialog,
|
||||
)
|
||||
|
||||
@@ -53,7 +50,9 @@ SOUNDS_PANEL_METRICS = replace(
|
||||
)
|
||||
|
||||
|
||||
class SoundsManagerView(Widget):
|
||||
class SoundsManagerView(PanelManagerView):
|
||||
METRICS = SOUNDS_PANEL_METRICS
|
||||
|
||||
def __init__(self, controller: StarPilotSoundsLayout):
|
||||
super().__init__()
|
||||
self._controller = controller
|
||||
@@ -62,11 +61,6 @@ class SoundsManagerView(Widget):
|
||||
self._can_click = True
|
||||
self._reset_rect = rl.Rectangle(0, 0, 0, 0)
|
||||
|
||||
self._scroll_panel = GuiScrollPanel2(horizontal=False)
|
||||
self._scrollbar = AetherScrollbar()
|
||||
self._content_height = 0.0
|
||||
self._scroll_offset = 0.0
|
||||
self._scroll_rect = rl.Rectangle(0, 0, 0, 0)
|
||||
self._tile_grid_h = 0.0
|
||||
|
||||
self._init_adjustors()
|
||||
@@ -245,33 +239,6 @@ class SoundsManagerView(Widget):
|
||||
self._pressed_target = None
|
||||
self._can_click = True
|
||||
|
||||
def _render(self, rect: rl.Rectangle):
|
||||
self.set_rect(rect)
|
||||
|
||||
frame, scroll_rect, content_width = init_list_panel(rect, PANEL_STYLE, metrics=SOUNDS_PANEL_METRICS)
|
||||
self._scroll_rect = scroll_rect
|
||||
|
||||
self._draw_header(frame.header)
|
||||
|
||||
self._content_height = self._measure_content_height(content_width)
|
||||
|
||||
self._scroll_panel.set_enabled(self.is_visible)
|
||||
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)
|
||||
)
|
||||
self._draw_scroll_content(scroll_rect, content_width)
|
||||
rl.end_scissor_mode()
|
||||
|
||||
if self._content_height > scroll_rect.height:
|
||||
self._scrollbar.render(scroll_rect, self._content_height, self._scroll_offset)
|
||||
draw_list_scroll_fades(scroll_rect, self._content_height, self._scroll_offset,
|
||||
AetherListColors.PANEL_BG)
|
||||
|
||||
def _measure_content_height(self, content_width: float) -> float:
|
||||
col_width = (content_width - SECTION_GAP) / 2
|
||||
left_h = 0.0
|
||||
|
||||
@@ -15,7 +15,6 @@ import pyray as rl
|
||||
from openpilot.system.hardware import HARDWARE
|
||||
from openpilot.system.ui.lib.application import gui_app, FontWeight
|
||||
from openpilot.system.ui.lib.multilang import tr, tr_noop
|
||||
from openpilot.system.ui.lib.scroll_panel2 import GuiScrollPanel2
|
||||
from openpilot.system.ui.lib.text_measure import measure_text_cached
|
||||
from openpilot.system.ui.widgets import DialogResult, Widget
|
||||
from openpilot.system.ui.widgets.confirm_dialog import ConfirmDialog, alert_dialog
|
||||
@@ -29,16 +28,13 @@ from openpilot.selfdrive.ui.layouts.settings.starpilot.scribble import draw_cust
|
||||
from openpilot.selfdrive.ui.layouts.settings.starpilot.aethergrid import (
|
||||
AETHER_LIST_METRICS,
|
||||
AetherAdjustorRow,
|
||||
AetherInteractiveMixin,
|
||||
AetherScrollbar,
|
||||
AetherSegmentedControl,
|
||||
AetherListColors,
|
||||
DEFAULT_PANEL_STYLE,
|
||||
PanelManagerView,
|
||||
TileGrid,
|
||||
ToggleTile,
|
||||
DEFAULT_PANEL_STYLE,
|
||||
init_list_panel,
|
||||
draw_list_group_shell,
|
||||
draw_list_scroll_fades,
|
||||
draw_section_header,
|
||||
draw_selection_list_row,
|
||||
draw_settings_panel_header,
|
||||
@@ -103,25 +99,20 @@ SYSTEM_PANEL_METRICS = replace(
|
||||
)
|
||||
|
||||
|
||||
class SystemSettingsManagerView(AetherInteractiveMixin, Widget):
|
||||
class SystemSettingsManagerView(PanelManagerView):
|
||||
HEADER_SUBTITLE_HEIGHT = 24
|
||||
HEADER_SUMMARY_GAP = 6
|
||||
HEADER_CARD_HEIGHT = 140
|
||||
TAB_HEIGHT = 68
|
||||
TAB_GAP = 10
|
||||
TAB_BOTTOM_GAP = 18
|
||||
COLUMN_GAP = 22
|
||||
TWO_COLUMN_BREAKPOINT = 1180
|
||||
ACTION_PILL_WIDTH = 132
|
||||
DANGER_PILL_WIDTH = 112
|
||||
METRICS = SYSTEM_PANEL_METRICS
|
||||
|
||||
def __init__(self, controller: StarPilotSystemLayout):
|
||||
super().__init__()
|
||||
self._controller = controller
|
||||
self._scroll_panel = GuiScrollPanel2(horizontal=False)
|
||||
self._scrollbar = AetherScrollbar()
|
||||
self._content_height = 0.0
|
||||
self._scroll_offset = 0.0
|
||||
self._adjustor_rows: dict[str, AetherAdjustorRow] = {}
|
||||
self._display_slider_keys = ["ScreenBrightness", "ScreenBrightnessOnroad", "ScreenTimeout", "ScreenTimeoutOnroad"]
|
||||
self._power_slider_keys = ["DeviceShutdown", "LowVoltageShutdown"]
|
||||
@@ -330,22 +321,6 @@ class SystemSettingsManagerView(AetherInteractiveMixin, Widget):
|
||||
)
|
||||
)
|
||||
|
||||
self._scroll_rect = rl.Rectangle(0, 0, 0, 0)
|
||||
|
||||
def _section_height(self, count: int, row_height: float) -> float:
|
||||
return 0.0 if count <= 0 else count * row_height
|
||||
|
||||
def _stacked_section_height(self, sections: list[float]) -> float:
|
||||
if not sections:
|
||||
return 0.0
|
||||
return sum(sections) + SECTION_GAP * (len(sections) - 1)
|
||||
|
||||
def _uses_two_columns(self, width: float) -> bool:
|
||||
return width >= self.TWO_COLUMN_BREAKPOINT
|
||||
|
||||
def _column_width(self, width: float) -> float:
|
||||
return (width - self.COLUMN_GAP) / 2 if self._uses_two_columns(width) else width
|
||||
|
||||
def _tab_subtitle(self, tab_id: str) -> str:
|
||||
if tab_id == "basics":
|
||||
return tr("{} controls + {} toggles").format(
|
||||
@@ -456,30 +431,9 @@ class SystemSettingsManagerView(AetherInteractiveMixin, Widget):
|
||||
if target_id == "static:first_aid":
|
||||
gui_app.push_widget(AetherBackupsCareDialog(self._controller))
|
||||
|
||||
|
||||
def _render(self, rect: rl.Rectangle):
|
||||
self._interactive_rects.clear()
|
||||
self.set_rect(rect)
|
||||
|
||||
frame, scroll_rect, content_width = init_list_panel(rect, PANEL_STYLE, metrics=SYSTEM_PANEL_METRICS)
|
||||
self._scroll_rect = scroll_rect
|
||||
|
||||
def _on_frame_created(self, frame) -> None:
|
||||
self._drive_mode_control.set_parent_rect(frame.header)
|
||||
|
||||
self._draw_header(frame.header)
|
||||
self._content_height = self._measure_content_height(content_width)
|
||||
self._scroll_panel.set_enabled(self.is_visible)
|
||||
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))
|
||||
self._draw_scroll_content(scroll_rect, content_width)
|
||||
rl.end_scissor_mode()
|
||||
|
||||
if self._content_height > scroll_rect.height:
|
||||
self._scrollbar.render(scroll_rect, self._content_height, self._scroll_offset)
|
||||
|
||||
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):
|
||||
draw_settings_panel_header(rect, tr("System Settings"),
|
||||
tr("Manage display, backups, connectivity, and device maintenance from one touch-first panel."),
|
||||
@@ -562,9 +516,6 @@ class SystemSettingsManagerView(AetherInteractiveMixin, Widget):
|
||||
else:
|
||||
return self._stacked_section_height([display_h, power_h, tiles_content_h + 24])
|
||||
|
||||
def _section_block_height(self, content_height: float) -> float:
|
||||
return SECTION_HEADER_HEIGHT + SECTION_HEADER_GAP + content_height
|
||||
|
||||
def _slider_section_height(self, keys: list[str], width: float) -> float:
|
||||
total = 0.0
|
||||
for key in keys:
|
||||
|
||||
@@ -5,7 +5,6 @@ import pyray as rl
|
||||
from openpilot.system.hardware import HARDWARE
|
||||
from openpilot.system.ui.lib.application import FontWeight, gui_app
|
||||
from openpilot.system.ui.lib.multilang import tr, tr_noop
|
||||
from openpilot.system.ui.lib.scroll_panel2 import GuiScrollPanel2
|
||||
from openpilot.system.ui.widgets import DialogResult, Widget
|
||||
from openpilot.system.ui.widgets.confirm_dialog import ConfirmDialog
|
||||
from openpilot.system.ui.widgets.label import gui_label
|
||||
@@ -14,19 +13,16 @@ from openpilot.system.ui.widgets.option_dialog import MultiOptionDialog
|
||||
from openpilot.selfdrive.ui.layouts.settings.starpilot.panel import _SettingsPage
|
||||
from openpilot.selfdrive.ui.layouts.settings.starpilot.aethergrid import (
|
||||
AetherListMetrics,
|
||||
AetherInteractiveMixin,
|
||||
AetherListColors,
|
||||
AetherScrollbar,
|
||||
AetherSliderDialog,
|
||||
DEFAULT_PANEL_STYLE,
|
||||
PanelManagerView,
|
||||
draw_list_group_shell,
|
||||
draw_list_scroll_fades,
|
||||
draw_section_header,
|
||||
draw_selection_list_row,
|
||||
draw_settings_list_row,
|
||||
draw_settings_panel_header,
|
||||
draw_soft_card,
|
||||
init_list_panel,
|
||||
TileGrid,
|
||||
ToggleTile,
|
||||
_with_alpha,
|
||||
@@ -92,22 +88,16 @@ FADE_HEIGHT = CUSTOM_METRICS.fade_height
|
||||
PANEL_STYLE = DEFAULT_PANEL_STYLE
|
||||
|
||||
|
||||
class VehicleSettingsManagerView(AetherInteractiveMixin, Widget):
|
||||
class VehicleSettingsManagerView(PanelManagerView):
|
||||
HEADER_SUBTITLE_HEIGHT = 22
|
||||
HEADER_SUMMARY_GAP = 10
|
||||
HEADER_CARD_HEIGHT = 100
|
||||
TWO_COLUMN_BREAKPOINT = 1180
|
||||
COLUMN_GAP = 22
|
||||
METRICS = CUSTOM_METRICS
|
||||
|
||||
def __init__(self, controller: "StarPilotVehicleSettingsLayout"):
|
||||
super().__init__()
|
||||
self._controller = controller
|
||||
self._scroll_panel = GuiScrollPanel2(horizontal=False)
|
||||
self._scrollbar = AetherScrollbar()
|
||||
self._content_height = 0.0
|
||||
self._scroll_offset = 0.0
|
||||
self._shell_rect = rl.Rectangle(0, 0, 0, 0)
|
||||
self._scroll_rect = rl.Rectangle(0, 0, 0, 0)
|
||||
|
||||
self._toggle_grid = TileGrid(columns=2, padding=12, min_tile_width=100)
|
||||
self._toggle_grid.set_touch_valid_callback(lambda: self._scroll_panel.is_touch_valid())
|
||||
@@ -242,24 +232,8 @@ class VehicleSettingsManagerView(AetherInteractiveMixin, Widget):
|
||||
self._last_model = current_model
|
||||
self._rebuild_toggle_grid()
|
||||
|
||||
def _uses_two_columns(self, width: float) -> bool:
|
||||
return width >= self.TWO_COLUMN_BREAKPOINT
|
||||
|
||||
def _column_width(self, width: float) -> float:
|
||||
return (width - self.COLUMN_GAP) / 2 if self._uses_two_columns(width) else width
|
||||
|
||||
def _section_height(self, count: int, row_height: float) -> float:
|
||||
return 0.0 if count <= 0 else count * row_height
|
||||
|
||||
def _section_block_height(self, content_height: float) -> float:
|
||||
if content_height <= 0:
|
||||
return 0.0
|
||||
return SECTION_HEADER_HEIGHT + SECTION_HEADER_GAP + content_height
|
||||
|
||||
def _stacked_section_height(self, sections: list[float]) -> float:
|
||||
if not sections:
|
||||
return 0.0
|
||||
return sum(sections) + SECTION_GAP * (len(sections) - 1)
|
||||
def _on_frame_created(self, frame) -> None:
|
||||
self._shell_rect = frame.shell
|
||||
|
||||
def _activate_target(self, target_id: str | None):
|
||||
if not target_id:
|
||||
@@ -270,28 +244,6 @@ class VehicleSettingsManagerView(AetherInteractiveMixin, Widget):
|
||||
elif prefix == "select":
|
||||
self._controller._on_select(value)
|
||||
|
||||
def _render(self, rect: rl.Rectangle):
|
||||
self.set_rect(rect)
|
||||
|
||||
frame, scroll_rect, content_width = init_list_panel(rect, PANEL_STYLE, CUSTOM_METRICS)
|
||||
self._shell_rect = frame.shell
|
||||
self._scroll_rect = scroll_rect
|
||||
|
||||
self._draw_header(frame.header)
|
||||
|
||||
self._content_height = self._measure_content_height(content_width)
|
||||
self._scroll_panel.set_enabled(self.is_visible)
|
||||
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))
|
||||
self._draw_scroll_content(scroll_rect, content_width)
|
||||
rl.end_scissor_mode()
|
||||
|
||||
if self._content_height > scroll_rect.height:
|
||||
self._scrollbar.render(scroll_rect, self._content_height, self._scroll_offset)
|
||||
|
||||
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):
|
||||
draw_settings_panel_header(rect, tr("Vehicle Settings"),
|
||||
tr("Configure vehicle fingerprint, driving features, and steering controls."),
|
||||
|
||||
Reference in New Issue
Block a user