mirror of
https://github.com/sunnypilot/sunnypilot.git
synced 2026-02-19 00:43:54 +08:00
system/ui: add text scrolling support to InputBox for long text (#35310)
* add text scrolling support to InputBox for long text * add 2 pixels buffer to the scissor region
This commit is contained in:
@@ -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
|
||||
font = gui_app.font()
|
||||
display_text = "*" * len(self._input_text) if self._password_mode else self._input_text
|
||||
|
||||
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))
|
||||
# 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)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user