#bevy #dsl #gui #gamedev


A DSL library that simplifies the creation of widgets for bevy_ui

3 releases (breaking)

0.3.0 Mar 7, 2023
0.2.0 Jan 30, 2023
0.1.0 Jan 18, 2023

#440 in Game dev


591 lines


A "domain specific language" designed to make building UIs in Bevy more pleasant. This DSL uses the same ingredients that bevy_ui uses, so those already familiar with bevy_ui should have an easy time learning it.

UI Example

use bevy_ui_dsl::*;

fn startup(mut commands: Commands, assets: Res<AssetServer>, mut scale: ResMut<UiScale>) {

    // Obligatory camera
    scale.scale = 2.0;

    // Spawns ui and gathers entity ids
    let mut hiya = None;
    let mut howdy = None;
    root(c_root, &assets, &mut commands, |p| {                                  // Spawns the root NodeBundle. AssetServer gets propagated.
        node((c_half, c_green), p, |p| {                                        // Spawns the left pane as a NodeBundle.
            text("This is the left pane!", c_text, c_pixel, p);                 // Spawns a TextBundle.
            text("Do you like it?", c_text, c_pixel, p);
            text_button("Hiya", c_button_left, c_pixel, p).set(&mut hiya);      // Spawns a ButtonBundle with a TextBundle child in the middle. Convenience widget.
            grid(6, 6, c_grid, p, |p, _row, _col| {                             // Spawns a NodeBundle container with a NodeBundle for each cell (6x6).
                image(c_inv_slot, p);
            text("Le grid", c_text, c_pixel, p);
        node((c_half, c_blue), p, |p| {
            text("This is the right pane!", c_text, c_pixel, p);
            text("Indeed, I do!", c_text, c_pixel, p);
            text_button("Howdy", c_button_right, c_pixel, p).set(&mut howdy);

    // Inserts marker components into the gathered entities.
    // Useful when you need to interact with specific entities in the UI

This system spawns a UI using widgets like root, node, text, text_button, etc. You can even create your own widgets! They're just functions!

In this example, root is a function that takes a class called c_root. The c_root function just manipulates a NodeBundle, which is NodeBundle::default() by default. Ultimately, the NodeBundle in question gets spawned.

Like root, node also takes in a class (or a tuple of classes) and spawns a NodeBundle. When a tuple of classes is supplied, the callback functions are applied in order of left to right.

Class Examples

fn c_root(b: &mut NodeBundle) {
    b.style.size = Size::new(Val::Percent(100.), Val::Percent(100.));

fn c_half(b: &mut NodeBundle) {
    let s = &mut b.style;
    s.size = Size::new(Val::Percent(50.), Val::Percent(100.));
    s.flex_direction = FlexDirection::Column;
    s.justify_content = JustifyContent::Center;
    s.align_items = AlignItems::Center;
    s.padding = UiRect::all(Val::Px(10.));

fn c_green(b: &mut NodeBundle) {
    b.background_color = Color::rgb_u8(125, 212, 148).into();

fn c_blue(b: &mut NodeBundle) {
    b.background_color = Color::rgb_u8(125, 164, 212).into();

fn c_text(_a: &AssetServer, b: &mut TextBundle) {
    b.style.margin = UiRect::all(Val::Px(10.));

fn c_button_left(assets: &AssetServer, b: &mut ButtonBundle) {
    let s = &mut b.style;
    s.size = Size::new(Val::Px(64.), Val::Px(24.));
    s.justify_content = JustifyContent::Center;
    s.align_items = AlignItems::Center;
    b.background_color = Color::rgb_u8(66, 135, 245).into();
    b.image = assets.load("button.png").into();

fn c_button_right(assets: &AssetServer, b: &mut ButtonBundle) {
    let s = &mut b.style;
    s.size = Size::new(Val::Px(64.), Val::Px(24.));
    s.justify_content = JustifyContent::Center;
    s.align_items = AlignItems::Center;
    b.background_color = Color::rgb_u8(57, 179, 118).into();
    b.image = assets.load("button.png").into();

fn c_grid(b: &mut NodeBundle) {
    b.style.size = Size::new(Val::Px(200.), Val::Px(200.));
    b.style.margin = UiRect::all(Val::Px(10.));

fn c_inv_slot(assets: &AssetServer, b: &mut ImageBundle) {
    b.style.size = Size::new(Val::Px(32.), Val::Px(32.));
    b.image = assets.load("item_slot.png").into();

fn c_pixel(assets: &AssetServer, s: &mut TextStyle) {
    s.font = assets.load("prstartk.ttf").into();
    s.font_size = 8.;
    s.color = Color::WHITE.into();

Some classes only depend a single bundle. Others depend on an AssetServer to manipulate their respective types. It is recommended that you only set the fields you wish to overwrite in your classes. Be careful, for instance, of using ..default() as this will overwrite even the fields you don't specify. This is very bad when combining classes using the tuple syntax.

Class Helpers

To make creating classes a little less verbose, there is an optional module you can import called class_helpers. This module is made available by enabling the feature flag class_helpers in your Cargo.toml file. It includes various helper functions and constants to make your life easier. It is recommended that you put your class functions in their own module when using these helpers to avoid namespace pollution with the rest of your UI code.

use bevy_ui_dsl::*;
use bevy_ui_dsl::class_helpers::*;

fn c_node(b: &mut NodeBundle) {
    let s = &mut b.style;
    s.size = size(pc(50), pc(50));
    s.flex_direction = COLUMN;
    s.justify_content = JUSTIFY_CENTER;
    s.align_items = ALIGN_CENTER;
    s.padding = all(px(10));

Widget Example

Creating a widget is just a matter of creating a function that follows a certain convention. No more, no less. It will usually require a Class or AssetClass (can be a callback function, or tuple of callback functions), a parent (needed to spawn the widget itself), and, for container widgets, a callback function used to spawn children of the widget. See widgets.rs for examples.

/// Spawns a [`NodeBundle`] with children.
pub fn node(
    class: impl Class<NodeBundle>,              // Class (or classes) that manipulate the bundle.
    parent: &mut UiChildBuilder,                // Parent entity to add NodeBundle to.
    children: impl FnOnce(&mut UiChildBuilder)  // Callback function that spawns children of the newly spawned NodeBundle.
) -> Entity {
    let mut bundle = NodeBundle::default();             // Initializes the NodeBundle.
    class.apply(&mut bundle);                           // Applies class (or classes) to that bundle.
    parent.spawn(bundle).with_children(children).id()   // Spawns updated bundle with children!


bevy bevy-ui-dsl
0.10 0.3
0.9 0.1 - 0.2


~1M SLoC