Files
StarPilot/openpilot/starpilot/ui/large_speed_geometry.py
T
2026-10-13 20:43:16 -05:00

107 lines
4.9 KiB
Python

"""Large speed stack dimensions shared by rendering, input and layout metadata."""
from functools import lru_cache
WIDTH = 344
MAX_ROW_HEIGHT = 210
LIMIT_ROW_HEIGHT = 260
CARD_HEIGHT = MAX_ROW_HEIGHT + LIMIT_ROW_HEIGHT
ACTION_HEIGHT = 108
ACTION_X = 88
ACTION_Y = 75 + CARD_HEIGHT + 22
ACTION_HEADER_HEIGHT = 44
GAP = 8
TOGGLE_HEIGHT = 44
SOURCE_ROW_HEIGHT = 56
PADDING = 24
def card_height(mode):
return CARD_HEIGHT if mode == 'split' else LIMIT_ROW_HEIGHT if mode == 'limit_only' else MAX_ROW_HEIGHT
@lru_cache(maxsize=8)
def _projection_widgets(width, height):
from openpilot.starpilot.system.android_auto.projection_layout import layout_metadata_for_viewport
return layout_metadata_for_viewport((width, height))['widgets']
def _action_regions(state):
from openpilot.starpilot.ui.onroad_customization import placement
from openpilot.starpilot.ui.onroad_state import slc_controls
from openpilot.starpilot.ui.presentation import Profile
header = ACTION_HEADER_HEIGHT if state.speed_limit.pending_speed_limit_mps is not None or state.speed_limit.action_feedback else 0
regions = [(l, t - header, r, b) for l, t, r, b in
(control.bounds for control in slc_controls(Profile.LARGE, state))]
saved = placement(state.customization, 'large', 'speed_limit_actions')
if not regions and state.speed_limit.action_feedback and saved['enabled']:
regions.append((saved['x'], saved['y'], saved['x'] + WIDTH, saved['y'] + ACTION_HEADER_HEIGHT))
return regions
def source_toggle_bounds(state):
from openpilot.starpilot.ui.onroad_customization import placement
from openpilot.starpilot.ui.unified_speed_presentation import resolve_unified_speed
card = placement(state.customization, 'large', 'cruise_limits')
shown = resolve_unified_speed(state)
height = card_height(shown.mode)
top = card['y'] + height + 22
for left, action_top, right, bottom in _action_regions(state):
if left < card['x'] + WIDTH and right > card['x'] and action_top < top + TOGGLE_HEIGHT and bottom > top:
top = bottom + 22
if top + TOGGLE_HEIGHT > 1050:
top = card['y'] - GAP - TOGGLE_HEIGHT
return card['x'], top, card['x'] + WIDTH, top + TOGGLE_HEIGHT
def source_panel_bounds(state, height, viewport):
"""Prefer the aligned column; move the popup only for constrained saved layouts."""
from openpilot.starpilot.ui.onroad_customization import placement, PROFILES, widget_size, CAMERA_WIDGETS
from openpilot.starpilot.ui.unified_speed_presentation import resolve_unified_speed
card = placement(state.customization, 'large', 'cruise_limits')
height_of_card = card_height(resolve_unified_speed(state).mode)
left, top, right, bottom = source_toggle_bounds(state)
occupied = [(card['x'], card['y'], card['x'] + WIDTH, card['y'] + height_of_card), (left, top, right, bottom)]
occupied.extend(_action_regions(state))
layouts = state.customization['layouts']['large']
projected = 'nav_card' in layouts
if projected:
from openpilot.starpilot.system.android_auto.projection_layout import placement_size
widgets = _projection_widgets(round(viewport.width + 60), round(viewport.height + 60))
else:
widgets = PROFILES['large']['widgets']
for key, widget in widgets.items():
if key in ('cruise_limits', 'speed_limit_actions', *CAMERA_WIDGETS):
continue
saved = layouts.get(key)
if saved is None:
continue
if saved['enabled']:
if key == 'cem_gauge':
from openpilot.starpilot.ui.onroad_cem_gauge import gauge_data
if gauge_data(state) is None:
continue
width, widget_height = (placement_size(key, widget, saved) if projected else widget_size(state.customization, 'large', key))
native = PROFILES['large']['widgets'].get(key)
dx = widget['default']['x'] - native['default']['x'] if projected and native else 0
dy = widget['default']['y'] - native['default']['y'] if projected and native else 0
x, y = saved['x'] + dx, saved['y'] + dy
occupied.append((x, y, x + width, y + widget_height))
candidates = [(left, bottom + GAP), (left, top - GAP - height),
(left, card['y'] - GAP - height), (right + GAP, top), (left - GAP - WIDTH, top),
(right + GAP, top - GAP - height), (left - GAP - WIDTH, top - GAP - height)]
# Keep the aligned choices first; another saved widget can fill that column.
# Its edges supply further columns using the same popup gap and clip checks.
for occupied_left, _, occupied_right, _ in occupied:
candidates.extend(((occupied_right + GAP, top), (occupied_left - GAP - WIDTH, top),
(occupied_right + GAP, top - GAP - height), (occupied_left - GAP - WIDTH, top - GAP - height)))
for x, y in candidates:
y = min(max(y, viewport.y), viewport.y + viewport.height - height)
if not viewport.x <= x <= viewport.x + viewport.width - WIDTH:
continue
if all(x + WIDTH <= l or x >= r or y + height <= t or y >= b for l, t, r, b in occupied):
return x, y, WIDTH, height
return None