Projekt_Sztuczna_Inteligencja/ui/input_box.py

422 lines
17 KiB
Python

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