PlayPy 0.4.0__tar.gz → 0.5.0__tar.gz

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.
Files changed (42) hide show
  1. playpy-0.5.0/PKG-INFO +715 -0
  2. playpy-0.5.0/README.md +703 -0
  3. {playpy-0.4.0 → playpy-0.5.0}/pyproject.toml +1 -1
  4. playpy-0.5.0/src/PlayPy.egg-info/PKG-INFO +715 -0
  5. playpy-0.5.0/src/PlayPy.egg-info/SOURCES.txt +32 -0
  6. playpy-0.5.0/src/playpy/__init__.py +816 -0
  7. {playpy-0.4.0 → playpy-0.5.0}/src/playpy/builtin/__init__.py +25 -8
  8. {playpy-0.4.0 → playpy-0.5.0}/src/playpy/builtin/effects.py +28 -23
  9. {playpy-0.4.0 → playpy-0.5.0}/src/playpy/builtin/elements.py +94 -70
  10. playpy-0.5.0/src/playpy/builtin/events.py +283 -0
  11. {playpy-0.4.0 → playpy-0.5.0}/src/playpy/core/__init__.py +7 -0
  12. {playpy-0.4.0 → playpy-0.5.0}/src/playpy/core/elements.py +40 -37
  13. {playpy-0.4.0 → playpy-0.5.0}/src/playpy/core/resources.py +15 -14
  14. playpy-0.5.0/src/playpy/core/state/__init__.py +36 -0
  15. playpy-0.5.0/src/playpy/core/state/asset.py +230 -0
  16. playpy-0.5.0/src/playpy/core/state/input.py +395 -0
  17. playpy-0.5.0/src/playpy/core/state/rect.py +399 -0
  18. playpy-0.5.0/src/playpy/core/state/tween.py +214 -0
  19. playpy-0.5.0/src/playpy/core/workspace/__init__.py +6 -0
  20. playpy-0.5.0/src/playpy/core/workspace/display.py +220 -0
  21. playpy-0.5.0/src/playpy/core/workspace/element_hierarchy.py +153 -0
  22. playpy-0.5.0/src/playpy/core/workspace/element_input.py +134 -0
  23. playpy-0.5.0/src/playpy/core/workspace/input.py +300 -0
  24. playpy-0.5.0/src/playpy/core/workspace/rendering.py +56 -0
  25. playpy-0.5.0/src/playpy/core/workspace/scenes.py +156 -0
  26. playpy-0.5.0/src/playpy/core/workspace/tween.py +61 -0
  27. playpy-0.5.0/src/playpy/core/workspace/workspace.py +286 -0
  28. playpy-0.4.0/PKG-INFO +0 -530
  29. playpy-0.4.0/README.md +0 -518
  30. playpy-0.4.0/src/PlayPy.egg-info/PKG-INFO +0 -530
  31. playpy-0.4.0/src/PlayPy.egg-info/SOURCES.txt +0 -20
  32. playpy-0.4.0/src/playpy/__init__.py +0 -607
  33. playpy-0.4.0/src/playpy/builtin/events.py +0 -153
  34. playpy-0.4.0/src/playpy/core/state.py +0 -931
  35. playpy-0.4.0/src/playpy/core/workspace.py +0 -624
  36. {playpy-0.4.0 → playpy-0.5.0}/LICENSE.txt +0 -0
  37. {playpy-0.4.0 → playpy-0.5.0}/setup.cfg +0 -0
  38. {playpy-0.4.0 → playpy-0.5.0}/src/PlayPy.egg-info/dependency_links.txt +0 -0
  39. {playpy-0.4.0 → playpy-0.5.0}/src/PlayPy.egg-info/requires.txt +0 -0
  40. {playpy-0.4.0 → playpy-0.5.0}/src/PlayPy.egg-info/top_level.txt +0 -0
  41. {playpy-0.4.0 → playpy-0.5.0}/src/playpy/builtin/components.py +0 -0
  42. {playpy-0.4.0 → playpy-0.5.0}/src/playpy/data/default_icon.ppm +0 -0
playpy-0.5.0/PKG-INFO ADDED
@@ -0,0 +1,715 @@
1
+ Metadata-Version: 2.4
2
+ Name: PlayPy
3
+ Version: 0.5.0
4
+ Summary: PlayPy is a lightweight Python library for creating games, tools, and interactive applications using a retained-mode UI and scene system built on top of pygame. It focuses on rapid prototyping, composable rendering, and simple but powerful layout primitives.
5
+ Author-email: angel <angyv2861@gmail.com>
6
+ Requires-Python: >=3.14
7
+ Description-Content-Type: text/markdown
8
+ License-File: LICENSE.txt
9
+ Requires-Dist: pygame-ce>=2.5.6
10
+ Requires-Dist: colorama>=0.4.6
11
+ Dynamic: license-file
12
+
13
+ # PlayPy (0.5.0)
14
+
15
+ PlayPy is a lightweight Python library for creating games, tools, and interactive applications using a retained-mode UI and scene system built on top of pygame. It focuses on rapid prototyping, composable rendering, and simple but powerful layout primitives.
16
+
17
+ ## Requirements
18
+
19
+ - Python `>=3.11`
20
+ - `pygame(-ce) >=2.6.1`
21
+
22
+ ## Installation
23
+
24
+ ```bash
25
+ pip install playpy
26
+ ```
27
+
28
+ If that does not work:
29
+
30
+ ```bash
31
+ python -m pip install playpy
32
+ ```
33
+
34
+ ---
35
+
36
+ # Core Concepts
37
+
38
+ ## Module State
39
+
40
+ Use the following methods to control the state of the module:
41
+
42
+ ```python
43
+ plp.init()
44
+ plp.quit()
45
+ ```
46
+
47
+ ---
48
+
49
+ ## Workspace
50
+
51
+ `Workspace` owns:
52
+ - the window
53
+ - render loop
54
+ - input state
55
+ - scene stack
56
+ - coroutine scheduler
57
+
58
+ Key methods:
59
+
60
+ ```python
61
+ ws.run()
62
+ ws.quit() # keep in mind that this method does not call plp.quit()
63
+ ws.wait(seconds)
64
+
65
+ ws.queue_scene_change(scene)
66
+ ws.queue_scene_push(scene)
67
+ ws.queue_scene_pop(scene=None)
68
+
69
+ ws.step()
70
+ ```
71
+
72
+ ### Scene Scope
73
+
74
+ Temporarily push a scene using a context manager:
75
+
76
+ ```python
77
+ with ws.scene_scope(scene) as (scn, handle):
78
+ ...
79
+ ```
80
+
81
+ Call:
82
+
83
+ ```python
84
+ handle.disconnect()
85
+ ```
86
+
87
+ to remotely pop the scoped scene.
88
+
89
+ The scoped scene will automatically be disconnected if not disconnected manually.
90
+
91
+ ---
92
+
93
+ ### Screen State
94
+ Use `ws.resizable`, `ws.maximized`, and `ws.fullscreen` to check and change current state.
95
+ The screen must be resizable to be maximized or fullscreen, and cannot be maximized and fullscreen at the same time.
96
+ Use `ws.resized`, `ws.maxed`, and `ws.restored` to check status on current frame.
97
+
98
+ ---
99
+
100
+ ## Layout Values
101
+
102
+ PlayPy uses two rectangle value types:
103
+
104
+ ### `FRect`:
105
+ Relative scale values.
106
+
107
+ ```python
108
+ plp.FRect(x, y, w, h)
109
+ ```
110
+
111
+ ### `Rect`:
112
+ Absolute pixel offsets.
113
+
114
+ ```python
115
+ plp.Rect(x, y, w, h)
116
+ ```
117
+
118
+ ---
119
+
120
+ Final element rectangle:
121
+
122
+ ```text
123
+ (scale * parent_size) + offset
124
+ ```
125
+
126
+ Helpers:
127
+
128
+ ```python
129
+ plp.empty_rect() -> Rect(0, 0, 0, 0)
130
+ plp.empty_frect() -> FRect(0, 0, 0, 0)
131
+ plp.full_screen_rect() -> (FRect(0, 0, 1, 1), Rect(0, 0, 0, 0))
132
+ ```
133
+
134
+ Rect types are iterable and support multiple constructor overloads.
135
+
136
+ ---
137
+
138
+ ## Assets
139
+
140
+ Assets are reusable wrappers around external files such as images, animations, and sounds.
141
+
142
+ PlayPy assets load lazily: they store the path when created, then load the underlying pygame resource when needed. You can also call `load()` ahead of time to preload an asset and reduce latency during gameplay.
143
+
144
+ ### Sprites and Animations
145
+
146
+ `Sprite`s load and store images. Initialize them by passing in a path. (`Path` or `str` object)
147
+
148
+ `Animation`s store multiple `Sprite`s and allow for changing of animation settings (FPS, loop, etc.)
149
+ Pass in either sprite paths (`Path` or `str` object), or `Sprite`s themselves.
150
+
151
+ ### Sounds
152
+
153
+ `Sound`s load and store user-imported sounds from sound files. Initialize them by passing in a path. (`Path` or `str` object)
154
+
155
+ Useful methods:
156
+ ```python
157
+ sound = plp.Sound("my_awesome_sound.wav")
158
+
159
+ sound.load()
160
+ sound.play(loop_amt=3, maxtime=10, fade_ms=500)
161
+ sound.stop()
162
+ sound.set_volume(.5)
163
+ volume = sound.get_volume()
164
+ ```
165
+
166
+ ## Tweening
167
+
168
+ `Tween`ing is a way to smoothly change values. (like element positions/colors)
169
+
170
+ `Tween` constructor:
171
+ ```python
172
+ tween = plp.Tween(
173
+ tweened: list[TweenedValue],
174
+ target: list[Any],
175
+ easing_function: TweenEasingFunction | Callable[[float], float] = "cubic",
176
+ easing_style: TweenEasingStyle | None = None,
177
+ length: float = 1,
178
+ looped: bool = False,
179
+ clear_on_finish: bool = True,
180
+ )
181
+ ```
182
+
183
+ Useful methods/properties include...
184
+ ```python
185
+ tween.looped # whether or not the tween loops
186
+ tween.length # the length of the tween
187
+ tween.elapsed # the current elapsed time in the tween
188
+ tween.finished # whether or not the tween has finished (always false on looped tweens)
189
+
190
+ tween.edit_easing(function, style) # changes the easing function and style of the tween
191
+ tween.play() # plays the tween at the current elapsed time
192
+ tween.pause() # stops the tween at the current elapsed time
193
+ tween.start() # plays the tween from the start, remapping the start points of the tween to the current values
194
+ tween.restart() # plays the tween from the start, reverting the current values of the tween to the start point
195
+ tween.stop() # instantly ends the tween; start() must be used to restart a stopped tween.
196
+ ```
197
+
198
+ ### Handling Tweened Values
199
+
200
+ `TweenedValue` handles all tweenable values.
201
+ ```python
202
+ val = plp.TweenedValue(
203
+ base_object: object | None,
204
+ key: Any
205
+ )
206
+ ```
207
+
208
+ `TweenedValue`s support:
209
+ - attributes of a class/instance: `TweenedValue(x, "y") = x.y`
210
+ - indices/keys of a `list`/`dict`: `TweenedValue(x, y) = x[y]`
211
+ - standalone values: `TweenedValue(None, x) = x`.
212
+
213
+ Useful methods/properties include...
214
+ ```python
215
+ tweened_value.hierarchy_type # returns the hierarchy type (see above) of the TweenedValue ("standalone" / "attribute" / "key" / "index")
216
+
217
+ tweened_value.get() # gets the current value of the TweenedValue
218
+ tweened_value.set(value) # sets the current value of the TweenedValue
219
+ ```
220
+
221
+ ### Integration Into Workspace
222
+
223
+ For the tween to update each frame, you must attach it to the workspace.
224
+
225
+ Active tween helper methods/properties:
226
+ ```python
227
+ ws.active_tweens # returns all active tweens (cannot be overwritten)
228
+
229
+ index = ws.add_tween(tween) # adds a new tween to the workspace and returns it
230
+
231
+ tween = ws.remove_tween(index) # removes a tween based on its index and returns it
232
+ tween = ws.remove_tween(tween) # removes a tween and returns it
233
+
234
+ tween = ws.get_tween(index) # gets a tween from its index
235
+
236
+ cleared_tweens = ws.clear_tweens() # clears all active tweens and returns them
237
+ ```
238
+
239
+ ---
240
+
241
+ # Parenting
242
+
243
+ Any `Element` can contain children.
244
+
245
+ ```python
246
+ child.parent = parent
247
+ ```
248
+
249
+ or:
250
+
251
+ ```python
252
+ parent.add_child(child)
253
+ ```
254
+
255
+ Relationship helpers:
256
+
257
+ ```python
258
+ is_parent_of()
259
+ is_child_of()
260
+
261
+ is_ancestor_of()
262
+ is_descendant_of()
263
+ ```
264
+
265
+ Properties:
266
+
267
+ ```python
268
+ parent
269
+ children
270
+ ancestors
271
+ descendants
272
+ ```
273
+
274
+ ---
275
+
276
+ # Rendering System
277
+
278
+ PlayPy uses a compositing pipeline.
279
+
280
+ `Element.draw()` returns a `SurfaceHandler` instead of drawing directly to the workspace.
281
+
282
+ This enables:
283
+ - outlines for arbitrary shapes
284
+ - gradients inheriting shape alpha
285
+ - subtree compositing
286
+ - layered visual effects
287
+ - future post-processing support
288
+
289
+ ---
290
+
291
+ # Elements
292
+
293
+ ## Core Elements
294
+
295
+ - `Panel`
296
+ - `Text`
297
+ - `Button`
298
+ - `Textbox`
299
+ - `Tooltip`
300
+ - `Line`
301
+ - `Scene`
302
+
303
+ All elements have the following attributes:
304
+
305
+ ```python
306
+ Element(
307
+ scale: FRectValue, # position/size relative to the parent of the element
308
+ offset: RectValue, # absolute position/size of the element
309
+ visible: bool = True, # whether or not this element is visible (processed while drawing) (Tooltip does not have this as it automates visibility)
310
+ enabled: bool = True, # whether or not this element is enabled (processed when input handling)
311
+ block_input_when_occluded: bool = False, # whether or not to block the input of this element when it is hovered and another element is hovered above this one
312
+ z: int = 0, # the z layer of the object
313
+ ignores_environment: bool = False, # whether or not this element ignores its environment. If an object ignores its environment, it will not obide by its parent's components or child clipping.
314
+ )
315
+ ```
316
+
317
+ Unless `ignores_environment` is set, elements natively cut off children that are outside of their bounds.
318
+
319
+ ## Effects
320
+
321
+ `Effect` is a subclass of `Element` that visually modifies its parent subtree.
322
+
323
+ Built-in effects:
324
+
325
+ - `Gradient`
326
+ - `ButtonGradient`
327
+ - `Outline`
328
+ - `BorderRadius`
329
+ - `VisualLayer`
330
+
331
+ Effects are ordered using `z`.
332
+
333
+ Typical layering:
334
+
335
+ ```text
336
+ Negative z:
337
+ backgrounds/gradients
338
+
339
+ Normal z:
340
+ content
341
+
342
+ Positive z:
343
+ outlines/glows/overlays
344
+ ```
345
+
346
+ ## Components
347
+
348
+ `Component`s modify behavior/layout but do not draw.
349
+
350
+ Built-in components:
351
+
352
+ - `Padding`
353
+ - `Font`
354
+ - `Camera`
355
+ - `Scrollable`
356
+ - `GlobalElement`
357
+
358
+ Attach/get/remove:
359
+
360
+ ```python
361
+ element.set_component(component)
362
+
363
+ element.get_component(ComponentType)
364
+
365
+ element.remove_component(ComponentType)
366
+ ```
367
+
368
+ or:
369
+
370
+ ```python
371
+ component.parent = element
372
+ ```
373
+
374
+ ---
375
+
376
+ # Input State
377
+
378
+ Access input using:
379
+
380
+ ```python
381
+ ws.input
382
+ ```
383
+
384
+ Controller profile changes are tracked on the workspace for the current frame:
385
+
386
+ ```python
387
+ ws.profile_changes
388
+ ws.profiles_added
389
+ ws.profiles_removed
390
+ ws.bad_joystick_indices
391
+ ```
392
+
393
+ Other miscellaneous controller properties / methods:
394
+
395
+ ```python
396
+ ws.get_controller_name(profile)
397
+
398
+ ws.rumble_controller(profile, strength, duration_ms)
399
+ ws.stop_rumble_controller(profile)
400
+ ```
401
+
402
+ Properties:
403
+
404
+ ```python
405
+ ws.input.keys_pressed
406
+ ws.input.key_downs
407
+ ws.input.key_ups
408
+
409
+ ws.input.mouse_buttons_pressed
410
+ ws.input.mouse_downs
411
+ ws.input.mouse_ups
412
+
413
+ ws.input.mouse_pos
414
+ ws.input.mouse_delta
415
+ ws.input.mouse_wheel
416
+
417
+ ws.input.controller_buttons_pressed
418
+ ws.input.controller_ups
419
+ ws.input.controller_downs
420
+
421
+ ws.input.controller_left_sticks
422
+ ws.input.controller_right_sticks
423
+ ws.input.controller_left_sticks_deltas
424
+ ws.input.controller_right_stick_deltas
425
+
426
+ ws.input.text_input
427
+
428
+ ws.input.dt
429
+ ws.input.running_fps
430
+ ws.input.runtime
431
+ ws.input.quit
432
+ ```
433
+
434
+ Helper methods:
435
+
436
+ ```python
437
+ ws.input.key_held()
438
+ ws.input.key_down()
439
+ ws.input.key_up()
440
+
441
+ ws.input.mousebutton_held()
442
+ ws.input.mousebutton_down()
443
+ ws.input.mousebutton_up()
444
+
445
+ ws.input.controllerbutton_held()
446
+ ws.input.controllerbutton_down()
447
+ ws.input.controllerbutton_up()
448
+
449
+ ws.input.left_stick(profile)
450
+ ws.input.right_stick(profile)
451
+ ws.input.left_stick_delta(profile)
452
+ ws.input.right_stick_delta(profile)
453
+
454
+ ws.input.input_action_held()
455
+ ws.input.input_action_down()
456
+ ws.input.input_action_up()
457
+ ```
458
+
459
+ Use `InputAction`s for easier keybind compatibility. Any matching key, mouse button, or controller button can trigger the action.
460
+
461
+ ```python
462
+ keybind = plp.InputAction(
463
+ keys = [plp.Key.SPACE, plp.Key.RETURN],
464
+ mouse_buttons = [plp.MouseButton.LEFT],
465
+ controller_buttons = [plp.ControllerButton.A, plp.ControllerButton.B],
466
+ profiles = [plp.InputProfile.KEYBOARD_MOUSE, plp.InputProfile.CONTROLLER_0]
467
+ )
468
+ ```
469
+
470
+ InputAction event helpers can run handlers directly from those keybinds:
471
+
472
+ ```python
473
+ @plp.on_input_action_down(ws, keybind)
474
+ def activate(w: plp.Workspace):
475
+ ...
476
+
477
+ @plp.while_input_action(ws, keybind)
478
+ def charge(w: plp.Workspace):
479
+ ...
480
+
481
+ @plp.on_input_action_up(ws, keybind)
482
+ def release(w: plp.Workspace):
483
+ ...
484
+ ```
485
+
486
+ Raw input event helpers are available for direct key, mouse button, and controller button checks:
487
+
488
+ ```python
489
+ @plp.on_key_down(ws, plp.Key.ESCAPE)
490
+ def quit_game(w: plp.Workspace):
491
+ w.quit()
492
+
493
+ @plp.on_mousebutton_down(ws, plp.MouseButton.LEFT)
494
+ def click(w: plp.Workspace):
495
+ ...
496
+
497
+ @plp.on_controllerbutton_down(ws, plp.ControllerButton.A, plp.InputProfile.CONTROLLER_0)
498
+ def confirm(w: plp.Workspace):
499
+ ...
500
+ ```
501
+
502
+ ---
503
+
504
+ # Hover State
505
+
506
+ Workspace hover helpers:
507
+
508
+ ```python
509
+ ws.is_mouse_top(element)
510
+ ws.is_mouse_over(element)
511
+
512
+ ws.just_hovered(element)
513
+ ws.just_unhovered(element)
514
+
515
+ ws.just_hovered_inclusive(element)
516
+ ws.just_unhovered_inclusive(element)
517
+ ```
518
+
519
+ ---
520
+
521
+ # Events
522
+
523
+ Decorator helpers create `Event` elements.
524
+
525
+ ```python
526
+ @plp.on_start(target)
527
+ @plp.on_update(target)
528
+ @plp.on_quit(target)
529
+
530
+ @plp.on_resize(target)
531
+ @plp.on_maximize(target)
532
+ @plp.on_restore(target)
533
+
534
+ @plp.on_scene_change(target)
535
+
536
+ @plp.on_profile_changed(target)
537
+ @plp.on_profile_added(target)
538
+ @plp.on_profile_removed(target)
539
+ @plp.on_controller_not_connected(target)
540
+
541
+ @plp.on_input_action_down(target, action)
542
+ @plp.on_input_action_up(target, action)
543
+ @plp.while_input_action(target, action)
544
+
545
+ @plp.on_key_down(target, key, profile=None)
546
+ @plp.on_key_up(target, key, profile=None)
547
+ @plp.while_key_held(target, key, profile=None)
548
+
549
+ @plp.on_mousebutton_down(target, mousebutton, profile=None)
550
+ @plp.on_mousebutton_up(target, mousebutton, profile=None)
551
+ @plp.while_mousebutton_held(target, mousebutton, profile=None)
552
+
553
+ @plp.on_controllerbutton_down(target, controllerbutton, profile=None)
554
+ @plp.on_controllerbutton_up(target, controllerbutton, profile=None)
555
+ @plp.while_controllerbutton_held(target, controllerbutton, profile=None)
556
+
557
+ @on_hover(...)
558
+ @on_unhover(...)
559
+ @while_hovered(...)
560
+
561
+ @on_hover_inclusive(...)
562
+ @on_unhover_inclusive(...)
563
+ @while_hovered_inclusive(...)
564
+
565
+ @plp.create_event(target, condition)
566
+ ```
567
+
568
+ Events attached to the main workspace are global by default.
569
+
570
+ Example:
571
+
572
+ ```python
573
+ @plp.on_update(ws)
574
+ def tick(w: plp.Workspace):
575
+ if w.input.key_down(plp.Key.ESCAPE):
576
+ w.quit()
577
+ ```
578
+
579
+ ---
580
+
581
+ # Coroutines
582
+
583
+ Event-like functions can yield.
584
+
585
+ If a handler returns a generator, PlayPy resumes it on future frames.
586
+
587
+ Example:
588
+
589
+ ```python
590
+ def flash(_: plp.Workspace):
591
+ for _ in range(60):
592
+ print("frame")
593
+ yield
594
+ ```
595
+
596
+ Supported in:
597
+ - event callbacks
598
+ - button callbacks
599
+ - textbox callbacks
600
+ - other event-like handlers
601
+
602
+ ---
603
+
604
+ # Textbox Features
605
+
606
+ `Textbox` supports:
607
+ - placeholders
608
+ - caret blinking
609
+ - text confirmation/reverting
610
+ - character filtering
611
+ - maximum length
612
+ - coroutine callbacks
613
+
614
+ Useful arguments:
615
+
616
+ ```python
617
+ is_char_accepted: Callable[[str], bool] | None,
618
+ on_text_updated: Callable[[workspace.Workspace], Generator[None, None, None] | None] | None,
619
+ confirm_on_click_off: bool,
620
+ ```
621
+
622
+ Special keys:
623
+
624
+ ```text
625
+ Backspace -> remove character
626
+ Delete -> clear text
627
+ Return -> confirm text
628
+ Escape -> revert text
629
+ ```
630
+
631
+ ---
632
+
633
+ # Scenes
634
+
635
+ `Scene`s act as "sub-workspaces": when they are alive, only descendants of them (and global elements) are processed and drawn.
636
+
637
+ Use previously mentioned workspace scene lifecycle methods (`ws.queue_scene_change`, etc.) to manage alive scene.
638
+
639
+ `Scene`s support lifecycle hooks:
640
+
641
+ ```python
642
+ on_enter()
643
+ on_exit()
644
+ on_pause()
645
+ on_resume()
646
+ ```
647
+
648
+ Scene changes are queued internally.
649
+
650
+ ---
651
+
652
+ # Cameras and Scrolling
653
+
654
+ `Camera` offsets descendant elements.
655
+
656
+ `Scrollable` extends camera functionality with built-in scrolling support.
657
+
658
+ Example:
659
+
660
+ ```python
661
+ scrollable = plp.Scrollable()
662
+ scrollable.parent = panel
663
+ ```
664
+
665
+ ---
666
+
667
+ ### VisualLayer
668
+
669
+ `VisualLayer`s render `Sprite`s, `Animation`s, colors, and shader-like effects.
670
+
671
+ `VisualLayer` constructor:
672
+ ```python
673
+ VisualLayer(
674
+ visual: plp.Sprite | plp.Animation | plp.ColorValue,
675
+ blend_mode: plp.BlendMode | None = None,
676
+ scale: plp.FRectValue = (0, 0, 1, 1),
677
+ offset: plp.RectValue = (0, 0, 0, 0),
678
+ z: int = -1_000_000,
679
+ visible: bool = True,
680
+ ignores_environment: bool = True
681
+ )
682
+ ```
683
+
684
+ Use the previously mentioned `ignores_environment` for different types of `VisualLayer`s.
685
+
686
+ ```python
687
+ # Overlay-style usage:
688
+ plp.VisualLayer(sprite, ignores_environment=True)
689
+
690
+ # Embedded panel/sprite usage:
691
+ plp.VisualLayer(sprite, plp.empty_frect(), (20, 20, 50, 50), ignores_environment=False)
692
+ ```
693
+
694
+ ---
695
+
696
+ # Logging
697
+
698
+ PlayPy replaces standard exceptions/logging with a categorized logging system.
699
+
700
+ ```python
701
+ plp.log(severity, category, message)
702
+ ```
703
+
704
+ Severities:
705
+
706
+ ```python
707
+ plp.Severity.INFO
708
+ plp.Severity.WARNING
709
+ plp.Severity.ERROR
710
+ plp.Severity.CRITICAL
711
+ ```
712
+
713
+ `ERROR` and `CRITICAL` are treated as `NoReturn`.
714
+
715
+ Use `plp.enter_debug_mode()` to prevent logs from halting the program.