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.
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#
- The API on your compile class path and the
xis_mvuidependency declared (Installation).
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:
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:
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:
MenuScreen.builder("main")starts a screen with the idmain. Ids are never shown; tests use them..title(…)is the text at the top.Text.translatable(key, fallback)shows the translation ofkeyif your lang file has one, or the English fallback if not, so the menu works before you write any lang file..add(…)appends a row to the scrolling list..footer(…)adds a button to the fixed row at the bottom.value(() -> …)andonChange(v -> …)connect the widget to your field. After every click the screen re-reads every supplier, so the slider greys out as soon as the toggle turns sounds off.host.back()closes the screen and returns to wherever the player opened the menu from.
3. Register it#
Call MultiversionUi.register once, on the client, while the game starts:
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:
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()));
}
}
{
"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:
{
"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#
- State and refresh: the one idea that explains how every widget behaves.
- Which widget to use: all sixteen widgets at a glance.
- Save and load settings: write
MyConfigto disk. - A settings menu: a bigger, complete example.