From 99f0c55ef24c18bee3ab89ce4b2fd6ed80b0280e Mon Sep 17 00:00:00 2001 From: deanlee Date: Mon, 10 Mar 2025 17:20:58 +0800 Subject: [PATCH] improve keyboard & list --- system/ui/lib/wifi_manager.py | 2 +- system/ui/widgets/keyboard.py | 40 ++++++++++++++++++++++++++--------- system/ui/widgets/network.py | 35 ++++++++++++++---------------- 3 files changed, 47 insertions(+), 30 deletions(-) diff --git a/system/ui/lib/wifi_manager.py b/system/ui/lib/wifi_manager.py index b85812eb98..44ac5e23fc 100644 --- a/system/ui/lib/wifi_manager.py +++ b/system/ui/lib/wifi_manager.py @@ -225,7 +225,7 @@ class WifiManager: # print("property changed", interface, changed, invalidated) if 'LastScan' in changed: asyncio.create_task(self._get_available_networks()) - elif "ActiveAccessPoint" in changed: + elif interface == NM_WIRELESS_IFACE and "ActiveAccessPoint" in changed: self.active_ap_path = changed["ActiveAccessPoint"].value asyncio.create_task(self._get_available_networks()) diff --git a/system/ui/widgets/keyboard.py b/system/ui/widgets/keyboard.py index 4d1ad1b2cd..4d970b46dc 100644 --- a/system/ui/widgets/keyboard.py +++ b/system/ui/widgets/keyboard.py @@ -44,25 +44,30 @@ keyboard_layouts = { class Keyboard: def __init__(self, max_text_size: int = 255): self._layout = keyboard_layouts["lowercase"] - self._input_text = "" self._max_text_size = max_text_size + self._string_pointer = rl.ffi.new("char[]", max_text_size) + self._input_text: str = '' + self._clear() @property def text(self) -> str: - return self._input_text - - def clear(self): - self._input_text = "" + result = rl.ffi.string(self._string_pointer).decode('utf-8') + self._clear() + return result def render(self, rect, title, sub_title): gui_label(rl.Rectangle(rect.x, rect.y, rect.width, 95), title, 90) gui_label(rl.Rectangle(rect.x, rect.y + 95, rect.width, 60), sub_title, 55, rl.GRAY) if gui_button(rl.Rectangle(rect.x + rect.width - 300, rect.y, 300, 100), "Cancel"): - return -1 + self._clear() + return 0 # Text box for input - rl.gui_text_box(rl.Rectangle(rect.x, rect.y + 160, rect.width, 100), self._input_text, self._max_text_size, True) - + self._sync_string_pointer() + rl.gui_text_box( + rl.Rectangle(rect.x, rect.y + 160, rect.width, 100), self._string_pointer, self._max_text_size, True + ) + self._input_text = rl.ffi.string(self._string_pointer).decode('utf-8') 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 @@ -77,7 +82,11 @@ class Keyboard: if i > 0: start_x += h_space - new_width = (key_width * 3 + h_space * 2) if key == SPACE_KEY else (key_width * 2 + h_space if key == ENTER_KEY else key_width) + new_width = ( + (key_width * 3 + h_space * 2) + if key == SPACE_KEY + else (key_width * 2 + h_space if key == ENTER_KEY else key_width) + ) key_rect = rl.Rectangle(start_x, row_y_start + row * (key_height + v_space), new_width, key_height) start_x += new_width @@ -87,7 +96,7 @@ class Keyboard: else: self.handle_key_press(key) - return 0 + return -1 def handle_key_press(self, key): if key in (SHIFT_DOWN_KEY, ABC_KEY): @@ -102,3 +111,14 @@ class Keyboard: self._input_text = self._input_text[:-1] elif key != BACKSPACE_KEY and len(self._input_text) < self._max_text_size: self._input_text += key + + def _clear(self): + self._input_text = '' + self._string_pointer[0] = b'\0' + + def _sync_string_pointer(self): + """Sync the C-string pointer with the internal Python string.""" + encoded = self._input_text.encode("utf-8")[:self._max_text_size - 1] # Leave room for null terminator + buffer = rl.ffi.buffer(self._string_pointer) + buffer[:len(encoded)] = encoded + self._string_pointer[len(encoded)] = b'\0' # Null terminate diff --git a/system/ui/widgets/network.py b/system/ui/widgets/network.py index 1aab7dceb6..3ee2806930 100644 --- a/system/ui/widgets/network.py +++ b/system/ui/widgets/network.py @@ -11,7 +11,7 @@ from openpilot.system.ui.widgets.keyboard import Keyboard from openpilot.system.ui.widgets.confirm_dialog import confirm_dialog NM_DEVICE_STATE_NEED_AUTH = 60 - +ITEM_HEIGHT = 160 class ActionState(IntEnum): NONE = 0 @@ -28,7 +28,6 @@ class WifiManagerUI: self.wifi_manager = wifi_manager self.wifi_manager.need_auth_callback = self._need_auth self._selected_network = None - self.item_height = 160 self.btn_width = 200 self.current_action: ActionState = ActionState.NONE self.scroll_panel = GuiScrollPanel() @@ -57,51 +56,49 @@ class WifiManagerUI: if self.current_action == ActionState.NEED_AUTH: result = self.keyboard.render(rect, 'Enter password', f'for {self._selected_network.ssid}') - if result == 0: - return - elif result == 1: - self.current_action = ActionState.NONE + if result == 1: asyncio.create_task(self.connect_to_network(self.keyboard.text)) - else: + elif result == 0: self.current_action = ActionState.NONE return self._draw_network_list(rect) def _draw_network_list(self, rect: rl.Rectangle): - content_rect = rl.Rectangle(rect.x, rect.y, rect.width, len(self.wifi_manager.networks) * self.item_height) + content_rect = rl.Rectangle(rect.x, rect.y, rect.width, len(self.wifi_manager.networks) * ITEM_HEIGHT) offset = self.scroll_panel.handle_scroll(rect, content_rect) clicked = self.scroll_panel.is_click_valid() rl.begin_scissor_mode(int(rect.x), int(rect.y), int(rect.width), int(rect.height)) for i, network in enumerate(self.wifi_manager.networks): - y_offset = i * self.item_height + offset.y - item_rect = rl.Rectangle(rect.x, y_offset, rect.width, self.item_height) + y_offset = rect.y + i * ITEM_HEIGHT + offset.y + item_rect = rl.Rectangle(rect.x, y_offset, rect.width, ITEM_HEIGHT) + if not rl.check_collision_recs(item_rect, rect): + continue - if rl.check_collision_recs(item_rect, rect): - self._draw_network_item(item_rect, network, clicked) - if i < len(self.wifi_manager.networks) - 1: - line_y = int(item_rect.y + item_rect.height - 1) - rl.draw_line(int(item_rect.x), int(line_y), int(item_rect.x + item_rect.width), line_y, rl.LIGHTGRAY) + self._draw_network_item(item_rect, network, clicked) + if i < len(self.wifi_manager.networks) - 1: + line_y = int(item_rect.y + item_rect.height - 1) + rl.draw_line(int(item_rect.x), int(line_y), int(item_rect.x + item_rect.width), line_y, rl.LIGHTGRAY) rl.end_scissor_mode() def _draw_network_item(self, rect, network: NetworkInfo, clicked: bool): - label_rect = rl.Rectangle(rect.x, rect.y, rect.width - self.btn_width * 2, self.item_height) - state_rect = rl.Rectangle(rect.x + rect.width - self.btn_width * 2 - 150, rect.y, 300, self.item_height) + label_rect = rl.Rectangle(rect.x, rect.y, rect.width - self.btn_width * 2, ITEM_HEIGHT) + state_rect = rl.Rectangle(rect.x + rect.width - self.btn_width * 2 - 150, rect.y, 300, ITEM_HEIGHT) gui_label(label_rect, network.ssid, 55) if network.is_connected and self.current_action == ActionState.NONE: rl.gui_label(state_rect, "Connected") - elif self.current_action == "Connecting" and self._selected_network and self._selected_network.ssid == network.ssid: + elif self.current_action == ActionState.CONNECTING and self._selected_network and self._selected_network.ssid == network.ssid: rl.gui_label(state_rect, "CONNECTING...") # If the network is saved, show the "Forget" button if self.wifi_manager.is_saved(network.ssid): forget_btn_rect = rl.Rectangle( rect.x + rect.width - self.btn_width, - rect.y + (self.item_height - 80) / 2, + rect.y + (ITEM_HEIGHT - 80) / 2, self.btn_width, 80, )