import time
import pyray as rl
from cereal . messaging import SubMaster
from openpilot . selfdrive . ui . ui_state import ui_state
from openpilot . system . ui . lib . application import gui_app , Widget
from openpilot . common . params import Params
class ExpButton ( Widget ) :
def __init__ ( self , button_size : int , icon_size : int ) :
super ( ) . __init__ ( )
self . _params = Params ( )
self . _experimental_mode : bool = False
self . _engageable : bool = False
# State hold mechanism
self . _hold_duration = 2.0 # seconds
self . _held_mode : bool | None = None
self . _hold_end_time : float | None = None
self . _white_color : rl . Color = rl . Color ( 255 , 255 , 255 , 255 )
self . _black_bg : rl . Color = rl . Color ( 0 , 0 , 0 , 166 )
self . _txt_wheel : rl . Texture = gui_app . texture ( ' icons/chffr_wheel.png ' , icon_size , icon_size )
self . _txt_exp : rl . Texture = gui_app . texture ( ' icons/experimental.png ' , icon_size , icon_size )
self . _rect : rl . Rectangle = rl . Rectangle ( 0 , 0 , button_size , button_size )
def update_state ( self , sm : SubMaster ) - > None :
selfdrive_state = sm [ " selfdriveState " ]
self . _experimental_mode = selfdrive_state . experimentalMode
self . _engageable = selfdrive_state . engageable or selfdrive_state . enabled
def handle_mouse_event ( self ) - > bool :
if rl . check_collision_point_rec ( rl . get_mouse_position ( ) , self . _rect ) :
if ( rl . is_mouse_button_released ( rl . MouseButton . MOUSE_BUTTON_LEFT ) and
self . _is_toggle_allowed ( ) ) :
new_mode = not self . _experimental_mode
self . _params . put_bool ( " ExperimentalMode " , new_mode )
# Hold new state temporarily
self . _held_mode = new_mode
self . _hold_end_time = time . time ( ) + self . _hold_duration
return True
return False
def _render ( self , rect : rl . Rectangle ) - > None :
self . _rect . x , self . _rect . y = rect . x , rect . y
center_x = int ( self . _rect . x + self . _rect . width / / 2 )
center_y = int ( self . _rect . y + self . _rect . height / / 2 )
mouse_over = rl . check_collision_point_rec ( rl . get_mouse_position ( ) , self . _rect )
mouse_down = rl . is_mouse_button_down ( rl . MouseButton . MOUSE_BUTTON_LEFT ) and self . _is_pressed
self . _white_color . a = 180 if ( mouse_down and mouse_over ) or not self . _engageable else 255
texture = self . _txt_exp if self . _held_or_actual_mode ( ) else self . _txt_wheel
rl . draw_circle ( center_x , center_y , self . _rect . width / 2 , self . _black_bg )
rl . draw_texture ( texture , center_x - texture . width / / 2 , center_y - texture . height / / 2 , self . _white_color )
def _held_or_actual_mode ( self ) :
now = time . time ( )
if self . _hold_end_time and now < self . _hold_end_time :
return self . _held_mode
if self . _hold_end_time and now > = self . _hold_end_time :
self . _hold_end_time = self . _held_mode = None
return self . _experimental_mode
def _is_toggle_allowed ( self ) :
if not self . _params . get_bool ( " ExperimentalModeConfirmed " ) :
return False
car_params = ui_state . sm [ " carParams " ]
if car_params . alphaLongitudinalAvailable :
return self . _params . get_bool ( " AlphaLongitudinalEnabled " )
else :
return car_params . openpilotLongitudinalControl