bevy_bsml 0.0.5

Bevy UI library inspired by svelte and tailwindcss
Documentation

bevy_bsml

bevy_bsml is a UI library built on top of bevy's official bevy_ui library to help easily create and reuse styled UI components.

bevy_bsml is very much inspired by svelte and tailwindcss. So, if you're a web dev, you may feel right at home, with components and inline styling.

To see the basic usages of bevy_bsml, check out examples:

What is BSML?

BSML stands for Bevy's Simple Markup Language, or BS Markup Language; feel free to pick the one you like more.

Why not HTML or XML?

Because the angle bracketed markup languages don't work well with rust macros, and (...) and {...} work naturally.

BSML Overview

1. Tag is inside parenthesis (MyComponent), followed by content nested in braces { ... }.

bsml!{
    (node) {
        (node)
    }
}

is same as

<div>
  <div />
</div>

You can have multiple elements inside braces:

bsml!{
    (node) {
        (node)
        (text) { "hello world" }
        (node) {
            (text) { "nested text" }
        }
    }
}

is same as

<div>
  <div />
  hello world
  <div>
    nested text
  </div>
</div>

2. There can be only one root element.

Valid cases:

bsml!{
    (node) { (node) }
}

bsml!{
  (node)
}

bsml!{
  (slot)
}

bsml!{
  (text) { "hello world" }
}

Invalid:

bsml!{
    (node) (node)
}

bsml!{
    (node) { (node) }
    (text) { "hello world" }
}

As you can see, if element doesn't have any nested elements, braces are optional.

So, if you have a UI Component MenuItem, which already has content layed out, you can skip braces when using the component.

pub struct MenuItem;
bsml!(MenuItem;
    (node) {
        (text) { "hello world" }
    }
)

pub struct Menu;
bsml!{Menu;
    (node) {
        (MenuItem)
        (MenuItem)
        (MenuItem)
    }
}

3. Building Blocks

Currently, bsml has 3 base elements: node, text, and slot.

node is like div in html; you can have nested elements in it, or just use one without nested elements just to style it.

bsml! {
    (node class=[W_FULL, H_FULL, JUSTIFY_CENTER, ITEMS_CENTER, BG_WHITE]) {
        (node class=[h_px(100.0), w_px(100.0), BG_BLUE_400]) // I just centered this div, i mean node
    }
}

text is an element for displaying text. This element is different than others. You must follow up the tag with braces, where its content is a string with optional arguments, exactly like arguments of format!(...).

#[derive(Component)]
pub struct MyComponent;
bsml! {MyComponent;
    (text class=[TEXT_BASE]) {
        "hello world"
    }
}

If it is a component which has fields, you can also use fields as arguments:

#[derive(Component)]
pub struct MyComponent {
    pub i: u8,
    pub name: String,
}
bsml! {MyComponent;
    (text class=[TEXT_BASE]) {
        "index: {}, name: {}", i, name
    }
}

limitations:

  • you cannot have arguments in strings:
    • valid: (text) { "index: {}, name: {}", i, name }
    • invalid: (text) { "index: {i}, name: {name}" }
  • you can only have component fields as arguments, no expressions. This may change in the future.
    • valid: (text) { "index: {}, name: {}", i, name }
    • invalid: (text) { "index: {}, name: {}", "0".parse::<u8>().unwrap(), "hello world" }

slot is an element used in components to let the users of the component fill in content. Nested elements in braces of slot element is the default content if not supplied by user.

#[derive(Component)]
pub struct Button;
bsml! {Button;
    (slot class=[BG_WHITE, hovered(BG_BLUE_400)]) {
        (text) { "I am button" } // default content
    }
}

#[derive(Component)]
pub struct ButtonList;
bsml! {ButtonList;
    (Button)                               // "I am button"
    (Button) { (text) { "hello world" } }  // "hello world"
}

4. Spawning a UI element

You can directly spawn a UI element without defining a component.

use bevy_bsml::{bsml, SpawnBsml};

commands.spawn_bsml(bsml!((node class=[BG_BLUE_200, hovered(BG_BLUE_400)]) {
    (text) { "hello world" }
});

You can also spawn the UI component:

use bevy_bsml::{bsml, SpawnBsml};

#[derive(Component)]
pub struct NameCard {
    name: String,
}
bsml!(NameCard;
  (node class=[BG_BLUE_200, hovered(BG_BLUE_400)]) {
    (text) { "hello {}", name }
  }
);

commands.spawn_bsml(NameCard { name: "jack".to_owned() });

5. Defining a Reusable Component

You can also define UI component that can be reused by other elements, by specifying component struct type followed by semicolon.

#[derive(Debug, Clone, Component)]
pub struct Menu;

bsml! {Menu;
    (node class=[W_FULL, H_FULL, JUSTIFY_CENTER, ITEMS_CENTER, BG_WHITE]) {
        (node class=[FLEX_COL, gap_y(20.0)]) {
            (MenuItem i={0} name={"Continue".to_owned()})
            (MenuItem i={1} name={"Setting".to_owned()})
            (MenuItem i={2} name={"Exit".to_owned()})
        }
    }
}

#[derive(Debug, Clone, Component)]
pub struct MenuItem {
    pub i: u8,
    pub name: String,
}

bsml! {MenuItem;
    (node
        class=[w_px(200.0), h_px(75.0), BG_BLUE_400, hovered(BG_BLUE_600), JUSTIFY_CENTER, ITEMS_CENTER]
    ) {
        (text class=[TEXT_BASE]) { "{}: {}", i, name }
    }
}

commands.spawn_bsml(Menu);

To Be Continued

I'll finish the rest of documentation when I have time. In the mean time, check the examples to see how things work!