From af0a77a26c41a8a661024e41850987d01326a679 Mon Sep 17 00:00:00 2001 From: mar77i Date: Mon, 27 Jan 2025 18:09:08 +0100 Subject: [PATCH] allow mouse to place cursor --- ui/ui.py | 57 ++++++++++++++++++++++++++++++++++++++++---------------- 1 file changed, 41 insertions(+), 16 deletions(-) diff --git a/ui/ui.py b/ui/ui.py index d5b7f1d..1f76a46 100644 --- a/ui/ui.py +++ b/ui/ui.py @@ -8,7 +8,6 @@ import pygame # todo: -# - [ ] textinput place cursor next to the mouse # - [ ] spinner @@ -365,14 +364,33 @@ class Cursor(UIChild, EventMethodDispatcher): DELAY_MS = 500 REPEAT_MS = 100 - def __init__(self, text_input): + def __init__(self, text_input, x_offset): super().__init__(text_input.parent) self.text_input = text_input self.old_value = text_input.value self.key_callback = None self.key = None self.repeat_ts = None - self.pos = len(text_input.value) + self.pos = self.pos_from_offset(x_offset) + + def pos_from_offset(self, x_offset): + value = self.text_input.value + a, a_x = 0, 0 + b, b_x = len(value), self.font.size(value)[0] + if x_offset <= a_x: + return a + elif x_offset >= b_x: + return b + while b - a > 1: + c = a + (b - a) // 2 + c_x = self.font.size(value[:c])[0] + if c_x < x_offset: + a, a_x = c, c_x + else: + b, b_x = c, c_x + if abs(a_x - x_offset) < abs(b_x - x_offset): + return a + return b @contextmanager def check_dirty(self): @@ -522,25 +540,26 @@ class TextInput(UIChild): self.callback = callback self.value = value self.value_filter = value_filter + self.offset = 0 @staticmethod def maybe_scroll_font_surface(font, value_to_cursor, fs, width, height): x = font.size(value_to_cursor)[0] fs_size = fs.get_size() if fs_size[0] < width: - return fs, fs_size, x + return fs, 0, x offset = 0 offset_centered = max(x - width // 2, 0) offset_right = max(fs_size[0] - width, 0) if offset_centered < offset_right: offset = offset_centered width = min(width, fs_size[0] - offset) - elif offset_right: + elif offset_right > 0: offset = offset_right if offset == 0: - return fs, fs_size, x + return fs, 0, x fs = fs.subsurface(pygame.Rect((offset, 0), (width, min(height, fs_size[1])))) - return (fs, (width, height), x - offset) + return fs, offset, x - offset def get_font_surface(self): fs = self.font.render(self.value, True, "gray") @@ -550,13 +569,14 @@ class TextInput(UIChild): if cursor.pos > len(self.value): cursor.pos = len(self.value) if self.value: - fs, fs_size, x = self.maybe_scroll_font_surface( + fs, self.offset, x = self.maybe_scroll_font_surface( self.font, self.value[:cursor.pos], fs, self.rect.width - 24, self.rect.height, ) + fs_size = fs.get_size() else: x = 1 fs_size = (x, self.font.size("W")[1]) @@ -574,14 +594,19 @@ class TextInput(UIChild): ) pygame.draw.rect(self.surf, "gray", self.rect, 1) - def focus(self): + def focus(self, x_offset): cursor = self.cursor + x_offset = x_offset - self.rect.left - 16 + self.offset if cursor is not None: if cursor.text_input is self: + new_pos = cursor.pos_from_offset(x_offset) + if new_pos != cursor.pos: + cursor.pos = new_pos + self.dirty = True return cursor.text_input.blur(True) self.dirty = True - self.cursor = Cursor(self) + self.cursor = Cursor(self, x_offset) def blur(self, restore=False): if self.cursor is not None: @@ -596,7 +621,7 @@ class TextInput(UIChild): def handle_mousebuttondown(self, ev): if ev.button == 1: if self.rect.collidepoint(ev.pos): - self.focus() + self.focus(ev.pos[0]) elif self.cursor is not None: self.blur(True) @@ -724,11 +749,11 @@ class DropDown(Button): self.dropdown_callback = callback class DropDownMenu(Modal): - def __init__(self, sibling): - parent = sibling.parent + def __init__(self, drop_down): + parent = drop_down.parent super().__init__(parent) - self.callback = sibling.dropdown_callback - rect = sibling.rect + self.callback = drop_down.dropdown_callback + rect = drop_down.rect self.buttons = [ Button( parent, @@ -738,7 +763,7 @@ class DropDown(Button): entry, partial(self.choose, i), ) - for i, entry in enumerate(sibling.entries) + for i, entry in enumerate(drop_down.entries) ] parent.children.extend(self.buttons) -- 2.51.0