From 0c013f61863230e22e8491cfd8945c600229e691 Mon Sep 17 00:00:00 2001 From: Cameron Clough Date: Mon, 19 May 2025 11:28:04 +0100 Subject: [PATCH] system/ui: add caps lock to keyboard (#35277) * add new icons shift-fill and capslock-fill, rm capslock * SHIFT_KEY_ON, SHIFT_KEY_OFF * capslock * rm arrow-down * a lot simpler * only one * just use time * layout name * rename shift * CONSTANT --- selfdrive/assets/icons/arrow-down.png | 3 -- selfdrive/assets/icons/capslock-fill.png | 3 ++ selfdrive/assets/icons/capslock.png | 3 -- selfdrive/assets/icons/shift-fill.png | 3 ++ selfdrive/assets/prep-svg.sh | 4 +- system/ui/widgets/keyboard.py | 53 +++++++++++++++++------- 6 files changed, 45 insertions(+), 24 deletions(-) delete mode 100644 selfdrive/assets/icons/arrow-down.png create mode 100644 selfdrive/assets/icons/capslock-fill.png delete mode 100644 selfdrive/assets/icons/capslock.png create mode 100644 selfdrive/assets/icons/shift-fill.png diff --git a/selfdrive/assets/icons/arrow-down.png b/selfdrive/assets/icons/arrow-down.png deleted file mode 100644 index 834c1cb8b9..0000000000 --- a/selfdrive/assets/icons/arrow-down.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:7e12a3cf36fdef107d237457e20b44e16320414f9de8a1791aff1ec5fd85ccef -size 2390 diff --git a/selfdrive/assets/icons/capslock-fill.png b/selfdrive/assets/icons/capslock-fill.png new file mode 100644 index 0000000000..66854e78f2 --- /dev/null +++ b/selfdrive/assets/icons/capslock-fill.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:6872a1047f1a534a037be7b1367640fe1bfb205a6e1c50420a2d1a946cda78ed +size 4397 diff --git a/selfdrive/assets/icons/capslock.png b/selfdrive/assets/icons/capslock.png deleted file mode 100644 index 61414d7230..0000000000 --- a/selfdrive/assets/icons/capslock.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:c6621d000d9514df1d636e9953941eadec415ff950724f26a61e0fcbaa7a3818 -size 5403 diff --git a/selfdrive/assets/icons/shift-fill.png b/selfdrive/assets/icons/shift-fill.png new file mode 100644 index 0000000000..1ce02d5822 --- /dev/null +++ b/selfdrive/assets/icons/shift-fill.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:e625ca991746abaaac375b095aa9a586601982232f4aae0fc2b17b2a524e9ce9 +size 3946 diff --git a/selfdrive/assets/prep-svg.sh b/selfdrive/assets/prep-svg.sh index d29ce3025d..2332cd25c5 100755 --- a/selfdrive/assets/prep-svg.sh +++ b/selfdrive/assets/prep-svg.sh @@ -6,11 +6,11 @@ ICONS_DIR="$DIR/icons" BOOTSTRAP_SVG="$DIR/../../third_party/bootstrap/bootstrap-icons.svg" ICON_IDS=( - arrow-down arrow-right backspace - capslock + capslock-fill shift + shift-fill ) ICON_FILL_COLOR="#fff" diff --git a/system/ui/widgets/keyboard.py b/system/ui/widgets/keyboard.py index bceffad943..5eebb49c83 100644 --- a/system/ui/widgets/keyboard.py +++ b/system/ui/widgets/keyboard.py @@ -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__":