Quick start

Explanation

Lunex works by first creating an entity that will contain the future UI. This entity is called UiTree and has a component with the same name. Afterwards, any entity that will be part of that UI needs to be spawned as it's child.

Boilerplate

#![allow(unused)]
fn main() {
use bevy_lunex::prelude::*;
}

Then we need to add UiPlugin to our app.

fn main() {
    App::new()
        .add_plugins((DefaultPlugins, UiPlugin))
        .run();
}

Thats it for the boilerplate!

Start

Because the library supports a lot of different use cases, we need to specify what dimensions will the UI be rendered with.

Right now we want to use the window size, so we will use the default marker component and add it to our camera. This will make the camera pipe it's size into our future UiTree which also needs to have the same marker applied.

#![allow(unused)]
fn main() {
commands.spawn((
    // Add this marker component provided by Lunex.
    MainUi,

    // Our camera bundle with depth 1000.0 because UI starts at `0` and goes up with each layer.
    Camera2dBundle { transform: Transform::from_xyz(0.0, 0.0, 1000.0), ..default() }
));
}

Now we need create our UiTree entity. The core components are UiTree + Dimension + Transform. The UiTreeBundle already contains these components for our ease of use.

The newly introduced Dimension component is used as the source size for the UI system. We also need to add the MovableByCamera component so our entity will receive updates from camera. The last step is adding the default MainUi marker as a generic.

#![allow(unused)]
fn main() {
commands.spawn((
    // This makes the UI entity able to receive camera data
    MovableByCamera,

    // This is our UI system
    UiTreeBundle::<MainUi>::from(UiTree::new("Hello UI!")),

)).with_children(|ui| {
    // Here we will spawn our UI as children
});
}

Now, any entity with UiLayout + UiLink spawned as a child of the UiTree will be managed as a UI entity. If it has a Transform component, it will get aligned based on the UiLayout calculations taking place in the parent UiTree. If it has a Dimension component then its size will also get updated by the UiTree output. This allows you to create your own systems reacting to changes in Dimension and Transform components.

You can add a UiImage2dBundle to the entity to add images to your widgets. Or you can add another UiTree as a child, which will use the computed size output in Dimension component instead of a Camera piping the size to it.

The generic in pack::<S>() represents state. For now leave it at Base, but when you for example later want to add hover animation use Hover instead.

#![allow(unused)]
fn main() {
ui.spawn((

    // Link the entity
    UiLink::<MainUi>::path("Root"),

    // Specify UI layout
    UiLayout::window_full().pos(Ab(20.0)).size(Rl(100.0) - Ab(40.0)).pack::<Base>(),
));

ui.spawn((

    // Link the entity
    UiLink::<MainUi>::path("Root/Rectangle"),

    // Specify UI layout
    UiLayout::solid().size(Ab((1920.0, 1080.0))).pack::<Base>(),

    // Add image to the entity
    UiImage2dBundle::from(assets.load("background.png")),
));
}

UiLink is what is used to define the the custom hierarchy. It uses / as the separator. If any of the names don't internally exist inside the parent UiTree, it will create them.

As you can see in the terminal (If you have enabled debug feature or added the UiDebugPlugin), the structure looks like this:

#![allow(unused)]
fn main() {
> MyUiSystem == Window [pos: (x: 0, y: 0) size: (x: 100%, y: 100%)]
    |-> Root == Window [pos: (x: 20, y: 20) size: (x: -40 + 100%, y: -40 + 100%)]
    |    |-> Rectangle == Solid [size: (x: 1920, y: 1080) align_x: 0 align_y: 0]
}