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/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
+ ]