f8e13417f2
date: 2026-08-15T14:20:10 master commit: d48cfa730bf21593b684efeaf3fd8940695e1dea
94 lines
3.5 KiB
Python
94 lines
3.5 KiB
Python
import time
|
|
import pyray as rl
|
|
|
|
from openpilot.system.ui.lib.application import gui_app, FontWeight
|
|
from openpilot.system.ui.widgets import Widget
|
|
from openpilot.system.ui.widgets.label import UnifiedLabel
|
|
from openpilot.selfdrive.ui.ui_state import ui_state
|
|
from openpilot.selfdrive.ui.body.animations import FaceAnimator, ASLEEP, INQUISITIVE, NORMAL, SLEEPY
|
|
|
|
GRID_COLS = 16
|
|
GRID_ROWS = 8
|
|
DOT_RADIUS = 50 if gui_app.big_ui() else 10
|
|
|
|
IDLE_TIMEOUT = 30.0 # seconds of no joystick input before playing INQUISITIVE
|
|
IDLE_STEER_THRESH = 0.5 # degrees — below this counts as no input
|
|
IDLE_SPEED_THRESH = 0.01 # m/s — below this counts as no input
|
|
|
|
|
|
# This class is used both in BIG (tizi) and small (mici) UIs
|
|
class BodyLayout(Widget):
|
|
def __init__(self):
|
|
super().__init__()
|
|
self._animator = FaceAnimator(ASLEEP)
|
|
self._turning_left = False
|
|
self._turning_right = False
|
|
self._last_input_time = time.monotonic()
|
|
self._was_active = False
|
|
self._offroad_label = UnifiedLabel("turn on ignition to use", 95 if gui_app.big_ui() else 45, FontWeight.DISPLAY,
|
|
alignment=rl.GuiTextAlignment.TEXT_ALIGN_CENTER,
|
|
alignment_vertical=rl.GuiTextAlignmentVertical.TEXT_ALIGN_MIDDLE)
|
|
|
|
def draw_dot_grid(self, rect: rl.Rectangle, dots: list[tuple[int, int]], color: rl.Color):
|
|
spacing = min(rect.height / GRID_ROWS, rect.width / GRID_COLS)
|
|
|
|
grid_w = (GRID_COLS - 1) * spacing
|
|
grid_h = (GRID_ROWS - 1) * spacing
|
|
|
|
offset_x = rect.x + (rect.width - grid_w) / 2
|
|
offset_y = rect.y + (rect.height - grid_h) / 2
|
|
|
|
for row, col in dots:
|
|
x = int(offset_x + col * spacing)
|
|
y = int(offset_y + row * spacing)
|
|
rl.draw_circle(x, y, DOT_RADIUS, color)
|
|
|
|
def _update_state(self):
|
|
super()._update_state()
|
|
|
|
sm = ui_state.sm
|
|
|
|
if ui_state.is_onroad():
|
|
if not self._was_active:
|
|
self._last_input_time = time.monotonic()
|
|
self._was_active = True
|
|
|
|
cs = sm['carState']
|
|
has_input = abs(cs.steeringAngleDeg) > IDLE_STEER_THRESH or abs(cs.vEgo) > IDLE_SPEED_THRESH
|
|
if has_input:
|
|
self._last_input_time = time.monotonic()
|
|
|
|
if time.monotonic() - self._last_input_time > IDLE_TIMEOUT:
|
|
self._animator.set_animation(INQUISITIVE)
|
|
else:
|
|
self._animator.set_animation(NORMAL)
|
|
else:
|
|
self._was_active = False
|
|
self._animator.set_animation(ASLEEP)
|
|
|
|
steer = sm['testJoystick'].axes[1] if len(sm['testJoystick'].axes) > 1 else 0
|
|
self._turning_left = steer >= 0.05
|
|
self._turning_right = steer <= -0.05
|
|
|
|
# play animation on screen tap
|
|
def _handle_mouse_release(self, mouse_pos):
|
|
super()._handle_mouse_release(mouse_pos)
|
|
if not self._was_active:
|
|
self._animator.set_animation(SLEEPY)
|
|
|
|
def _render(self, rect: rl.Rectangle):
|
|
dots = self._animator.get_dots()
|
|
animation = self._animator._animation
|
|
if self._turning_left and animation.left_turn_remove:
|
|
remove_set = set(animation.left_turn_remove)
|
|
dots = [d for d in dots if d not in remove_set]
|
|
elif self._turning_right and animation.right_turn_remove:
|
|
remove_set = set(animation.right_turn_remove)
|
|
dots = [d for d in dots if d not in remove_set]
|
|
self.draw_dot_grid(rect, dots, rl.WHITE)
|
|
|
|
if ui_state.is_offroad():
|
|
rl.draw_rectangle(int(self.rect.x), int(self.rect.y), int(self.rect.width), int(self.rect.height), rl.Color(0, 0, 0, 175))
|
|
upper_half = rl.Rectangle(rect.x, rect.y, rect.width, rect.height / 2)
|
|
self._offroad_label.render(upper_half)
|