Made the folder structure match more the Python package standards.

This commit is contained in:
The Wobbler 2024-12-07 18:12:37 +01:00
parent 4721e5e7fa
commit 5f5175e653
16 changed files with 2 additions and 0 deletions

1
wobbl_tools/__init__.py Normal file
View file

@ -0,0 +1 @@
#!/usr/bin/python3

135
wobbl_tools/buntcheck.py Normal file
View file

@ -0,0 +1,135 @@
#!/usr/bin/python3
seven_row = [] # put the row of 7 in a list.
for number in range(15, 256, 6):
seven_row.append(number)
color_names = [
"black",
"orange",
"dark_green",
"dark_yellow",
"dark_blue",
"purple",
"dark_turquoise",
"light_gray",
"gray",
"red",
"green",
"yellow",
"blue",
"pink",
"turquoise",
"white"
]
color_ansi = {
"reset": "\033[0m",
"rs": "\033[0m",
"bold": "\033[1m",
"italic": "\033[3m",
"underline": "\033[4m",
"slow_blink": "\033[5m",
"rapid_blink": "\033[6m"
}
for color in range(0, 16):
color_ansi[color_names[color]] = f"\033[38;5;{color}m"
reset = color_ansi["reset"]
def get_text_colors(only_default: bool=False):
color_codes = []
for color in range(0, 16):
color_codes.append(f"\033[38;5;{color}m")
if only_default:
return color_codes
else:
for color in range(16, 256):
color_codes.append(f"\033[38;5;{color}m")
return color_codes
def get_background_colors(only_default: bool = False):
color_codes = []
for color in range(0, 16):
color_codes.append(f"\033[48;5;{color}m")
if only_default:
return color_codes
else:
for color in range(16, 256):
color_codes.append(f"\033[48;5;{color}m")
return color_codes
# Font effects
def print_font_effects():
print("Font effects")
print("reset: \\033[0m")
print(color_ansi["bold"] + "Bold: \\033[1m" + reset)
print(color_ansi["italic"] + "Italic: \\033[3m" + reset)
print(color_ansi["underline"] + "Underline: \\033[4m" + reset)
print(color_ansi["slow_blink"] + "Slow blink: \\033[5m" + reset)
print(color_ansi["rapid_blink"] + "Rapid blink: \\033[6m" + reset)
# Text colors
def print_text_colors():
print("Text")
print("Linux, BSD, div. Unixe, Apple & Windows ANSII 16 Colors:")
color_codes = ""
for color in range(0, 16):
color_codes += f"\033[38;5;{color}m\\033[38;5;{color}m "
color_codes += " " * (1 - (len(str(color)) - 1))
if color == 7:
color_codes += f"{reset}\n"
print(color_codes, reset)
print("\nLinux, BSD, div. Unixe XTERM 256 Colors:")
color_codes = ""
for color in range(16, 256):
color_codes += f"\033[38;5;{color}m\\033[38;5;{color}m "
color_codes += " " * (2 - (len(str(color)) - 1))
if color in seven_row:
color_codes += f"{reset}\n"
print(color_codes, reset)
# Background
def print_background_colors():
print("Background")
print("Linux, BSD, div. Unixe, Apple & Windows ANSII 16 Colors:")
color_codes = ""
for color in range(0, 16):
color_codes += f"\033[48;5;{color}m\\033[48;5;{color}m "
color_codes += " " * (1 - (len(str(color)) - 1))
if color == 7:
color_codes += f"{reset}\n"
print(color_codes, reset)
print("\nLinux, BSD, div. Unixe XTERM 256 Colors:")
color_codes = ""
for color in range(16, 256):
color_codes += f"\033[48;5;{color}m\\033[48;5;{color}m "
color_codes += " " * (2 - (len(str(color)) - 1))
if color in seven_row:
color_codes += f"{reset}\n"
print(color_codes, reset)
if __name__ == "__main__":
print()
print_font_effects()
print()
print_text_colors()
print_background_colors()

153
wobbl_tools/data_file.py Normal file
View file

@ -0,0 +1,153 @@
#!/usr/bin/python3
"""
Using this module, you can store data in files easily.
"""
import os
import json
from deprecated import deprecated
@deprecated(reason="This function is not safe to use!!!")
class DictFile:
"""
This class is not safe to use!
Any code in the file will be executed!
"""
def __init__(self, path: str=None):
self.path = path
if path is None: # create empty settings dict if path is none else load settings from file
self.settings = {}
else:
self.load_from_file(path)
def load_from_file(self, path):
file = open(path, "r")
self.settings = eval(file.read())
file.close()
def save(self, path: str=None):
if path is None:
path = self.path
file = open(path, "w")
file.write(str(self))
file.close()
def __str__(self): # make the dict not be just one line
new_settings_str = "{\n"
for key in self.settings:
new_settings_str += "\t" + repr(key) + ": " + repr(self.settings[key]) + ",\n"
new_settings_str = new_settings_str[:-2] + "\n}"
return new_settings_str
def __repr__(self):
return repr(self.settings)
def __getitem__(self, key):
return self.settings[key]
def __setitem__(self, key, value):
self.settings[key] = value
def __delitem__(self, key):
self.settings.pop(key)
def __iter__(self):
return self.settings
def __len__(self):
return len(self.settings)
class DataclassJSONFile:
"""
Store a dataclass in a JSON file.
"""
def __init__(self, file_path, dataclass, class_instance=None):
self.file_path = file_path
self.dataclass = dataclass
self.class_instance = class_instance
if class_instance is None: # load the class instance from the file if it is None
self.load_from_file(file_path)
def load_from_file(self, file_path):
file = open(file_path, "r")
class_dict = json.load(file)
file.close()
self.class_instance = self.dataclass(**class_dict)
def save_to_file(self):
class_dict = self.class_instance.__dict__
file = open(self.file_path, "w")
json.dump(class_dict, file)
file.close()
def __repr__(self):
return repr(self.class_instance)
def __getitem__(self, key):
return getattr(self.class_instance, key)
def __setitem__(self, key, value):
setattr(self.class_instance, key, value)
def __delitem__(self, key):
delattr(self.class_instance, key)
def __iter__(self):
return self.class_instance.__iter__()
def __len__(self):
return len(self.class_instance.__dict__)
# these 2 functions do the exact same thing as the functions in the class above
def load_dataclass_json(dataclass, file_path: str, builtin_save: bool=True):
"""
Loads a dataclass instance from a json file.
:param dataclass: The dataclass from which the instance will be created.
:param str file_path: The path to the json file from which the data gets loaded.
:param bool builtin_save: If True, you can simply call instance.save(file_path) to store the data.
:return: An instance of the dataclass containing the data from the json file.
"""
if os.path.exists(file_path):
file = open(file_path, "r")
class_dict = json.load(file)
file.close()
else:
class_dict = {}
instance = dataclass(**class_dict)
if builtin_save:
dataclass.save = save_dataclass_json
return instance
def save_dataclass_json(class_instance, file_path: str):
"""
Saves a dataclass instance to a json file.
:param class_instance: The instance of the dataclass to be saved.
:param str file_path: The path to the file in which the data gets written.
"""
class_dict = class_instance.__dict__
class_dict = dict(filter(lambda pair: not callable(pair[1]), class_dict.items())) # filter out functions
file = open(file_path, "w")
json.dump(class_dict, file)
file.close()

View file

@ -0,0 +1,108 @@
#!/usr/bin/python3
import pygame
from pygame._sdl2 import Window
from dataclasses import dataclass
import gui
@dataclass
class Settings:
fps: int = 60
window_size: tuple = (1000, 600)
class Examples:
def __init__(self):
pygame.init()
self.screen = pygame.display.set_mode((1000, 600), pygame.RESIZABLE)
pygame.display.set_caption("Game")
self.window = Window.from_display_module()
self.loading_surface = self.generate_loading_surface()
self.screen.blit(self.loading_surface, (0, 0))
pygame.display.update()
self.settings = Settings()
self.fps = self.settings.fps
# colors
self.gray = (20, 20, 20)
# pygame objects
self.clock = pygame.time.Clock()
self.mouse = pygame.mouse
self.big_font = pygame.font.Font(pygame.font.get_default_font(), 32)
# loading finished
self.window.size = self.settings.window_size
self.gui = gui.GUI(self)
self.running = True
def generate_loading_surface(self):
default_font = pygame.font.SysFont("ubuntu", 32, True)
loading_text = default_font.render("Loading...", True, (240, 240, 240))
w, h = self.screen.get_size()
loading_surface = pygame.Surface((w, h), flags=pygame.SRCALPHA)
loading_surface.fill((40, 40, 40))
loading_surface.blit(loading_text,
(w // 2 - loading_text.get_width() // 2, h // 2 - loading_text.get_height() // 2))
return loading_surface
def loop(self):
self.screen.fill(self.gray)
self.get_events()
if not self.running:
return
self.gui.draw()
if self.loading_surface.get_alpha() > 0:
self.screen.blit(self.loading_surface, (0, 0))
self.loading_surface.set_alpha(self.loading_surface.get_alpha() - 4)
pygame.display.update()
self.clock.tick(self.fps)
def get_events(self):
for event in pygame.event.get():
if event.type == pygame.QUIT:
self.exit()
return
elif event.type == pygame.VIDEORESIZE:
self.window_update(event.size)
elif event.type == pygame.MOUSEBUTTONDOWN:
pos = self.mouse.get_pos()
pressed = self.mouse.get_pressed()
if self.gui.current_page in self.gui.buttons:
for button in self.gui.buttons[self.gui.current_page]:
button.check(pos, pressed)
def exit(self):
self.running = False
print("Bye!")
def window_update(self, size):
self.settings.window_size = size
self.gui.update()
if __name__ == "__main__":
examples = Examples()
while examples.running:
examples.loop()

140
wobbl_tools/examples/gui.py Normal file
View file

@ -0,0 +1,140 @@
#!/usr/bin/python3
import pygame
from wobbl_tools.pygame_tools.utils import *
from wobbl_tools.pygame_tools.widgets import TextButton, Button, MultilineText
class GUI:
def __init__(self, app):
self.app = app
self.buttons = {}
self.example_menu = ExampleMenu(self)
self.multiline_text_example = MultilineTextExample(self)
self.current_page = "example_menu"
self.pages = self.get_list_of_pages()
def draw(self):
page = getattr(self, self.current_page)
page.draw()
def update(self):
for page_name in self.pages:
page = getattr(self, page_name)
page.update()
def get_list_of_pages(self):
pages = []
for name, page in self.__dict__.items():
if issubclass(type(page), Page):
pages.append(name)
return pages
def reload(self):
for page_name in self.pages:
page = getattr(self, page_name)
page.load()
class Page:
def __init__(self, gui):
self.gui = gui
self.app = gui.app
self.surface = pygame.Surface(self.app.screen.get_size(), flags=pygame.SRCALPHA)
self.load()
self.update()
def draw(self):
self.app.screen.blit(self.surface, (0, 0))
def update(self):
pass
def load(self):
pass
class ExampleMenu(Page):
def load(self):
button_texts = ["Multiline Text"]
buttons = []
for button_text in button_texts:
name_lower = button_text.lower().replace(" ", "_")
setattr(self, name_lower + "_button", len(buttons))
buttons.append(TextButton(button_text, (0, 0), change_page, (self, name_lower + "_example")))
start_pos = (10, 10)
spacing = 10
x, y = start_pos
i = 0
for button in buttons:
if x + button.image.get_width() > self.app.screen.get_width():
x = start_pos[0]
y += button.surface.get_height() + spacing
button.position = (x, y)
else:
button.position = (x, y)
w, h = button.size
button.rect = pygame.Rect((x, y), (w, h))
button.button = Button(button.rect, button.function, button.args, key=button.key)
buttons[i] = button
x += button.image.get_width() + spacing
i += 1
self.gui.buttons["example_menu"] = buttons
def update(self):
self.surface.fill((20, 20, 20))
for button in self.gui.buttons["example_menu"]:
button.draw(self.surface)
class MultilineTextExample(Page):
def load(self):
self.heading = self.app.big_font.render("Multiline Text", True, white)
self.text = MultilineText(
"If you want to create multiple lines of text in pygame, " +
"you have to create multiple text objects and blit them one another.\n" +
'The class "MultilineText()" does this automatically. ' +
'You can make new lines by adding "\\n" to the string. ' +
"It can also automatically create newlines if the line is longer than a given length.\n" +
"You can resize this window and the text will automatically fit on the window.",
max_width=self.app.screen.get_width()
)
def update(self):
self.surface = pygame.transform.scale(self.surface, self.app.screen.get_size())
self.surface.fill((20, 20, 20))
self.load()
self.surface.blit(self.heading, (self.app.screen.get_width() / 2 - self.heading.get_width() / 2, 10))
self.surface.blit(self.text.surface, (10, 50))
def change_page(self, page):
self.gui.current_page = page

View file

@ -0,0 +1,93 @@
#!/usr/bin/python3
from scipy.sparse import csc_array, csr_array
# --- How it works: (3D Matrix)---
# Matrices can only expand in all positive directions.
# That means, the zero point of the matrix stays at the same position while the matrix expands,
# but the opposite corner (where no xyz direction is zero) moves away from the zero point while it expands.
# If you draw a line through the zero point of the matrix and the opposite completely positive corner,
# you get the direction in that the matrix will expand. (this direction will change if you change the aspect ratio of -
# the matrix)
# But we need a matrix that can expand in the negative directions too.
# To create such a matrix, we use another matrix that we flip completely and put behind the zero point of a first -
# matrix, on the same expanding direction of the first matrix.
# Because the second matrix is flipped, our expanding direction can now go in the opposite direction,
# but because you cant create another cube from two cubes, we don't have a complete matrix.
# So we add six more matrices to make our infinite matrix expandable in the yz, x, y, xz, xy, z directions.
class Infinite3DMatrixCSC:
def __init__(self, size: tuple=(32, 32, 32), size_negative: tuple=(32, 32, 32)):
self.size = size
self.size_negative = size_negative
self.chunk_size = (
(size[0] + size_negative[0]) / 2,
(size[1] + size_negative[1]) / 2,
(size[2] + size_negative[2]) / 2
) # get average
px = size[0]
py = size[1]
pz = size[2]
nx = size_negative[0]
ny = size_negative[1]
nz = size_negative[2]
# if (
# not float(self.size[0]).is_integer() and
# not float(self.chunk_size[1]).is_integer() and
# not float(self.chunk_size[2]).is_integer()
# ): # if the size is possible
# raise ValueError("Cant calculate equal chunk size.")
self.xyz_matrix = csc_array(size) # create the 8 matrices
self.negative_matrix = csc_array(size_negative)
self.yz_matrix = csc_array((nx, py, pz))
self.x_matrix = csc_array((px, ny, nz))
self.y_matrix = csc_array((nx, py, nz))
self.xz_matrix = csc_array((px, ny, pz))
self.xy_matrix = csc_array((px, py, nz))
self.z_matrix = csc_array((nx, ny, pz)) # Nix, Nie, Pizza!
def __getitem__(self, item):
self.get_item(item)
def resize_matrices(self):
px = self.size[0]
py = self.size[1]
pz = self.size[2]
nx = self.size_negative[0]
ny = self.size_negative[1]
nz = self.size_negative[2]
self.xyz_matrix.resize(self.size)
def resize(self, new_size: tuple, new_size_negative: tuple):
if not new_size is None:
self.size = new_size
if not new_size_negative is None:
self.size_negative = new_size_negative
def expand(self, add_positive: tuple, add_negative: tuple):
if not add_positive is None:
self.size = coord_add(self.size, add_positive)
if not add_negative is None:
self.size_negative = coord_add(self.size_negative, add_negative)
def get_item(self, position: tuple):
x, y = position
def coord_add(coord1: tuple, coord2: tuple):
return coord1[0] + coord2[0], coord1[1] + coord2[1], coord1[2] + coord2[2]
def coord_sub(coord1: tuple, coord2: tuple):
return coord1[0] - coord2[0], coord1[1] - coord2[1], coord1[2] - coord2[2]
Infinite3DMatrixCSC((5, 5))

89
wobbl_tools/math/spvo.py Normal file
View file

@ -0,0 +1,89 @@
#!/usr/bin/python3
"""
SPVO = Sparse Voxel Octree
A Sparse Voxel Octree is a way of storing multidimensional data. (In this case it is 3D Data.)
It works similarly to a Sparse Voxel Matrix, but it is a bit compressed.
In a Sparse Voxel Octree, you have a root-node. The root node gets split up into 8 child nodes,
which can be all split up into 8 more child nodes which can be split up too and so on.
Thanks to this "compression", there are no zero values on places where there is just air unlike as in a normal matrix.
"""
class Node:
def __init__(self, data=None):
self.children = [None] * 8
self.leaf = True
self.data = data
class SparseVoxelOctree:
def __init__(self):
self.root_node = Node()
self.origin = (0, 0, 0)
def _get_child_index(self, x, y, z, size):
"""
Get the index of a child.
Index: The position of the node in the node parent's children list.
"""
index = 0
if x >= size // 2: # x pos
# there are only 2 possible x coordinate values in a 2x2 cube, so we use the 1st bit in index for x
# xyz: the coordinates of the parent node
index |= 1 # set 1st bit to 1
x -= size // 2 # get parent x
# and the same for y and z
if y >= size // 2: # y pos
index |= 2
y -= size // 2
if z >= size // 2: # z pos
index |= 4
z -= size // 2
return index, x, y, z
def _extend_tree(self, x, y, z):
new_root = Node()
new_root.leaf = False
index, nx, ny, nz = self._get_child_index(x, y, z, 2)
new_root.children[index] = self.root_node
self.root_node = new_root
self.origin = (
self.origin[0] - (x >= 0) * 2 + 1,
self.origin[1] - (y >= 0) * 2 + 1,
self.origin[2] - (z >= 0) * 2 + 1
)
def _insert(self, node, x, y, z, size):
while not size == 1:
index, x, y, z = self._get_child_index(x, y, z, size)
if node.children[index] is None:
node.children[index] = Node()
size //= 2
node.leaf = True
def insert(self, x, y, z):
# move origin if necessary
while not (0 <= x < 2 ** 30 and 0 <= y < 2 ** 30 and 0 <= z < 2 ** 30):
self._extend_tree(x, y, z)
x, y, z = (
x - self.origin[0],
y - self.origin[1],
z - self.origin[2]
)
self._insert(self.root_node, x, y, z, 2 ** 30)

View file

@ -0,0 +1,2 @@
#!/usr/bin/python3

View file

@ -0,0 +1,50 @@
#!/usr/bin/python3
import pygame
from wobbl_tools import text
from typing import Union, Callable
pygame.init()
log = text.Log()
buttonlist = False
buttons = []
# colors
black = (8, 8, 8)
gray = (40, 40, 40)
light_gray = (128, 128, 128)
white = (250, 250, 250)
default_font = pygame.font.Font(pygame.font.get_default_font(), 16)
def set_rot_point(img, pos):
"""
Blits the image on an empty surface to change the center of rotation.
"""
w, h = img.get_size()
w, h = w * 2, h * 2
img2 = pygame.Surface((w, h), pygame.SRCALPHA)
img2.blit(img, (w / 2 - pos[0], h / 2 - pos[1]))
return img2, (w, h)
def crop_surface(surface: pygame.Surface, position: tuple, size: tuple):
new_surface = pygame.Surface(size, flags=pygame.SRCALPHA)
x, y = position
x = -x
y = -y
new_surface.blit(surface, (x, y))
return new_surface
def check_buttons(mouse_pos, pressed):
for button in buttons:
button.check(mouse_pos, pressed)

View file

@ -0,0 +1,7 @@
#!/usr/bin/python3
from wobbl_tools.pygame_tools.widgets.hover import Hover
from wobbl_tools.pygame_tools.widgets.button import Button
from wobbl_tools.pygame_tools.widgets.text_button import TextButton
from wobbl_tools.pygame_tools.widgets.text_input import TextInput
from wobbl_tools.pygame_tools.widgets.multiline_text import MultilineText

View file

@ -0,0 +1,29 @@
#!/usr/bin/python3
from wobbl_tools.pygame_tools.utils import *
from wobbl_tools.pygame_tools.widgets import Hover
class Button:
"""
Creates a button from a pygame.Rect.
"""
def __init__(self, rect: pygame.Rect, function=None, args: tuple=None, key: int=0): # key: 0 = left 1 = mouse wheel pressed 2 = right
self.rect = rect
self.function = function
self.args = args
self.key = key
self.active = True
self.hover = Hover(rect, function, args)
if buttonlist:
buttons.append(self)
def check(self, mouse_pos: tuple, pressed): # check if the button is pressed
if self.active:
if pressed[self.key]:
return self.hover.check(mouse_pos)
return False

View file

@ -0,0 +1,37 @@
#!/usr/bin/python3
from wobbl_tools.pygame_tools.utils import *
class Hover:
"""
Used to execute a function when the mouse hovers over a rectangle.
"""
def __init__(self, rect: pygame.Rect, function=None, args: tuple=None):
"""
:param rect: A pygame.Rect, if the mouse hovers over this rectangle, the function gets executed.
:param function: The function that gets executed when the mouse hovers over the rect.
:param tuple args: A tuple that contains the arguments that will get passed to the function.
"""
self.rect = rect
self.function = function
self.args = args
self.active = True
def check(self, mouse_pos: tuple):
if self.active:
mx, my = mouse_pos
if self.rect.collidepoint((mx, my)):
if not self.function is None:
if self.args is None:
self.function()
else:
self.function(*self.args)
return True
return False

View file

@ -0,0 +1,105 @@
#!/usr/bin/python3
from wobbl_tools.pygame_tools.utils import *
class MultilineText:
"""
Creates a surface with text on it.
You can use "\\n" to create a newline.
When the max_width parameter is set, newlines generate automatically.
If you know the width of the widest character in your font, you can set the char_width parameter,
it will make the text creation a bit faster.
Use "surface.blit(multiline_text.surface, pos)" to draw it on a surface.
"""
def __init__(self, text: str, font: pygame.font.Font=default_font, color: tuple=white, max_width: int=None):
self.text = text
self.font = font
self.color = color
self.max_width = max_width
self.surface = self.generate_surface()
def generate_surface(self):
lines = []
line = ""
length = 1
if self.max_width is None:
for char in self.text:
if char == "\n":
lines.append(line)
length = 1
line = ""
else:
line += char
length += 1
else:
for char in self.text:
if char == "\n":
lines.append(line)
length = 1
line = ""
elif self.font.size(line)[0] >= self.max_width - 8:
if " " in line:
# remove last word
words = line.split(" ")
last_word = words[len(words) - 1]
words.pop(len(words) - 1)
line = ""
for word in words: # convert the list back to a string
line += word + " "
line = line[:len(line) - 1] # remove last space character
lines.append(line)
line = last_word + char
else:
lines.append(line)
length = 1
line = char
else:
line += char
length += 1
lines.append(line)
texts = []
for line in lines:
texts.append(self.font.render(line, True, self.color))
if self.max_width is None:
w = max(texts, key=lambda t: t.get_width()).get_width()
else:
w = self.max_width
h = texts[0].get_height()
sh = h * len(lines)
surface = pygame.Surface((w, sh), flags=pygame.SRCALPHA)
x, y = 0, 0
for text in texts:
surface.blit(text, (x, y))
y += h
return surface

View file

@ -0,0 +1,64 @@
#!/usr/bin/python3
from wobbl_tools.pygame_tools.utils import *
from wobbl_tools.pygame_tools.widgets import Button
class TextButton(pygame.sprite.Sprite):
"""
Creates a button from just some string and a position.
"""
def __init__(
self,
text: str,
position: tuple,
function=None,
args: tuple=None,
key: int=0,
text_color: tuple=white,
bg_color: tuple=gray,
font: pygame.font.Font=default_font,
padding: tuple=(8, 8),
border_radius: int=0,
line_thickness: int = 0
):
pygame.sprite.Sprite.__init__(self)
self.text = text
self.position = position
self.text_color = text_color
self.font = font
self.function = function
self.args = args
self.key = key
self.bg_color = bg_color
self.padding = padding
self.border_radius = border_radius
self.line_thickness = line_thickness
self.image, self.size = self.generate_surface()
self.rect = self.make_rect()
self.button = Button(self.rect, self.function, args, self.key)
def generate_surface(self):
text_object = self.font.render(self.text, True, self.text_color)
w, h = text_object.get_size()
w, h = w + self.padding[0] * 2, h + self.padding[1] * 2
surface = pygame.Surface((w, h), pygame.SRCALPHA)
pygame.draw.rect(surface, self.bg_color, pygame.Rect((0, 0), (w, h)), self.line_thickness, self.border_radius)
surface.blit(text_object, (self.padding[0], self.padding[1]))
return surface, (w, h)
def make_rect(self):
return pygame.Rect(self.position, self.size)
def draw(self, surface: pygame.Surface):
surface.blit(self.image, self.position)
def check(self, mouse_pos, pressed):
return self.button.check(mouse_pos, pressed)

View file

@ -0,0 +1,140 @@
#!/usr/bin/python3
from wobbl_tools.pygame_tools.utils import *
class TextInput:
"""
Creates an input object at a given position.
"""
def __init__(
self,
position: Union[tuple, Callable],
surface: pygame.Surface,
width: int = None,
font: pygame.font.Font = default_font,
bg_color: tuple = (255, 255, 255),
text_color: tuple = black,
desc_text_color: tuple = light_gray,
desc_text: str = "",
default_text: str = "",
padding: tuple = (8, 8),
cursor_blink_interval: int = 30,
border_radius: int = -1,
max_length: int = 128
):
self.surface = surface
self.font = font
self.bg_color = bg_color
self.text_color = text_color
self.desc_text_color = desc_text_color
self.desc_text = desc_text
self.text = default_text
self.padding = padding
self.focused = True
self.cursor_blink_interval = cursor_blink_interval
self.ticks_til_blink = cursor_blink_interval
self.blink = False
self.border_radius = border_radius
self.max_length = max_length
self.cursor_position = len(default_text)
sx, sy = surface.get_size()
if width is None:
self.width = round(sx / 4)
else:
self.width = width
self.description_text_object = font.render(self.desc_text, True, self.desc_text_color)
self.text_object = font.render(self.text, True, self.text_color)
self.text_blink_object = font.render(self.text + "|", True, self.text_color)
self.height = self.text_object.get_height()
if type(position) is tuple:
self.position = position
else:
self.position = position(size=self.size)
self.callable_position = position
self.rect = self.generate_rect()
def generate_rect(self):
x, y = self.position
px, py = self.padding
x -= px
y -= py
return pygame.Rect((x, y), (self.width + px * 2, self.height + py * 2))
def blit(self):
pygame.draw.rect(self.surface, self.bg_color, self.rect, border_radius=self.border_radius)
if self.focused:
if self.blink:
self.surface.blit(self.text_blink_object, self.position)
else:
self.surface.blit(self.text_object, self.position)
def loop(self):
"""Call this function every tick to make the cursor blink."""
self.ticks_til_blink -= 1
if self.ticks_til_blink == -1:
self.ticks_til_blink = self.cursor_blink_interval
self.blink = not self.blink
def keypress(self, event: pygame.event, pressed_keys, special_keys):
"""
Call this function when a key is pressed to get user input like this:
for event in pygame.event.get():
if event.type == pygame.KEYDOWN:
TextInput.keypress(event, pygame.key.get_pressed(), pygame.key.get_mods())
"""
if self.max_length is None or len(self.text) <= self.max_length:
key_unicode = event.unicode
key = event.key
if key < 32 or key > 101106 or special_keys & pygame.KMOD_CTRL or special_keys & pygame.KMOD_RCTRL or key > 126 and key < 161:
if key == pygame.K_BACKSPACE:
if self.cursor_position > 0:
self.text = text.rsap(self.text, "", self.cursor_position - 1)
self.cursor_position -= 1
elif key == pygame.K_LEFT:
if self.cursor_position > 0:
self.cursor_position -= 1
elif key == pygame.K_RIGHT:
if self.cursor_position < len(self.text):
self.cursor_position += 1
else:
self.text = text.asap(self.text, key_unicode, self.cursor_position)
self.cursor_position += 1
self.regenerate_objects()
def regenerate_objects(self):
self.rect = self.generate_rect()
self.text_object = self.font.render(self.text, True, self.text_color)
self.text_blink_object = self.font.render(text.asap(self.text, "|", self.cursor_position), True,
self.text_color)
t_width, t_height = self.text_blink_object.get_size()
if t_width > self.width:
self.text_blink_object = crop_surface(self.text_blink_object,
(t_width - (t_width - (t_width - self.width)), 0),
(t_width - (t_width - self.width), t_height))
self.text_object = crop_surface(self.text_object, (t_width - (t_width - (t_width - self.width)), 0),
(t_width - (t_width - self.width), t_height))