State, Spring & Theme

The reactive data layer, the spring physics animation solver, and the iOS-inspired colour theming system.

pylay.Statepylay.Springpylay.iOSThemepylay.ColorPalette
PyLay Forms tab — sliders and switches demonstrating reactive State
Reactive State in action — sliders and switches share state, updating in real time.

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.

class State
Observable value. Fires bound callbacks on change.
State(initial_value: Any)
Property / MethodDescription
valueRead 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.

class Spring
Spring(value=0.0, response=0.35, damping_fraction=0.7, velocity=0.0)
ParameterDescription
valueInitial position. The spring starts here.
responseNatural period in seconds (SwiftUI-style). Lower values = faster spring. Typical range: 0.2–0.6.
damping_fractionBounciness. 1.0 = critically damped (no overshoot). 0.7 = slight bounce. 0.0 = infinite oscillation.
velocityInitial velocity. Use for momentum-preserving animations.
update(dt: float) → float

Advance the spring by dt seconds using an exact analytical solution (stable at any frame rate). Returns the new position.

snap_to(value: float) → None

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

PropertyLight modeDark mode
backgroundWhiteNear-black
secondary_backgroundLight greyDark grey
tertiary_backgroundVery light greyDarker grey
labelBlackWhite
secondary_labelDark greyLight grey
tertiary_labelMedium greyMedium grey
fill / fill_secondary / fill_tertiarySemi-transparent fills, progressively lighter
accentBlue (ColorPalette.BLUE) — primary interactive colour
success / warning / dangerGreen / 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 ...