import pygame from math import floor from ui.auxiliary_decorator import _return_itself from ui.button import Button from ui.button import get_fixed_rgb from ui.button import _transform_rgb from ui.text_box import _return_value_or_default class InputBox(Button): backspace_tick = 0 backspace_deleted_streak = 0 backspace_breakpoint = 6 deleting_speeds = [12, 4] was_enter_hit = False # constructor that can be used to set parameters of InputBox instance def __init__( self, position, dimensions, text="", input_box_position=None, input_box_dimensions=None, user_input="", valid_input_characters="abcdefghijklmnoprstuwxyz1234567890", box_color=(195, 195, 195), input_box_color=(225, 245, 245), writing_highlight_color=(150, 255, 255), inner_box_color=(205, 205, 205), bottom_strip_color=(180, 180, 180), font=pygame.font.get_default_font(), font_color=(50, 50, 50), font_size=None, is_visible=True, is_active=True, is_selected=False, input_centered=False, fit_text=True, clear_input_on_click=False, outline=True, irregular_outline=False, outline_additional_pixel=False, outline_hover_inclusive=True, outline_thickness=None, outline_color=(50, 50, 50), box_transform_hover=1, box_transform_inactive=1, box_transform_selected=1, font_transform_hover=1, font_transform_inactive=1, font_transform_selected=1, outline_transform_hover=1, outline_transform_inactive=0.9, input_box_transform_hover=1.07, input_box_transform_selected=2, input_box_transform_inactive=0.9 ): super().__init__( position=position, dimensions=dimensions, text=text, box_color=box_color, font_color=font_color, is_visible=is_visible, is_active=is_active, is_selected=is_selected, fit_text=fit_text, outline=outline, irregular_outline=irregular_outline, outline_additional_pixel=outline_additional_pixel, outline_hover_inclusive=outline_hover_inclusive, outline_thickness=outline_thickness, outline_color=outline_color, box_transform_hover=box_transform_hover, box_transform_inactive=box_transform_inactive, box_transform_selected=box_transform_selected, font_transform_hover=font_transform_hover, font_transform_inactive=font_transform_inactive, font_transform_selected=font_transform_selected, outline_transform_hover=outline_transform_hover, outline_transform_inactive=outline_transform_inactive ) # extracting and setting values smaller_dimension = min(dimensions) box_x, box_y = position width, height = dimensions # picking default input_box position and dimensions depending on the boxes width if width < 70: default_input_box_position = (box_x + width / 20, box_y + height / 2) default_input_box_dimensions = (width - width / 10, 3 * height / 10) else: default_input_box_position = (box_x + width / 12, box_y + height / 2) default_input_box_dimensions = (width - width / 6, 3 * height / 10) # setting attributes self.input_box_position = _return_value_or_default(input_box_position, default_input_box_position) self.input_box_dimensions = _return_value_or_default(input_box_dimensions, default_input_box_dimensions) self.user_input = user_input self.valid_input_characters = valid_input_characters self.ib_color = get_fixed_rgb(input_box_color) self.typing_highlight_color = get_fixed_rgb(writing_highlight_color) self.inner_box_color = get_fixed_rgb(inner_box_color) self.bottom_strip_color = get_fixed_rgb(bottom_strip_color) self.clear_input_on_click = clear_input_on_click self.font_size = _return_value_or_default(font_size, floor(smaller_dimension / 2.8)) self.font = pygame.font.SysFont(font, self.font_size) self.input_font_size = max(15, floor(self.input_box_dimensions[1] - 2)) self.input_font = pygame.font.SysFont(font, self.input_font_size) self.is_active = is_active self.input_centered = input_centered self.ib_transform_hover = input_box_transform_hover self.ib_transform_selected = input_box_transform_selected self.ib_transform_inactive = input_box_transform_inactive self.backspace_pressed_down = False # rendering text to get it's width and height rendered_text = self.font.render(text, True, (0, 0, 0)) # if text is out of bounds and fit_text=True - resizing text to fit the box if self.fit_text and rendered_text.get_width() > self.input_box_dimensions[0]: self.font_size = floor(self.font_size / (rendered_text.get_width() / width + 0.1)) self.font = pygame.font.SysFont(font, self.font_size) # counting colors on: hover, inactive, selected self.ib_hover_color = get_fixed_rgb(tuple(_transform_rgb(self.ib_color, self.ib_transform_hover))) self.ib_inactive_color = get_fixed_rgb(tuple(_transform_rgb(self.ib_color, self.ib_transform_inactive))) self.ib_selected_color = get_fixed_rgb(tuple(_transform_rgb(self.ib_color, self.ib_transform_selected))) # draws, updates and selects on mouse click the InputBox instance def run(self, window, mouse_position, events): self.select_on_click(mouse_position, events) self.clear_user_input_on_click(mouse_position, events) self.draw(window, mouse_position) self.update(events) @_return_itself # draws an InputBox instance (doesn't run interactions [for everything use run(self, ...) method]) def draw(self, window, mouse_position, *args, **kwargs): text_attribute_value = self.text self.text = "" super().draw(window, mouse_position, *args, **kwargs) # resetting original attribute values self.text = text_attribute_value # if is_visible=True drawing the InputBox if self.is_visible: # extracting and setting values from attributes box_x, box_y = self.position width, height = self.dimensions input_box_x, input_box_y = self.input_box_position input_width, input_height = self.input_box_dimensions # setting "transparent" background color text_background_color = self.ib_color # setting values accordingly to InputBox'es state # is selected if self.is_selected: input_box_color = self.ib_selected_color text_background_color = self.typing_highlight_color # is active and mouse is not over elif not self.is_over(mouse_position) and self.is_active: input_box_color = self.ib_color # is active and mouse is over elif self.is_active: input_box_color = self.ib_hover_color # is not active else: input_box_color = self.ib_inactive_color # drawing inner (upper) box pygame.draw.rect(window, self.inner_box_color, (input_box_x, box_y + height / 10, input_width, height - input_height - height / 2.5) ) # drawing input (lower) box pygame.draw.rect(window, input_box_color, (input_box_x, input_box_y, input_width, input_height)) # drawing bottom strip pygame.draw.rect(window, self.bottom_strip_color, (box_x, box_y + 9 * height / 10, width, 1 * height / 10)) # rendering text and counting its coordinates rendered_text = self.font.render(self.text, True, self.font_color) rendered_text_x = box_x + width / 2 - rendered_text.get_width() / 2 rendered_text_y = box_y + height / 4 - rendered_text.get_height() / 2 # drawing the text on the coordinates window.blit(rendered_text, (rendered_text_x, rendered_text_y)) # rendering input rendered_input_text = self.input_font.render(self.user_input, True, self.font_color, text_background_color) # picking input coordinates on center or not depending on input_centered flag value if self.input_centered: rendered_input_text_x = input_box_x + input_width / 2 - rendered_input_text.get_width() / 2 rendered_input_text_y = input_box_y + input_height / 2 - rendered_input_text.get_height() / 2 else: rendered_input_text_x = input_box_x + 2 rendered_input_text_y = input_box_y + input_height / 2 - rendered_input_text.get_height() / 2 # drawing the input on the coordinates window.blit(rendered_input_text, (rendered_input_text_x, rendered_input_text_y)) # updates InputBox'es attributes if user types something in it def update(self, events): # resetting attribute value self.was_enter_hit = False # if the InputBox is selected - updating the input text if self.is_selected: self._delete_characters_if_backspace_pressed() # rendering input to get it's width and height rendered_input = self.input_font.render(self.user_input, True, self.font_color, self.ib_inactive_color) # deleting or adding characters depending on user's input for event in events: if event.type == pygame.KEYDOWN: if event.key == pygame.K_BACKSPACE: if not self.backspace_pressed_down: self.user_input = self.user_input[:-1] self.backspace_pressed_down = True elif event.key == pygame.K_RETURN: self.is_selected = False self.was_enter_hit = True # if text isn't too long - adding a character elif rendered_input.get_width() + 10 < self.input_box_dimensions[0]: if event.unicode in self.valid_input_characters: self.user_input += event.unicode elif event.type == pygame.KEYUP: if event.key == pygame.K_BACKSPACE: self.backspace_pressed_down = False self.backspace_tick = 0 self.backspace_deleted_streak = 0 # checks if the InputBox is clicked and changes is_selected to True or False def select_on_click(self, mouse_position, events): # if is active set is_selected attribute accordingly if self.is_active: for event in events: if event.type == pygame.MOUSEBUTTONUP: self.set_is_selected(self.is_over(mouse_position)) # clears user's input on click def clear_user_input_on_click(self, mouse_position, events): if self.is_active and self.clear_input_on_click: for event in events: if event.type == pygame.MOUSEBUTTONUP and self.is_over(mouse_position): self.clear_input() # returns True if user input is empty, or False otherwise def empty(self): return not any(self.user_input) # clears user's input def clear_input(self): self.user_input = "" # returns InputBox'es user's input def get_input(self): return self.user_input # sets chosen coordinates def set_coordinates( self, position=None, dimensions=None, input_box_position=None, input_box_dimensions=None, outline_thickness=None ): super().set_coordinates(position=position, dimensions=dimensions, outline_thickness=outline_thickness) if input_box_position is not None: self.input_box_position = input_box_position if input_box_dimensions is not None: self.input_box_dimensions = input_box_dimensions # sets chosen text attributes def set_texts(self, text=None, user_input=None): super().set_text(text=text) if user_input is not None: self.user_input = user_input # sets chosen font attributes def set_font(self, font=None, font_size=None): if font_size is not None: self.font_size = font_size # rendering text to get it's width and height rendered_text = self.font.render(self.text, True, (0, 0, 0)) # if text is out of bounds and fit_text=True - resizing text to fit the box if self.fit_text and rendered_text.get_width() > self.input_box_dimensions[0]: self.font_size = floor(self.font_size / (rendered_text.get_width() / self.dimensions[0] + 0.1)) self.font = pygame.font.SysFont(font, self.font_size) super().set_font(font=font) # sets chosen color attributes def set_colors( self, box_color=None, input_box_color=None, inner_box_color=None, writing_highlight_color=None, bottom_strip_color=None, font_color=None, outline_color=None, box_transform_hover=None, box_transform_inactive=None, font_transform_hover=None, font_transform_inactive=None, input_box_transform_hover=None, input_box_transform_selected=None, input_box_transform_inactive=None, ): super().set_colors( box_color=box_color, font_color=font_color, outline_color=outline_color, box_transform_hover=box_transform_hover, box_transform_inactive=box_transform_inactive, font_transform_hover=font_transform_hover, font_transform_inactive=font_transform_inactive ) if input_box_color is not None: self.ib_color = get_fixed_rgb(input_box_color) if writing_highlight_color is not None: self.typing_highlight_color = writing_highlight_color if inner_box_color is not None: self.inner_box_color = get_fixed_rgb(inner_box_color) if bottom_strip_color is not None: self.bottom_strip_color = get_fixed_rgb(bottom_strip_color) if input_box_transform_hover is not None: self.ib_transform_hover = input_box_transform_hover if input_box_transform_selected is not None: self.ib_transform_selected = input_box_transform_selected if input_box_transform_inactive is not None: self.ib_transform_inactive = input_box_transform_inactive self.ib_hover_color = get_fixed_rgb(tuple(_transform_rgb(self.ib_color, self.ib_transform_hover))) self.ib_inactive_color = get_fixed_rgb(tuple(_transform_rgb(self.ib_color, self.ib_transform_inactive))) self.ib_selected_color = get_fixed_rgb(tuple(_transform_rgb(self.ib_color, self.ib_transform_selected))) # sets chosen flag attributes def set_flags( self, is_visible=None, is_active=None, input_centered=None, fit_text=None, outline=None, irregular_outline=None, outline_additional_pixel=None, outline_hover_inclusive=None ): super().set_flags( is_visible=is_visible, outline=outline, irregular_outline=None, outline_additional_pixel=None, outline_hover_inclusive=None ) if is_active is not None: self.is_active = is_active if input_centered is not None: self.input_centered = input_centered if fit_text is not None: self.fit_text = fit_text def _delete_characters_if_backspace_pressed(self): if self.backspace_pressed_down and \ (self.backspace_tick + 1) % self.deleting_speeds[0] == 0 and \ self.backspace_deleted_streak <= 6: self.user_input = self.user_input[:-1] self.backspace_tick = 0 self.backspace_deleted_streak += 1 elif self.backspace_pressed_down and \ (self.backspace_tick + 1) % self.deleting_speeds[1] == 0 and \ self.backspace_deleted_streak > 6: self.user_input = self.user_input[:-1] self.backspace_tick = 0 self.backspace_deleted_streak += 1 elif self.backspace_pressed_down: self.backspace_tick += 1