Metadata-Version: 2.4
Name: PlayPy
Version: 0.4.0
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.
Author-email: angel <angyv2861@gmail.com>
Requires-Python: >=3.14
Description-Content-Type: text/markdown
License-File: LICENSE.txt
Requires-Dist: pygame-ce>=2.5.6
Requires-Dist: colorama>=0.4.6
Dynamic: license-file

# PlayPy (0.4.0)

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.

## Requirements

- Python `>=3.11`
- `pygame(-ce) >=2.6.1`

## Installation

```bash
pip install playpy
```

If that does not work:

```bash
python -m pip install playpy
```

---

# Core Concepts

## Workspace

`Workspace` owns:
- the window
- render loop
- input state
- scene stack
- coroutine scheduler

Key methods:

```python
ws.run()
ws.quit()
ws.wait(seconds)

ws.queue_scene_change(scene)
ws.queue_scene_push(scene)
ws.queue_scene_pop(scene=None)

ws.step()
```

### Scene Scope

Temporarily push a scene using a context manager:

```python
with ws.scene_scope(scene) as (scn, handle):
    ...
```

Call:

```python
handle.disconnect()
```

to remotely pop the scoped scene.

The scoped scene will automatically be disconnected if not disconnected manually.

---

## Layout Values

PlayPy uses two rectangle value types:

### `FRect`:
Relative scale values.

```python
plp.FRect(x, y, w, h)
```

### `Rect`:
Absolute pixel offsets.

```python
plp.Rect(x, y, w, h)
```

---

Final element rectangle:

```text
(scale * parent_size) + offset
```

Helpers:

```python
plp.empty_rect() -> Rect(0, 0, 0, 0)
plp.empty_frect() -> FRect(0, 0, 0, 0)
plp.full_screen_rect() -> (FRect(0, 0, 1, 1), Rect(0, 0, 0, 0))
```

Rect types are iterable and support multiple constructor overloads.

---

## Assets

Assets are reusable wrappers around external files such as images, animations, and sounds.

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.

### Sprites and Animations

`Sprite`s load and store images. Initialize them by passing in a path. (`Path` or `str` object)

`Animation`s store multiple `Sprite`s and allow for changing of animation settings (FPS, loop, etc.)
Pass in either sprite paths (`Path` or `str` object), or `Sprite`s themselves.

### Sounds

`Sound`s load and store user-imported sounds from sound files. Initialize them by passing in a path. (`Path` or `str` object)

Useful methods:
```python
sound = plp.Sound("my_awesome_sound.wav")

sound.load()
sound.play(loop_amt=3, maxtime=10, fade_ms=500)
sound.stop()
sound.set_volume(.5)
volume = sound.get_volume()
```

---

# Parenting

Any `Element` can contain children.

```python
child.parent = parent
```

or:

```python
parent.add_child(child)
```

Relationship helpers:

```python
is_parent_of()
is_child_of()

is_ancestor_of()
is_descendant_of()
```

Properties:

```python
parent
children
ancestors
descendants
```

---

# Rendering System

PlayPy now uses a compositing pipeline.

`Element.draw()` returns a `SurfaceHandler` instead of drawing directly to the workspace.

This enables:
- outlines for arbitrary shapes
- gradients inheriting shape alpha
- subtree compositing
- layered visual effects
- future post-processing support

---

# Elements

## Core Elements

- `Panel`
- `Text`
- `Button`
- `Textbox`
- `Line`
- `Scene`

## Effects

`Effect` is a subclass of `Element` that visually modifies its parent subtree.

Built-in effects:

- `Gradient`
- `ButtonGradient`
- `Outline`
- `BorderRadius`
- `VisualLayer`

Effects are ordered using `z`.

Typical layering:

```text
Negative z:
    backgrounds/gradients

Normal z:
    content

Positive z:
    outlines/glows/overlays
```

## Components

`Component`s modify behavior/layout but do not draw.

Built-in components:

- `Padding`
- `Font`
- `Camera`
- `Scrollable`
- `GlobalElement`

Attach/get/remove:

```python
element.set_component(component)

element.get_component(ComponentType)

element.remove_component(ComponentType)
```

or:

```python
component.parent = element
```

---

# Input State

Access input using:

```python
workspace.input
```

Controller profile changes are tracked on the workspace for the current frame:

```python
workspace.profile_changes
workspace.profiles_added
workspace.profiles_removed
```

Properties:

```python
keys_pressed
key_downs
key_ups

mouse_buttons_pressed
mouse_downs
mouse_ups

mouse_pos
mouse_delta
mouse_wheel

controller_buttons_pressed
controller_ups
controller_downs

controller_left_sticks
controller_right_sticks
controller_left_sticks_deltas
controller_right_stick_deltas

text_input

dt
runtime
quit
```

Helper methods:

```python
key_held()
key_down()
key_up()

mousebutton_held()
mousebutton_down()
mousebutton_up()

controllerbutton_held()
controllerbutton_down()
controllerbutton_up()

left_stick(profile)
right_stick(profile)
left_stick_delta(profile)
right_stick_delta(profile)

input_action_held()
input_action_down()
input_action_up()
```

Use `InputAction`s for easier keybind compatibility. Any matching key, mouse button, or controller button can trigger the action.

```python
keybind = plp.InputAction(
    keys = [plp.Key.SPACE, plp.Key.RETURN],
    mouse_buttons = [plp.MouseButton.LEFT],
    controller_buttons = [plp.ControllerButton.A, plp.ControllerButton.B],
    profiles = [plp.InputProfile.KEYBOARD_MOUSE, plp.InputProfile.CONTROLLER_0]
)
```

---

# Hover State

Workspace hover helpers:

```python
is_mouse_top(element)
is_mouse_over(element)

just_hovered(element)
just_unhovered(element)

just_hovered_inclusive(element)
just_unhovered_inclusive(element)
```

Event helpers:

```python
@on_hover(...)
@on_unhover(...)
@while_hovered(...)

@on_hover_inclusive(...)
@on_unhover_inclusive(...)
@while_hovered_inclusive(...)
```

---

# Events

Decorator helpers create `Event` elements.

```python
@plp.on_start(target)
@plp.on_update(target)
@plp.on_quit(target)

@plp.on_scene_change(target)

@plp.on_profile_changed(target)
@plp.on_profile_added(target)
@plp.on_profile_removed(target)

@plp.create_event(target, condition)
```

Events attached to the main workspace are global by default.

Example:

```python
@plp.on_update(ws)
def tick(w: plp.Workspace):
    if w.input.key_down(plp.Key.ESCAPE):
        w.quit()
```

---

# Coroutines

Event-like functions can yield.

If a handler returns a generator, PlayPy resumes it on future frames.

Example:

```python
def flash(_: plp.Workspace):
    for _ in range(60):
        print("frame")
        yield
```

Supported in:
- event callbacks
- button callbacks
- textbox callbacks
- other event-like handlers

---

# Textbox Features

`Textbox` supports:
- placeholders
- caret blinking
- text confirmation/reverting
- character filtering
- maximum length
- coroutine callbacks

Useful arguments:

```python
is_char_accepted=
on_text_updated=
confirm_on_click_off=
```

Special keys:

```text
Backspace -> remove character
Delete    -> clear text
Return    -> confirm text
Escape    -> revert text
```

---

# Scenes

`Scene`s support lifecycle hooks:

```python
on_enter()
on_exit()
on_pause()
on_resume()
```

Scene changes are queued internally.

---

# Cameras and Scrolling

`Camera` offsets descendant elements.

`Scrollable` extends camera functionality with built-in scrolling support.

Example:

```python
scrollable = plp.Scrollable()
scrollable.parent = panel
```

---

### VisualLayer

`VisualLayer`s render `Sprite`s, `Animation`s, colors, and shader-like effects.

`VisualLayer` constructor:
```python
VisualLayer(
    visual: plp.Sprite | plp.Animation | plp.ColorValue,
    blend_mode: plp.BlendMode = plp.BlendMode.RGB_SUBTRACT,
    scale: plp.FRectValue = (0, 0, 1, 1),
    offset: plp.RectValue = (0, 0, 0, 0),
    z: int = -1_000_000,
    visible: bool = True,
)
```

---

# Logging

PlayPy replaces standard exceptions/logging with a categorized logging system.

```python
plp.log(severity, category, message)
```

Severities:

```python
plp.Severity.INFO
plp.Severity.WARNING
plp.Severity.ERROR
plp.Severity.CRITICAL
```

`ERROR` and `CRITICAL` are treated as `NoReturn`.
