ui: AlertFadeAnimator for longitudinal-related statuses (#1748)

This commit is contained in:
Jason Wen
2026-03-01 13:50:44 -05:00
committed by GitHub
parent daaec59464
commit 9c5bf2ce0a
3 changed files with 53 additions and 43 deletions

View File

@@ -10,6 +10,7 @@ from openpilot.selfdrive.ui.onroad.hud_renderer import COLORS
from openpilot.selfdrive.ui.ui_state import ui_state
from openpilot.system.ui.lib.application import gui_app, FontWeight
from openpilot.system.ui.lib.text_measure import measure_text_cached
from openpilot.system.ui.sunnypilot.lib.utils import AlertFadeAnimator
from openpilot.system.ui.widgets import Widget
@@ -18,12 +19,13 @@ class SmartCruiseControlRenderer(Widget):
super().__init__()
self.vision_enabled = False
self.vision_active = False
self.vision_frame = 0
self.map_enabled = False
self.map_active = False
self.map_frame = 0
self.long_override = False
self._vision_fade = AlertFadeAnimator(gui_app.target_fps)
self._map_fade = AlertFadeAnimator(gui_app.target_fps)
self.font = gui_app.font(FontWeight.BOLD)
def update(self):
@@ -41,21 +43,10 @@ class SmartCruiseControlRenderer(Widget):
if sm.updated["carControl"]:
self.long_override = sm["carControl"].cruiseControl.override
if self.vision_active:
self.vision_frame += 1
else:
self.vision_frame = 0
self._vision_fade.update(self.vision_active)
self._map_fade.update(self.map_active)
if self.map_active:
self.map_frame += 1
else:
self.map_frame = 0
@staticmethod
def _pulse_element(frame):
return not (frame % gui_app.target_fps < (gui_app.target_fps / 2.5))
def _draw_icon(self, rect_center_x, rect_height, x_offset, y_offset, name):
def _draw_icon(self, rect_center_x, rect_height, x_offset, y_offset, name, alpha=1.0):
text = name
font_size = 36
padding_v = 5
@@ -65,9 +56,12 @@ class SmartCruiseControlRenderer(Widget):
box_height = int(sz.y + padding_v * 2)
if self.long_override:
box_color = COLORS.OVERRIDE
color = COLORS.OVERRIDE
box_color = rl.Color(color.r, color.g, color.b, int(alpha * 255))
else:
box_color = rl.Color(0, 255, 0, 255)
box_color = rl.Color(0, 255, 0, int(alpha * 255))
text_color = rl.Color(0, 0, 0, int(alpha * 255))
screen_y = rect_height / 4 + y_offset
@@ -75,13 +69,14 @@ class SmartCruiseControlRenderer(Widget):
box_y = screen_y - box_height / 2
# Draw rounded background box
rl.draw_rectangle_rounded(rl.Rectangle(box_x, box_y, box_width, box_height), 0.2, 10, box_color)
if alpha > 0.01:
rl.draw_rectangle_rounded(rl.Rectangle(box_x, box_y, box_width, box_height), 0.2, 10, box_color)
# Draw text centered in the box (black color for contrast against bright green/grey)
text_pos_x = box_x + (box_width - sz.x) / 2
text_pos_y = box_y + (box_height - sz.y) / 2
# Draw text centered in the box (black color for contrast against bright green/grey)
text_pos_x = box_x + (box_width - sz.x) / 2
text_pos_y = box_y + (box_height - sz.y) / 2
rl.draw_text_ex(self.font, text, rl.Vector2(text_pos_x, text_pos_y), font_size, 0, rl.BLACK)
rl.draw_text_ex(self.font, text, rl.Vector2(text_pos_x, text_pos_y), font_size, 0, text_color)
def _render(self, rect: rl.Rectangle):
x_offset = -260
@@ -101,10 +96,10 @@ class SmartCruiseControlRenderer(Widget):
y_scc_m = orders[idx]
idx += 1
scc_vision_pulse = self._pulse_element(self.vision_frame)
if (self.vision_enabled and not self.vision_active) or (self.vision_active and scc_vision_pulse):
self._draw_icon(rect.x + rect.width / 2, rect.height, x_offset, y_scc_v, "SCC-V")
if self.vision_enabled:
alpha = self._vision_fade.alpha if self.vision_active else 1.0
self._draw_icon(rect.x + rect.width / 2, rect.height, x_offset, y_scc_v, "SCC-V", alpha)
scc_map_pulse = self._pulse_element(self.map_frame)
if (self.map_enabled and not self.map_active) or (self.map_active and scc_map_pulse):
self._draw_icon(rect.x + rect.width / 2, rect.height, x_offset, y_scc_m, "SCC-M")
if self.map_enabled:
alpha = self._map_fade.alpha if self.map_active else 1.0
self._draw_icon(rect.x + rect.width / 2, rect.height, x_offset, y_scc_m, "SCC-M", alpha)

View File

@@ -11,7 +11,6 @@ import pyray as rl
from cereal import custom
from openpilot.common.constants import CV
from openpilot.common.filter_simple import FirstOrderFilter
from openpilot.selfdrive.ui.onroad.hud_renderer import UI_CONFIG
from openpilot.selfdrive.ui.ui_state import ui_state
from openpilot.sunnypilot.selfdrive.controls.lib.speed_limit.common import Mode as SpeedLimitMode
@@ -19,6 +18,7 @@ from openpilot.system.hardware import HARDWARE
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.sunnypilot.lib.utils import AlertFadeAnimator
from openpilot.system.ui.widgets import Widget
METER_TO_FOOT = 3.28084
@@ -58,23 +58,14 @@ class SpeedLimitAlertRenderer:
self.arrow_blank = rl.load_texture_from_image(blank_image)
rl.unload_image(blank_image)
self._pre_active_alpha_filter = FirstOrderFilter(1.0, 0.05, 1 / gui_app.target_fps)
self._pre_active_alert_frame = 0
self._pre_active_fade = AlertFadeAnimator(gui_app.target_fps, duration_on=0.75, rc=0.05)
def update(self):
assist_state = ui_state.sm['longitudinalPlanSP'].speedLimit.assist.state
if assist_state == AssistState.preActive:
self._pre_active_alert_frame += 1
if (self._pre_active_alert_frame % gui_app.target_fps) < (gui_app.target_fps * 0.75):
self._pre_active_alpha_filter.x = 1.0
else:
self._pre_active_alpha_filter.update(0.0)
else:
self._pre_active_alert_frame = 0
self._pre_active_alpha_filter.update(1.0)
self._pre_active_fade.update(assist_state == AssistState.preActive)
def speed_limit_pre_active_icon_helper(self):
icon_alpha = max(0.0, min(self._pre_active_alpha_filter.x * 255.0, 255.0))
icon_alpha = max(0.0, min(self._pre_active_fade.alpha * 255.0, 255.0))
txt_icon = self.arrow_blank
icon_margin_x = 10
icon_margin_y = 18
@@ -197,7 +188,7 @@ class SpeedLimitRenderer(Widget, SpeedLimitAlertRenderer):
sign_rect = rl.Rectangle(x, y, width, UI_CONFIG.set_speed_height + 6 * 2)
alpha = self._pre_active_alpha_filter.x
alpha = self._pre_active_fade.alpha
if ui_state.speed_limit_mode != SpeedLimitMode.off:
self._draw_sign_main(sign_rect, alpha)

View File

@@ -10,3 +10,27 @@ from openpilot.system.ui.sunnypilot.widgets.list_view import ButtonActionSP
class NoElideButtonAction(ButtonActionSP):
def get_width_hint(self):
return super().get_width_hint() + 1
class AlertFadeAnimator:
def __init__(self, target_fps: int, duration_on: float = 0.75, rc: float = 0.05):
from openpilot.common.filter_simple import FirstOrderFilter
self._filter = FirstOrderFilter(1.0, rc, 1 / target_fps)
self._frame = 0
self._target_fps = target_fps
self._duration_on = duration_on
def update(self, active: bool):
if active:
self._frame += 1
if (self._frame % self._target_fps) < (self._target_fps * self._duration_on):
self._filter.x = 1.0
else:
self._filter.update(0.0)
else:
self._frame = 0
self._filter.update(1.0)
@property
def alpha(self) -> float:
return self._filter.x