App & UIElement

The application host and the base class shared by every widget and layout.

pylay.Apppylay.UIElementcore/app.py · core/element.py
PyLay app running — App and UIElement in action
App manages the window, event loop, and the root UIElement tree.

App

class App
Application host. Manages the window, event loop, animation clock, and root layout.
App(
    title="App",
    width=960,
    height=680,
    dark_mode=True,
    managed_display=True,
)
ParameterTypeDefaultDescription
titlestr"App"Window title bar text (managed mode only).
width / heightint960 / 680Initial window size in pixels.
dark_modeboolTrueInitial theme. Toggle later with app.apply_theme() or app.toggle_theme().
managed_displayboolTrueTrue: App creates and owns the pygame window and calls the event loop via run(). False: Embedded mode — you supply the surface and drive the loop.

Key properties

PropertyTypeDescription
rootUIElementRoot layout. Add all top-level widgets and layouts here via app.root.add_child(...).
width / heightintCurrent window dimensions. Updated automatically on resize.
screenpygame.Surface | NoneActive pygame surface. None in embedded mode before attach() is called.
on_resizeCallable[[int, int], None] | NoneCalled with the new (width, height) whenever the window is resized.

Methods

set_root(root_element: UIElement) → None

Replace the root layout. The previous root is discarded. Use this to swap between top-level views.

apply_theme(dark: bool | None = None) → None

Set the theme. Calls iOSTheme.update_theme() and invalidates all widget theme caches. Pass None to toggle. Pass True for dark, False for light.

toggle_theme() → None

Shorthand for apply_theme(not iOSTheme.dark).

process_event(event: pygame.event.Event) → bool

Feed one pygame event into the UI tree. Returns False if the app should quit (QUIT event received).

process_events(events=None) → bool

Feed multiple events. If events is None, calls pygame.event.get() internally. Returns False to quit.

frame(dt=None, *, clear=True) → float

Advance all spring animations by dt seconds, then draw. Returns the actual delta time used. Call inside your own loop in embedded mode.

run() → None

Start the built-in main loop. Blocks until the window is closed. Only valid when managed_display=True.

attach(surface: pygame.Surface) → None

Bind an external surface (embedded mode). Must be called before frame() or process_events().

Embedded mode example

import pygame as pg
from pylay import App, VStack, Button

pg.init()
screen = pg.display.set_mode((1024, 768), pg.RESIZABLE)

app = App(managed_display=False)
app.attach(screen)

root = VStack(padding=24, anchor="top_left", x=0, y=0, width=1.0, height=1.0)
root.add_child(Button("Press me", on_click=lambda: print("!"),
                      anchor="top_left", x=0, y=0, width=150, height=44))
app.set_root(root)

clock = pg.time.Clock()
running = True
while running:
    dt = clock.tick(60) / 1000
    if not app.process_events():
        running = False
    app.frame(dt)

UIElement

Base class for every widget and layout. Provides the layout constraint resolver, event dispatch, spring animation, and the render pipeline. You rarely subclass this directly — use FloatLayout or PygameCanvas as starting points.

class UIElement
Base UI element. All widgets and layouts extend this.
UIElement(
    anchor="top_left",
    x=0, y=0,
    width=100, height=100,
    visible=True,
    enabled=True,
)
ParameterTypeDescription
anchorstr"top_left", "top_center", "top_right", "center_left", "center", "center_right", "bottom_left", "bottom_center", "bottom_right"
x / yint | floatOffset from the anchor point. Positive values move right / down. Float 0.0–1.0 = fraction of parent size.
width / heightint | floatElement size. 1.0 = fill parent, 0.5 = 50% of parent, integer = absolute pixels.
visibleboolHidden elements do not render but still occupy space in layout.
enabledboolDisabled elements do not receive input events.

Key properties

PropertyTypeDescription
rectpygame.RectResolved pixel bounding box. Updated by layout(). Use this for hit testing or custom rendering.
childrenlist[UIElement]Direct child elements.
parentUIElement | NoneParent element, or None if this is the root.
clips_to_boundsboolIf True, children are clipped to this element's rect. Automatically True on Layout and PygameCanvas.

Key methods

add_child(child: UIElement) → None

Append a child to this element. Sets child.parent = self. The child's layout is resolved on the next frame.

remove_child(child: UIElement) → None

Remove a child from this element.

animate_to(property_name, target, response=0.35, damping_fraction=0.7) → None

Start a spring animation on any numeric property of this element. response is the natural period in seconds (lower = faster). damping_fraction is bounciness: 1.0 = no overshoot, 0.5 = light bounce.

layout(parent_rect=None, skip_self=False) → None

Resolve layout constraints to self.rect, then propagate to all children. Called automatically each frame — you rarely need to call this manually.