Define a menu using a chirp file

The Book

We will now use cuicui_chirp to define the UI. To do so, we need to add it as a dependency first:

[dependencies]
# previous dependencies
# ...
# New dependency:
cuicui_chirp = "0.12.0"

The app setup requires adding cuicui_chirp::loader::Plugin::new::<UiDsl>(), we also setup hot reloading by setting the asset plugin.

    App::new()
        .add_plugins((
            DefaultPlugins.set(AssetPlugin { file_path, ..default() }),
            cuicui_layout_bevy_ui::Plugin,
            // You still need to add manually the asset loader for UiDsl!
            cuicui_chirp::loader::Plugin::new::<UiDsl>(),
        ))
        .add_systems(Startup, setup)
        .run();

Documentation

Methods available in chirp files are the methods available in the choosen DSL type (in this case, it would be the UiDsl methods). Check the documentation page for the corresponding type you are using as DSL. All methods that accept an &mut self are candidate.

The setup system, where we previously spawned the whole scene, is now completely trivial, we just spawn a single entity with a ChirpBundle.

fn setup(mut cmds: Commands, serv: Res<AssetServer>) {
    cmds.spawn((Camera2dBundle::default(), LayoutRootCamera));

    cmds.spawn(ChirpBundle::new(serv.load("chirp_menu.chirp")));
}

chirp_menu.chirp is located the assets/ folder.

This chapter assumes you’ve read the previous chapter. We will use it as a base for this.

So where to start? Well, let’s copy/past the code from the simple menu example into chirp_menu.chirp and see what happens:

Root(screen_root row distrib_start main_margin(50.) image(&bg)) {
    Column(column rules(px(100), pct(100)) main_margin(10.) image(&board)) {
        TitleCard(width(pct(100)) image(&title_card))
        TitleCard2(width(pct(50)) image(&title_card))
        Entity(image(&button_bg) width(pct(80)) text("CONTINUE"))
        Entity(image(&button_bg) width(pct(80)) text("NEW GAME"))
        Entity(image(&button_bg) width(pct(80)) text("LOAD GAME"))
        Entity(image(&button_bg) width(pct(80)) text("SETTINGS"))
        Entity(image(&button_bg) width(pct(80)) text("ADDITIONAL CONTENT"))
        Entity(image(&button_bg) width(pct(80)) text("CREDITS"))
        Entity(image(&button_bg) width(pct(80)) text("QUIT GAME"))
    }
}

Of course this doesn’t work! But here the error format is different. The game compiles, cuicui_chirp tries to load the file and displays errors it encountered instead of spawning a scene:

Error:   × Failed to load 'Handle<bevy_render::texture::image::Image>' from file '&button_bg':
  │ No such file or directory (os error 2)
    ╭─[chirp_menu.chirp:10:1]
 10 │         Entity(image(&button_bg) width(pct(80)) text("ADDITIONAL CONTENT"))
 11 │         Entity(image(&button_bg) width(pct(80)) text("CREDITS"))
 12 │         Entity(image(&button_bg) width(pct(80)) text("QUIT GAME"))
    ·                      ──────────
 13 │     }
 14 │ }
    ╰────
  help: The error comes from the ParseDsl implementation.
Error:   × Rule format was not recognized: 'pct(80)', rules end with '%', '*' or 'px'.
  │ Examples: '53%', '0.35*' and '1024px'
    ╭─[chirp_menu.chirp:10:1]
 10 │         Entity(image(&button_bg) width(pct(80)) text("ADDITIONAL CONTENT"))
 11 │         Entity(image(&button_bg) width(pct(80)) text("CREDITS"))
 12 │         Entity(image(&button_bg) width(pct(80)) text("QUIT GAME"))
    ·                                        ───────
 13 │     }
 14 │ }
    ╰────
  help: The error comes from the ParseDsl implementation.

The part of the error message we are the most interested in is the bit of text after Error:

× Failed to load ‘Handle<bevy_render::texture::image::Image>’ from file ‘&button_bg’:
│ No such file or directory (os error 2)

and

× Rule format was not recognized: ‘pct(80)’, rules end with ‘%’, ‘*’ or ‘px’.
│ Examples: ‘53%’, ‘0.35*’ and ‘1024px’

Don’t close the window! Chirp files are hot-reloadable, you can edit the file and see the effect live.

We have two kind of errors here:

  1. Argument to the image method.
  2. Argument to width and rule.

For (1), methods that accept a Handle<T> in rust accept a string argument in chirp files. For (2), chirp files use the FromStr implementation on Rule to parse them, again, as the error message states.

So let’s replace the variables from the DSL example with the file path and change the syntax on rules:

Root(screen_root row distrib_start main_margin(50) image("background.jpg")) {
    Column(column rules(100px, 100pct) main_margin(10) image("board.png")) {
        TitleCard(width(100pct) image("logo.png"))
        TitleCard2(width(50pct) image("logo.png"))
        Entity(image("button.png") width(80%) text("CONTINUE"))
        Entity(image("button.png") width(80%) text("NEW GAME"))
        Entity(image("button.png") width(80%) text("LOAD GAME"))
        Entity(image("button.png") width(80%) text("SETTINGS"))
        Entity(image("button.png") width(80%) text("ADDITIONAL CONTENT"))
        Entity(image("button.png") width(80%) text("CREDITS"))
        Entity(image("button.png") width(80%) text("QUIT GAME"))
    }
}

Save the file and …

New set of errors, but not as many. We forgot to convert pct to % in some places. Let’s fix this and save again.

The scene from simple menu, now loaded

Rules syntax by context

So how to write cuicui_layout rules? Here is a table:

Note that pct, child and px are rust functions and must be imported.

Rulein dsl!in chirp
Childrenchild2*
Parentpct95%
Fixedpx120px

Templates

This is already good. And it was much faster than before! Didn’t even need to close and re-open the game once!

But, as before, we’d like to make this shorter. To do this, we’ll extract the button entity into a template definition. In chirp, you define templates at the beginning of the file with the fn keyword, and you use them like you would use a rust macro:

// Define a template
fn button() {
    Button(image("button.png") width(80%) text("Button"))
}
Root(screen_root row distrib_start main_margin(50) image("background.jpg")) {
    Column(column rules(150px, 100%) main_margin(10) image("board.png")) {
        TitleCard(width(100%) image("logo.png"))
        TitleCard2(width(50%) image("logo.png"))
        // Call it like a rust macro
        button!()
        button!()
        button!()
        button!()
        button!()
        button!()
        button!()
    }
}

Again, all you need to do is hit the save shortcut in your text editor, and the changes show up directly on screen. (Or errors in the terminal, if any)

All buttons now have the “Button” text

Template arguments

Well, we still want to have different names per button. Miracle, templates support parameters. They are like argument to rust functions:

// button_text is a parameter
fn button(button_text) {
    //     'named' allows us to set the entity name dynamically
    //     vvvvv   We can use the template parameter as argument to methods
    //     vvvvv vvvvvvvvvvv                                      vvvvvvvvvvv
    Entity(named(button_text) image("button.png") width(80%) text(button_text))
}

And when calling the template, we pass an argument:

        TitleCard(width(100%) image("logo.png"))
        TitleCard2(width(50%) image("logo.png"))
        // just pass the button name as argument
        button!("CONTINUE")
        button!("NEW GAME")
        button!("LOAD GAME")
        button!("SETTINGS")
        button!("ADDITIONAL CONTENT")
        button!("CREDITS")
        button!("QUIT GAME")

Template parameter substitution rules

Currently, it is not possible to use template parameters everywhere. See limitations.

Template extras

Now we want each button to have a different color. There are seven of them, like the seven dwarfes, the seven fingers of the hand, and seven colors of the rainbow!

We could add a second parameter to our template, but instead, we’ll use a method extra:

        button!("CONTINUE")(bg(red))
        button!("NEW GAME")(bg(orange))
        button!("LOAD GAME")(bg(yellow))
        button!("SETTINGS")(bg(green))
        button!("ADDITIONAL CONTENT")(bg(cyan))
        button!("CREDITS")(bg(blue))
        button!("QUIT GAME")(bg(violet))

Now the buttons’s outline are varycolored

A bit ugly. We should make "button.png" white so that it mixes with the rainbow colors correctly.

In the chirp file, what happens here is that we are adding the bg(color) method to the entity spawned by button!. In effect button!("CREDITS")(bg(blue)), if we expand the template, becomes:

//                                 bg(blue) method is added to the end vvvvvvvv
Entity(named("CREDITS") image("button.png") width(80%) text("CREDITS") bg(blue))

Template extras also work with children nodes, within {}.

And that’s pretty much it when it comes to cuicui_chirp. Next, we will add a bit of interactivity.