⚠️ Beta State

PyBevy is in an early and experimental stage. The API is incomplete, subject to breaking changes without notice, and you should expect bugs. Many features are still under development.

Basic User Interface

Display text and create a simple, interactive button.

Introduction

User Interface (UI) in pybevy is built using the same ECS principles. UI elements are entities with components like Node, Text, and Button. Layout is controlled using a flexbox-like system.

from pybevy.prelude import *
from pybevy.ui import Node, Text, Button, BackgroundColor, BorderColor, UiRect, AlignItems, JustifyContent, PositionType, Interaction, Val
from pybevy.text import TextFont
 
# Define some colors for our button states
NORMAL_BUTTON = Color.srgb(0.15, 0.15, 0.15)
HOVERED_BUTTON = Color.srgb(0.25, 0.25, 0.25)
PRESSED_BUTTON = Color.srgb(0.35, 0.75, 0.35)

Setup

We'll spawn a Camera2d for the UI and a root Node that centers its children. Inside, we'll create a Button entity. The button itself is a Node, and it has a Text entity as a child to display its label.

def setup(commands: Commands):
    commands.spawn(Camera2d())
 
    commands.spawn(
        Node(
            width=Val.percent(100),
            height=Val.percent(100),
            align_items=AlignItems.Center,
            justify_content=JustifyContent.Center,
        )
    ).with_children(
        lambda parent: parent.spawn(
            Button,
            Node(
                width=Val.px(150),
                height=Val.px(65),
                border=UiRect.all(Val.px(5)),
                justify_content=JustifyContent.Center,
                align_items=AlignItems.Center,
            ),
            BorderColor.all(Color.BLACK),
            BackgroundColor(NORMAL_BUTTON),
        ).with_child(
            lambda parent: parent.spawn(
                Text.new("Button"),
                TextFont(font_size=40.0),
            )
        )
    )

Button Interaction System

This system queries for all Button entities that have had their Interaction state changed. Based on the state (Pressed, Hovered, or None), we update the button's BackgroundColor and its child's Text.

def button_system(
    interaction_query: Query[tuple[Interaction, Mut[BackgroundColor], Mut[Children]], Changed[Interaction], With[Button]],
    text_query: Query[Mut[Text]],
):
    for interaction, color, children in interaction_query:
        text = text_query.get_mut(children[0])
        if interaction == Interaction.Pressed:
            text.sections[0].value = "Press"
            color.color = PRESSED_BUTTON
        elif interaction == Interaction.Hovered:
            text.sections[0].value = "Hover"
            color.color = HOVERED_BUTTON
        else:
            text.sections[0].value = "Button"
            color.color = NORMAL_BUTTON

Running the App

Run the app to see the button. Hover over it and click it to see the color and text change!

@entrypoint
def main(app: App) -> App:
    return (
        app
        .add_plugins(DefaultPlugins)
        .add_systems(Startup, setup)
        .add_systems(Update, button_system)
    )
 
if __name__ == "__main__":
    main().run()