@ -1,3 +1,5 @@
import time
from typing import Literal
import pyray as rl
from openpilot . system . ui . lib . application import gui_app , FontWeight
from openpilot . system . ui . lib . button import ButtonStyle , gui_button
@ -5,30 +7,32 @@ from openpilot.system.ui.lib.inputbox import InputBox
from openpilot . system . ui . lib . label import gui_label
KEY_FONT_SIZE = 96
DOUBLE_CLICK_THRESHOLD = 0.5 # seconds
# Constants for special keys
CONTENT_MARGIN = 50
BACKSPACE_KEY = " <- "
ENTER_KEY = " -> "
SPACE_KEY = " "
SHIFT_KEY = " ↑ "
SHIFT_DOWN_KEY = " ↓ "
SHIFT_INACTIVE_KEY = " SHIFT_OFF "
SHIFT_ACTIVE_KEY = " SHIFT_ON "
CAPS_LOCK_KEY = " CAPS "
NUMERIC_KEY = " 123 "
SYMBOL_KEY = " #+= "
ABC_KEY = " ABC "
# Define keyboard layouts as a dictionary for easier access
keyboard_layouts = {
KEYBOARD_LAYOUTS = {
" lowercase " : [
[ " q " , " w " , " e " , " r " , " t " , " y " , " u " , " i " , " o " , " p " ] ,
[ " a " , " s " , " d " , " f " , " g " , " h " , " j " , " k " , " l " ] ,
[ SHIFT_KEY , " z " , " x " , " c " , " v " , " b " , " n " , " m " , BACKSPACE_KEY ] ,
[ SHIFT_INACTIVE_ KEY , " z " , " x " , " c " , " v " , " b " , " n " , " m " , BACKSPACE_KEY ] ,
[ NUMERIC_KEY , " / " , " - " , SPACE_KEY , " . " , ENTER_KEY ] ,
] ,
" uppercase " : [
[ " Q " , " W " , " E " , " R " , " T " , " Y " , " U " , " I " , " O " , " P " ] ,
[ " A " , " S " , " D " , " F " , " G " , " H " , " J " , " K " , " L " ] ,
[ SHIFT_DOWN _KEY , " Z " , " X " , " C " , " V " , " B " , " N " , " M " , BACKSPACE_KEY ] ,
[ SHIFT_ACTIVE _KEY , " Z " , " X " , " C " , " V " , " B " , " N " , " M " , BACKSPACE_KEY ] ,
[ NUMERIC_KEY , " / " , " - " , SPACE_KEY , " . " , ENTER_KEY ] ,
] ,
" numbers " : [
@ -48,7 +52,10 @@ keyboard_layouts = {
class Keyboard :
def __init__ ( self , max_text_size : int = 255 , min_text_size : int = 0 , password_mode : bool = False , show_password_toggle : bool = False ) :
self . _layout = keyboard_layouts [ " lowercase " ]
self . _layout_name : Literal [ " lowercase " , " uppercase " , " numbers " , " specials " ] = " lowercase "
self . _caps_lock = False
self . _last_shift_press_time = 0
self . _max_text_size = max_text_size
self . _min_text_size = min_text_size
self . _input_box = InputBox ( max_text_size )
@ -59,8 +66,9 @@ class Keyboard:
self . _eye_closed_texture = gui_app . texture ( " icons/eye_closed.png " , 81 , 54 )
self . _key_icons = {
BACKSPACE_KEY : gui_app . texture ( " icons/backspace.png " , 80 , 80 ) ,
SHIFT_KEY : gui_app . texture ( " icons/shift.png " , 80 , 80 ) ,
SHIFT_DOWN_KEY : gui_app . texture ( " icons/arrow-down.png " , 80 , 80 ) ,
SHIFT_INACTIVE_KEY : gui_app . texture ( " icons/shift.png " , 80 , 80 ) ,
SHIFT_ACTIVE_KEY : gui_app . texture ( " icons/shift-fill.png " , 80 , 80 ) ,
CAPS_LOCK_KEY : gui_app . texture ( " icons/capslock-fill.png " , 80 , 80 ) ,
ENTER_KEY : gui_app . texture ( " icons/arrow-right.png " , 80 , 80 ) ,
}
@ -84,13 +92,15 @@ class Keyboard:
input_box_rect = rl . Rectangle ( rect . x + input_margin , rect . y + 160 , rect . width - input_margin , 100 )
self . _render_input_area ( input_box_rect )
layout = KEYBOARD_LAYOUTS [ self . _layout_name ]
h_space , v_space = 15 , 15
row_y_start = rect . y + 300 # Starting Y position for the first row
key_height = ( rect . height - 300 - 3 * v_space ) / 4
key_max_width = ( rect . width - ( len ( self . _ layout[ 2 ] ) - 1 ) * h_space ) / len ( self . _ layout[ 2 ] )
key_max_width = ( rect . width - ( len ( layout [ 2 ] ) - 1 ) * h_space ) / len ( layout [ 2 ] )
# Iterate over the rows of keys in the current layout
for row , keys in enumerate ( self . _ layout) :
for row , keys in enumerate ( layout ) :
key_width = min ( ( rect . width - ( 180 if row == 1 else 0 ) - h_space * ( len ( keys ) - 1 ) ) / len ( keys ) , key_max_width )
start_x = rect . x + ( 90 if row == 1 else 0 )
@ -105,6 +115,8 @@ class Keyboard:
is_enabled = key != ENTER_KEY or len ( self . _input_box . text ) > = self . _min_text_size
result = - 1
if key in self . _key_icons :
if key == SHIFT_ACTIVE_KEY and self . _caps_lock :
key = CAPS_LOCK_KEY
texture = self . _key_icons [ key ]
result = gui_button ( key_rect , " " , icon = texture , button_style = ButtonStyle . PRIMARY if key == ENTER_KEY else ButtonStyle . NORMAL , is_enabled = is_enabled )
else :
@ -148,18 +160,27 @@ class Keyboard:
)
def handle_key_press ( self , key ) :
if key in ( SHIFT_DOWN_KEY , ABC_KEY ) :
self . _layout = keyboard_layouts [ " lowercase " ]
elif key == SHIFT_KEY :
self . _layout = keyboard_layouts [ " uppercase " ]
if key in ( CAPS_LOCK_KEY , ABC_KEY ) :
self . _caps_lock = False
self . _layout_name = " lowercase "
elif key == SHIFT_INACTIVE_KEY :
self . _last_shift_press_time = time . monotonic ( )
self . _layout_name = " uppercase "
elif key == SHIFT_ACTIVE_KEY :
if time . monotonic ( ) - self . _last_shift_press_time < DOUBLE_CLICK_THRESHOLD :
self . _caps_lock = True
else :
self . _layout_name = " lowercase "
elif key == NUMERIC_KEY :
self . _layout = keyboard_layouts [ " numbers " ]
self . _layout_name = " numbers "
elif key == SYMBOL_KEY :
self . _layout = keyboard_layouts [ " specials " ]
self . _layout_name = " specials "
elif key == BACKSPACE_KEY :
self . _input_box . delete_char_before_cursor ( )
else :
self . _input_box . add_char_at_cursor ( key )
if not self . _caps_lock and self . _layout_name == " uppercase " :
self . _layout_name = " lowercase "
if __name__ == " __main__ " :