From 8cb14bccbb04e56666a1dc1db0f067b47151495e Mon Sep 17 00:00:00 2001 From: mar77i Date: Mon, 17 Feb 2025 16:27:13 +0100 Subject: [PATCH] right-aligned text inputs --- bookpaint/bookpaint_menu.py | 5 ++- ui/text_input.py | 88 +++++++++++++++++++++++++------------ 2 files changed, 65 insertions(+), 28 deletions(-) diff --git a/bookpaint/bookpaint_menu.py b/bookpaint/bookpaint_menu.py index 6bb376a..dd489a7 100644 --- a/bookpaint/bookpaint_menu.py +++ b/bookpaint/bookpaint_menu.py @@ -1,3 +1,5 @@ +from pathlib import Path + import pygame from layout import BarLayout, GridLayout @@ -76,7 +78,8 @@ class BookPaintMenu(Modal): self, rect.inflate((pad, pad)), self.set_book_path, - "book/", + str(Path("book/").absolute()), + unfocused_align=TextInput.RIGHT, ) ColorButton( self, diff --git a/ui/text_input.py b/ui/text_input.py index 3f30c35..51fa4a3 100644 --- a/ui/text_input.py +++ b/ui/text_input.py @@ -1,6 +1,6 @@ from contextlib import contextmanager from dataclasses import dataclass -from functools import partial +from functools import cached_property, partial from math import floor from time import time from typing import Optional, Callable @@ -65,15 +65,37 @@ def search_same_isspace_forward(value, pos): class TextInput(Focusable, Child): - def __init__(self, parent, rect, callback, value="", value_filter=None): + LEFT = 0 + RIGHT = 1 + PADDING = 16 + + def __init__( + self, + parent, + rect, + callback, + value="", + value_filter=None, + unfocused_align=LEFT, + ): super().__init__(parent) + assert rect.width > self.PADDING * 2 self.rect = rect self.callback = callback self.value = value self.value_filter = value_filter + assert unfocused_align in (self.LEFT, self.RIGHT) + self.unfocused_align = unfocused_align self.offset = 0 self.cursor = None + @cached_property + def padded_rect(self): + return pygame.Rect( + (self.rect.left + self.PADDING, self.rect.top), + (self.rect.width - self.PADDING * 2, self.rect.height), + ) + @staticmethod def maybe_scroll_font_surface(font, value_to_cursor, fs, width, height): x = font.size(value_to_cursor)[0] @@ -94,27 +116,38 @@ class TextInput(Focusable, Child): ) return fs, offset, x - offset - def get_font_surface(self): + def get_focused_font_surface(self): if self.cursor.pos > len(self.value): self.cursor.pos = len(self.value) - fs = self.font.render(self.value, True, "gray") - rect = self.rect - if self.value: - fs, self.offset, x = self.maybe_scroll_font_surface( - self.font, - self.value[:self.cursor.pos], - fs, - rect.width - 24, - rect.height, - ) - if x == fs.get_width(): - x -= 1 - else: - x = 0 + if not self.value: fs = pygame.Surface((1, self.font.size("W")[1]), pygame.SRCALPHA, 32) + pygame.draw.line(fs, "orange", (0, 0), (0, fs.get_height())) + return fs + fs = self.font.render(self.value, True, "gray") + fs, self.offset, x = self.maybe_scroll_font_surface( + self.font, + self.value[:self.cursor.pos], + fs, + *self.padded_rect.size, + ) + if x == fs.get_width(): + x -= 1 pygame.draw.line(fs, "orange", (x, 0), (x, fs.get_height())) return fs + def get_unfocused_font_surface(self): + fs = self.font.render(self.value, True, "gray") + if self.unfocused_align == self.LEFT: + self.offset = 0 + return fs + fs_size = fs.get_size() + if fs_size[0] <= self.padded_rect.width: + return fs + self.offset = fs_size[0] - self.padded_rect.width + return fs.subsurface( + pygame.Rect((self.offset, 0), (self.padded_rect.width, fs_size[1])) + ) + def update(self): if getattr(self.cursor, "key_callback", None) is None: return @@ -124,20 +157,21 @@ class TextInput(Focusable, Child): def draw(self): pygame.draw.rect(self.surf, "black", self.rect) if self.focused: - fs = self.get_font_surface() + fs = self.get_focused_font_surface() else: - fs = self.font.render(self.value, True, "gray") + fs = self.get_unfocused_font_surface() self.surf.subsurface(self.rect).blit( - fs, (16, (self.rect.height - fs.get_height()) // 2) + fs, (self.PADDING, (self.rect.height - fs.get_height()) // 2) ) pygame.draw.rect(self.surf, "gray", self.rect, 1) def pos_from_offset(self, x_offset): value = self.value + len_value = len(value) if x_offset is None: - return len(value) + return len_value a, a_x = 0, 0 - b, b_x = len(value), self.font.size(value)[0] + b, b_x = len_value, self.font.size(value)[0] if x_offset <= a_x: return a elif x_offset >= b_x: @@ -147,11 +181,11 @@ class TextInput(Focusable, Child): c_x = self.font.size(value[:c])[0] if c_x < x_offset: a, a_x = c, c_x - else: + elif c_x > x_offset: b, b_x = c, c_x - if abs(a_x - x_offset) < abs(b_x - x_offset): - return a - return b + else: + return c + return a if abs(a_x - x_offset) < abs(b_x - x_offset) else b def handle_mousebuttondown(self, ev): if ev.button != 1: @@ -160,7 +194,7 @@ class TextInput(Focusable, Child): self.activate() with self.check_dirty(): self.cursor.pos = self.pos_from_offset( - ev.pos[0] - self.rect.left - 16 + self.offset + ev.pos[0] - self.padded_rect.left + self.offset ) elif self.focused: self.deactivate(True) -- 2.51.0