Your first menu

Build a working mod menu with a toggle, a slider and a Done button, register it, and open it from the Options screen.

Last updated

Steps

In five steps you build a menu with a toggle, a slider and a Done button, and open it from the game's Options screen.

Prerequisites#

Steps#

1. Keep the values in your own class#

The library never stores your settings. Widgets read them from your code and write changes back, so first make a place for the values:

MyConfig.java
package com.example.mymod;

public final class MyConfig {
    public boolean sounds = true;
    public int volume = 80;

    public void save() {
        // Write the values to disk; see the "Save and load settings" guide.
    }
}

2. Describe the menu#

A menu is a ScreenFactory. Its create method builds the first screen each time the player opens the menu:

MyMenu.java
package com.example.mymod;

import dev.xichil.mvui.Button;
import dev.xichil.mvui.MenuScreen;
import dev.xichil.mvui.Screen;
import dev.xichil.mvui.ScreenFactory;
import dev.xichil.mvui.Slider;
import dev.xichil.mvui.Text;
import dev.xichil.mvui.Toggle;
import dev.xichil.mvui.UiHost;
import dev.xichil.mvui.UiTexts;

public final class MyMenu implements ScreenFactory {
    private final MyConfig config;

    public MyMenu(MyConfig config) {
        this.config = config;
    }

    @Override
    public Text buttonLabel() {
        return Text.translatable("mymod.menu", "My Mod...");   // the Options screen button
    }

    @Override
    public Screen create(UiHost host) {
        return MenuScreen.builder("main")
                .title(Text.translatable("mymod.title", "My Mod"))
                .add(Toggle.builder("sounds")
                        .label(Text.translatable("mymod.sounds", "Sounds"))
                        .value(() -> config.sounds)          // read on every refresh
                        .onChange(v -> config.sounds = v)    // you store the new value
                        .build())
                .add(Slider.builder("volume")
                        .label(Text.translatable("mymod.volume", "Volume"))
                        .range(0, 100).step(5)
                        .value(() -> config.volume)
                        .onChange(v -> config.volume = (int) v)
                        .enabled(() -> config.sounds)       // greyed out while sounds are off
                        .build())
                .footer(Button.builder("done")
                        .label(UiTexts.DONE)
                        .onPress(() -> {
                            config.save();
                            host.back();                    // close the menu
                        })
                        .build())
                .build();
    }
}

What each part does:

3. Register it#

Call MultiversionUi.register once, on the client, while the game starts:

MyModClient.java
MultiversionUi.register("mymod", Text.translatable("mymod.name", "My Mod"), new MyMenu(config));

The first argument is your mod id exactly as in your mod metadata. The second is your mod's name as the "Mod menus" list shows it when several mods have menus.

Where the call goes depends on the loader:

Fabric, Quilt, Legacy Fabric

In your client entrypoint:

MyModClient.java
package com.example.mymod;

import dev.xichil.mvui.MultiversionUi;
import dev.xichil.mvui.Text;
import net.fabricmc.api.ClientModInitializer;

public final class MyModClient implements ClientModInitializer {
    @Override
    public void onInitializeClient() {
        MultiversionUi.register("mymod", Text.translatable("mymod.name", "My Mod"), new MyMenu(new MyConfig()));
    }
}
fabric.mod.json
{
  "entrypoints": {
    "client": ["com.example.mymod.MyModClient"]
  }
}

Forge, NeoForge

In your @Mod class's constructor. On Forge and NeoForge 1.13.2 and newer this also makes the menu your mod's Config screen in the mod list, because Forge attributes it to the mod whose constructor is running.

On Forge 1.7.10 to 1.12.2, also set guiFactory in the annotation to get the mod list's Config button: MultiversionUi.CPW_FORGE_GUI_FACTORY on 1.7.10 and MultiversionUi.LEGACY_FORGE_GUI_FACTORY on 1.8 to 1.12.2. Register your menu has the code for each era, and how to keep the call off dedicated servers.

4. Open it in the game#

Start the client and open Options. There is a new button labelled with your buttonLabel(), "My Mod...". When more than one mod registers a menu, the button reads Mod menus... and opens a list with one button per mod instead.

5. Add translations (optional)#

Add your keys to your mod's lang file. From 1.13 that is assets/mymod/lang/en_us.json:

assets/mymod/lang/en_us.json
{
  "mymod.menu": "My Mod...",
  "mymod.name": "My Mod",
  "mymod.title": "My Mod",
  "mymod.sounds": "Sounds",
  "mymod.volume": "Volume"
}

UiTexts.DONE uses vanilla's own gui.done key, so it is translated already. See Translate your menu for 1.12.2 and older.

Next steps#