Which widget to use

Every widget of xis-mvui at a glance - what the player sees, when to pick it, the options all widgets share and which may go in the footer.

Last updated

On this page

A screen is a MenuScreen: a title, a scrolling list of entries (one widget per row) and a footer row of buttons. The widget set is fixed; you compose screens from these.

Pick a widget#

You need… Use The player sees
an action Button a vanilla button
read-only text Label text on its own row; long text wraps
the result of an action StatusLine a colored message line, empty until shown
an on/off setting Toggle a button "label: ON" / "label: OFF"
several independent yes/no choices Checkbox a check box with its label beside it
one of a few values (an enum) Cycle a button "label: value" that steps on each click
one of many values Dropdown like a cycle; a click opens a picker screen
a list of items, one selected SelectList a scrolling list like the world list
a number in a range, dragged Slider a vanilla slider "label: value"
an exact number, typed NumberField a text field with range errors below it
one line of text TextInput a text field with a label above and an error line below
several lines of text MultiLineInput a text box of 2 – 12 lines with word wrap
a list of strings to edit StringListEditor a list, an input field, Add and Remove
a key or mouse button Keybind a button "label: key" like the Controls screen
a color ColorPicker a button "label: #RRGGBB" with a swatch; opens an editor
a group of entries Section a titled group, collapsible ("▶ title" / "▼ title")

The other screen type is ConfirmDialog, a yes/no question on its own screen (Several screens and dialogs).

How every widget is built#

Each widget has a builder that starts with an id and ends with build():

Toggle sounds = Toggle.builder("sounds")          // id: unique on the screen
        .label(Text.translatable("mymod.sounds", "Sounds"))
        .value(() -> config.sounds)
        .onChange(v -> config.sounds = v)
        .build();                                  // throws if a required field is missing

A missing required field makes build() throw IllegalStateException naming the widget and the field. Setters of optional fields reject null at once.

Options every widget has#

Builder method Default Effect
enabled(BooleanSupplier) or enabled(boolean) enabled while false the widget is greyed out and ignores input; it still shows its label, value and tooltip
tooltip(Supplier<Text>) or tooltip(Text) none text shown while the mouse is over the widget; the supplier may return null for none. Disabled widgets show it too, so it's a good place to say why.
visible(BooleanSupplier) visible while false the entry has no row at all, and the rows below move up
Button.builder("export")
        .label(Text.translatable("mymod.export", "Export"))
        .onPress(this::export)
        .enabled(() -> !profiles.isEmpty())
        .tooltip(() -> profiles.isEmpty() ? Text.translatable("mymod.export.none", "Nothing to export") : null)
        .visible(() -> config.advanced)
        .build();

Every setter that takes a supplier also has a constant overload; the supplier form is re-read on every refresh (State and refresh).

MenuScreen.Builder.footer(entry) puts an entry in the row at the bottom, which stays put while the list scrolls. Use it for the screen's main actions: Done, Save, Cancel.

May go in the footer May not
Button, Label, StatusLine, Toggle, Cycle, Dropdown, SelectList, TextInput, NumberField Checkbox, Slider, MultiLineInput, StringListEditor, Keybind, ColorPicker, Section

A footer entry also must have no visible(…) and must not be in a section. Anything else makes footer(…) throw. In practice, keep the footer to a few buttons.

Building a screen#

MenuScreen.builder("main")
        .title(Text.translatable("mymod.title", "My Mod"))   // required
        .add(entry)                                         // a row; a Section brings its children along
        .add(stringListEditor)                              // its four entries
        .footer(button)                                     // the bottom row, left to right
        .build();                                           // throws on a missing title or a duplicate id