Source code for miniworldmaker.containers.widgets
import tkinter as tk
from tkinter import filedialog
from typing import Union
import pygame
from miniworldmaker.base import file_manager
from miniworldmaker.tools import inspection
[docs]
class Widget:
"""A Widget which can be placed in the Toolbar.
A widget can have 'text' and an 'image'.
"""
def __init__(self):
self.myfont = pygame.font.SysFont("monospace", 15)
self.event = "no event"
self.parent = None
self._text = ""
self.speed = 1
self._image = None
self.surface = None
self.timed = False
self._dirty = 1
# size and position
self._topleft = (0, 0) # Set in Toolbar repaint
self._width = 0 # Set in Toolbar repaint
self._height = 30
self._margin_bottom = 10
self._margin_top = 0
self._margin_left = 0
self._margin_right = 0
self.padding_left = 10
self.padding_top = 5
self.padding_bottom = 5
self.padding_right = 0
self.clear()
# text
self._text_padding_left = 5
self._text_padding_top = 0
self._text_align = "image"
# background
self._background_color = (200, 200, 200)
# background-image
self._img_width = 22
self._img_source = None
# border
self._border = False
self._border_width = 1
self._border_color = (0, 0, 0, 255)
self.dirty = 1
self._repaint()
self.on_setup()
[docs]
def get_local_pos(self, position):
x = position[0] - self._topleft[0]
y = position[1] - self._topleft[1]
return x, y
@property
def text_align(self):
"""Defines how text is aligned.
If widget has an image, text is aligned left, else it can be set to "left", "center" or "right".
"""
if not self._img_source:
return "left"
else:
return self._text_align
@text_align.setter
def text_align(self, value):
self._text_align = value
self.dirty = 1
@property
def text_padding_left(self) -> int:
"""Left text_padding"""
return self._text_padding_left
@text_padding_left.setter
def text_padding_left(self, value: int):
self._text_padding_left = value
self.dirty = 1
@property
def text_padding_top(self) -> int:
"""Top text_padding"""
return self._text_padding_top
@text_padding_top.setter
def text_padding_top(self, value: int):
self._text_padding_top = value
self.dirty = 1
@property
def text_padding_top_left(self) -> tuple:
"""Top-left text_padding"""
return (self._text_padding_left, self.text_padding_top)
@text_padding_top_left.setter
def text_padding_top_left(self, value: int):
self._text_padding_top = value
self._text_padding_left = value
self.dirty = 1
@property
def img_width(self) -> float:
"""Width of image.
(Height will be autoset by padding-left, padding-top and padding.right)
"""
return self._img_width
@img_width.setter
def img_width(self, value: float):
self._img_width = value
self.dirty = 1
@property
def margin_bottom(self) -> int:
"""Margin below widget"""
return self._margin_bottom
@margin_bottom.setter
def margin_bottom(self, value: int):
self._margin_bottom = value
self.dirty = 1
@property
def margin_top(self) -> int:
"""Margin above widget"""
return self._margin_top
@margin_top.setter
def margin_top(self, value: int):
self._margin_top = value
self.dirty = 1
@property
def margin_left(self) -> int:
"""left margin"""
return self._margin_left
@margin_left.setter
def margin_left(self, value: int):
self._margin_left = value
self.dirty = 1
@property
def margin_right(self) -> int:
"""right margin"""
return self._margin_right
@margin_right.setter
def margin_right(self, value: int):
self._margin_right = value
self.dirty = 1
@property
def height(self) -> int:
"""Widget height"""
return self._height
@property
def width(self) -> int:
"""Widget width (read only value)"""
return self._width
@height.setter
def height(self, value: int):
self._height = value
@property
def background_color(self) -> tuple:
return self._background_color
@background_color.setter
def background_color(self, value: tuple):
self.set_background_color(value)
[docs]
def set_background_color(self, value) -> "Widget":
self._background_color = value
self.dirty = 1
return self
@property
def dirty(self):
return self._dirty
@dirty.setter
def dirty(self, value):
self._dirty = value
if self.parent:
self.parent.dirty = value
[docs]
def clear(self):
self.surface = pygame.Surface((self.width, self.height), pygame.SRCALPHA)
self.dirty = 1
return self.surface
[docs]
def remove(self):
"""Removes the widget from toolbar"""
self.parent.widgets.remove(self)
self.parent.dirty = 1
def _repaint(self):
"""Repaints the widget."""
if self.dirty == 1:
self.clear()
self.surface.fill(self.background_color)
if self._img_source is not None:
if type(self._img_source) == str:
source = file_manager.FileManager.get_image_path(self._img_source)
image = pygame.image.load(source)
elif type(self._img_source) == tuple:
image = pygame.Surface((1, 1))
image.fill(self._img_source)
else:
raise ValueError("img_source has wrong type")
image = pygame.transform.scale(
image, (self.img_width, self.height - self.padding_top - self.padding_bottom)
)
self.surface.blit(image, (self.padding_left, self.padding_top))
if self._border:
border_rect = pygame.Rect(0, 0, self.width, self.height)
pygame.draw.rect(self.surface, self._border_color, border_rect, self._border_width)
# Blit text to surface
label = self.myfont.render(self._text, True, (0, 0, 0))
if self.text_align == "img" or self.text_align == "image":
self.surface.blit(
label,
(
self.padding_left + self.img_width + self._text_padding_left,
self.padding_top + self._text_padding_top,
),
)
if self.text_align == "left":
self.surface.blit(
label, (self.padding_left + self._text_padding_left, self.padding_top + self.text_padding_top)
)
self.dirty = 0
@property
def text(self) -> str:
"""The text which is displayed on the widget."""
return self._text
@text.setter
def text(self, value: str):
self.set_text(value)
[docs]
def set_text(self, text: Union[str, int, float]):
"""Sets text of widget.
int and float values are converted to string.
"""
if type(text) == int or type(text) == float:
text = str(text)
self._text = text
self.dirty = 1
[docs]
def set_image(self, _img_source: Union[str, tuple]):
"""sets image of widget
Args:
_img_source (str): path to image or tuple with color
"""
self._img_source = _img_source
self.dirty = 1
[docs]
def set_border(self, color: tuple = (0, 0, 0, 255), width: int = 1):
"""sets border of widget
Args:
color (_type_): _description_
width (_type_): _description_
"""
self._border = True
self._border_color = color
self._width = width
self.dirty = 1
def __str__(self):
return "{0} : {1}".format(self.__class__.__name__, self._text)
[docs]
def register(self, method: callable) -> callable:
"""
Used as decorator
e.g.
@register
def method...
"""
bound_method = inspection.Inspection(self).bind_method(method)
return bound_method
[docs]
class Button(Widget):
"""A Toolbar Button
The Button can receive events.
If the button is clicked
Args:
text (str): _description_
img_path (str): path to an image
"""
def __init__(self, text: str, img_path: str = None):
super().__init__()
self.set_text(text)
self.event = "button_pressed"
if img_path:
self.set_image(img_path)
self.data = text
[docs]
def on_mouse_left(self, mouse_pos):
"""This event is called when the button is clicked -
By default, a message with the button text is then sent to the board.
Examples:
Send a event on button-click:
.. code-block:: python
toolbar = Toolbar()
button = ToolbarButton("Start Rocket")
toolbar.add_widget(button)
board.add_container(toolbar, "right")
@board.register
def on_message(self, message):
if message == "Start Rocket":
rocket.started = True
"""
self.parent.send_message(self._text)
class ToolbarButton(Button):
"""for legacy code"""
pass
[docs]
class Label(Widget):
def __init__(self, text, img_path=None):
super().__init__()
if img_path:
self.set_image(img_path)
self.set_text(text)
self.event = "label"
self.data = text
self.background_color = (255, 255, 255, 0)
class ToolbarLabel(Label):
"""for legacy Code"""
pass
class SaveButton(Widget):
def __init__(
self,
board,
text,
filename: str = None,
img_path: str = None,
):
super().__init__()
if img_path:
self.set_image(img_path)
self.set_text(text)
self.event = "label"
self.data = text
self.app = board.app
self.file = filename
self.tokens = None
def on_mouse_left(self, mouse_pos):
if self.file is None:
tk.Tk().withdraw()
self.file = filedialog.asksaveasfilename(
initialdir="./", title="Select file", filetypes=(("db files", "*.db"), ("all files", "*.*"))
)
self.app.running_board.save_to_db(self.file)
self.app.running_board.send_message("Saved new world", self.file)
else:
self.app.running_board.save_to_db(self.file)
self.app.running_board.send_message("Saved new world", self.file)
print("Board was saved to file:", self.file)
class LoadButton(Widget):
def __init__(
self,
board,
text,
filename,
img_path=None,
):
super().__init__()
if img_path:
self.set_image(img_path)
self.set_text(text)
self.file = filename
self.app = board.app
def on_mouse_left(self, mouse_pos):
tk.Tk().withdraw()
if self.file is None:
self.file = filedialog.askopenfilename(
initialdir="./", title="Select file", filetypes=(("db files", "*.db"), ("all files", "*.*"))
)
self.app.running_board.load_board_from_db(self.file)
class ClearButton(Widget):
def __init__(
self,
board,
text,
img_path=None,
):
super().__init__()
self.set_text(text)
self.board = board
self.app = board.app
def on_mouse_left(self, mouse_pos):
self.board.clear()
class CounterLabel(Widget):
"""A counter label contains a `description` and a `counter`. The counter starts with value 0 and can be modified with
`add` and `sub`
"""
def __init__(self, description, img_path=None):
super().__init__()
if img_path:
self.set_image(img_path)
self.value = 0
self.description = description
self.set_text("{0} : {1}".format(self.description, str(self.value)))
self.data = str(0)
def add(self, value):
self.value += value
self.update_text()
def sub(self, value):
self.value -= value
self.update_text()
def get_value(self):
return self.value
def set(self, value):
self.value = value
self.update_text()
def update_text(self):
self.set_text("{0} : {1}".format(self.description, str(self.value)))
class TimeLabel(Widget):
def __init__(self, board, text, img_path=None):
super().__init__()
if img_path:
self.set_image(img_path)
self.board = board
self.value = self.board.frame
self.text = text
self.set_text("{0} : {1}".format(self.text, str(self.value)))
self.data = str(0)
self.timed = True
def update(self):
self.value = self.board.frame
self.set_text("{0} : {1}".format(self.text, str(self.value)))
class FPSLabel(Widget):
def __init__(self, board, text, img_path=None):
super().__init__()
if img_path:
self.set_image(img_path)
self.board = board
self.value = self.board.clock.get_fps()
self.text = text
self.set_text("{0} : {1}".format(self.text, str(self.value)))
self.data = str(0)
self.timed = True
def update(self):
self.value = self.board.clock.get_fps()
self.set_text("{0} : {1}".format(self.text, str(self.value)))
class ContainerWidget(Widget):
"""Widget containing multiple widgets.
The widgets inside of this widget are displayed from left to right.
"""
def __init__(self, widgets):
for widget in widgets:
widget.parent = self
self.widgets = widgets
self.inner_padding = 5
super().__init__()
self.dirty = 1
def _repaint(self):
self.clear()
if self.dirty == 1:
actual_x = 0
self.surface.fill(self.background_color)
for widget in self.widgets:
widget._width = self.width / len(self.widgets)
widget.dirty = 1
widget._repaint()
self.surface.blit(widget.surface, (actual_x, 0))
actual_x += widget._width + self.inner_padding
def get_widget(self, pos):
local_pos = self.get_local_pos(pos)
actual_x = 0
for widget in self.widgets:
if actual_x <= local_pos[0] <= actual_x + widget.width:
return widget
actual_x += widget._width + self.inner_padding
def on_mouse_left(self, mouse_pos):
widget = self.get_widget(mouse_pos)
if widget:
widget.on_mouse_left(mouse_pos)
class YesNoButton(ContainerWidget):
def __init__(self, yes_text, no_text):
self.yes = Button(yes_text)
self.no = Button(no_text)
super().__init__([self.yes, self.no])
self.background_color = (255, 255, 255, 0)
self.dirty = 1
def get_yes_button(self):
return self.yes
def get_no_button(self):
return self.no
class SimplePagination(ContainerWidget):
def __init__(self, container, next_text, last_text, ):
self.next = Button(next_text)
self.last = Button(last_text)
self.container = container
self.next.container = container
self.last.container = container
super().__init__([self.last, self.next])
self.background_color = (255, 255, 255, 0)
def on_mouse_left(self, pos):
new_first = self.container.first + self.container.max_widgets
if new_first <= len(self.container.widgets):
self.container.first = new_first
self.next.register(on_mouse_left)
def on_mouse_left(self, pos):
new_first = self.container.first - self.container.max_widgets
if new_first >= 0:
self.container.first = new_first
else:
self.container.first = 0
self.last.register(on_mouse_left)
self.dirty = 1