mirror of
https://github.com/firestar5683/StarPilot.git
synced 2026-07-08 07:02:06 +08:00
BigUI WIP: Mici the Ticis & Tizis
This commit is contained in:
@@ -1,14 +1,14 @@
|
||||
import time
|
||||
import pyray as rl
|
||||
from dataclasses import dataclass
|
||||
from cereal import custom, messaging, log
|
||||
from cereal import messaging, log, custom
|
||||
from openpilot.selfdrive.ui.ui_state import ui_state
|
||||
from openpilot.common.filter_simple import BounceFilter, FirstOrderFilter
|
||||
from openpilot.system.hardware import TICI
|
||||
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
|
||||
from openpilot.system.ui.widgets.label import Label
|
||||
from openpilot.system.ui.widgets.label import UnifiedLabel
|
||||
|
||||
AlertSize = log.SelfdriveState.AlertSize
|
||||
AlertStatus = log.SelfdriveState.AlertStatus
|
||||
@@ -16,27 +16,31 @@ StarPilotAlertStatus = custom.StarPilotSelfdriveState.AlertStatus
|
||||
|
||||
ALERT_MARGIN = 40
|
||||
ALERT_PADDING = 60
|
||||
ALERT_LINE_SPACING = 45
|
||||
ALERT_BORDER_RADIUS = 30
|
||||
|
||||
ALERT_FONT_SMALL = 66
|
||||
ALERT_FONT_MEDIUM = 74
|
||||
ALERT_FONT_BIG = 88
|
||||
|
||||
ALERT_HEIGHTS = {
|
||||
AlertSize.small: 271,
|
||||
AlertSize.mid: 420,
|
||||
}
|
||||
|
||||
SELFDRIVE_STATE_TIMEOUT = 5 # Seconds
|
||||
SELFDRIVE_UNRESPONSIVE_TIMEOUT = 10 # Seconds
|
||||
ALERT_SLIDE_DISTANCES = {
|
||||
AlertSize.small: 40,
|
||||
AlertSize.mid: 70,
|
||||
AlertSize.full: 100,
|
||||
}
|
||||
|
||||
SMALL_FONT_SIZE = 60
|
||||
MID_FONT_SIZE_1 = 88
|
||||
MID_FONT_SIZE_2 = 56
|
||||
|
||||
SELFDRIVE_STATE_TIMEOUT = 5
|
||||
SELFDRIVE_UNRESPONSIVE_TIMEOUT = 10
|
||||
|
||||
# Constants
|
||||
ALERT_COLORS = {
|
||||
AlertStatus.normal: rl.Color(0x15, 0x15, 0x15, 0xF1), # #151515 with alpha 0xF1
|
||||
AlertStatus.userPrompt: rl.Color(0xDA, 0x6F, 0x25, 0xF1), # #DA6F25 with alpha 0xF1
|
||||
AlertStatus.critical: rl.Color(0xC9, 0x22, 0x31, 0xF1), # #C92231 with alpha 0xF1
|
||||
StarPilotAlertStatus.starpilot: rl.Color(0x17, 0x86, 0x44, 0xF1),
|
||||
AlertStatus.normal: rl.Color(0, 0, 0, 255),
|
||||
AlertStatus.userPrompt: rl.Color(255, 115, 0, 255),
|
||||
AlertStatus.critical: rl.Color(255, 0, 21, 255),
|
||||
StarPilotAlertStatus.starpilot: rl.Color(23, 134, 68, 255),
|
||||
}
|
||||
|
||||
|
||||
@@ -46,9 +50,9 @@ class Alert:
|
||||
text2: str = ""
|
||||
size: int = 0
|
||||
status: int = 0
|
||||
alert_type: str = ""
|
||||
|
||||
|
||||
# Pre-defined alert instances
|
||||
ALERT_STARTUP_PENDING = Alert(
|
||||
text1=tr("openpilot Unavailable"),
|
||||
text2=tr("Waiting to start"),
|
||||
@@ -74,30 +78,28 @@ ALERT_CRITICAL_REBOOT = Alert(
|
||||
class AlertRenderer(Widget):
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
self.font_regular: rl.Font = gui_app.font(FontWeight.NORMAL)
|
||||
self.font_bold: rl.Font = gui_app.font(FontWeight.BOLD)
|
||||
|
||||
# font size is set dynamically
|
||||
self._full_text1_label = Label("", font_size=0, font_weight=FontWeight.BOLD, text_alignment=rl.GuiTextAlignment.TEXT_ALIGN_CENTER,
|
||||
text_alignment_vertical=rl.GuiTextAlignmentVertical.TEXT_ALIGN_TOP)
|
||||
self._full_text2_label = Label("", font_size=ALERT_FONT_BIG, text_alignment=rl.GuiTextAlignment.TEXT_ALIGN_CENTER,
|
||||
text_alignment_vertical=rl.GuiTextAlignmentVertical.TEXT_ALIGN_TOP)
|
||||
self._alert_text1_label = UnifiedLabel(text="", font_size=48, font_weight=FontWeight.BOLD,
|
||||
line_height=0.86, letter_spacing=-0.02)
|
||||
self._alert_text2_label = UnifiedLabel(text="", font_size=32, font_weight=FontWeight.NORMAL,
|
||||
line_height=0.86, letter_spacing=0.025)
|
||||
|
||||
self._prev_alert: Alert | None = None
|
||||
|
||||
self._alert_y_filter = BounceFilter(0, 0.1, 1 / gui_app.target_fps, initialized=False)
|
||||
self._alpha_filter = FirstOrderFilter(0, 0.05, 1 / gui_app.target_fps)
|
||||
|
||||
def get_alert(self, sm: messaging.SubMaster) -> Alert | None:
|
||||
"""Generate the current alert based on selfdrive state."""
|
||||
ss = sm['selfdriveState']
|
||||
|
||||
# Check if selfdriveState messages have stopped arriving
|
||||
recv_frame = sm.recv_frame['selfdriveState']
|
||||
if not sm.updated['selfdriveState']:
|
||||
recv_frame = sm.recv_frame['selfdriveState']
|
||||
time_since_onroad = time.monotonic() - ui_state.started_time
|
||||
|
||||
# 1. Never received selfdriveState since going onroad
|
||||
waiting_for_startup = recv_frame < ui_state.started_frame
|
||||
if waiting_for_startup and time_since_onroad > 5:
|
||||
return ALERT_STARTUP_PENDING
|
||||
|
||||
# 2. Lost communication with selfdriveState after receiving it
|
||||
if TICI and not waiting_for_startup:
|
||||
ss_missing = time.monotonic() - sm.recv_time['selfdriveState']
|
||||
if ss_missing > SELFDRIVE_STATE_TIMEOUT:
|
||||
@@ -106,82 +108,159 @@ class AlertRenderer(Widget):
|
||||
return ALERT_CRITICAL_REBOOT
|
||||
|
||||
if ss.alertSize != AlertSize.none:
|
||||
alert = Alert(text1=ss.alertText1, text2=ss.alertText2, size=ss.alertSize.raw, status=ss.alertStatus.raw)
|
||||
ret = Alert(text1=ss.alertText1, text2=ss.alertText2, size=ss.alertSize.raw, status=ss.alertStatus.raw,
|
||||
alert_type=ss.alertType)
|
||||
else:
|
||||
starpilot_ss = sm["starpilotSelfdriveState"]
|
||||
if starpilot_ss.alertSize == custom.StarPilotSelfdriveState.AlertSize.none:
|
||||
return None
|
||||
alert = Alert(text1=starpilot_ss.alertText1, text2=starpilot_ss.alertText2,
|
||||
size=starpilot_ss.alertSize.raw, status=starpilot_ss.alertStatus.raw)
|
||||
ret = Alert(text1=starpilot_ss.alertText1, text2=starpilot_ss.alertText2,
|
||||
size=starpilot_ss.alertSize.raw, status=starpilot_ss.alertStatus.raw,
|
||||
alert_type=starpilot_ss.alertType)
|
||||
|
||||
if alert.status == AlertStatus.normal and ui_state.starpilot_toggles.get("hide_alerts", False):
|
||||
if ret.status == AlertStatus.normal and ui_state.starpilot_toggles.get("hide_alerts", False):
|
||||
return None
|
||||
|
||||
# Don't get old alert
|
||||
if recv_frame < ui_state.started_frame:
|
||||
if sm.recv_frame['selfdriveState'] < ui_state.started_frame:
|
||||
return None
|
||||
|
||||
return alert
|
||||
self._prev_alert = ret
|
||||
return ret
|
||||
|
||||
def _render(self, rect: rl.Rectangle):
|
||||
def will_render(self) -> tuple[Alert | None, bool]:
|
||||
alert = self.get_alert(ui_state.sm)
|
||||
if not alert:
|
||||
return
|
||||
|
||||
alert_rect = self._get_alert_rect(rect, alert.size)
|
||||
self._draw_background(alert_rect, alert)
|
||||
|
||||
text_rect = rl.Rectangle(
|
||||
alert_rect.x + ALERT_PADDING,
|
||||
alert_rect.y + ALERT_PADDING,
|
||||
alert_rect.width - 2 * ALERT_PADDING,
|
||||
alert_rect.height - 2 * ALERT_PADDING
|
||||
)
|
||||
self._draw_text(text_rect, alert)
|
||||
return alert or self._prev_alert, alert is None
|
||||
|
||||
def _get_alert_rect(self, rect: rl.Rectangle, size: int) -> rl.Rectangle:
|
||||
if size == AlertSize.full:
|
||||
return rect
|
||||
|
||||
h = ALERT_HEIGHTS.get(size, rect.height)
|
||||
return rl.Rectangle(rect.x + ALERT_MARGIN, rect.y + rect.height - h + ALERT_MARGIN,
|
||||
rect.width - ALERT_MARGIN * 2, h - ALERT_MARGIN * 2)
|
||||
return rl.Rectangle(rect.x, rect.y + rect.height - h, rect.width, h)
|
||||
|
||||
def _draw_background(self, rect: rl.Rectangle, alert: Alert) -> None:
|
||||
color = ALERT_COLORS.get(alert.status, ALERT_COLORS[AlertStatus.normal])
|
||||
def _render(self, rect: rl.Rectangle) -> bool:
|
||||
alert = self.get_alert(ui_state.sm)
|
||||
|
||||
if alert.size != AlertSize.full:
|
||||
roundness = ALERT_BORDER_RADIUS / (min(rect.width, rect.height) / 2)
|
||||
rl.draw_rectangle_rounded(rect, roundness, 10, color)
|
||||
self._alpha_filter.update(0 if alert is None else 1)
|
||||
|
||||
if alert is not None:
|
||||
self._rect = self._get_alert_rect(rect, alert.size)
|
||||
self._alert_y_filter.update(self._rect.y)
|
||||
elif self._prev_alert is not None:
|
||||
self._rect = self._get_alert_rect(rect, self._prev_alert.size)
|
||||
slide = ALERT_SLIDE_DISTANCES.get(self._prev_alert.size, 100)
|
||||
self._alert_y_filter.update(self._rect.y - slide)
|
||||
else:
|
||||
rl.draw_rectangle_rec(rect, color)
|
||||
return False
|
||||
|
||||
if alert is None:
|
||||
if self._alpha_filter.x > 0.01 and self._prev_alert is not None:
|
||||
alert = self._prev_alert
|
||||
else:
|
||||
self._prev_alert = None
|
||||
return False
|
||||
|
||||
self._draw_background(alert)
|
||||
self._draw_text(alert)
|
||||
|
||||
return True
|
||||
|
||||
def _draw_background(self, alert: Alert) -> None:
|
||||
color = ALERT_COLORS.get(alert.status, ALERT_COLORS[AlertStatus.normal])
|
||||
color = rl.Color(color.r, color.g, color.b, int(255 * 0.90 * self._alpha_filter.x))
|
||||
translucent_color = rl.Color(color.r, color.g, color.b, int(0 * self._alpha_filter.x))
|
||||
|
||||
if alert.size == AlertSize.full:
|
||||
bg_height = int(self._rect.height)
|
||||
solid_height = round(bg_height * 0.2)
|
||||
rl.draw_rectangle(int(self._rect.x), int(self._rect.y), int(self._rect.width), solid_height, color)
|
||||
rl.draw_rectangle_gradient_v(int(self._rect.x), int(self._rect.y + solid_height), int(self._rect.width),
|
||||
int(bg_height - solid_height), color, translucent_color)
|
||||
else:
|
||||
bg_height = int(self._rect.height)
|
||||
solid_height = round(bg_height * 0.2)
|
||||
roundness = ALERT_BORDER_RADIUS / (min(self._rect.width, self._rect.height) / 2)
|
||||
rl.draw_rectangle_rounded(rl.Rectangle(self._rect.x, self._rect.y, self._rect.width, solid_height),
|
||||
roundness, 10, color)
|
||||
rl.draw_rectangle_gradient_v(int(self._rect.x), int(self._rect.y + solid_height),
|
||||
int(self._rect.width), int(bg_height - solid_height),
|
||||
color, translucent_color)
|
||||
|
||||
def _draw_text(self, alert: Alert) -> None:
|
||||
text_color = rl.Color(255, 255, 255, int(255 * 0.9 * self._alpha_filter.x))
|
||||
text2_color = rl.Color(255, 255, 255, int(255 * 0.65 * self._alpha_filter.x))
|
||||
|
||||
text_rect = rl.Rectangle(
|
||||
self._rect.x + ALERT_PADDING,
|
||||
self._alert_y_filter.x,
|
||||
self._rect.width - ALERT_PADDING * 2,
|
||||
self._rect.height,
|
||||
)
|
||||
|
||||
def _draw_text(self, rect: rl.Rectangle, alert: Alert) -> None:
|
||||
if alert.size == AlertSize.small:
|
||||
self._draw_centered(alert.text1, rect, self.font_bold, ALERT_FONT_MEDIUM)
|
||||
self._alert_text1_label.set_text(alert.text1)
|
||||
self._alert_text1_label.set_text_color(text_color)
|
||||
self._alert_text1_label.set_font_size(SMALL_FONT_SIZE)
|
||||
self._alert_text1_label.set_alignment(rl.GuiTextAlignment.TEXT_ALIGN_CENTER)
|
||||
self._alert_text1_label.set_alignment_vertical(rl.GuiTextAlignmentVertical.TEXT_ALIGN_MIDDLE)
|
||||
self._alert_text1_label.render(text_rect)
|
||||
|
||||
elif alert.size == AlertSize.mid:
|
||||
self._draw_centered(alert.text1, rect, self.font_bold, ALERT_FONT_BIG, center_y=False)
|
||||
rect.y += ALERT_FONT_BIG + ALERT_LINE_SPACING
|
||||
self._draw_centered(alert.text2, rect, self.font_regular, ALERT_FONT_SMALL, center_y=False)
|
||||
self._alert_text1_label.set_text(alert.text1)
|
||||
self._alert_text1_label.set_text_color(text_color)
|
||||
self._alert_text1_label.set_font_size(MID_FONT_SIZE_1)
|
||||
self._alert_text1_label.set_alignment(rl.GuiTextAlignment.TEXT_ALIGN_CENTER)
|
||||
self._alert_text1_label.set_alignment_vertical(rl.GuiTextAlignmentVertical.TEXT_ALIGN_MIDDLE)
|
||||
self._alert_text1_label.render(text_rect)
|
||||
|
||||
if alert.text2:
|
||||
content_h = self._alert_text1_label.get_content_height(int(text_rect.width))
|
||||
center_y = self._alert_y_filter.x + (self._rect.height - content_h) / 2
|
||||
text2_rect = rl.Rectangle(
|
||||
text_rect.x,
|
||||
center_y + content_h,
|
||||
text_rect.width,
|
||||
self._rect.height - (center_y + content_h - self._alert_y_filter.x),
|
||||
)
|
||||
self._alert_text2_label.set_text(alert.text2)
|
||||
self._alert_text2_label.set_text_color(text2_color)
|
||||
self._alert_text2_label.set_font_size(MID_FONT_SIZE_2)
|
||||
self._alert_text2_label.set_alignment(rl.GuiTextAlignment.TEXT_ALIGN_CENTER)
|
||||
self._alert_text2_label.set_alignment_vertical(rl.GuiTextAlignmentVertical.TEXT_ALIGN_TOP)
|
||||
self._alert_text2_label.render(text2_rect)
|
||||
|
||||
else:
|
||||
is_long = len(alert.text1) > 15
|
||||
font_size1 = 132 if is_long else 177
|
||||
if len(alert.text1) <= 12:
|
||||
font_size = 120
|
||||
elif len(alert.text1) <= 16:
|
||||
font_size = 110
|
||||
else:
|
||||
font_size = 100
|
||||
|
||||
top_offset = 200 if is_long or '\n' in alert.text1 else 270
|
||||
title_rect = rl.Rectangle(rect.x, rect.y + top_offset, rect.width, 600)
|
||||
self._full_text1_label.set_font_size(font_size1)
|
||||
self._full_text1_label.set_text(alert.text1)
|
||||
self._full_text1_label.render(title_rect)
|
||||
self._alert_text1_label.set_text(alert.text1)
|
||||
self._alert_text1_label.set_text_color(text_color)
|
||||
self._alert_text1_label.set_font_size(font_size)
|
||||
self._alert_text1_label.set_alignment(rl.GuiTextAlignment.TEXT_ALIGN_CENTER)
|
||||
self._alert_text1_label.set_alignment_vertical(rl.GuiTextAlignmentVertical.TEXT_ALIGN_MIDDLE)
|
||||
self._alert_text1_label.render(text_rect)
|
||||
|
||||
bottom_offset = 361 if is_long else 420
|
||||
subtitle_rect = rl.Rectangle(rect.x, rect.y + rect.height - bottom_offset, rect.width, 300)
|
||||
self._full_text2_label.set_text(alert.text2)
|
||||
self._full_text2_label.render(subtitle_rect)
|
||||
|
||||
def _draw_centered(self, text, rect, font, font_size, center_y=True, color=rl.WHITE) -> None:
|
||||
text_size = measure_text_cached(font, text, font_size)
|
||||
x = rect.x + (rect.width - text_size.x) / 2
|
||||
y = rect.y + ((rect.height - text_size.y) / 2 if center_y else 0)
|
||||
rl.draw_text_ex(font, text, rl.Vector2(x, y), font_size, 0, color)
|
||||
if alert.text2:
|
||||
if len(alert.text2) > 24:
|
||||
t2_size = 48
|
||||
elif len(alert.text2) > 18:
|
||||
t2_size = 52
|
||||
else:
|
||||
t2_size = 56
|
||||
content_h = self._alert_text1_label.get_content_height(int(text_rect.width))
|
||||
center_y = self._alert_y_filter.x + (self._rect.height - content_h) / 2
|
||||
text2_rect = rl.Rectangle(
|
||||
text_rect.x,
|
||||
center_y + content_h,
|
||||
text_rect.width,
|
||||
self._rect.height - (center_y + content_h - self._alert_y_filter.x),
|
||||
)
|
||||
self._alert_text2_label.set_text(alert.text2)
|
||||
self._alert_text2_label.set_text_color(text2_color)
|
||||
self._alert_text2_label.set_font_size(t2_size)
|
||||
self._alert_text2_label.set_alignment(rl.GuiTextAlignment.TEXT_ALIGN_CENTER)
|
||||
self._alert_text2_label.set_alignment_vertical(rl.GuiTextAlignmentVertical.TEXT_ALIGN_TOP)
|
||||
self._alert_text2_label.render(text2_rect)
|
||||
|
||||
@@ -189,7 +189,7 @@ class AugmentedRoadView(CameraView):
|
||||
super()._render(self._content_rect)
|
||||
|
||||
# Draw all UI overlays
|
||||
current_alert = self.alert_renderer.get_alert(ui_state.sm)
|
||||
alert_to_render, _not_animating_out = self.alert_renderer.will_render()
|
||||
self.model_renderer.render(self._content_rect)
|
||||
self._hud_renderer.render(self._content_rect)
|
||||
self.alert_renderer.render(self._content_rect)
|
||||
|
||||
@@ -289,10 +289,9 @@ class StarPilotOnroadView(AugmentedRoadView):
|
||||
|
||||
|
||||
def _render_bottom_row_widgets(self):
|
||||
# Hide if alerts are active
|
||||
from cereal import log
|
||||
AlertSize = log.SelfdriveState.AlertSize
|
||||
if ui_state.sm["selfdriveState"].alertSize != AlertSize.none:
|
||||
# Hide if any alert (stock or StarPilot) is active
|
||||
alert_showing, _ = self.alert_renderer.will_render()
|
||||
if alert_showing is not None:
|
||||
return
|
||||
|
||||
dm = self.driver_state_renderer
|
||||
@@ -377,9 +376,8 @@ class StarPilotOnroadView(AugmentedRoadView):
|
||||
render_weather_icon(weather_rect)
|
||||
|
||||
def _render_pedals(self):
|
||||
from cereal import log
|
||||
AlertSize = log.SelfdriveState.AlertSize
|
||||
if ui_state.sm["selfdriveState"].alertSize != AlertSize.none:
|
||||
alert_showing, _ = self.alert_renderer.will_render()
|
||||
if alert_showing is not None:
|
||||
return
|
||||
|
||||
dm = self.driver_state_renderer
|
||||
|
||||
Reference in New Issue
Block a user