IQ.Pilot Release Commit @ 0798119
This commit is contained in:
251
selfdrive/ui/onroad/hud_renderer.py
Normal file
251
selfdrive/ui/onroad/hud_renderer.py
Normal file
@@ -0,0 +1,251 @@
|
||||
import pyray as rl
|
||||
from dataclasses import dataclass
|
||||
from openpilot.common.constants import CV
|
||||
from openpilot.selfdrive.ui.onroad.exp_button import ExpButton
|
||||
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.multilang import tr
|
||||
from openpilot.system.ui.lib.text_measure import measure_text_cached
|
||||
from openpilot.system.ui.widgets import Widget
|
||||
|
||||
# Constants
|
||||
SET_SPEED_NA = 255
|
||||
KM_TO_MILE = 0.621371
|
||||
CRUISE_DISABLED_CHAR = '–'
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class UIConfig:
|
||||
header_height: int = 300
|
||||
border_size: int = 30
|
||||
button_size: int = 192
|
||||
set_speed_width_metric: int = 186
|
||||
set_speed_width_imperial: int = 174
|
||||
set_speed_height: int = 228
|
||||
wheel_icon_size: int = 144
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class FontSizes:
|
||||
current_speed: int = 176
|
||||
speed_unit: int = 66
|
||||
max_speed: int = 28
|
||||
set_speed: int = 74
|
||||
limit_speed: int = 64
|
||||
limit_offset: int = 30
|
||||
limit_unit: int = 22
|
||||
limit_label: int = 24
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class Colors:
|
||||
WHITE = rl.WHITE
|
||||
DISENGAGED = rl.Color(145, 155, 149, 255)
|
||||
OVERRIDE = rl.Color(145, 155, 149, 255) # Added
|
||||
ENGAGED = rl.Color(0x0C, 0x94, 0x96, 0xFF)
|
||||
LIMIT_ENGAGED = rl.Color(0x27, 0xF5, 0xD3, 0xFF)
|
||||
DISENGAGED_BG = rl.Color(0, 0, 0, 153)
|
||||
OVERRIDE_BG = rl.Color(145, 155, 149, 204)
|
||||
ENGAGED_BG = rl.Color(128, 216, 166, 204)
|
||||
GREY = rl.Color(166, 166, 166, 255)
|
||||
DARK_GREY = rl.Color(114, 114, 114, 255)
|
||||
BLACK_TRANSLUCENT = rl.Color(0, 0, 0, 166)
|
||||
WHITE_TRANSLUCENT = rl.Color(255, 255, 255, 200)
|
||||
BORDER_TRANSLUCENT = rl.Color(255, 255, 255, 75)
|
||||
HEADER_GRADIENT_START = rl.Color(0, 0, 0, 114)
|
||||
HEADER_GRADIENT_END = rl.BLANK
|
||||
|
||||
|
||||
UI_CONFIG = UIConfig()
|
||||
FONT_SIZES = FontSizes()
|
||||
COLORS = Colors()
|
||||
|
||||
|
||||
class HudRenderer(Widget):
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
"""Initialize the HUD renderer."""
|
||||
self.is_cruise_set: bool = False
|
||||
self.is_cruise_available: bool = True
|
||||
self.set_speed: float = SET_SPEED_NA
|
||||
self.speed: float = 0.0
|
||||
self.v_ego_cluster_seen: bool = False
|
||||
self.limit_speed_text: str = "---"
|
||||
self.limit_offset_text: str = ""
|
||||
self.limit_available: bool = False
|
||||
|
||||
self._font_semi_bold: rl.Font = gui_app.font(FontWeight.SEMI_BOLD)
|
||||
self._font_bold: rl.Font = gui_app.font(FontWeight.BOLD)
|
||||
self._font_medium: rl.Font = gui_app.font(FontWeight.MEDIUM)
|
||||
|
||||
self._exp_button: ExpButton = ExpButton(UI_CONFIG.button_size, UI_CONFIG.wheel_icon_size)
|
||||
|
||||
def _update_state(self) -> None:
|
||||
"""Update HUD state based on car state and controls state."""
|
||||
sm = ui_state.sm
|
||||
if sm.recv_frame["carState"] < ui_state.started_frame:
|
||||
self.is_cruise_set = False
|
||||
self.set_speed = SET_SPEED_NA
|
||||
self.speed = 0.0
|
||||
return
|
||||
|
||||
controls_state = sm['controlsState']
|
||||
car_state = sm['carState']
|
||||
|
||||
v_cruise_cluster = car_state.vCruiseCluster
|
||||
self.set_speed = (
|
||||
controls_state.vCruiseDEPRECATED if v_cruise_cluster == 0.0 else v_cruise_cluster
|
||||
)
|
||||
self.is_cruise_set = 0 < self.set_speed < SET_SPEED_NA
|
||||
self.is_cruise_available = self.set_speed != -1
|
||||
|
||||
if self.is_cruise_set and not ui_state.is_metric:
|
||||
self.set_speed *= KM_TO_MILE
|
||||
|
||||
v_ego_cluster = car_state.vEgoCluster
|
||||
self.v_ego_cluster_seen = self.v_ego_cluster_seen or v_ego_cluster != 0.0
|
||||
v_ego = v_ego_cluster if self.v_ego_cluster_seen else car_state.vEgo
|
||||
speed_conversion = CV.MS_TO_KPH if ui_state.is_metric else CV.MS_TO_MPH
|
||||
self.speed = max(0.0, v_ego * speed_conversion)
|
||||
|
||||
def _render(self, rect: rl.Rectangle) -> None:
|
||||
"""Render HUD elements to the screen."""
|
||||
# Draw the header background
|
||||
rl.draw_rectangle_gradient_v(
|
||||
int(rect.x),
|
||||
int(rect.y),
|
||||
int(rect.width),
|
||||
UI_CONFIG.header_height,
|
||||
COLORS.HEADER_GRADIENT_START,
|
||||
COLORS.HEADER_GRADIENT_END,
|
||||
)
|
||||
|
||||
if self.is_cruise_available:
|
||||
self._draw_set_speed(rect)
|
||||
|
||||
self._draw_current_speed(rect)
|
||||
|
||||
button_x = rect.x + rect.width - UI_CONFIG.border_size - UI_CONFIG.button_size
|
||||
button_y = rect.y + UI_CONFIG.border_size
|
||||
self._exp_button.render(rl.Rectangle(button_x, button_y, UI_CONFIG.button_size, UI_CONFIG.button_size))
|
||||
|
||||
def user_interacting(self) -> bool:
|
||||
return self._exp_button.is_pressed
|
||||
|
||||
def _draw_set_speed(self, rect: rl.Rectangle) -> None:
|
||||
"""Draw the compact stacked LIMIT / MAX speed indicator box."""
|
||||
set_speed_width = UI_CONFIG.set_speed_width_metric if ui_state.is_metric else UI_CONFIG.set_speed_width_imperial
|
||||
x = rect.x + 60 + (UI_CONFIG.set_speed_width_imperial - set_speed_width) // 2
|
||||
y = rect.y + 45
|
||||
|
||||
set_speed_rect = rl.Rectangle(x, y, set_speed_width, UI_CONFIG.set_speed_height)
|
||||
rl.draw_rectangle_rounded(set_speed_rect, 0.35, 10, COLORS.BLACK_TRANSLUCENT)
|
||||
rl.draw_rectangle_rounded_lines_ex(set_speed_rect, 0.35, 10, 6, COLORS.BORDER_TRANSLUCENT)
|
||||
split_y = y + 112
|
||||
rl.draw_line_ex(
|
||||
rl.Vector2(x + 18, split_y),
|
||||
rl.Vector2(x + set_speed_width - 18, split_y),
|
||||
3,
|
||||
COLORS.BORDER_TRANSLUCENT,
|
||||
)
|
||||
|
||||
max_color = COLORS.DARK_GREY
|
||||
set_speed_color = COLORS.WHITE
|
||||
limit_value_color = COLORS.WHITE if self.limit_available else COLORS.DARK_GREY
|
||||
if self.is_cruise_set:
|
||||
if ui_state.status == UIStatus.ENGAGED:
|
||||
max_color = COLORS.ENGAGED
|
||||
if self.limit_available:
|
||||
limit_value_color = COLORS.LIMIT_ENGAGED
|
||||
elif ui_state.status == UIStatus.DISENGAGED:
|
||||
max_color = COLORS.DISENGAGED
|
||||
elif ui_state.status == UIStatus.OVERRIDE:
|
||||
max_color = COLORS.OVERRIDE
|
||||
|
||||
limit_value_text = self.limit_speed_text
|
||||
if len(limit_value_text) <= 2:
|
||||
limit_value_size = FONT_SIZES.limit_speed
|
||||
elif len(limit_value_text) == 3:
|
||||
limit_value_size = 56
|
||||
else:
|
||||
limit_value_size = 48
|
||||
limit_value_width = measure_text_cached(self._font_bold, limit_value_text, limit_value_size).x
|
||||
limit_offset_text = self.limit_offset_text if self.limit_available else ""
|
||||
limit_offset_width = 0.0
|
||||
if limit_offset_text:
|
||||
limit_offset_width = measure_text_cached(self._font_semi_bold, limit_offset_text, FONT_SIZES.limit_offset).x + 8
|
||||
limit_value_x = x + (set_speed_width - limit_value_width - limit_offset_width) / 2
|
||||
rl.draw_text_ex(
|
||||
self._font_bold,
|
||||
limit_value_text,
|
||||
rl.Vector2(limit_value_x, y + 14),
|
||||
limit_value_size,
|
||||
0,
|
||||
limit_value_color,
|
||||
)
|
||||
if limit_offset_text:
|
||||
rl.draw_text_ex(
|
||||
self._font_semi_bold,
|
||||
limit_offset_text,
|
||||
rl.Vector2(limit_value_x + limit_value_width + 8, y + 22),
|
||||
FONT_SIZES.limit_offset,
|
||||
0,
|
||||
COLORS.WHITE_TRANSLUCENT,
|
||||
)
|
||||
|
||||
if self.limit_available:
|
||||
limit_unit_text = tr("LIMIT")
|
||||
limit_unit_width = measure_text_cached(self._font_medium, limit_unit_text, FONT_SIZES.limit_unit).x
|
||||
rl.draw_text_ex(
|
||||
self._font_medium,
|
||||
limit_unit_text,
|
||||
rl.Vector2(x + (set_speed_width - limit_unit_width) / 2, y + 78),
|
||||
FONT_SIZES.limit_unit,
|
||||
0,
|
||||
COLORS.WHITE_TRANSLUCENT,
|
||||
)
|
||||
else:
|
||||
limit_label_text = tr("LIMIT")
|
||||
limit_label_width = measure_text_cached(self._font_semi_bold, limit_label_text, FONT_SIZES.limit_label).x
|
||||
rl.draw_text_ex(
|
||||
self._font_semi_bold,
|
||||
limit_label_text,
|
||||
rl.Vector2(x + (set_speed_width - limit_label_width) / 2, y + 74),
|
||||
FONT_SIZES.limit_label,
|
||||
0,
|
||||
COLORS.GREY,
|
||||
)
|
||||
|
||||
max_text = tr("MAX")
|
||||
max_text_width = measure_text_cached(self._font_semi_bold, max_text, FONT_SIZES.max_speed).x
|
||||
rl.draw_text_ex(
|
||||
self._font_semi_bold,
|
||||
max_text,
|
||||
rl.Vector2(x + (set_speed_width - max_text_width) / 2, y + 118),
|
||||
FONT_SIZES.max_speed,
|
||||
0,
|
||||
max_color,
|
||||
)
|
||||
|
||||
set_speed_text = CRUISE_DISABLED_CHAR if not self.is_cruise_set else str(round(self.set_speed))
|
||||
speed_text_width = measure_text_cached(self._font_bold, set_speed_text, FONT_SIZES.set_speed).x
|
||||
rl.draw_text_ex(
|
||||
self._font_bold,
|
||||
set_speed_text,
|
||||
rl.Vector2(x + (set_speed_width - speed_text_width) / 2, y + 136),
|
||||
FONT_SIZES.set_speed,
|
||||
0,
|
||||
set_speed_color,
|
||||
)
|
||||
|
||||
def _draw_current_speed(self, rect: rl.Rectangle) -> None:
|
||||
"""Draw the current vehicle speed and unit."""
|
||||
speed_text = str(round(self.speed))
|
||||
speed_text_size = measure_text_cached(self._font_bold, speed_text, FONT_SIZES.current_speed)
|
||||
speed_pos = rl.Vector2(rect.x + rect.width / 2 - speed_text_size.x / 2, 180 - speed_text_size.y / 2)
|
||||
rl.draw_text_ex(self._font_bold, speed_text, speed_pos, FONT_SIZES.current_speed, 0, COLORS.WHITE)
|
||||
|
||||
unit_text = tr("km/h") if ui_state.is_metric else tr("mph")
|
||||
unit_text_size = measure_text_cached(self._font_medium, unit_text, FONT_SIZES.speed_unit)
|
||||
unit_pos = rl.Vector2(rect.x + rect.width / 2 - unit_text_size.x / 2, 290 - unit_text_size.y / 2)
|
||||
rl.draw_text_ex(self._font_medium, unit_text, unit_pos, FONT_SIZES.speed_unit, 0, COLORS.WHITE_TRANSLUCENT)
|
||||
Reference in New Issue
Block a user