diff --git a/system/ui/lib/inputbox.py b/system/ui/lib/inputbox.py index 78ee03bdbd..b164fa4f2a 100644 --- a/system/ui/lib/inputbox.py +++ b/system/ui/lib/inputbox.py @@ -14,6 +14,8 @@ class InputBox: self._key_press_time = 0 self._repeat_delay = 30 self._repeat_rate = 4 + self._text_offset = 0 + self._visible_width = 0 @property def text(self): @@ -23,6 +25,7 @@ class InputBox: def text(self, value): self._input_text = value[: self._max_text_size] self._cursor_position = len(self._input_text) + self._update_text_offset() def set_password_mode(self, password_mode): self._password_mode = password_mode @@ -30,6 +33,7 @@ class InputBox: def clear(self): self._input_text = '' self._cursor_position = 0 + self._text_offset = 0 def set_cursor_position(self, position): """Set the cursor position and reset the blink counter.""" @@ -37,6 +41,29 @@ class InputBox: self._cursor_position = position self._blink_counter = 0 self._show_cursor = True + self._update_text_offset() + + def _update_text_offset(self): + """Ensure the cursor is visible by adjusting text offset.""" + if self._visible_width == 0: + return + + font = gui_app.font() + display_text = "*" * len(self._input_text) if self._password_mode else self._input_text + padding = 10 + + if self._cursor_position > 0: + cursor_x = rl.measure_text_ex(font, display_text[: self._cursor_position], self._font_size, 0).x + else: + cursor_x = 0 + + visible_width = self._visible_width - (padding * 2) + + # Adjust offset if cursor would be outside visible area + if cursor_x < self._text_offset: + self._text_offset = max(0, cursor_x - padding) + elif cursor_x > self._text_offset + visible_width: + self._text_offset = cursor_x - visible_width + padding def add_char_at_cursor(self, char): """Add a character at the current cursor position.""" @@ -63,6 +90,10 @@ class InputBox: return False def render(self, rect, color=rl.BLACK, border_color=rl.DARKGRAY, text_color=rl.WHITE, font_size=80): + # Store dimensions for text offset calculations + self._visible_width = rect.width + self._font_size = font_size + # Handle mouse input self._handle_mouse_input(rect, font_size) @@ -82,10 +113,14 @@ class InputBox: font = gui_app.font() display_text = "*" * len(self._input_text) if self._password_mode else self._input_text padding = 10 + + # Clip text within input box bounds + buffer = 2 + rl.begin_scissor_mode(int(rect.x + padding - buffer), int(rect.y), int(rect.width - padding * 2 + buffer * 2), int(rect.height)) rl.draw_text_ex( font, display_text, - rl.Vector2(int(rect.x + padding), int(rect.y + rect.height / 2 - font_size / 2)), + rl.Vector2(int(rect.x + padding - self._text_offset), int(rect.y + rect.height / 2 - font_size / 2)), font_size, 0, text_color, @@ -97,9 +132,14 @@ class InputBox: if len(display_text) > 0 and self._cursor_position > 0: cursor_x += rl.measure_text_ex(font, display_text[: self._cursor_position], font_size, 0).x + # Apply text offset to cursor position + cursor_x -= self._text_offset + cursor_height = font_size + 4 cursor_y = rect.y + rect.height / 2 - cursor_height / 2 - rl.draw_line(int(cursor_x), int(cursor_y), int(cursor_x), int(cursor_y + cursor_height), rl.LIGHTGRAY) + rl.draw_line(int(cursor_x), int(cursor_y), int(cursor_x), int(cursor_y + cursor_height), rl.WHITE) + + rl.end_scissor_mode() def _handle_mouse_input(self, rect, font_size): """Handle mouse clicks to position cursor.""" @@ -107,14 +147,22 @@ class InputBox: if rl.is_mouse_button_pressed(rl.MOUSE_LEFT_BUTTON) and rl.check_collision_point_rec(mouse_pos, rect): # Calculate cursor position from click if len(self._input_text) > 0: - text_width = rl.measure_text_ex(gui_app.font(), self._input_text, font_size, 0).x - text_pos_x = rect.x + 10 - - if mouse_pos.x - text_pos_x > text_width: - self.set_cursor_position(len(self._input_text)) - else: - click_ratio = (mouse_pos.x - text_pos_x) / text_width - self.set_cursor_position(int(len(self._input_text) * click_ratio)) + font = gui_app.font() + display_text = "*" * len(self._input_text) if self._password_mode else self._input_text + + # Find the closest character position to the click + relative_x = mouse_pos.x - (rect.x + 10) + self._text_offset + best_pos = 0 + min_distance = float('inf') + + for i in range(len(self._input_text) + 1): + char_width = rl.measure_text_ex(font, display_text[:i], font_size, 0).x + distance = abs(relative_x - char_width) + if distance < min_distance: + min_distance = distance + best_pos = i + + self.set_cursor_position(best_pos) else: self.set_cursor_position(0)