Biscuit tweaks

This commit is contained in:
firestarsdog
2026-08-08 04:47:00 -04:00
parent 42d3cf42d8
commit 6eba716ff8
2 changed files with 166 additions and 105 deletions
+158 -99
View File
@@ -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)