State, Spring & Theme
The reactive data layer, the spring physics animation solver, and the iOS-inspired colour theming system.
State
An observable value container. Any widget that accepts a State object subscribes to it and re-renders automatically when the value changes. Use State any time you have data that multiple widgets need to reflect, or that changes at runtime.
State(initial_value: Any)
| Property / Method | Description |
|---|---|
| value | Read or write the current value. Writing fires all listeners if the value changed (uses != comparison). |
| set(new_val) | Alias for .value = new_val. |
| bind(callback) | Register a callback. Called immediately with the current value, then on every future change. |
from pylay import State, Label, Slider
volume = State(0.8)
# Widgets auto-bind to state:
label = Label(volume, font_size=14, ...) # shows "0.8", updates automatically
slider = Slider(value=volume, ...) # slider position tracks state
# Manual bind:
volume.bind(lambda v: print(f"Volume changed to {v:.2f}"))
# Update from anywhere:
volume.value = 0.5 # label and slider both update; callback fires
volume.set(1.0) # same effect
State with non-widget code
from pylay import State
is_playing = State(False)
def on_play_change(playing):
if playing:
audio.start()
else:
audio.stop()
is_playing.bind(on_play_change)
# Toggle from a button:
btn = Button("Play / Pause",
on_click=lambda: is_playing.set(not is_playing.value), ...)
Spring
An analytical spring physics solver. All PyLay animations — button press scale, tab bar sliding, switch toggle, focus ring expansion — run through this class. Use it directly for custom animations.
Spring(value=0.0, response=0.35, damping_fraction=0.7, velocity=0.0)
| Parameter | Description |
|---|---|
| value | Initial position. The spring starts here. |
| response | Natural period in seconds (SwiftUI-style). Lower values = faster spring. Typical range: 0.2–0.6. |
| damping_fraction | Bounciness. 1.0 = critically damped (no overshoot). 0.7 = slight bounce. 0.0 = infinite oscillation. |
| velocity | Initial velocity. Use for momentum-preserving animations. |
Advance the spring by dt seconds using an exact analytical solution (stable at any frame rate). Returns the new position.
Instantly jump to value with zero velocity. Use to skip animations when needed.
from pylay import Spring
# Custom animation: fade in an overlay (0 → 1 over ~0.3s)
fade = Spring(value=0.0, response=0.3, damping_fraction=0.85)
fade.target = 1.0
# In your update loop:
def update(dt):
alpha = fade.update(dt)
overlay.set_alpha(int(alpha * 255))
Theme & ColorPalette
The global iOSTheme instance holds all theme-aware colours. Widget renderers read from it directly. Call app.apply_theme(dark) to switch — all cached colours are invalidated and the next frame redraws everything.
iOSTheme colour properties
| Property | Light mode | Dark mode |
|---|---|---|
| background | White | Near-black |
| secondary_background | Light grey | Dark grey |
| tertiary_background | Very light grey | Darker grey |
| label | Black | White |
| secondary_label | Dark grey | Light grey |
| tertiary_label | Medium grey | Medium grey |
| fill / fill_secondary / fill_tertiary | Semi-transparent fills, progressively lighter | |
| accent | Blue (ColorPalette.BLUE) — primary interactive colour | |
| success / warning / danger | Green / Orange / Red | |
ColorPalette constants
from pylay import ColorPalette
ColorPalette.BLUE # pygame.Color(0, 122, 255)
ColorPalette.GREEN # pygame.Color(52, 199, 89)
ColorPalette.RED # pygame.Color(255, 59, 48)
ColorPalette.ORANGE # pygame.Color(255, 149, 0)
ColorPalette.YELLOW # pygame.Color(255, 204, 0)
ColorPalette.TEAL # pygame.Color(48, 176, 199)
ColorPalette.PURPLE # pygame.Color(175, 82, 222)
ColorPalette.PINK # pygame.Color(255, 45, 85)
# Use in widget constructors:
switch = Switch(active_color=ColorPalette.TEAL, ...)
Reading theme colours in custom rendering
from pylay import iOSTheme, PygameCanvas
import pygame as pg
class MyWidget(PygameCanvas):
def draw_content(self, surface, rect):
# Always read from iOSTheme — auto-updates on theme switch
bg = iOSTheme.secondary_background
text = iOSTheme.label
pg.draw.rect(surface, bg, rect, border_radius=8)
# ... render text ...