Several screens and dialogs

Open sub-screens, go back, close the whole menu and ask yes-or-no questions with ConfirmDialog before destructive actions.

Last updated

Steps

A menu can have any number of screens. This page shows how to move between them and how to confirm a destructive action.

The screen stack#

The UiHost your create method receives keeps a stack of screens:

Call Effect
host.open(screen) Pushes screen on top. Its back() returns to the current one.
host.back() Pops the current screen. From the first screen, returns to wherever the menu was opened from (the Options screen, the mod list, or your own parent).
host.close() Pops every screen at once, returning to wherever the menu was opened from.

Keep the host (pass it to the methods that build screens) so buttons can navigate.

Open a sub-screen#

Build each screen in a method, and build it fresh every time it opens:

MyMenu.java
@Override
public Screen create(UiHost host) {
    return MenuScreen.builder("main")
            .title(Text.translatable("mymod.title", "My Mod"))
            .add(Button.builder("advanced")
                    .label(Text.translatable("mymod.advanced", "Advanced..."))
                    .onPress(() -> host.open(advancedScreen(host)))
                    .build())
            .footer(Button.builder("done").label(UiTexts.DONE).onPress(host::back).build())
            .build();
}

private Screen advancedScreen(UiHost host) {
    return MenuScreen.builder("advanced")
            .title(Text.translatable("mymod.advanced.title", "Advanced"))
            .add(Toggle.builder("debug")
                    .label(Text.translatable("mymod.debug", "Debug output"))
                    .value(() -> config.debug)
                    .onChange(v -> config.debug = v)
                    .build())
            .footer(Button.builder("back").label(UiTexts.DONE).onPress(host::back).build())
            .build();
}

Pass data into the screen-building method when a sub-screen is about one item:

.onPress(() -> host.open(profileScreen(host, profiles.selected())))

Ask before a destructive action#

ConfirmDialog is a yes/no question on its own screen, drawn like vanilla's "Delete world?" screen:

StatusLine status = StatusLine.builder("status").build();

Button reset = Button.builder("reset")
        .label(Text.translatable("mymod.reset", "Reset..."))
        .onPress(() -> host.open(ConfirmDialog.builder("reset.confirm")
                .title(Text.translatable("mymod.reset.title", "Reset every setting?"))
                .message(Text.translatable("mymod.reset.message", "This can't be undone."))
                .yesLabel(Text.translatable("mymod.reset.yes", "Reset"))
                .noLabel(UiTexts.CANCEL)
                .onYes(() -> {
                    config.reset();
                    status.show(StatusLine.Kind.SUCCESS, Text.translatable("mymod.reset.done", "Settings reset"));
                })
                .build()))
        .build();
Builder method Required Default
title(Text) yes
message(Text) yes
onYes(Runnable) yes
yesLabel(Text) no UiTexts.YES. A verb ("Delete", "Overwrite") is clearer.
noLabel(Text) no UiTexts.NO
onNo(Runnable) no does nothing; the dialog just closes

When the player answers, the dialog closes first and then the answer's action runs. So the action runs with the screen that opened the dialog as the current one: it can write to that screen's StatusLine (as above) or call host.open to go somewhere else. That screen is refreshed afterwards.

Leave the whole menu#

host.close() from a deep screen returns straight to where the menu was opened, for example after a "Save and exit" button:

Button.builder("saveExit")
        .label(Text.translatable("mymod.saveExit", "Save and exit"))
        .onPress(() -> {
            config.save();
            host.close();
        })
        .build();

Test it#

FakeHost keeps the same stack. host.current().id() names the open screen and host.depth() counts the stack. On a ConfirmDialog, click yes or no:

host.click("reset");
assertEquals("reset.confirm", host.current().id());
host.click("yes");
assertEquals("main", host.current().id());

Next steps#