|
|
|
@ -1,9 +1,12 @@ |
|
|
|
|
from functools import partial |
|
|
|
|
import time |
|
|
|
|
from typing import Literal |
|
|
|
|
|
|
|
|
|
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.button import ButtonStyle, gui_button |
|
|
|
|
from openpilot.system.ui.widgets.button import ButtonStyle, Button |
|
|
|
|
from openpilot.system.ui.widgets.inputbox import InputBox |
|
|
|
|
from openpilot.system.ui.widgets.label import gui_label |
|
|
|
|
|
|
|
|
@ -73,6 +76,9 @@ class Keyboard(Widget): |
|
|
|
|
self._backspace_press_time: float = 0.0 |
|
|
|
|
self._backspace_last_repeat: float = 0.0 |
|
|
|
|
|
|
|
|
|
self._render_return_status = -1 |
|
|
|
|
self._cancel_button = Button("Cancel", self._cancel_button_callback) |
|
|
|
|
|
|
|
|
|
self._eye_open_texture = gui_app.texture("icons/eye_open.png", 81, 54) |
|
|
|
|
self._eye_closed_texture = gui_app.texture("icons/eye_closed.png", 81, 54) |
|
|
|
|
self._key_icons = { |
|
|
|
@ -83,6 +89,18 @@ class Keyboard(Widget): |
|
|
|
|
ENTER_KEY: gui_app.texture("icons/arrow-right.png", 80, 80), |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
self._all_keys = {} |
|
|
|
|
for l in KEYBOARD_LAYOUTS: |
|
|
|
|
for _, keys in enumerate(KEYBOARD_LAYOUTS[l]): |
|
|
|
|
for _, key in enumerate(keys): |
|
|
|
|
if key in self._key_icons: |
|
|
|
|
texture = self._key_icons[key] |
|
|
|
|
self._all_keys[key] = Button("", partial(self._key_callback, key), icon=texture, |
|
|
|
|
button_style=ButtonStyle.PRIMARY if key == ENTER_KEY else ButtonStyle.NORMAL) |
|
|
|
|
else: |
|
|
|
|
self._all_keys[key] = Button(key, partial(self._key_callback, key)) |
|
|
|
|
self._all_keys[CAPS_LOCK_KEY] = Button("", partial(self._key_callback, CAPS_LOCK_KEY), icon=self._key_icons[CAPS_LOCK_KEY]) |
|
|
|
|
|
|
|
|
|
@property |
|
|
|
|
def text(self): |
|
|
|
|
return self._input_box.text |
|
|
|
@ -97,13 +115,21 @@ class Keyboard(Widget): |
|
|
|
|
self._title = title |
|
|
|
|
self._sub_title = sub_title |
|
|
|
|
|
|
|
|
|
def _cancel_button_callback(self): |
|
|
|
|
self.clear() |
|
|
|
|
self._render_return_status = 0 |
|
|
|
|
|
|
|
|
|
def _key_callback(self, k): |
|
|
|
|
if k == ENTER_KEY: |
|
|
|
|
self._render_return_status = 1 |
|
|
|
|
else: |
|
|
|
|
self.handle_key_press(k) |
|
|
|
|
|
|
|
|
|
def _render(self, rect: rl.Rectangle): |
|
|
|
|
rect = rl.Rectangle(rect.x + CONTENT_MARGIN, rect.y + CONTENT_MARGIN, rect.width - 2 * CONTENT_MARGIN, rect.height - 2 * CONTENT_MARGIN) |
|
|
|
|
gui_label(rl.Rectangle(rect.x, rect.y, rect.width, 95), self._title, 90, font_weight=FontWeight.BOLD) |
|
|
|
|
gui_label(rl.Rectangle(rect.x, rect.y + 95, rect.width, 60), self._sub_title, 55, font_weight=FontWeight.NORMAL) |
|
|
|
|
if gui_button(rl.Rectangle(rect.x + rect.width - 386, rect.y, 386, 125), "Cancel"): |
|
|
|
|
self.clear() |
|
|
|
|
return 0 |
|
|
|
|
self._cancel_button.render(rl.Rectangle(rect.x + rect.width - 386, rect.y, 386, 125)) |
|
|
|
|
|
|
|
|
|
# Draw input box and password toggle |
|
|
|
|
input_margin = 25 |
|
|
|
@ -111,7 +137,7 @@ class Keyboard(Widget): |
|
|
|
|
self._render_input_area(input_box_rect) |
|
|
|
|
|
|
|
|
|
# Process backspace key repeat if it's held down |
|
|
|
|
if not rl.is_mouse_button_down(rl.MouseButton.MOUSE_BUTTON_LEFT): |
|
|
|
|
if not self._all_keys[BACKSPACE_KEY]._is_pressed: |
|
|
|
|
self._backspace_pressed = False |
|
|
|
|
|
|
|
|
|
if self._backspace_pressed: |
|
|
|
@ -146,33 +172,22 @@ class Keyboard(Widget): |
|
|
|
|
start_x += new_width |
|
|
|
|
|
|
|
|
|
is_enabled = key != ENTER_KEY or len(self._input_box.text) >= self._min_text_size |
|
|
|
|
result = -1 |
|
|
|
|
|
|
|
|
|
# Check for backspace key press-and-hold |
|
|
|
|
mouse_pos = rl.get_mouse_position() |
|
|
|
|
mouse_over_key = rl.check_collision_point_rec(mouse_pos, key_rect) |
|
|
|
|
|
|
|
|
|
if key == BACKSPACE_KEY and mouse_over_key: |
|
|
|
|
if rl.is_mouse_button_pressed(rl.MouseButton.MOUSE_BUTTON_LEFT): |
|
|
|
|
self._backspace_pressed = True |
|
|
|
|
self._backspace_press_time = time.monotonic() |
|
|
|
|
self._backspace_last_repeat = time.monotonic() |
|
|
|
|
if key == BACKSPACE_KEY and self._all_keys[BACKSPACE_KEY]._is_pressed and not self._backspace_pressed: |
|
|
|
|
self._backspace_pressed = True |
|
|
|
|
self._backspace_press_time = time.monotonic() |
|
|
|
|
self._backspace_last_repeat = time.monotonic() |
|
|
|
|
|
|
|
|
|
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) |
|
|
|
|
self._all_keys[key].enabled = is_enabled |
|
|
|
|
self._all_keys[key].render(key_rect) |
|
|
|
|
else: |
|
|
|
|
result = gui_button(key_rect, key, KEY_FONT_SIZE, is_enabled=is_enabled) |
|
|
|
|
|
|
|
|
|
if result: |
|
|
|
|
if key == ENTER_KEY: |
|
|
|
|
return 1 |
|
|
|
|
else: |
|
|
|
|
self.handle_key_press(key) |
|
|
|
|
self._all_keys[key].enabled = is_enabled |
|
|
|
|
self._all_keys[key].render(key_rect) |
|
|
|
|
|
|
|
|
|
return -1 |
|
|
|
|
return self._render_return_status |
|
|
|
|
|
|
|
|
|
def _render_input_area(self, input_rect: rl.Rectangle): |
|
|
|
|
if self._show_password_toggle: |
|
|
|
|