Skip to content

Configurator UI

A configurator is a small UI component for one editable value or one group of values. Accessors create configurators, inspectors display them, and editors listen to their change events.

Configurator UI structure
Configurator UI structure: 1. ConfiguratorGroup, 2. ArrayConfiguratorGroup, 3. regular ValueConfigurator rows.

The three marked areas are the pieces most custom editors reuse. A ConfiguratorGroup creates collapsible sections, an ArrayConfiguratorGroup edits arrays or collections, and a ValueConfigurator row edits one value through one inline control.

Configurator

Configurator is the base class. It provides:

  • a label area;
  • an inline content container;
  • an optional tips icon;
  • copy and paste context menu support;
  • notifyChanges();
  • the Configurator.CHANGE_EVENT event.

Most concrete configurators add their control into inlineContainer.

NumberConfigurator speed = new NumberConfigurator(
        "Speed",
        () -> model.speed,
        value -> model.speed = value.floatValue(),
        1.0f,
        true
);

When a custom configurator changes the model, call:

notifyChanges();

That event is what Inspector uses to run listeners and record history.

ValueConfigurator

ValueConfigurator<T> is the core base class for most editable property rows. It connects one UI control to one value.

Constructor arguments:

public ValueConfigurator(
        String name,
        Supplier<@Nullable T> supplier,
        Consumer<@Nullable T> onUpdate,
        @Nullable T defaultValue,
        boolean forceUpdate
)

What each part does:

  • supplier: reads the current value from the target object.
  • onUpdate: writes a changed value back to the target object.
  • defaultValue: fallback value and default paste/drop type.
  • forceUpdate: when true, screenTick() refreshes the configurator from supplier.

ValueConfigurator separates two update directions:

  • onValueUpdatePassively(newValue): update the configurator's internal value and visual widget without notifying listeners.
  • updateValueActively(newValue): user changed the widget; update value, call onUpdate, and fire Configurator.CHANGE_EVENT.

That split matters. If your widget is refreshed from the model, use the passive path. If the user edits the widget, use the active path.

protected void onSliderChanged(float value) {
    updateValueActively(value);
}

@Override
protected void onValueUpdatePassively(Float newValue) {
    super.onValueUpdatePassively(newValue);
    slider.setValue(newValue == null ? defaultValue : newValue, false);
}

It also provides copy/paste and drag-drop hooks:

  • setCopiable(...)
  • setPastable(...)
  • canDropObject(...)
  • onDropObject(...)

Most simple custom configurators should extend ValueConfigurator<T>, not Configurator directly.

ConfiguratorGroup

ConfiguratorGroup contains other configurators. It is used for class-level @Configurable, nested subConfigurable fields, and manual sections.

ConfiguratorGroup display = new ConfiguratorGroup("Display", false);
display.addConfigurator(new StringConfigurator("Name", () -> name, v -> name = v, "", true));
father.addConfigurator(display);

Groups can be collapsed. @Configurable(collapse = ..., canCollapse = ...) maps to the same behavior when the group is generated by annotations.

ArrayConfiguratorGroup

Arrays and collections use ArrayConfiguratorGroup. The group owns a list of child configurators, and can allow add, remove, and reorder operations.

@ConfigList is the normal way to tune this behavior:

@Configurable(name = "Entries")
@ConfigList(canAdd = true, canRemove = true, canReorder = true)
public List<Entry> entries = new ArrayList<>();

Common Concrete Configurators

You will usually see these classes from accessors or custom panels:

Type Class Description
Boolean BooleanConfigurator Toggle-style editor for boolean / Boolean.
Number NumberConfigurator Text-field based numeric editor with range and mouse-wheel step support.
String StringConfigurator Single-line text editor; can be restricted to resource-location syntax.
Text area TextAreaConfigurator Multi-line text editor.
Color ColorConfigurator Integer color picker, usually selected by @ConfigColor.
HDR color HDRColorConfigurator High dynamic range color editor.
Selector SelectorConfigurator Dropdown-style selector for fixed candidates, often enums.
Toggle selector ToggleSelectorConfigurator Compact selector using toggle/icon style candidates.
Conditional selector ConfiguratorSelectorConfigurator Selector that also rebuilds a child ConfiguratorGroup for the selected value.
Search SearchComponentConfigurator Search field for large or dynamic candidate sets.
Registry search RegistrySearchComponent Search UI for Minecraft registry values.
Tag key TagKeySearchComponent Search UI for tag-key style values.
NBT/tag TagConfigurator Editor for NBT Tag values.
Data component DataComponentConfigurator, TypedDataComponentConfigurator Editors for Minecraft data component values.
Texture IGuiTextureConfigurator Chooses and configures registered IGuiTexture implementations.
Renderer IRendererConfigurator Chooses and configures registered IRenderer implementations.
Transform reference TransformRefConfigurator Scene-editor transform reference editor.
Layout value LengthPercentConfigurator, LPAConfigurator, DimensionConfigurator Editors for layout-related numeric/unit values.
Optional float FloatOptionalConfigurator Float editor that can represent an empty optional value.
Group header HeaderConfigurator Non-value header row used to separate sections.
Array/list ArrayConfiguratorGroup Add/remove/reorder editor for arrays and collections.

Do not start with manual UI for every field. Let annotations and accessors handle ordinary properties, then add manual configurators only where the editor needs custom behavior.

Custom Configurator

Create a custom configurator when one value needs a special widget. The pattern is:

  1. extend ValueConfigurator<T>;
  2. create the UI control and add it to inlineContainer;
  3. when the user changes the control, call updateValueActively(value);
  4. override onValueUpdatePassively(...) to refresh the control when the model changes;
  5. optionally expose copy, paste, and drag/drop behavior.
public class ToggleNameConfigurator extends ValueConfigurator<Boolean> {
    private final Toggle toggle;

    public ToggleNameConfigurator(
            String name,
            Supplier<Boolean> supplier,
            Consumer<Boolean> onUpdate,
            Boolean defaultValue,
            boolean forceUpdate
    ) {
        super(name, supplier, onUpdate, defaultValue, forceUpdate);
        setCopiable(value -> value);

        toggle = new Toggle();
        toggle.toggleLabel.setText("");
        toggle.setOn(value == null ? defaultValue : value, false);
        toggle.setOnToggleChanged(this::updateValueActively);

        inlineContainer.addChild(toggle);
    }

    @Override
    protected void onValueUpdatePassively(Boolean newValue) {
        if (newValue == null) newValue = defaultValue;
        if (newValue.equals(value)) return;
        super.onValueUpdatePassively(newValue);
        toggle.setOn(newValue, false);
    }
}

Use it manually:

father.addConfigurator(new ToggleNameConfigurator(
        "Enabled",
        () -> model.enabled,
        value -> model.enabled = value,
        true,
        true
));

Or return it from an accessor:

@Override
public Configurator create(
        String name,
        Supplier<Boolean> supplier,
        Consumer<Boolean> consumer,
        boolean forceUpdate,
        @Nullable Field field,
        @Nullable Object owner
) {
    return new ToggleNameConfigurator(name, supplier, consumer, true, forceUpdate);
}

Extend Configurator directly only for UI that does not represent one value, such as a header, action row, preview panel, or compound editor that manages several children itself.

Copy And Paste

Configurator can expose copy and paste actions in its right-click menu.

configurator
        .setCopiableDirect(model.value)
        .setPastable(Value.class, value -> {
            model.value = value;
            configurator.notifyChanges();
        });

This is useful for repeated editor data such as colors, vectors, textures, and renderer settings.