ui: AlertFadeAnimator for longitudinal-related statuses (#1748)
This commit is contained in:
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user