PlayPy 0.2.1__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- playpy/__init__.py +222 -0
- playpy/builtin.py +638 -0
- playpy/data/default_icon.ppm +5 -0
- playpy/elements.py +205 -0
- playpy/resources.py +147 -0
- playpy/state.py +565 -0
- playpy/workspace.py +432 -0
- playpy-0.2.1.dist-info/METADATA +191 -0
- playpy-0.2.1.dist-info/RECORD +12 -0
- playpy-0.2.1.dist-info/WHEEL +5 -0
- playpy-0.2.1.dist-info/licenses/LICENSE +21 -0
- playpy-0.2.1.dist-info/top_level.txt +1 -0
playpy/elements.py
ADDED
|
@@ -0,0 +1,205 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from abc import ABC, abstractmethod
|
|
4
|
+
from typing import TypeGuard, TypeVar, TYPE_CHECKING
|
|
5
|
+
|
|
6
|
+
import pygame as pg
|
|
7
|
+
|
|
8
|
+
from .state import FRect, Rect
|
|
9
|
+
from . import resources
|
|
10
|
+
|
|
11
|
+
if TYPE_CHECKING:
|
|
12
|
+
from .workspace import Workspace
|
|
13
|
+
|
|
14
|
+
_T = TypeVar("_T", bound="UIModifier")
|
|
15
|
+
|
|
16
|
+
class UIModifier:
|
|
17
|
+
def __init__(self) -> None:
|
|
18
|
+
resources.require_init()
|
|
19
|
+
self._parent: "UIElement | None" = None
|
|
20
|
+
|
|
21
|
+
@property
|
|
22
|
+
def parent(self):
|
|
23
|
+
return self._parent
|
|
24
|
+
|
|
25
|
+
@parent.setter
|
|
26
|
+
def parent(self, value: "UIElement | None"):
|
|
27
|
+
if value is self._parent:
|
|
28
|
+
return
|
|
29
|
+
|
|
30
|
+
# Detach from existing parent, if still registered there.
|
|
31
|
+
if self._parent is not None:
|
|
32
|
+
current = self._parent._modifiers.get(type(self))
|
|
33
|
+
if current is self:
|
|
34
|
+
del self._parent._modifiers[type(self)]
|
|
35
|
+
|
|
36
|
+
self._parent = value
|
|
37
|
+
|
|
38
|
+
# Attach to new parent, replacing existing modifier of same type.
|
|
39
|
+
if value is not None:
|
|
40
|
+
existing = value._modifiers.get(type(self))
|
|
41
|
+
if existing is not None and existing is not self:
|
|
42
|
+
existing._parent = None
|
|
43
|
+
value._modifiers[type(self)] = self
|
|
44
|
+
|
|
45
|
+
@parent.deleter
|
|
46
|
+
def parent(self):
|
|
47
|
+
self.parent = None
|
|
48
|
+
|
|
49
|
+
class UIElement(ABC):
|
|
50
|
+
def __init__(
|
|
51
|
+
self,
|
|
52
|
+
scale: FRect,
|
|
53
|
+
offset: Rect,
|
|
54
|
+
visible: bool = True,
|
|
55
|
+
enabled: bool = True,
|
|
56
|
+
z: int = 0,
|
|
57
|
+
):
|
|
58
|
+
resources.require_init()
|
|
59
|
+
self._children: list[UIElement] = []
|
|
60
|
+
self._parent: "UIElement | Workspace | None" = None
|
|
61
|
+
self.scale = scale
|
|
62
|
+
self.offset = offset
|
|
63
|
+
self.enabled = enabled
|
|
64
|
+
self._visible = visible
|
|
65
|
+
self.z = z
|
|
66
|
+
self._modifiers: dict[type[UIModifier], UIModifier] = {}
|
|
67
|
+
self.block_input_when_occluded = True
|
|
68
|
+
|
|
69
|
+
@property
|
|
70
|
+
def visible(self):
|
|
71
|
+
if not isinstance(self._parent, UIElement):
|
|
72
|
+
return self._visible
|
|
73
|
+
return self._visible and self._parent.visible
|
|
74
|
+
|
|
75
|
+
@property
|
|
76
|
+
def parent(self) -> "UIElement | Workspace | None":
|
|
77
|
+
return self._parent
|
|
78
|
+
|
|
79
|
+
@parent.setter
|
|
80
|
+
def parent(self, value: "UIElement | Workspace | None"):
|
|
81
|
+
if self._parent is not None:
|
|
82
|
+
self._parent._children.remove(self)
|
|
83
|
+
self._parent = value
|
|
84
|
+
if value is not None:
|
|
85
|
+
value._children.append(self)
|
|
86
|
+
|
|
87
|
+
@property
|
|
88
|
+
def ancestors(self) -> "list[Workspace | UIElement]":
|
|
89
|
+
if self.parent is None:
|
|
90
|
+
return []
|
|
91
|
+
elif isinstance(self.parent, Workspace):
|
|
92
|
+
return [self.parent]
|
|
93
|
+
else:
|
|
94
|
+
return self.parent.ancestors + [self.parent]
|
|
95
|
+
|
|
96
|
+
@property
|
|
97
|
+
def children(self) -> list["UIElement"]:
|
|
98
|
+
return self._children
|
|
99
|
+
|
|
100
|
+
@property
|
|
101
|
+
def descendants(self) -> list["UIElement"]:
|
|
102
|
+
descendants: list[UIElement] = []
|
|
103
|
+
for child in self._children:
|
|
104
|
+
descendants.append(child)
|
|
105
|
+
descendants.extend(child.descendants)
|
|
106
|
+
return descendants
|
|
107
|
+
|
|
108
|
+
def add_child(self, child: "UIElement", z: int | None = None) -> None:
|
|
109
|
+
if z is not None:
|
|
110
|
+
child.z = z
|
|
111
|
+
child.parent = self
|
|
112
|
+
|
|
113
|
+
def remove_child(self, child: "UIElement") -> None:
|
|
114
|
+
child.parent = None
|
|
115
|
+
|
|
116
|
+
def set_modifier(self, modifier: UIModifier) -> None:
|
|
117
|
+
modifier.parent = self
|
|
118
|
+
|
|
119
|
+
def get_modifier(self, mod_type: type[_T]) -> _T | None:
|
|
120
|
+
return self._modifiers.get(mod_type) # type: ignore
|
|
121
|
+
|
|
122
|
+
def remove_modifier(self, mod_type: type) -> None:
|
|
123
|
+
if mod_type in self._modifiers:
|
|
124
|
+
self._modifiers[mod_type].parent = None
|
|
125
|
+
|
|
126
|
+
@abstractmethod
|
|
127
|
+
def draw(self, workspace: "Workspace"): ...
|
|
128
|
+
|
|
129
|
+
@abstractmethod
|
|
130
|
+
def handle_input(self, workspace: "Workspace"): ...
|
|
131
|
+
|
|
132
|
+
def get_rect_px(self, workspace: "Workspace") -> pg.Rect:
|
|
133
|
+
return workspace.get_element_rect(self)
|
|
134
|
+
|
|
135
|
+
def is_mouse_over(self, workspace: "Workspace") -> bool:
|
|
136
|
+
return workspace.is_mouse_over(self)
|
|
137
|
+
|
|
138
|
+
def is_mouse_top(self, workspace: "Workspace") -> bool:
|
|
139
|
+
return workspace.is_mouse_top(self)
|
|
140
|
+
|
|
141
|
+
def is_descendant_of(self, ancestor: "UIElement | Workspace") -> bool:
|
|
142
|
+
current = self._parent
|
|
143
|
+
while current is not None:
|
|
144
|
+
if current is ancestor:
|
|
145
|
+
return True
|
|
146
|
+
if not isinstance(current, UIElement):
|
|
147
|
+
break
|
|
148
|
+
current = current._parent
|
|
149
|
+
return False
|
|
150
|
+
|
|
151
|
+
def is_ancestor_of(self, descendant: "UIElement") -> bool:
|
|
152
|
+
return descendant.is_descendant_of(self)
|
|
153
|
+
|
|
154
|
+
def is_parent_of(self, child: "UIElement") -> bool:
|
|
155
|
+
return child._parent is self
|
|
156
|
+
|
|
157
|
+
def is_child_of(self, parent: "UIElement"):
|
|
158
|
+
return parent.is_parent_of(self)
|
|
159
|
+
|
|
160
|
+
def destroy(self):
|
|
161
|
+
self.parent = None
|
|
162
|
+
for child in self.children:
|
|
163
|
+
child.destroy()
|
|
164
|
+
|
|
165
|
+
class Scene(UIElement):
|
|
166
|
+
def __init__(
|
|
167
|
+
self,
|
|
168
|
+
scale: FRect | None = None,
|
|
169
|
+
offset: Rect | None = None,
|
|
170
|
+
visible: bool = True,
|
|
171
|
+
enabled: bool = True,
|
|
172
|
+
z: int = 0,
|
|
173
|
+
name: str | None = None,
|
|
174
|
+
):
|
|
175
|
+
if scale is None:
|
|
176
|
+
scale = FRect(0, 0, 1, 1)
|
|
177
|
+
if offset is None:
|
|
178
|
+
offset = Rect(0, 0, 0, 0)
|
|
179
|
+
super().__init__(scale, offset, visible=visible, enabled=enabled, z=z)
|
|
180
|
+
self.name = name
|
|
181
|
+
|
|
182
|
+
def on_enter(self, workspace: "Workspace"):
|
|
183
|
+
pass
|
|
184
|
+
|
|
185
|
+
def on_exit(self, workspace: "Workspace"):
|
|
186
|
+
pass
|
|
187
|
+
|
|
188
|
+
def on_pause(self, workspace: "Workspace"):
|
|
189
|
+
pass
|
|
190
|
+
|
|
191
|
+
def on_resume(self, workspace: "Workspace"):
|
|
192
|
+
pass
|
|
193
|
+
|
|
194
|
+
def draw(self, workspace: "Workspace"):
|
|
195
|
+
pass
|
|
196
|
+
|
|
197
|
+
def handle_input(self, workspace: "Workspace"):
|
|
198
|
+
pass
|
|
199
|
+
|
|
200
|
+
|
|
201
|
+
__all__ = [
|
|
202
|
+
"UIModifier",
|
|
203
|
+
"UIElement",
|
|
204
|
+
"Scene",
|
|
205
|
+
]
|
playpy/resources.py
ADDED
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import os
|
|
4
|
+
import tomllib
|
|
5
|
+
|
|
6
|
+
os.environ["PYGAME_HIDE_SUPPORT_PROMPT"] = "1"
|
|
7
|
+
|
|
8
|
+
import inspect
|
|
9
|
+
import sys
|
|
10
|
+
from pathlib import Path
|
|
11
|
+
from enum import Enum, auto
|
|
12
|
+
|
|
13
|
+
import pygame as pg
|
|
14
|
+
import colorama as clr
|
|
15
|
+
|
|
16
|
+
clr.init()
|
|
17
|
+
|
|
18
|
+
__version__ = "0.2.1"
|
|
19
|
+
|
|
20
|
+
DATA_DIR = Path(__file__).resolve().parent / "data"
|
|
21
|
+
DEFAULT_ICON_PATH = DATA_DIR / "default_icon.ppm"
|
|
22
|
+
|
|
23
|
+
_init_message = (
|
|
24
|
+
clr.Style.BRIGHT + f"Hello from PlayPy! (V{__version__})\n" +
|
|
25
|
+
clr.Style.DIM + f"Pygame V{pg.ver}\n" +
|
|
26
|
+
clr.Style.NORMAL + clr.Fore.YELLOW + "[WARNING]: This package is a beta. Things will be changed, and bugs might appear.\n" +
|
|
27
|
+
clr.Style.RESET_ALL + "PlayPy is a lightweight Python library for creating simple games and interactive applications with ease. It provides a straightforward API for handling graphics, input, and basic game mechanics, making it ideal for beginners and those looking to quickly prototype their ideas."
|
|
28
|
+
)
|
|
29
|
+
|
|
30
|
+
_initialized = False
|
|
31
|
+
_debug = False
|
|
32
|
+
|
|
33
|
+
def enter_debug_mode():
|
|
34
|
+
if _initialized:
|
|
35
|
+
log(Severity.ERROR, InitializationError, 'Please use "enter_debug_mode" before all other methods as it cannot be used after initialization.')
|
|
36
|
+
_debug = True
|
|
37
|
+
|
|
38
|
+
def init():
|
|
39
|
+
global _initialized
|
|
40
|
+
if not _initialized:
|
|
41
|
+
pg.init()
|
|
42
|
+
_initialized = True
|
|
43
|
+
if _init_message:
|
|
44
|
+
print(_init_message)
|
|
45
|
+
|
|
46
|
+
def require_init():
|
|
47
|
+
if not _initialized:
|
|
48
|
+
log(Severity.WARNING, InitializationWarning, "Please call the `plp.init()` function before doing anything else in this module.")
|
|
49
|
+
init()
|
|
50
|
+
|
|
51
|
+
class Log: pass
|
|
52
|
+
|
|
53
|
+
class LogWarning(Log): pass
|
|
54
|
+
|
|
55
|
+
class LogError(Log): pass
|
|
56
|
+
|
|
57
|
+
class InitializationError(LogError): pass
|
|
58
|
+
|
|
59
|
+
class InitializationWarning(LogWarning): pass
|
|
60
|
+
|
|
61
|
+
class InvalidDirectory(LogError): pass
|
|
62
|
+
|
|
63
|
+
class InvalidValue(LogError): pass
|
|
64
|
+
|
|
65
|
+
class Severity(Enum):
|
|
66
|
+
INFO = auto()
|
|
67
|
+
WARNING = auto()
|
|
68
|
+
ERROR = auto()
|
|
69
|
+
CRITICAL = auto()
|
|
70
|
+
|
|
71
|
+
sev_color: dict[Severity, str] = {
|
|
72
|
+
Severity.INFO: clr.Fore.BLUE,
|
|
73
|
+
Severity.WARNING: clr.Fore.YELLOW,
|
|
74
|
+
Severity.ERROR: clr.Fore.RED,
|
|
75
|
+
Severity.CRITICAL: clr.Style.BRIGHT + clr.Fore.RED
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
sev_stack_size: dict[Severity, int | None] = {
|
|
79
|
+
Severity.INFO: 0,
|
|
80
|
+
Severity.WARNING: 3,
|
|
81
|
+
Severity.ERROR: 5,
|
|
82
|
+
Severity.CRITICAL: None
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
no_stack_error_type = InvalidDirectory
|
|
86
|
+
|
|
87
|
+
def no_stack_error(log_category: type[Log]):
|
|
88
|
+
print(
|
|
89
|
+
f"{sev_color[Severity.CRITICAL]}[CRITICAL]: <Location not found>\n" +
|
|
90
|
+
f" [{no_stack_error_type.__name__}]: Could not print [{log_category.__name__}] log as the current stack was not found.{clr.Style.RESET_ALL}"
|
|
91
|
+
)
|
|
92
|
+
sys.exit(1)
|
|
93
|
+
|
|
94
|
+
def log(severity: Severity, category: type[Log], message: str):
|
|
95
|
+
full_msg: str = ""
|
|
96
|
+
|
|
97
|
+
current_frame = inspect.currentframe()
|
|
98
|
+
if not current_frame:
|
|
99
|
+
no_stack_error(category)
|
|
100
|
+
return
|
|
101
|
+
current_frame = current_frame.f_back
|
|
102
|
+
if not current_frame:
|
|
103
|
+
no_stack_error(category)
|
|
104
|
+
return
|
|
105
|
+
|
|
106
|
+
warn_info = inspect.getframeinfo(current_frame)
|
|
107
|
+
|
|
108
|
+
full_msg += f"{sev_color[severity]}[{severity.name}] In {warn_info.filename}, line {warn_info.lineno}\n"
|
|
109
|
+
|
|
110
|
+
if severity != Severity.CRITICAL:
|
|
111
|
+
full_msg += clr.Style.RESET_ALL
|
|
112
|
+
|
|
113
|
+
full_msg += f" [{category.__name__}]: {message}\n"
|
|
114
|
+
|
|
115
|
+
full_msg += clr.Style.RESET_ALL + clr.Style.DIM
|
|
116
|
+
|
|
117
|
+
current_frame = current_frame.f_back
|
|
118
|
+
stack_left = sev_stack_size[severity]
|
|
119
|
+
|
|
120
|
+
while current_frame and (stack_left is None or stack_left > 0):
|
|
121
|
+
info = inspect.getframeinfo(current_frame)
|
|
122
|
+
full_msg += f" {info.filename}:{info.lineno}{'\n ' + info.code_context[0].strip() if info.code_context else ''}\n"
|
|
123
|
+
current_frame = current_frame.f_back
|
|
124
|
+
if stack_left is not None:
|
|
125
|
+
stack_left -= 1
|
|
126
|
+
|
|
127
|
+
full_msg += clr.Style.RESET_ALL
|
|
128
|
+
|
|
129
|
+
print(full_msg)
|
|
130
|
+
|
|
131
|
+
if (severity == Severity.ERROR or severity == Severity.CRITICAL) and not _debug:
|
|
132
|
+
sys.exit(1)
|
|
133
|
+
|
|
134
|
+
__all__ = [
|
|
135
|
+
"DATA_DIR",
|
|
136
|
+
"DEFAULT_ICON_PATH",
|
|
137
|
+
"__version__",
|
|
138
|
+
"enter_debug_mode",
|
|
139
|
+
"init",
|
|
140
|
+
"Log",
|
|
141
|
+
"LogWarning",
|
|
142
|
+
"LogError",
|
|
143
|
+
"InitializationError",
|
|
144
|
+
"InitializationWarning",
|
|
145
|
+
"Severity",
|
|
146
|
+
"log"
|
|
147
|
+
]
|