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/builtin.py
ADDED
|
@@ -0,0 +1,638 @@
|
|
|
1
|
+
import pygame as pg
|
|
2
|
+
from pathlib import Path
|
|
3
|
+
from typing import Callable, Literal
|
|
4
|
+
import textwrap
|
|
5
|
+
import os
|
|
6
|
+
|
|
7
|
+
from . import state
|
|
8
|
+
from . import elements
|
|
9
|
+
from . import workspace
|
|
10
|
+
from . import resources
|
|
11
|
+
|
|
12
|
+
__all__ = [
|
|
13
|
+
"UIPadding",
|
|
14
|
+
"UIOutline",
|
|
15
|
+
"UIBorderRadius",
|
|
16
|
+
"UIGradient",
|
|
17
|
+
"UIFont",
|
|
18
|
+
"UIPanel",
|
|
19
|
+
"UIScrollablePanel",
|
|
20
|
+
"UIText",
|
|
21
|
+
"UIButton",
|
|
22
|
+
"UITextbox",
|
|
23
|
+
"Event",
|
|
24
|
+
"create_event",
|
|
25
|
+
"on_start",
|
|
26
|
+
"on_update",
|
|
27
|
+
"on_quit",
|
|
28
|
+
"on_scene_change",
|
|
29
|
+
"on_modal_change",
|
|
30
|
+
"on_hover",
|
|
31
|
+
"on_unhover",
|
|
32
|
+
"while_hovered",
|
|
33
|
+
"on_hover_inclusive",
|
|
34
|
+
"on_unhover_inclusive",
|
|
35
|
+
"while_hovered_inclusive",
|
|
36
|
+
]
|
|
37
|
+
|
|
38
|
+
class UIPadding(elements.UIModifier):
|
|
39
|
+
def __init__(
|
|
40
|
+
self,
|
|
41
|
+
scale: float = 0,
|
|
42
|
+
offset: int = 10
|
|
43
|
+
) -> None:
|
|
44
|
+
super().__init__()
|
|
45
|
+
self.scale = scale
|
|
46
|
+
self.offset = offset
|
|
47
|
+
|
|
48
|
+
class UIOutline(elements.UIModifier):
|
|
49
|
+
def __init__(
|
|
50
|
+
self,
|
|
51
|
+
color: state.ColorValue = (0, 0, 0),
|
|
52
|
+
width: int = 5,
|
|
53
|
+
edge_type: Literal["inset", "middle", "outset"] = "middle"
|
|
54
|
+
):
|
|
55
|
+
super().__init__()
|
|
56
|
+
self.color = color
|
|
57
|
+
self.width = width
|
|
58
|
+
self.edge_type = edge_type
|
|
59
|
+
|
|
60
|
+
class UIBorderRadius(elements.UIModifier):
|
|
61
|
+
def __init__(
|
|
62
|
+
self,
|
|
63
|
+
radius: int = 0,
|
|
64
|
+
):
|
|
65
|
+
super().__init__()
|
|
66
|
+
self.radius = radius
|
|
67
|
+
|
|
68
|
+
class UIGradient(elements.UIModifier):
|
|
69
|
+
def __init__(
|
|
70
|
+
self,
|
|
71
|
+
start_color: state.ColorValue = (0, 0, 0),
|
|
72
|
+
end_color: state.ColorValue = (255, 255, 255),
|
|
73
|
+
direction: Literal["vertical", "horizontal"] = "vertical",
|
|
74
|
+
):
|
|
75
|
+
super().__init__()
|
|
76
|
+
self.start_color = start_color
|
|
77
|
+
self.end_color = end_color
|
|
78
|
+
self.direction = direction
|
|
79
|
+
|
|
80
|
+
class UIFont(elements.UIModifier):
|
|
81
|
+
def __init__(
|
|
82
|
+
self,
|
|
83
|
+
font_path: str | Path | None = None,
|
|
84
|
+
font_size: int | None = None,
|
|
85
|
+
bold: bool | None = None,
|
|
86
|
+
italic: bool | None = None,
|
|
87
|
+
antialias: bool | None = None,
|
|
88
|
+
):
|
|
89
|
+
super().__init__()
|
|
90
|
+
self.font_path = font_path
|
|
91
|
+
self.font_size = font_size
|
|
92
|
+
self.bold = bold
|
|
93
|
+
self.italic = italic
|
|
94
|
+
self.antialias = antialias
|
|
95
|
+
|
|
96
|
+
class GlobalElement(elements.UIModifier):
|
|
97
|
+
def __init__(self):
|
|
98
|
+
super().__init__()
|
|
99
|
+
|
|
100
|
+
def _lerp_color(a: state.ColorValue, b: state.ColorValue, t: float) -> tuple[int, int, int, int]:
|
|
101
|
+
if len(a) == 3:
|
|
102
|
+
a = (a[0], a[1], a[2], 255)
|
|
103
|
+
if len(b) == 3:
|
|
104
|
+
b = (b[0], b[1], b[2], 255)
|
|
105
|
+
return (
|
|
106
|
+
round(a[0] + (b[0] - a[0]) * t),
|
|
107
|
+
round(a[1] + (b[1] - a[1]) * t),
|
|
108
|
+
round(a[2] + (b[2] - a[2]) * t),
|
|
109
|
+
round(a[3] + (b[3] - a[3]) * t),
|
|
110
|
+
)
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
def _resolve_font_path(font_path: str | Path | None) -> str | None:
|
|
114
|
+
if font_path is None:
|
|
115
|
+
return None
|
|
116
|
+
path = Path(font_path)
|
|
117
|
+
if not path.is_absolute():
|
|
118
|
+
candidate = os.curdir / path
|
|
119
|
+
if candidate.exists():
|
|
120
|
+
return str(candidate)
|
|
121
|
+
if path.exists():
|
|
122
|
+
return str(path)
|
|
123
|
+
return None
|
|
124
|
+
|
|
125
|
+
def _wrap_text_to_width(font: pg.font.Font, text: str, max_width: int) -> list[str]:
|
|
126
|
+
if max_width <= 0:
|
|
127
|
+
return text.splitlines() if text else [""]
|
|
128
|
+
lines: list[str] = []
|
|
129
|
+
for raw_line in text.splitlines() or [""]:
|
|
130
|
+
if raw_line == "":
|
|
131
|
+
lines.append("")
|
|
132
|
+
continue
|
|
133
|
+
words = raw_line.split(" ")
|
|
134
|
+
current = ""
|
|
135
|
+
for word in words:
|
|
136
|
+
candidate = word if current == "" else f"{current} {word}"
|
|
137
|
+
if font.size(candidate)[0] <= max_width:
|
|
138
|
+
current = candidate
|
|
139
|
+
else:
|
|
140
|
+
if current:
|
|
141
|
+
lines.append(current)
|
|
142
|
+
# If a single word is too long, hard-wrap it.
|
|
143
|
+
if font.size(word)[0] > max_width:
|
|
144
|
+
# rough wrap by characters, then refine with font size
|
|
145
|
+
approx = textwrap.wrap(word, width=max(1, int(len(word) * max_width / max(font.size(word)[0], 1))))
|
|
146
|
+
for chunk in approx[:-1]:
|
|
147
|
+
lines.append(chunk)
|
|
148
|
+
current = approx[-1] if approx else ""
|
|
149
|
+
else:
|
|
150
|
+
current = word
|
|
151
|
+
lines.append(current)
|
|
152
|
+
return lines
|
|
153
|
+
|
|
154
|
+
|
|
155
|
+
class UIPanel(elements.UIElement):
|
|
156
|
+
def __init__(
|
|
157
|
+
self,
|
|
158
|
+
scale: state.FRect,
|
|
159
|
+
offset: state.Rect,
|
|
160
|
+
color: state.ColorValue,
|
|
161
|
+
visible: bool = True,
|
|
162
|
+
enabled: bool = True,
|
|
163
|
+
z: int = 0,
|
|
164
|
+
scrollable: bool = False,
|
|
165
|
+
scroll_speed: int = 40,
|
|
166
|
+
):
|
|
167
|
+
super().__init__(scale, offset, visible, enabled, z)
|
|
168
|
+
self.color = color
|
|
169
|
+
self.scrollable = scrollable
|
|
170
|
+
self.scroll_speed = scroll_speed
|
|
171
|
+
self.scroll_y = 0
|
|
172
|
+
|
|
173
|
+
def handle_input(self, workspace: workspace.Workspace):
|
|
174
|
+
if not self.scrollable:
|
|
175
|
+
return
|
|
176
|
+
panel_rect = self.get_rect_px(workspace)
|
|
177
|
+
if panel_rect.collidepoint(workspace.input.mouse_pos):
|
|
178
|
+
if workspace.input.mouse_wheel != 0:
|
|
179
|
+
self.scroll_y -= workspace.input.mouse_wheel * self.scroll_speed
|
|
180
|
+
self.scroll_y = max(0, self.scroll_y)
|
|
181
|
+
|
|
182
|
+
def draw(self, workspace: "workspace.Workspace"):
|
|
183
|
+
radius_modifier = self.get_modifier(UIBorderRadius)
|
|
184
|
+
border_radius = -1
|
|
185
|
+
|
|
186
|
+
if radius_modifier is not None:
|
|
187
|
+
border_radius = radius_modifier.radius
|
|
188
|
+
|
|
189
|
+
rect = self.get_rect_px(workspace)
|
|
190
|
+
|
|
191
|
+
gradient_modifier = self.get_modifier(UIGradient)
|
|
192
|
+
if gradient_modifier is not None and rect.w > 0 and rect.h > 0:
|
|
193
|
+
surf = pg.Surface((rect.w, rect.h), pg.SRCALPHA)
|
|
194
|
+
if gradient_modifier.direction == "horizontal":
|
|
195
|
+
denom = max(1, rect.w - 1)
|
|
196
|
+
for x in range(rect.w):
|
|
197
|
+
t = x / denom
|
|
198
|
+
color = _lerp_color(gradient_modifier.start_color, gradient_modifier.end_color, t)
|
|
199
|
+
pg.draw.line(surf, color, (x, 0), (x, rect.h - 1))
|
|
200
|
+
else:
|
|
201
|
+
denom = max(1, rect.h - 1)
|
|
202
|
+
for y in range(rect.h):
|
|
203
|
+
t = y / denom
|
|
204
|
+
color = _lerp_color(gradient_modifier.start_color, gradient_modifier.end_color, t)
|
|
205
|
+
pg.draw.line(surf, color, (0, y), (rect.w - 1, y))
|
|
206
|
+
if border_radius >= 0:
|
|
207
|
+
mask = pg.Surface((rect.w, rect.h), pg.SRCALPHA)
|
|
208
|
+
pg.draw.rect(mask, (255, 255, 255, 255), mask.get_rect(), border_radius=border_radius)
|
|
209
|
+
surf.blit(mask, (0, 0), special_flags=pg.BLEND_RGBA_MULT)
|
|
210
|
+
workspace.screen.blit(surf, rect.topleft)
|
|
211
|
+
else:
|
|
212
|
+
pg.draw.rect(workspace.screen, self.color, rect, border_radius=border_radius)
|
|
213
|
+
|
|
214
|
+
outline_modifier = self.get_modifier(UIOutline)
|
|
215
|
+
if outline_modifier is not None:
|
|
216
|
+
xy_offset = (-2 if outline_modifier.edge_type == "outset" else -1 if outline_modifier.edge_type == "middle" else 0) * outline_modifier.width // 2
|
|
217
|
+
|
|
218
|
+
out_rect = rect.copy()
|
|
219
|
+
|
|
220
|
+
out_rect.x += xy_offset
|
|
221
|
+
out_rect.y += xy_offset
|
|
222
|
+
out_rect.w -= xy_offset * 2
|
|
223
|
+
out_rect.h -= xy_offset * 2
|
|
224
|
+
|
|
225
|
+
pg.draw.rect(workspace.screen, outline_modifier.color, out_rect, outline_modifier.width, border_radius=border_radius)
|
|
226
|
+
|
|
227
|
+
|
|
228
|
+
class UIScrollablePanel(UIPanel):
|
|
229
|
+
def __init__(
|
|
230
|
+
self,
|
|
231
|
+
scale: state.FRect,
|
|
232
|
+
offset: state.Rect,
|
|
233
|
+
color: state.ColorValue,
|
|
234
|
+
visible: bool = True,
|
|
235
|
+
enabled: bool = True,
|
|
236
|
+
z: int = 0,
|
|
237
|
+
scroll_speed: int = 40,
|
|
238
|
+
):
|
|
239
|
+
super().__init__(scale, offset, color, visible, enabled, z, scrollable=True, scroll_speed=scroll_speed)
|
|
240
|
+
|
|
241
|
+
|
|
242
|
+
class UIText(elements.UIElement):
|
|
243
|
+
def __init__(
|
|
244
|
+
self,
|
|
245
|
+
scale: state.FRect,
|
|
246
|
+
offset: state.Rect,
|
|
247
|
+
text: str,
|
|
248
|
+
color: state.ColorValue = (0, 0, 0),
|
|
249
|
+
align: Literal[
|
|
250
|
+
"topleft", "topright", "midtop",
|
|
251
|
+
"midleft", "center", "midright",
|
|
252
|
+
"bottomleft", "bottomright", "midbottom",
|
|
253
|
+
] = "topleft",
|
|
254
|
+
visible: bool = True,
|
|
255
|
+
enabled: bool = True,
|
|
256
|
+
z: int = 0,
|
|
257
|
+
):
|
|
258
|
+
super().__init__(scale, offset, visible, enabled, z)
|
|
259
|
+
self.text = text
|
|
260
|
+
self.color = color
|
|
261
|
+
self.align = align
|
|
262
|
+
|
|
263
|
+
def draw(self, workspace: "workspace.Workspace"):
|
|
264
|
+
font_mod = self.get_modifier(UIFont)
|
|
265
|
+
font_path = None
|
|
266
|
+
font_size = 24
|
|
267
|
+
bold = False
|
|
268
|
+
italic = False
|
|
269
|
+
antialias = True
|
|
270
|
+
|
|
271
|
+
if font_mod is not None:
|
|
272
|
+
if font_mod.font_path is not None:
|
|
273
|
+
font_path = _resolve_font_path(font_mod.font_path)
|
|
274
|
+
if font_mod.font_size is not None:
|
|
275
|
+
font_size = font_mod.font_size
|
|
276
|
+
if font_mod.bold is not None:
|
|
277
|
+
bold = font_mod.bold
|
|
278
|
+
if font_mod.italic is not None:
|
|
279
|
+
italic = font_mod.italic
|
|
280
|
+
if font_mod.antialias is not None:
|
|
281
|
+
antialias = font_mod.antialias
|
|
282
|
+
|
|
283
|
+
font = pg.font.Font(font_path, font_size)
|
|
284
|
+
font.set_bold(bold)
|
|
285
|
+
font.set_italic(italic)
|
|
286
|
+
rect = self.get_rect_px(workspace)
|
|
287
|
+
lines = _wrap_text_to_width(font, self.text, rect.w)
|
|
288
|
+
line_h = font.get_linesize()
|
|
289
|
+
block_h = line_h * len(lines)
|
|
290
|
+
|
|
291
|
+
# Determine starting position based on alignment
|
|
292
|
+
if "top" in self.align:
|
|
293
|
+
start_y = rect.y
|
|
294
|
+
elif "bottom" in self.align:
|
|
295
|
+
start_y = rect.bottom - block_h
|
|
296
|
+
else:
|
|
297
|
+
start_y = rect.centery - block_h // 2
|
|
298
|
+
|
|
299
|
+
if "left" in self.align:
|
|
300
|
+
start_x = rect.x
|
|
301
|
+
elif "right" in self.align:
|
|
302
|
+
start_x = rect.right
|
|
303
|
+
else:
|
|
304
|
+
start_x = rect.centerx
|
|
305
|
+
|
|
306
|
+
for i, line in enumerate(lines):
|
|
307
|
+
surface = font.render(line, antialias, self.color)
|
|
308
|
+
text_rect = surface.get_rect()
|
|
309
|
+
if "left" in self.align:
|
|
310
|
+
text_rect.x = start_x
|
|
311
|
+
elif "right" in self.align:
|
|
312
|
+
text_rect.right = start_x
|
|
313
|
+
else:
|
|
314
|
+
text_rect.centerx = start_x
|
|
315
|
+
text_rect.y = start_y + i * line_h
|
|
316
|
+
workspace.screen.blit(surface, text_rect)
|
|
317
|
+
|
|
318
|
+
def handle_input(self, workspace: workspace.Workspace):
|
|
319
|
+
pass
|
|
320
|
+
|
|
321
|
+
class UIButton(UIPanel):
|
|
322
|
+
def __init__(
|
|
323
|
+
self,
|
|
324
|
+
scale: state.FRect,
|
|
325
|
+
offset: state.Rect,
|
|
326
|
+
text: str,
|
|
327
|
+
on_click: Callable[[workspace.Workspace], None] | None = None,
|
|
328
|
+
color: state.ColorValue = (200, 200, 200),
|
|
329
|
+
hover_color: state.ColorValue | None = None,
|
|
330
|
+
pressed_color: state.ColorValue | None = None,
|
|
331
|
+
text_color: state.ColorValue = (0, 0, 0),
|
|
332
|
+
font_path: str | None = None,
|
|
333
|
+
font_size: int = 24,
|
|
334
|
+
visible: bool = True,
|
|
335
|
+
enabled: bool = True,
|
|
336
|
+
z: int = 0,
|
|
337
|
+
):
|
|
338
|
+
super().__init__(scale, offset, color, visible, enabled, z)
|
|
339
|
+
self.on_click = on_click
|
|
340
|
+
self.idle_color = color
|
|
341
|
+
self.hover_color = hover_color if hover_color is not None else color
|
|
342
|
+
self.pressed_color = pressed_color if pressed_color is not None else color
|
|
343
|
+
self._pressed = False
|
|
344
|
+
self._label = UIText(
|
|
345
|
+
state.FRect(0, 0, 1, 1),
|
|
346
|
+
state.Rect(0, 0, 0, 0),
|
|
347
|
+
text=text,
|
|
348
|
+
color=text_color,
|
|
349
|
+
align="center",
|
|
350
|
+
enabled=False,
|
|
351
|
+
z=1,
|
|
352
|
+
)
|
|
353
|
+
self._label.parent = self
|
|
354
|
+
self._font_mod = UIFont(font_path=font_path, font_size=font_size)
|
|
355
|
+
self._label.set_modifier(self._font_mod)
|
|
356
|
+
|
|
357
|
+
@property
|
|
358
|
+
def label(self):
|
|
359
|
+
return self._label
|
|
360
|
+
|
|
361
|
+
@property
|
|
362
|
+
def text(self):
|
|
363
|
+
return self._label.text
|
|
364
|
+
|
|
365
|
+
@text.setter
|
|
366
|
+
def text(self, value: str):
|
|
367
|
+
self._label.text = value
|
|
368
|
+
|
|
369
|
+
@property
|
|
370
|
+
def text_color(self):
|
|
371
|
+
return self._label.color
|
|
372
|
+
|
|
373
|
+
@text_color.setter
|
|
374
|
+
def text_color(self, value: state.ColorValue):
|
|
375
|
+
self._label.color = value
|
|
376
|
+
|
|
377
|
+
@property
|
|
378
|
+
def font_path(self):
|
|
379
|
+
return self._font_mod.font_path
|
|
380
|
+
|
|
381
|
+
@font_path.setter
|
|
382
|
+
def font_path(self, value: str | None):
|
|
383
|
+
self._font_mod.font_path = value
|
|
384
|
+
|
|
385
|
+
@property
|
|
386
|
+
def font_size(self):
|
|
387
|
+
return self._font_mod.font_size
|
|
388
|
+
|
|
389
|
+
@font_size.setter
|
|
390
|
+
def font_size(self, value: int):
|
|
391
|
+
self._font_mod.font_size = value
|
|
392
|
+
|
|
393
|
+
def draw(self, workspace: "workspace.Workspace"):
|
|
394
|
+
self.color = self.idle_color
|
|
395
|
+
if self.is_mouse_over(workspace):
|
|
396
|
+
self.color = self.hover_color
|
|
397
|
+
if self._pressed:
|
|
398
|
+
self.color = self.pressed_color
|
|
399
|
+
super().draw(workspace)
|
|
400
|
+
|
|
401
|
+
def handle_input(self, workspace: workspace.Workspace):
|
|
402
|
+
if self.is_mouse_top(workspace) and self.is_mouse_over(workspace):
|
|
403
|
+
if workspace.input.mousebutton_down(state.MouseButton.LEFT):
|
|
404
|
+
self._pressed = True
|
|
405
|
+
if self._pressed and workspace.input.mousebutton_up(state.MouseButton.LEFT):
|
|
406
|
+
if self.is_mouse_top(workspace):
|
|
407
|
+
if self.on_click is not None:
|
|
408
|
+
self.on_click(workspace)
|
|
409
|
+
self._pressed = False
|
|
410
|
+
|
|
411
|
+
|
|
412
|
+
class UITextbox(UIPanel):
|
|
413
|
+
def __init__(
|
|
414
|
+
self,
|
|
415
|
+
scale: state.FRect,
|
|
416
|
+
offset: state.Rect,
|
|
417
|
+
text: str = "",
|
|
418
|
+
placeholder: str = "",
|
|
419
|
+
color: state.ColorValue = (255, 255, 255),
|
|
420
|
+
font_path: str | None = None,
|
|
421
|
+
font_size: int = 24,
|
|
422
|
+
text_color: state.ColorValue = (0, 0, 0),
|
|
423
|
+
placeholder_color: state.ColorValue = (120, 120, 120),
|
|
424
|
+
align: Literal['topleft', 'topright', 'midtop', 'midleft', 'center', 'midright', 'bottomleft', 'bottomright', 'midbottom'] = "topleft",
|
|
425
|
+
max_length: int | None = None,
|
|
426
|
+
visible: bool = True,
|
|
427
|
+
enabled: bool = True,
|
|
428
|
+
z: int = 0,
|
|
429
|
+
):
|
|
430
|
+
super().__init__(scale, offset, color, visible, enabled, z)
|
|
431
|
+
self.text = text
|
|
432
|
+
self.placeholder = placeholder
|
|
433
|
+
self.text_color = text_color
|
|
434
|
+
self.placeholder_color = placeholder_color
|
|
435
|
+
self._value_label = UIText(state.FRect(0, 0, 1, 1), state.Rect(0, 0, 0, 0), "", self.placeholder_color, align, enabled=False)
|
|
436
|
+
self._value_label.parent = self
|
|
437
|
+
self._font_mod = UIFont(font_path=font_path, font_size=font_size)
|
|
438
|
+
self._value_label.set_modifier(self._font_mod)
|
|
439
|
+
self.max_length = max_length
|
|
440
|
+
self.focused = False
|
|
441
|
+
self._caret_visible = True
|
|
442
|
+
self._caret_timer = 0.0
|
|
443
|
+
|
|
444
|
+
@property
|
|
445
|
+
def value_label(self):
|
|
446
|
+
return self._value_label
|
|
447
|
+
|
|
448
|
+
@property
|
|
449
|
+
def font_path(self):
|
|
450
|
+
return self._font_mod.font_path
|
|
451
|
+
|
|
452
|
+
@font_path.setter
|
|
453
|
+
def font_path(self, value: str | None):
|
|
454
|
+
self._font_mod.font_path = value
|
|
455
|
+
|
|
456
|
+
@property
|
|
457
|
+
def font_size(self):
|
|
458
|
+
return self._font_mod.font_size
|
|
459
|
+
|
|
460
|
+
@font_size.setter
|
|
461
|
+
def font_size(self, value: int):
|
|
462
|
+
self._font_mod.font_size = value
|
|
463
|
+
|
|
464
|
+
def draw(self, workspace: "workspace.Workspace"):
|
|
465
|
+
super().draw(workspace)
|
|
466
|
+
display_text = self.text if self.text else self.placeholder
|
|
467
|
+
color = self.text_color if self.text else self.placeholder_color
|
|
468
|
+
self._value_label.text = display_text
|
|
469
|
+
self._value_label.color = color
|
|
470
|
+
|
|
471
|
+
rect = self.get_rect_px(workspace)
|
|
472
|
+
|
|
473
|
+
if self.focused:
|
|
474
|
+
self._caret_timer += workspace.input.dt
|
|
475
|
+
if self._caret_timer > 0.5:
|
|
476
|
+
self._caret_timer = 0.0
|
|
477
|
+
self._caret_visible = not self._caret_visible
|
|
478
|
+
if self._caret_visible:
|
|
479
|
+
caret_x = self._value_label.get_rect_px(workspace).right + 2
|
|
480
|
+
caret_y1 = rect.y + 6
|
|
481
|
+
caret_y2 = rect.y + rect.h - 6
|
|
482
|
+
pg.draw.line(workspace.screen, self.text_color, (caret_x, caret_y1), (caret_x, caret_y2), 2)
|
|
483
|
+
|
|
484
|
+
def handle_input(self, workspace: workspace.Workspace):
|
|
485
|
+
if workspace.input.mousebutton_down(state.MouseButton.LEFT):
|
|
486
|
+
self.focused = self.is_mouse_top(workspace) and self.is_mouse_over(workspace)
|
|
487
|
+
|
|
488
|
+
if not self.focused:
|
|
489
|
+
return
|
|
490
|
+
|
|
491
|
+
for key in workspace.input.key_downs:
|
|
492
|
+
if key == state.Key.BACKSPACE:
|
|
493
|
+
self.text = self.text[:-1]
|
|
494
|
+
elif key == state.Key.RETURN:
|
|
495
|
+
pass
|
|
496
|
+
|
|
497
|
+
for chunk in workspace.input.text_input:
|
|
498
|
+
if not chunk:
|
|
499
|
+
continue
|
|
500
|
+
if any(ord(ch) < 32 for ch in chunk):
|
|
501
|
+
continue
|
|
502
|
+
if self.max_length is not None and len(self.text) >= self.max_length:
|
|
503
|
+
break
|
|
504
|
+
self.text += chunk
|
|
505
|
+
|
|
506
|
+
class Event(elements.UIElement):
|
|
507
|
+
def __init__(
|
|
508
|
+
self,
|
|
509
|
+
condition_function: Callable[[workspace.Workspace], bool],
|
|
510
|
+
event_function: Callable[[workspace.Workspace], None],
|
|
511
|
+
negative_function: Callable[[workspace.Workspace], None] | None = None,
|
|
512
|
+
enabled: bool = True,
|
|
513
|
+
once: bool = False,
|
|
514
|
+
global_event: bool = True,
|
|
515
|
+
):
|
|
516
|
+
super().__init__(state.empty_frect(), state.empty_rect(), False, enabled, 9999)
|
|
517
|
+
self.event_func = event_function
|
|
518
|
+
self.cond_func = condition_function
|
|
519
|
+
self.neg_func = negative_function
|
|
520
|
+
self.once = once
|
|
521
|
+
|
|
522
|
+
if global_event:
|
|
523
|
+
GlobalElement().parent = self
|
|
524
|
+
|
|
525
|
+
def draw(self, workspace: workspace.Workspace):
|
|
526
|
+
pass
|
|
527
|
+
|
|
528
|
+
def handle_input(self, workspace: workspace.Workspace):
|
|
529
|
+
if self.cond_func(workspace):
|
|
530
|
+
self.event_func(workspace)
|
|
531
|
+
if self.once:
|
|
532
|
+
self.enabled = False
|
|
533
|
+
elif self.neg_func:
|
|
534
|
+
self.neg_func(workspace)
|
|
535
|
+
if self.once:
|
|
536
|
+
self.enabled = False
|
|
537
|
+
|
|
538
|
+
def create_event(target: workspace.Workspace | elements.UIElement, cond_func: Callable[[workspace.Workspace], bool]):
|
|
539
|
+
def wrapper(event_func: Callable[[workspace.Workspace], None]):
|
|
540
|
+
new_event = Event(cond_func, event_func)
|
|
541
|
+
new_event.parent = target
|
|
542
|
+
return wrapper
|
|
543
|
+
|
|
544
|
+
|
|
545
|
+
def on_start(target: workspace.Workspace | elements.UIElement):
|
|
546
|
+
def wrapper(event_func: Callable[[workspace.Workspace], None]):
|
|
547
|
+
if isinstance(target, elements.Scene):
|
|
548
|
+
new_event = Event(lambda w, scene=target: w.current_scene is scene, event_func)
|
|
549
|
+
new_event.parent = target
|
|
550
|
+
return
|
|
551
|
+
elif isinstance(target, elements.UIElement):
|
|
552
|
+
new_event = Event(lambda w, modal=target: w.current_modal is modal, event_func)
|
|
553
|
+
new_event.parent = target
|
|
554
|
+
return
|
|
555
|
+
new_event = Event(lambda w: w.input.runtime == 0, event_func, once=True)
|
|
556
|
+
new_event.parent = target
|
|
557
|
+
return wrapper
|
|
558
|
+
|
|
559
|
+
|
|
560
|
+
def on_update(target: workspace.Workspace | elements.UIElement):
|
|
561
|
+
def wrapper(event_func: Callable[[workspace.Workspace], None]):
|
|
562
|
+
if isinstance(target, elements.Scene):
|
|
563
|
+
new_event = Event(lambda w, scene=target: w.current_scene is scene, event_func)
|
|
564
|
+
new_event.parent = target
|
|
565
|
+
return
|
|
566
|
+
elif isinstance(target, elements.UIElement):
|
|
567
|
+
new_event = Event(lambda w, modal=target: w.current_modal is modal, event_func)
|
|
568
|
+
new_event.parent = target
|
|
569
|
+
return
|
|
570
|
+
new_event = Event(lambda w: True, event_func)
|
|
571
|
+
new_event.parent = target
|
|
572
|
+
return wrapper
|
|
573
|
+
|
|
574
|
+
|
|
575
|
+
def on_quit(target: workspace.Workspace | elements.UIElement):
|
|
576
|
+
def wrapper(event_func: Callable[[workspace.Workspace], None]):
|
|
577
|
+
new_event = Event(lambda w: w.input.quit, event_func)
|
|
578
|
+
new_event.parent = target
|
|
579
|
+
return wrapper
|
|
580
|
+
|
|
581
|
+
|
|
582
|
+
def on_scene_change(target: workspace.Workspace | elements.UIElement):
|
|
583
|
+
def wrapper(event_func: Callable[[workspace.Workspace], None]):
|
|
584
|
+
if isinstance(target, elements.Scene):
|
|
585
|
+
new_event = Event(
|
|
586
|
+
lambda w, scene=target: w.scene_changed and w.current_scene is scene,
|
|
587
|
+
event_func,
|
|
588
|
+
)
|
|
589
|
+
new_event.parent = target
|
|
590
|
+
return
|
|
591
|
+
new_event = Event(lambda w: w.scene_changed, event_func)
|
|
592
|
+
new_event.parent = target
|
|
593
|
+
return wrapper
|
|
594
|
+
|
|
595
|
+
|
|
596
|
+
def on_modal_change(target: workspace.Workspace | elements.UIElement):
|
|
597
|
+
def wrapper(event_func: Callable[[workspace.Workspace], None]):
|
|
598
|
+
if isinstance(target, elements.UIElement) and not isinstance(target, elements.Scene):
|
|
599
|
+
new_event = Event(
|
|
600
|
+
lambda w, modal=target: w.modal_changed and w.active_modal is modal,
|
|
601
|
+
event_func,
|
|
602
|
+
)
|
|
603
|
+
new_event.parent = target
|
|
604
|
+
return
|
|
605
|
+
new_event = Event(lambda w: w.modal_changed, event_func)
|
|
606
|
+
new_event.parent = target
|
|
607
|
+
return wrapper
|
|
608
|
+
|
|
609
|
+
def on_hover(target: workspace.Workspace | elements.UIElement, hovered: elements.UIElement):
|
|
610
|
+
def wrapper(event_func: Callable[[workspace.Workspace], None]):
|
|
611
|
+
Event(lambda w: w.just_hovered(hovered), event_func).parent = target
|
|
612
|
+
return wrapper
|
|
613
|
+
|
|
614
|
+
def on_unhover(target: workspace.Workspace | elements.UIElement, hovered: elements.UIElement):
|
|
615
|
+
def wrapper(event_func: Callable[[workspace.Workspace], None]):
|
|
616
|
+
Event(lambda w: w.just_unhovered(hovered), event_func).parent = target
|
|
617
|
+
return wrapper
|
|
618
|
+
|
|
619
|
+
def while_hovered(target: workspace.Workspace | elements.UIElement, hovered: elements.UIElement):
|
|
620
|
+
def wrapper(event_func: Callable[[workspace.Workspace], None]):
|
|
621
|
+
Event(lambda w: w.is_mouse_top(hovered), event_func).parent = target
|
|
622
|
+
return wrapper
|
|
623
|
+
|
|
624
|
+
def on_hover_inclusive(target: workspace.Workspace | elements.UIElement, hovered: elements.UIElement):
|
|
625
|
+
def wrapper(event_func: Callable[[workspace.Workspace], None]):
|
|
626
|
+
Event(lambda w: w.just_hovered_inclusive(hovered), event_func).parent = target
|
|
627
|
+
return wrapper
|
|
628
|
+
|
|
629
|
+
def on_unhover_inclusive(target: workspace.Workspace | elements.UIElement, hovered: elements.UIElement):
|
|
630
|
+
def wrapper(event_func: Callable[[workspace.Workspace], None]):
|
|
631
|
+
Event(lambda w: w.just_unhovered_inclusive(hovered), event_func).parent = target
|
|
632
|
+
return wrapper
|
|
633
|
+
|
|
634
|
+
def while_hovered_inclusive(target: workspace.Workspace | elements.UIElement, hovered: elements.UIElement):
|
|
635
|
+
def wrapper(event_func: Callable[[workspace.Workspace], None]):
|
|
636
|
+
Event(lambda w: w.is_mouse_over(hovered), event_func).parent = target
|
|
637
|
+
return wrapper
|
|
638
|
+
|