SkillAgentSearch skills...

Belly

Define the Bevy UI tree with `eml!`, style it using a very-css-like `ess` syntax and relate data data with `bind!` and `connect!`

Install / Use

/learn @jkb0o/Belly
About this skill

Quality Score

0/100

Supported Platforms

Universal

README

About


The belly is a plugin for a bevy game engine that helps to declaratively define a user interface with eml markup (macros & asset), style it with a very CSS-like ess syntax, and define data flow using from! & to! bind macros and/or connect events to handlers (funcs).

API Reference

Example

// examples/color-picker.rs
// cargo run --example color-picker
use belly::prelude::*;
use bevy::prelude::*;

fn main() {
    App::new()
        .add_plugins(DefaultPlugins)
        .add_plugins(BellyPlugin)
        .add_systems(Startup, setup)
        .run();
}

const COLORS: &[&'static str] = &[
    // from https://colorswall.com/palette/105557
    // Red     Pink       Purple     Deep Purple
    "#f44336", "#e81e63", "#9c27b0", "#673ab7",
    // Indigo  Blue       Light Blue Cyan
    "#3f51b5", "#2196f3", "#03a9f4", "#00bcd4",
    // Teal    Green      Light      Green Lime
    "#009688", "#4caf50", "#8bc34a", "#cddc39",
    // Yellow  Amber      Orange     Deep Orange
    "#ffeb3b", "#ffc107", "#ff9800", "#ff5722",
];

fn setup(mut commands: Commands) {
    commands.spawn(Camera2dBundle::default());
    commands.add(StyleSheet::load("color-picker.ess"));
    let colorbox = commands.spawn_empty().id();
    commands.add(eml! {
        <body>
            <span c:controls>
                <slider c:red
                    bind:value=to!(colorbox, BackgroundColor:0|r)
                    bind:value=from!(colorbox, BackgroundColor:0.r())
                />
                <slider c:green
                    bind:value=to!(colorbox, BackgroundColor:0|g)
                    bind:value=from!(colorbox, BackgroundColor:0.g())
                />
                <slider c:blue
                    bind:value=to!(colorbox, BackgroundColor:0|b)
                    bind:value=from!(colorbox, BackgroundColor:0.b())
                />
                <slider c:alpha
                    bind:value=to!(colorbox, BackgroundColor:0|a)
                    bind:value=from!(colorbox, BackgroundColor:0.a())
                />
            </span>
            <img c:colorbox-holder src="trbg.png">
                <span {colorbox} c:colorbox s:background-color=managed()
                    on:ready=run!(|c: &mut BackgroundColor| c.0 = Color::WHITE)/>
            </img>
            <span c:colors>
            <for color in = COLORS>
                <button on:press=run!(for colorbox |c: &mut BackgroundColor| { c.0 = Color::from_hex(color) })>
                    <span s:background-color=*color s:width="100%" s:height="100%"/>
                </button>
            </for>
            </span>
        </body>
    });
}

Color Picker

The main tasks the plugin is about to solve are:

  • fill the space in the bevy UI system (inputs, scrolls, text layout, etc.)
  • reduce the boilerplate defining the UI
  • allow to effectively separate the layout, styling, data & logic from each other
  • build the basis to provide various tools for game developers & designers

<a name="features"></a> Features:

  • Hierarcy definition using eml macro or asset
  • Style definition using direct attributes or ess stylesheet assets
  • Hot-reloading for eml and ess assets
  • Data bindings for resources, components, params and content
  • Event -> handler connections
  • Predefined styles properties for every piece os UI
  • Basic templating (for-loops, slots)
  • Out of the box default styles & fonts
  • Ability to define custom widgets, properties, and bind transformers
  • Style & behaviour extending
  • Predefined widgets for configuring layout, generating content and handling input:
    • body, div, span, br, strong
    • img, progressbar, label
    • textinput, slider, button, buttongroup
  • Styleboxes (9-patch-slices/image-border)

<a name="upcoming-features"></a> Upcoming features:

  • Complete rich-text processing
  • True inline/block/inline-block elements
  • Binding transitions (changing values over time)
  • Style transitions (changing style properties over time)
  • Scene-based widgets
  • Styled drawing primitives (lines, rects, curves, shapes)
  • Asset validation tools
  • In-game developer panel with UI tree & style inspector
  • More widgets (tabview, scrollarea, checkbox, attach, line, popup, tooltip)
  • Developer tools (vscode plugin)
  • Localization
  • Scripting
  • Theming
  • Asset loading progress handling
  • More templating (if/else)

<a name="table-of-contents"></a> Table of contents



<a name="setup"></a> Prerequisites & Setup


As far as the project has no cargo release yet, the only way to discover functionality is to clone the repo & play with examples:

git clone https://github.com/jkb0o/belly.git
cd belly
cargo run --example color-picker

If you are brave enough, you can connect the plugin by referencing the GitHub repo in your Cargo.toml.


<a name="basics"></a> Basics


In the belly, you define UI layout using eml. It is possible to do it directly from the code using the eml! macro or by loading the .eml asset and adding EmlScene. In the case of macro eml is more than just markup, but more like templating language, jsx from the javascript world. In the case of the .eml asset, eml is just an XML file with no special syntax. From now I'll focus on eml! macro:

// examples/hello-world.rs
// cargo run --example hello-world
use belly::prelude::*;
use bevy::prelude::*;

fn main() {
    App::new()
        .add_plugins(DefaultPlugins)
        .add_plugins(BellyPlugin)
        .add_systems(Startup, setup)
        .run();
}

fn setup(mut commands: Commands) {
    commands.spawn(Camera2dBundle::default());
    commands.add(eml! {
        <body s:padding="50px">
            "Hello, "<strong>"world"</strong>"!"
        </body>
    });
}

Hello World Example

The eml! macro expands its content into multiple calls which spawn entities with required components based on the tags and their attributes. Most of these tags in the belly called Widgets. When the belly builds the tree it unfolds each widget into one (or more) entities. The structure from the example above becomes something like this:

  • Node (body)
    • TextNode ("Hello, ")
    • Node (strong)
      • TextNode ("world")
    • TextNode ("!")

In addition to common bevy UI components (Node, Text, BackgroundColor, etc.) belly inserts an Element component. It holds information about tag name(s), styles, classes, and states.

For configuring widget style & behavior you can pass attributes within the tag. These attributes are called params in the belly and may be passed in different ways:

  • common params passed as key-value pairs: <img src="icon.pgn">
  • style params passed using s: prefix: <span s:padding="50px>
  • class params passed using c: prefix (<span c:some-class>) or by class param (<span class="some-class-1 some-class-2">)
  • binds passed using bind: prefix: <buttongroup bind:value=to!(img, Img:src)>
  • connections passed using on: prefix: <button on:press=|_| info!("I'm pressed!")/>
  • entity passed using curly braces: <span {span_id}> or using entity param: <span entity=span_id>
  • components passed using with param: <button with=(MyComponent, another_component_instance)/>

<a name="tags-widgets-content"></a> Tags, Widgets & Content


As I said eml consists of tags with attributes. Everything between the open-closing tag pair (<span>...</span>) becomes the children of this tag. The tag also may have no children at all, the self-closing tag used in this case: <button/>. Every tag may have this kind of child:

  • other tags: <span><button/></span>
  • string literals: <span>"Hello world!"</span>
  • rust blocks: <span>{ some_content() }</span>

Rust block can be any expression that returns impl IntoContent. String implements the IntoContent trait for example, as well as Vec<Entity> does. Some other types provide this implementation too, binds, for example, I'll talk about this later.

As I mentioned earlier, almost every tag meant to be Widgetit produces one or more entities with their own set of components, styles, and states. I will talk about widgets all the time. Later I'll introduce to you non-widget tags & some templating features of belly but for now, let's focus on widgets and styling features.


<a name="styling"></a> Styling


Usually, in bevy, you define styles (how your content looks) by passing properties to UI bundles. Unfortunately, it can produce a lot of boilerplate. belly can help you to create more readable styling code/content in multiple ways.

View on GitHub
GitHub Stars435
CategoryDevelopment
Updated16d ago
Forks32

Languages

Rust

Security Score

95/100

Audited on Mar 12, 2026

No findings