Files
2026-07-20 11:06:57 -05:00

163 lines
6.1 KiB
Python

"""
Copyright © IQ.Lvbs, apart of Project Teal Lvbs, All Rights Reserved, licensed under https://konn3kt.com/tos
"""
import time
import numpy as np
import pyray as rl
from openpilot.common.params import Params
from openpilot.selfdrive.ui import UI_BORDER_SIZE
from openpilot.selfdrive.ui.onroad.driver_state import DriverStateRenderer, BTN_SIZE, ARC_LENGTH
from openpilot.iqpilot.ui.onroad.hud_overlays import DeveloperUiRenderer
from openpilot.system.ui.lib.application import gui_app, FontWeight
from openpilot.system.ui.lib.text_measure import measure_text_cached
# LongitudinalPersonality ordinals (matches cereal enum: relaxed=0, standard=1, aggressive=2)
_PERSONALITY_RELAXED = 0
_PERSONALITY_STANDARD = 1
_PERSONALITY_AGGRESSIVE = 2
PERSONALITY_COLORS = {
_PERSONALITY_RELAXED: rl.Color(0x17, 0xC9, 0x64, 0xFF), # green
_PERSONALITY_STANDARD: rl.Color(0x0C, 0x94, 0x96, 0xFF), # teal
_PERSONALITY_AGGRESSIVE: rl.Color(0xE8, 0x2C, 0x2C, 0xFF), # red
}
PERSONALITY_NAMES = {
_PERSONALITY_RELAXED: "Relaxed",
_PERSONALITY_STANDARD: "Standard",
_PERSONALITY_AGGRESSIVE: "Aggressive",
}
_TOAST_DURATION = 2.0 # seconds
_TOAST_FONT_SIZE = 52
_TOAST_PAD_X = 52
_TOAST_PAD_Y = 22
_TOAST_BOTTOM_MARGIN = UI_BORDER_SIZE + 36
_TOAST_RADIUS = 0.45
_TOAST_FADE = 0.25 # fade-in / fade-out window
class DriverStateRendererIQ(DriverStateRenderer):
def __init__(self):
super().__init__()
self._params = Params()
self._personality: int = _PERSONALITY_STANDARD
self._personality_color: rl.Color = PERSONALITY_COLORS[_PERSONALITY_STANDARD]
self._toast_end_time: float = 0.0
self._toast_text: str = ""
self._toast_color: rl.Color = rl.WHITE
self._font = gui_app.font(FontWeight.SEMI_BOLD)
self.dev_ui_offset = DeveloperUiRenderer.get_bottom_dev_ui_offset()
self._dm_background = gui_app.texture("icons_mici/onroad/driver_monitoring/dm_background.png", BTN_SIZE, BTN_SIZE)
self._dm_person = gui_app.texture("icons_mici/onroad/driver_monitoring/dm_person.png", 118, 118)
self._dm_cone = gui_app.texture("icons_mici/onroad/driver_monitoring/dm_cone.png", 118, 118)
def _update_state(self):
super()._update_state()
personality = self._params.get("LongitudinalPersonality", return_default=True)
if personality is not None:
self._personality = int(personality)
self._personality_color = PERSONALITY_COLORS.get(self._personality, PERSONALITY_COLORS[_PERSONALITY_STANDARD])
def cycle_personality(self):
next_p = (self._personality + 1) % 3
self._params.put_nonblocking("LongitudinalPersonality", next_p)
self._personality = next_p
self._personality_color = PERSONALITY_COLORS[next_p]
self._toast_text = PERSONALITY_NAMES[next_p]
self._toast_color = PERSONALITY_COLORS[next_p]
self._toast_end_time = time.monotonic() + _TOAST_DURATION
def _render(self, _):
fade = max(0.35, 1.0 - self.dm_fade_state)
alpha = int(255 * fade)
pc = self._personality_color
rl.draw_texture(
self._dm_background,
int(self.position_x - self._dm_background.width / 2),
int(self.position_y - self._dm_background.height / 2),
rl.Color(pc.r, pc.g, pc.b, alpha),
)
rl.draw_texture(
self._dm_person,
int(self.position_x - self._dm_person.width / 2),
int(self.position_y - self._dm_person.height / 2),
rl.Color(255, 255, 255, int(alpha * 0.9)),
)
if self.is_active:
dest_rect = rl.Rectangle(self.position_x, self.position_y, self._dm_cone.width, self._dm_cone.height)
rl.draw_texture_pro(
self._dm_cone,
rl.Rectangle(0, 0, self._dm_cone.width, self._dm_cone.height),
dest_rect,
rl.Vector2(dest_rect.width / 2, dest_rect.height / 2),
180.0,
rl.Color(pc.r, pc.g, pc.b, alpha),
)
else:
rl.draw_circle(int(self.position_x), int(self.position_y), 14, rl.Color(255, 255, 255, alpha))
self._draw_personality_toast()
def _draw_personality_toast(self):
now = time.monotonic()
remaining = self._toast_end_time - now
if remaining <= 0 or not self._toast_text:
return
elapsed = _TOAST_DURATION - remaining
fade_in = min(1.0, elapsed / _TOAST_FADE)
fade_out = min(1.0, remaining / _TOAST_FADE)
a = int(255 * fade_in * fade_out)
text_size = measure_text_cached(self._font, self._toast_text, _TOAST_FONT_SIZE)
toast_w = text_size.x + _TOAST_PAD_X * 2
toast_h = text_size.y + _TOAST_PAD_Y * 2
cx = self._rect.x + self._rect.width / 2
toast_x = cx - toast_w / 2
toast_y = self._rect.y + self._rect.height - _TOAST_BOTTOM_MARGIN - toast_h
tc = self._toast_color
toast_rect = rl.Rectangle(toast_x, toast_y, toast_w, toast_h)
rl.draw_rectangle_rounded(toast_rect, _TOAST_RADIUS, 10, rl.Color(tc.r, tc.g, tc.b, a))
rl.draw_text_ex(
self._font, self._toast_text,
rl.Vector2(toast_x + _TOAST_PAD_X, toast_y + _TOAST_PAD_Y),
_TOAST_FONT_SIZE, 0,
rl.Color(255, 255, 255, a),
)
def _pre_calculate_drawing_elements(self):
"""Pre-calculate all drawing elements based on the current rectangle"""
width, height = self._rect.width, self._rect.height
offset = UI_BORDER_SIZE + BTN_SIZE // 2
self.position_x = self._rect.x + (width - offset if self.is_rhd else offset)
self.position_y = self._rect.y + height - offset - self.dev_ui_offset
positioned_keypoints = self.face_keypoints_transformed + np.array([self.position_x, self.position_y])
for i in range(len(positioned_keypoints)):
self.face_lines[i].x = positioned_keypoints[i][0]
self.face_lines[i].y = positioned_keypoints[i][1]
delta_x = -self.driver_pose_sins[1] * ARC_LENGTH / 2.0
delta_y = -self.driver_pose_sins[0] * ARC_LENGTH / 2.0
h_width = abs(delta_x)
self.h_arc_data = self._calculate_arc_data(
delta_x, h_width, self.position_x, self.position_y - ARC_LENGTH / 2,
self.driver_pose_sins[1], self.driver_pose_diff[1], is_horizontal=True
)
v_height = abs(delta_y)
self.v_arc_data = self._calculate_arc_data(
delta_y, v_height, self.position_x - ARC_LENGTH / 2, self.position_y,
self.driver_pose_sins[0], self.driver_pose_diff[0], is_horizontal=False
)