mirror of
https://github.com/firestar5683/StarPilot.git
synced 2026-08-20 07:43:48 +08:00
Biscuit tweaks
This commit is contained in:
@@ -3,12 +3,12 @@ from typing import Optional
|
|||||||
|
|
||||||
import pyray as rl
|
import pyray as rl
|
||||||
from openpilot.common.constants import CV
|
from openpilot.common.constants import CV
|
||||||
from openpilot.selfdrive.ui.ui_state import ui_state
|
from openpilot.selfdrive.ui.ui_state import ui_state, UIStatus
|
||||||
from openpilot.system.ui.lib.application import gui_app, FontWeight
|
from openpilot.system.ui.lib.application import gui_app, FontWeight
|
||||||
from openpilot.system.ui.lib.multilang import tr
|
from openpilot.system.ui.lib.multilang import tr
|
||||||
from openpilot.system.ui.lib.text_measure import measure_text_cached
|
from openpilot.system.ui.lib.text_measure import measure_text_cached
|
||||||
from openpilot.selfdrive.ui.onroad.starpilot.widget_style import (
|
from openpilot.selfdrive.ui.onroad.starpilot.widget_style import (
|
||||||
CONTROL_BG, CONTROL_BORDER_WIDTH, SLC_HEIGHT,
|
CONTROL_BG, CONTROL_BORDER, CONTROL_BORDER_WIDTH, CONTROL_ROUNDNESS, CONTROL_SEGMENTS, SLC_HEIGHT,
|
||||||
draw_control_card, roundness_for,
|
draw_control_card, roundness_for,
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -24,19 +24,21 @@ RED_RING_WIDTH = 20
|
|||||||
# Pending sign blink cadence — 1s period, 50% duty cycle.
|
# Pending sign blink cadence — 1s period, 50% duty cycle.
|
||||||
PENDING_BLINK_MS = 500
|
PENDING_BLINK_MS = 500
|
||||||
|
|
||||||
# Source display metadata: title, abbreviation, raw-value key (display order).
|
# Source display metadata: source name, main label, value key, bubble label, icon.
|
||||||
SOURCE_DEFS = [
|
SOURCE_DEFS = [
|
||||||
("Dashboard", "Dash", "dashboard_sl", "Dashboard", "dashboard"),
|
("Dashboard", "Dash", "dashboard_sl", "Dashboard", "dashboard"),
|
||||||
("Map Data", "Maps", "map_sl", "Map", "map"),
|
("Map Data", "MAP", "map_sl", "Map Data", "map"),
|
||||||
("Vision", "Vision", "vision_sl", "Camera", "camera"),
|
("Vision", "VISION", "vision_sl", "Vision", "camera"),
|
||||||
("Mapbox", "Mapbox", "mapbox_sl", "Mapbox", "map"),
|
("Mapbox", "MBOX", "mapbox_sl", "Mapbox", "map"),
|
||||||
("Upcoming", "Next", "next_sl", "Navigation", "navigation"),
|
("Upcoming", "NEXT", "next_sl", "Next", "next"),
|
||||||
]
|
]
|
||||||
|
|
||||||
# Fonts
|
# Fonts
|
||||||
FONT_LABEL = 30
|
FONT_LABEL = 30
|
||||||
FONT_SPEED = 78
|
FONT_SOURCE = 40 # Set Speed MAX label size.
|
||||||
FONT_OFFSET = 30
|
FONT_SPEED = 90 # Set Speed value size.
|
||||||
|
FONT_OFFSET = 29 # Compact offset text.
|
||||||
|
OFFSET_CHIP_SEGMENTS = 8 # Capsule curve segments.
|
||||||
FONT_EU_LARGE = 70
|
FONT_EU_LARGE = 70
|
||||||
FONT_EU_SMALL = 60
|
FONT_EU_SMALL = 60
|
||||||
FONT_EU_OFFSET = 40
|
FONT_EU_OFFSET = 40
|
||||||
@@ -202,6 +204,17 @@ def _active_source_label(state: dict) -> str:
|
|||||||
return _ACTIVE_SOURCE_LABELS.get(source, source.upper())
|
return _ACTIVE_SOURCE_LABELS.get(source, source.upper())
|
||||||
|
|
||||||
|
|
||||||
|
def _source_label_color(alpha: int) -> rl.Color:
|
||||||
|
"""Match Set Speed's MAX label color."""
|
||||||
|
if ui_state.status == UIStatus.ENGAGED:
|
||||||
|
base = rl.Color(128, 216, 166, 255)
|
||||||
|
elif ui_state.status in (UIStatus.DISENGAGED, UIStatus.OVERRIDE):
|
||||||
|
base = rl.Color(145, 155, 149, 255)
|
||||||
|
else:
|
||||||
|
base = rl.Color(166, 166, 166, 255)
|
||||||
|
return _speed_limit_pulse_color(base, alpha)
|
||||||
|
|
||||||
|
|
||||||
# ── US MUTCD Sign ─────────────────────────────────────────────────────
|
# ── US MUTCD Sign ─────────────────────────────────────────────────────
|
||||||
|
|
||||||
def _draw_offset_chip(rect: rl.Rectangle, offset_str: str, alpha: int) -> None:
|
def _draw_offset_chip(rect: rl.Rectangle, offset_str: str, alpha: int) -> None:
|
||||||
@@ -209,18 +222,19 @@ def _draw_offset_chip(rect: rl.Rectangle, offset_str: str, alpha: int) -> None:
|
|||||||
font = _get_semi_bold()
|
font = _get_semi_bold()
|
||||||
text_size = measure_text_cached(font, offset_str, FONT_OFFSET)
|
text_size = measure_text_cached(font, offset_str, FONT_OFFSET)
|
||||||
chip_w = max(64.0, text_size.x + 24.0)
|
chip_w = max(64.0, text_size.x + 24.0)
|
||||||
chip_h = 38.0
|
chip_h = 36.0
|
||||||
chip_rect = rl.Rectangle(
|
chip_rect = rl.Rectangle(
|
||||||
rect.x + (rect.width - chip_w) / 2,
|
rect.x + (rect.width - chip_w) / 2,
|
||||||
rect.y + rect.height - chip_h - 14,
|
rect.y + rect.height - chip_h - 10,
|
||||||
chip_w,
|
chip_w,
|
||||||
chip_h,
|
chip_h,
|
||||||
)
|
)
|
||||||
chip_border = rl.Color(255, 255, 255, alpha)
|
chip_border = rl.Color(CONTROL_BORDER.r, CONTROL_BORDER.g, CONTROL_BORDER.b,
|
||||||
|
min(alpha, CONTROL_BORDER.a))
|
||||||
chip_fill = rl.Color(0, 0, 0, min(120, alpha))
|
chip_fill = rl.Color(0, 0, 0, min(120, alpha))
|
||||||
roundness = roundness_for(chip_rect, 18)
|
roundness = roundness_for(chip_rect, 18)
|
||||||
rl.draw_rectangle_rounded(chip_rect, roundness, 12, chip_fill)
|
rl.draw_rectangle_rounded(chip_rect, roundness, OFFSET_CHIP_SEGMENTS, chip_fill)
|
||||||
rl.draw_rectangle_rounded_lines_ex(chip_rect, roundness, 12, 2, chip_border)
|
rl.draw_rectangle_rounded_lines_ex(chip_rect, roundness, OFFSET_CHIP_SEGMENTS, 2, chip_border)
|
||||||
rl.draw_text_ex(
|
rl.draw_text_ex(
|
||||||
font,
|
font,
|
||||||
offset_str,
|
offset_str,
|
||||||
@@ -240,15 +254,16 @@ def _draw_us_sign(x: float, y: float, sign_width: float, sign_height: float,
|
|||||||
visible frame geometry with Set Speed. Border and text colors continue to
|
visible frame geometry with Set Speed. Border and text colors continue to
|
||||||
use the existing Vision pulse and pending blink behavior.
|
use the existing Vision pulse and pending blink behavior.
|
||||||
"""
|
"""
|
||||||
# Pending: border blinks white <-> red. Active: border is white.
|
# Pending: blink white/red. Active: shared blue-grey.
|
||||||
if pending:
|
if pending:
|
||||||
blink_on = int(rl.get_time() * 1000) % 1000 < PENDING_BLINK_MS
|
blink_on = int(rl.get_time() * 1000) % 1000 < PENDING_BLINK_MS
|
||||||
base_border = rl.Color(255, 255, 255, alpha) if blink_on else rl.Color(201, 34, 49, alpha)
|
base_border = rl.Color(255, 255, 255, alpha) if blink_on else rl.Color(201, 34, 49, alpha)
|
||||||
else:
|
else:
|
||||||
base_border = rl.Color(255, 255, 255, alpha)
|
base_border = rl.Color(CONTROL_BORDER.r, CONTROL_BORDER.g, CONTROL_BORDER.b,
|
||||||
|
min(alpha, CONTROL_BORDER.a))
|
||||||
|
|
||||||
# Compose the blink base with the active vision pulse (no-op outside window).
|
# Compose the blink base with the active vision pulse (no-op outside window).
|
||||||
border_color = _speed_limit_pulse_color(base_border, alpha)
|
border_color = _speed_limit_pulse_color(base_border, base_border.a)
|
||||||
# White value text reads on the translucent road background.
|
# White value text reads on the translucent road background.
|
||||||
text_color = _speed_limit_pulse_color(rl.Color(255, 255, 255, 255), alpha)
|
text_color = _speed_limit_pulse_color(rl.Color(255, 255, 255, 255), alpha)
|
||||||
|
|
||||||
@@ -271,21 +286,21 @@ def _draw_us_sign(x: float, y: float, sign_width: float, sign_height: float,
|
|||||||
rl.draw_text_ex(font_bold, speed_text, rl.Vector2(cx - speed_size.x / 2, y + 85), FONT_SPEED - 6, 0, text_color)
|
rl.draw_text_ex(font_bold, speed_text, rl.Vector2(cx - speed_size.x / 2, y + 85), FONT_SPEED - 6, 0, text_color)
|
||||||
elif show_offset:
|
elif show_offset:
|
||||||
# Offset ON: source at the top, speed below it, and the offset in a chip.
|
# Offset ON: source at the top, speed below it, and the offset in a chip.
|
||||||
source_size = measure_text_cached(font_semi, source_label, FONT_LABEL)
|
source_size = measure_text_cached(font_semi, source_label, FONT_SOURCE)
|
||||||
source_color = _speed_limit_pulse_color(_WHITE, alpha)
|
source_color = _source_label_color(alpha)
|
||||||
rl.draw_text_ex(font_semi, source_label, rl.Vector2(cx - source_size.x / 2, y + 20), FONT_LABEL, 0, source_color)
|
rl.draw_text_ex(font_semi, source_label, rl.Vector2(cx - source_size.x / 2, y + 8), FONT_SOURCE, 0, source_color)
|
||||||
|
|
||||||
speed_size = measure_text_cached(font_bold, speed_text, FONT_SPEED)
|
speed_size = measure_text_cached(font_bold, speed_text, FONT_SPEED)
|
||||||
rl.draw_text_ex(font_bold, speed_text, rl.Vector2(cx - speed_size.x / 2, y + 54), FONT_SPEED, 0, text_color)
|
rl.draw_text_ex(font_bold, speed_text, rl.Vector2(cx - speed_size.x / 2, y + 44), FONT_SPEED, 0, text_color)
|
||||||
_draw_offset_chip(card_rect, offset_str, alpha)
|
_draw_offset_chip(card_rect, offset_str, alpha)
|
||||||
else:
|
else:
|
||||||
# Offset OFF: source at the top, speed centered in the remaining space.
|
# Offset OFF: match Set Speed typography.
|
||||||
source_size = measure_text_cached(font_semi, source_label, FONT_LABEL)
|
source_size = measure_text_cached(font_semi, source_label, FONT_SOURCE)
|
||||||
source_color = _speed_limit_pulse_color(_WHITE, alpha)
|
source_color = _source_label_color(alpha)
|
||||||
rl.draw_text_ex(font_semi, source_label, rl.Vector2(cx - source_size.x / 2, y + 20), FONT_LABEL, 0, source_color)
|
rl.draw_text_ex(font_semi, source_label, rl.Vector2(cx - source_size.x / 2, y + 27), FONT_SOURCE, 0, source_color)
|
||||||
|
|
||||||
speed_size = measure_text_cached(font_bold, speed_text, FONT_SPEED)
|
speed_size = measure_text_cached(font_bold, speed_text, FONT_SPEED)
|
||||||
rl.draw_text_ex(font_bold, speed_text, rl.Vector2(cx - speed_size.x / 2, y + 78), FONT_SPEED, 0, text_color)
|
rl.draw_text_ex(font_bold, speed_text, rl.Vector2(cx - speed_size.x / 2, y + 77), FONT_SPEED, 0, text_color)
|
||||||
|
|
||||||
|
|
||||||
# ── EU Vienna Sign ────────────────────────────────────────────────────
|
# ── EU Vienna Sign ────────────────────────────────────────────────────
|
||||||
@@ -385,37 +400,24 @@ def _draw_sign(state: dict, rect: rl.Rectangle, *, pending: bool = False):
|
|||||||
|
|
||||||
# ── Sources Bubble (expandable overlay) ────────────────────────────────
|
# ── Sources Bubble (expandable overlay) ────────────────────────────────
|
||||||
|
|
||||||
_SOURCE_PANEL_WIDTH = 300
|
# Fixed width fits the longest source label and a three-digit speed at 26px.
|
||||||
_SOURCE_PANEL_GAP = 28
|
_SOURCE_PANEL_WIDTH = 248
|
||||||
_SOURCE_PANEL_PAD_X = 18
|
_SOURCE_PANEL_GAP = 20
|
||||||
|
_SOURCE_PANEL_PAD_X = 9
|
||||||
_SOURCE_PANEL_PAD_Y = 4
|
_SOURCE_PANEL_PAD_Y = 4
|
||||||
_SOURCE_PANEL_BG = rl.Color(0, 0, 0, 145)
|
_SOURCE_PANEL_BG = rl.Color(0, 0, 0, 145)
|
||||||
|
_SOURCE_PANEL_BORDER = rl.Color(196, 205, 208, 72)
|
||||||
_SOURCE_DIVIDER = rl.Color(196, 205, 208, 70)
|
_SOURCE_DIVIDER = rl.Color(196, 205, 208, 70)
|
||||||
|
_SOURCE_ACTIVE_BAR = rl.Color(CONTROL_BORDER.r, CONTROL_BORDER.g, CONTROL_BORDER.b, 230)
|
||||||
_SOURCE_ICON_MUTED = rl.Color(196, 205, 208, 190)
|
_SOURCE_ICON_MUTED = rl.Color(196, 205, 208, 190)
|
||||||
_SOURCE_LABEL = rl.Color(255, 255, 255, 215)
|
_SOURCE_LABEL = rl.Color(255, 255, 255, 215)
|
||||||
_SOURCE_FONT = 34
|
_SOURCE_FONT = 26
|
||||||
_SOURCE_MIN_FONT = 22
|
_SOURCE_ICON_TEXT_GAP = 8
|
||||||
_SOURCE_TEXT_GAP = 12
|
_SOURCE_ICON_SIZE = 28
|
||||||
_SOURCE_ICON_SIZE = 30
|
_SOURCE_ACTIVE_BAR_WIDTH = 5.0
|
||||||
|
_SOURCE_ACTIVE_BAR_HEIGHT = 32.0
|
||||||
|
_SOURCE_ACTIVE_BAR_X = 2.0
|
||||||
def _fit_sources_row(font, label: str, value_text: str, row_h: float, available_w: float):
|
_SOURCE_ACTIVE_BAR_ROW_INSET = 6.0
|
||||||
font_size = min(_SOURCE_FONT, max(_SOURCE_MIN_FONT, int(row_h * 0.82)))
|
|
||||||
label_size = measure_text_cached(font, label, font_size)
|
|
||||||
value_size = measure_text_cached(font, value_text, font_size)
|
|
||||||
needed_w = label_size.x + value_size.x + _SOURCE_TEXT_GAP
|
|
||||||
|
|
||||||
if needed_w > available_w:
|
|
||||||
font_size = max(_SOURCE_MIN_FONT, int(font_size * (available_w / needed_w)))
|
|
||||||
label_size = measure_text_cached(font, label, font_size)
|
|
||||||
value_size = measure_text_cached(font, value_text, font_size)
|
|
||||||
while label_size.x + value_size.x + _SOURCE_TEXT_GAP > available_w and font_size > _SOURCE_MIN_FONT:
|
|
||||||
font_size -= 1
|
|
||||||
label_size = measure_text_cached(font, label, font_size)
|
|
||||||
value_size = measure_text_cached(font, value_text, font_size)
|
|
||||||
|
|
||||||
fits = label_size.x + value_size.x + _SOURCE_TEXT_GAP <= available_w
|
|
||||||
return font_size, value_size, fits
|
|
||||||
|
|
||||||
|
|
||||||
def _draw_source_icon(icon_key: str, x: float, y: float, size: float, color: rl.Color) -> None:
|
def _draw_source_icon(icon_key: str, x: float, y: float, size: float, color: rl.Color) -> None:
|
||||||
@@ -425,58 +427,106 @@ def _draw_source_icon(icon_key: str, x: float, y: float, size: float, color: rl.
|
|||||||
stroke = max(2.0, size / 12.0)
|
stroke = max(2.0, size / 12.0)
|
||||||
|
|
||||||
if icon_key == "map":
|
if icon_key == "map":
|
||||||
|
map_stroke = max(2.0, size * 0.075)
|
||||||
left = x + size * 0.12
|
left = x + size * 0.12
|
||||||
|
fold_left = x + size * 0.37
|
||||||
|
fold_right = x + size * 0.63
|
||||||
right = x + size * 0.88
|
right = x + size * 0.88
|
||||||
top = y + size * 0.18
|
top = y + size * 0.20
|
||||||
bottom = y + size * 0.82
|
top_low = y + size * 0.27
|
||||||
fold = size * 0.25
|
bottom = y + size * 0.80
|
||||||
rl.draw_line_ex(rl.Vector2(left, top), rl.Vector2(left, bottom), stroke, color)
|
bottom_low = y + size * 0.73
|
||||||
rl.draw_line_ex(rl.Vector2(left, top), rl.Vector2(left + fold, top + size * 0.12), stroke, color)
|
outline = [
|
||||||
rl.draw_line_ex(rl.Vector2(left + fold, top + size * 0.12), rl.Vector2(left + fold, bottom + size * 0.12), stroke, color)
|
rl.Vector2(left, top),
|
||||||
rl.draw_line_ex(rl.Vector2(left + fold, top + size * 0.12), rl.Vector2(left + fold * 2.0, top), stroke, color)
|
rl.Vector2(fold_left, top_low),
|
||||||
rl.draw_line_ex(rl.Vector2(left + fold * 2.0, top), rl.Vector2(left + fold * 2.0, bottom), stroke, color)
|
rl.Vector2(fold_right, top),
|
||||||
rl.draw_line_ex(rl.Vector2(left + fold * 2.0, top), rl.Vector2(right, top + size * 0.12), stroke, color)
|
rl.Vector2(right, top_low),
|
||||||
rl.draw_line_ex(rl.Vector2(right, top + size * 0.12), rl.Vector2(right, bottom + size * 0.12), stroke, color)
|
rl.Vector2(right, bottom),
|
||||||
|
rl.Vector2(fold_right, bottom_low),
|
||||||
|
rl.Vector2(fold_left, bottom),
|
||||||
|
rl.Vector2(left, bottom_low),
|
||||||
|
]
|
||||||
|
for index, point in enumerate(outline):
|
||||||
|
rl.draw_line_ex(point, outline[(index + 1) % len(outline)], map_stroke, color)
|
||||||
|
for point in outline:
|
||||||
|
rl.draw_circle_v(point, map_stroke / 2, color)
|
||||||
|
rl.draw_line_ex(outline[1], outline[6], map_stroke, color)
|
||||||
|
rl.draw_line_ex(outline[2], outline[5], map_stroke, color)
|
||||||
elif icon_key == "camera":
|
elif icon_key == "camera":
|
||||||
body = rl.Rectangle(x + size * 0.08, y + size * 0.28, size * 0.84, size * 0.54)
|
body = rl.Rectangle(x + size * 0.09, y + size * 0.29, size * 0.82, size * 0.52)
|
||||||
rl.draw_rectangle_rounded(body, 0.25, 8, color)
|
rl.draw_rectangle_rounded(body, 0.20, 8, color)
|
||||||
lens = rl.Vector2(cx, y + size * 0.55)
|
lens = rl.Vector2(cx, y + size * 0.54)
|
||||||
rl.draw_circle_v(lens, size * 0.17, _SOURCE_PANEL_BG)
|
lens_outer = size * 0.17
|
||||||
rl.draw_circle_lines(int(lens.x), int(lens.y), size * 0.17, color)
|
rl.draw_circle_v(lens, lens_outer, _SOURCE_PANEL_BG)
|
||||||
|
rl.draw_ring(lens, size * 0.105, lens_outer, 0, 360, max(24, int(size * 0.25)), color)
|
||||||
rl.draw_rectangle_rounded(
|
rl.draw_rectangle_rounded(
|
||||||
rl.Rectangle(x + size * 0.28, y + size * 0.16, size * 0.24, size * 0.17),
|
rl.Rectangle(x + size * 0.30, y + size * 0.18, size * 0.23, size * 0.15),
|
||||||
0.25, 6, color,
|
0.18, 8, color,
|
||||||
)
|
)
|
||||||
|
elif icon_key == "next":
|
||||||
|
arrow_stroke = max(2.0, size * 0.08)
|
||||||
|
arrow_tip = rl.Vector2(x + size * 0.88, cy)
|
||||||
|
rl.draw_line_ex(rl.Vector2(x + size * 0.10, cy), arrow_tip, arrow_stroke, color)
|
||||||
|
for endpoint in (
|
||||||
|
rl.Vector2(x + size * 0.60, y + size * 0.18),
|
||||||
|
rl.Vector2(x + size * 0.60, y + size * 0.82),
|
||||||
|
):
|
||||||
|
rl.draw_line_ex(arrow_tip, endpoint, arrow_stroke, color)
|
||||||
|
rl.draw_circle_v(arrow_tip, arrow_stroke / 2, color)
|
||||||
elif icon_key == "navigation":
|
elif icon_key == "navigation":
|
||||||
head = rl.Vector2(x + size * 0.82, y + size * 0.16)
|
pin_center = rl.Vector2(cx, y + size * 0.36)
|
||||||
left = rl.Vector2(x + size * 0.16, y + size * 0.78)
|
pin_radius = size * 0.22
|
||||||
right = rl.Vector2(x + size * 0.64, y + size * 0.84)
|
rl.draw_circle_v(pin_center, pin_radius, color)
|
||||||
rl.draw_triangle(left, head, right, color)
|
rl.draw_triangle(
|
||||||
rl.draw_line_ex(left, head, stroke, color)
|
rl.Vector2(cx - pin_radius * 0.82, y + size * 0.40),
|
||||||
rl.draw_line_ex(head, right, stroke, color)
|
rl.Vector2(cx + pin_radius * 0.82, y + size * 0.40),
|
||||||
|
rl.Vector2(cx, y + size * 0.86),
|
||||||
|
color,
|
||||||
|
)
|
||||||
|
rl.draw_circle_v(pin_center, size * 0.09, _SOURCE_PANEL_BG)
|
||||||
else: # Dashboard / fallback
|
else: # Dashboard / fallback
|
||||||
rl.draw_ring(rl.Vector2(cx, cy + size * 0.10), size * 0.27, size * 0.34, 200, 340, 24, color)
|
dashboard_scale = 1.22
|
||||||
|
pivot = rl.Vector2(cx, cy + size * 0.17)
|
||||||
|
inner_radius = size * 0.27 * dashboard_scale
|
||||||
|
outer_radius = size * 0.34 * dashboard_scale
|
||||||
|
ring_segments = max(24, int(size * 0.25))
|
||||||
|
rl.draw_ring(pivot, inner_radius, outer_radius, 190, 350, ring_segments, color)
|
||||||
|
cap_radius = (outer_radius - inner_radius) / 2
|
||||||
|
for angle in (190, 350):
|
||||||
|
radians = math.radians(angle)
|
||||||
|
rl.draw_circle_v(
|
||||||
|
rl.Vector2(
|
||||||
|
pivot.x + math.cos(radians) * (inner_radius + cap_radius),
|
||||||
|
pivot.y + math.sin(radians) * (inner_radius + cap_radius),
|
||||||
|
),
|
||||||
|
cap_radius,
|
||||||
|
color,
|
||||||
|
)
|
||||||
|
needle_angle = math.radians(-48)
|
||||||
|
needle_length = inner_radius + stroke * 0.15
|
||||||
rl.draw_line_ex(
|
rl.draw_line_ex(
|
||||||
rl.Vector2(cx, cy + size * 0.10),
|
pivot,
|
||||||
rl.Vector2(cx + size * 0.18, cy - size * 0.12),
|
rl.Vector2(
|
||||||
|
pivot.x + math.cos(needle_angle) * needle_length,
|
||||||
|
pivot.y + math.sin(needle_angle) * needle_length,
|
||||||
|
),
|
||||||
stroke,
|
stroke,
|
||||||
color,
|
color,
|
||||||
)
|
)
|
||||||
rl.draw_circle_v(rl.Vector2(cx, cy + size * 0.10), stroke, color)
|
rl.draw_circle_v(pivot, max(2.0, size * 0.06 * dashboard_scale), color)
|
||||||
|
|
||||||
|
|
||||||
def _draw_sources_bubble(state: dict, sign_rect: rl.Rectangle):
|
def _draw_sources_bubble(state: dict, sign_rect: rl.Rectangle):
|
||||||
"""Draw the expanded source list attached to the SLC card."""
|
"""Draw the expanded source list attached to the SLC card."""
|
||||||
font_bold = _get_bold()
|
|
||||||
font_semi = _get_semi_bold()
|
font_semi = _get_semi_bold()
|
||||||
active_source = state['speed_limit_source']
|
active_source = state['speed_limit_source']
|
||||||
|
|
||||||
rows = []
|
rows = []
|
||||||
for title, abbrev, value_key, panel_label, icon_key in SOURCE_DEFS:
|
for title, _abbrev, value_key, panel_label, icon_key in SOURCE_DEFS:
|
||||||
value = state[value_key]
|
value = state[value_key]
|
||||||
if value == 0:
|
if value == 0 and active_source != title:
|
||||||
continue
|
continue
|
||||||
rows.append((title, abbrev, panel_label, icon_key, value, active_source == title))
|
rows.append((panel_label, icon_key, value, active_source == title))
|
||||||
|
|
||||||
if not rows:
|
if not rows:
|
||||||
return
|
return
|
||||||
@@ -487,15 +537,17 @@ def _draw_sources_bubble(state: dict, sign_rect: rl.Rectangle):
|
|||||||
_SOURCE_PANEL_WIDTH,
|
_SOURCE_PANEL_WIDTH,
|
||||||
sign_rect.height,
|
sign_rect.height,
|
||||||
)
|
)
|
||||||
rl.draw_rectangle_rounded(panel_rect, roundness_for(panel_rect), 16, _SOURCE_PANEL_BG)
|
rl.draw_rectangle_rounded(panel_rect, CONTROL_ROUNDNESS, CONTROL_SEGMENTS, _SOURCE_PANEL_BG)
|
||||||
|
rl.draw_rectangle_rounded_lines_ex(
|
||||||
|
panel_rect, CONTROL_ROUNDNESS, CONTROL_SEGMENTS, 1, _SOURCE_PANEL_BORDER,
|
||||||
|
)
|
||||||
|
|
||||||
row_h = (panel_rect.height - 2 * _SOURCE_PANEL_PAD_Y) / len(rows)
|
row_h = (panel_rect.height - 2 * _SOURCE_PANEL_PAD_Y) / len(rows)
|
||||||
content_left = panel_rect.x + _SOURCE_PANEL_PAD_X
|
content_left = panel_rect.x + _SOURCE_PANEL_PAD_X
|
||||||
content_right = panel_rect.x + panel_rect.width - _SOURCE_PANEL_PAD_X
|
content_right = panel_rect.x + panel_rect.width - _SOURCE_PANEL_PAD_X
|
||||||
label_left = content_left + _SOURCE_ICON_SIZE + _SOURCE_TEXT_GAP
|
label_left = content_left + _SOURCE_ICON_SIZE + _SOURCE_ICON_TEXT_GAP
|
||||||
available_w = content_right - label_left
|
|
||||||
|
|
||||||
for index, (title, abbrev, panel_label, icon_key, value, is_active) in enumerate(rows):
|
for index, (panel_label, icon_key, value, is_active) in enumerate(rows):
|
||||||
row_y = panel_rect.y + _SOURCE_PANEL_PAD_Y + index * row_h
|
row_y = panel_rect.y + _SOURCE_PANEL_PAD_Y + index * row_h
|
||||||
if index:
|
if index:
|
||||||
divider_y = row_y
|
divider_y = row_y
|
||||||
@@ -506,26 +558,33 @@ def _draw_sources_bubble(state: dict, sign_rect: rl.Rectangle):
|
|||||||
_SOURCE_DIVIDER,
|
_SOURCE_DIVIDER,
|
||||||
)
|
)
|
||||||
|
|
||||||
text_font = font_bold if is_active else font_semi
|
text_font = font_semi
|
||||||
label_text = panel_label
|
label_text = panel_label
|
||||||
value_text = str(int(round(value)))
|
value_text = "\u2013" if value <= 0 else str(int(round(value)))
|
||||||
font_size, value_size, fits = _fit_sources_row(
|
font_size = _SOURCE_FONT
|
||||||
text_font, label_text, value_text, row_h, available_w
|
value_size = measure_text_cached(text_font, value_text, font_size)
|
||||||
)
|
|
||||||
if not fits:
|
|
||||||
label_text = abbrev
|
|
||||||
font_size, value_size, _ = _fit_sources_row(
|
|
||||||
text_font, label_text, value_text, row_h, available_w
|
|
||||||
)
|
|
||||||
|
|
||||||
baseline_y = row_y + (row_h - font_size) / 2
|
baseline_y = row_y + (row_h - value_size.y) / 2
|
||||||
icon_y = row_y + (row_h - _SOURCE_ICON_SIZE) / 2
|
icon_y = row_y + (row_h - _SOURCE_ICON_SIZE) / 2
|
||||||
|
if is_active:
|
||||||
|
active_bar_height = min(
|
||||||
|
_SOURCE_ACTIVE_BAR_HEIGHT,
|
||||||
|
max(10.0, row_h - 2 * _SOURCE_ACTIVE_BAR_ROW_INSET),
|
||||||
|
)
|
||||||
|
active_bar_rect = rl.Rectangle(
|
||||||
|
panel_rect.x + _SOURCE_ACTIVE_BAR_X,
|
||||||
|
row_y + (row_h - active_bar_height) / 2,
|
||||||
|
_SOURCE_ACTIVE_BAR_WIDTH,
|
||||||
|
active_bar_height,
|
||||||
|
)
|
||||||
|
rl.draw_rectangle_rounded(active_bar_rect, 0.5, 4, _SOURCE_ACTIVE_BAR)
|
||||||
|
|
||||||
icon_color = _WHITE if is_active else _SOURCE_ICON_MUTED
|
icon_color = _WHITE if is_active else _SOURCE_ICON_MUTED
|
||||||
_draw_source_icon(icon_key, content_left, icon_y, _SOURCE_ICON_SIZE, icon_color)
|
_draw_source_icon(icon_key, content_left, icon_y, _SOURCE_ICON_SIZE, icon_color)
|
||||||
|
|
||||||
label_pos = rl.Vector2(label_left, baseline_y)
|
label_pos = rl.Vector2(label_left, baseline_y)
|
||||||
value_pos = rl.Vector2(content_right - value_size.x, baseline_y)
|
value_pos = rl.Vector2(content_right - value_size.x, baseline_y)
|
||||||
label_color = _SOURCE_LABEL
|
label_color = _WHITE if is_active else _SOURCE_LABEL
|
||||||
value_color = _WHITE if is_active else _SOURCE_LABEL
|
value_color = _WHITE if is_active else _SOURCE_LABEL
|
||||||
rl.draw_text_ex(text_font, label_text, label_pos, font_size, 0, label_color)
|
rl.draw_text_ex(text_font, label_text, label_pos, font_size, 0, label_color)
|
||||||
rl.draw_text_ex(text_font, value_text, value_pos, font_size, 0, value_color)
|
rl.draw_text_ex(text_font, value_text, value_pos, font_size, 0, value_color)
|
||||||
|
|||||||
@@ -7,10 +7,12 @@ import pyray as rl
|
|||||||
# here prevents the Set Speed and SLC implementations from drifting apart.
|
# here prevents the Set Speed and SLC implementations from drifting apart.
|
||||||
CONTROL_WIDTH = 176
|
CONTROL_WIDTH = 176
|
||||||
SET_SPEED_HEIGHT = 196
|
SET_SPEED_HEIGHT = 196
|
||||||
SLC_HEIGHT = 216
|
SLC_HEIGHT = SET_SPEED_HEIGHT
|
||||||
CONTROL_RADIUS = 16
|
# Stable Raylib shape settings for scaled rendering.
|
||||||
CONTROL_SEGMENTS = 16
|
CONTROL_RADIUS = 31
|
||||||
CONTROL_BORDER_WIDTH = 3
|
CONTROL_ROUNDNESS = 0.35
|
||||||
|
CONTROL_SEGMENTS = 10
|
||||||
|
CONTROL_BORDER_WIDTH = 6
|
||||||
CONTROL_BG = rl.Color(0, 0, 0, 166)
|
CONTROL_BG = rl.Color(0, 0, 0, 166)
|
||||||
CONTROL_BORDER = rl.Color(196, 205, 208, 180)
|
CONTROL_BORDER = rl.Color(196, 205, 208, 180)
|
||||||
# The layout manager has historically anchored the left controls at x + 146.
|
# The layout manager has historically anchored the left controls at x + 146.
|
||||||
@@ -27,6 +29,6 @@ def draw_control_card(rect: rl.Rectangle, *, fill: rl.Color = CONTROL_BG,
|
|||||||
border: rl.Color = CONTROL_BORDER,
|
border: rl.Color = CONTROL_BORDER,
|
||||||
border_width: float = CONTROL_BORDER_WIDTH) -> None:
|
border_width: float = CONTROL_BORDER_WIDTH) -> None:
|
||||||
"""Draw the common translucent rounded card used by left-hand controls."""
|
"""Draw the common translucent rounded card used by left-hand controls."""
|
||||||
roundness = roundness_for(rect)
|
roundness = CONTROL_ROUNDNESS
|
||||||
rl.draw_rectangle_rounded(rect, roundness, CONTROL_SEGMENTS, fill)
|
rl.draw_rectangle_rounded(rect, roundness, CONTROL_SEGMENTS, fill)
|
||||||
rl.draw_rectangle_rounded_lines_ex(rect, roundness, CONTROL_SEGMENTS, border_width, border)
|
rl.draw_rectangle_rounded_lines_ex(rect, roundness, CONTROL_SEGMENTS, border_width, border)
|
||||||
|
|||||||
Reference in New Issue
Block a user