Home

This is an archive of the old GitHub wiki! This will be re-written in the near future.

Welcome to YACL's wiki! This page will guide you on how to use YACL:

Notes

A few things to clear up before we get started...

  • This page will be laid out in the chronological order a developer would use to implement YACL.

  • This mod is only available for Fabric and Quilt.

  • All code examples presume you use Yarn mappings.

  • You must have basic/intermediate Java knowledge.

A quick overview

For people who just want to skip to the full example, click here

Backbones of the config

The main class that contains everything is YetAnotherConfigLib (how fitting!). This interface accepts a name, config categories, a save function, and an init function.

YACL's API is builder based, not class based, so everything is defined inside of a method, let's pretend we have a config class, it has a save function, and a few properties, and a method called MyConfig#createGui()

import net.minecraft.client.screen.Screen;

public class MyConfig {
    public boolean booleanToggle = true;
    public int intSlider = 5;

    public void save() { /* save your config! */ }

    public Screen createGui(Screen parent) {
        // time to use YOCL!
    }
}

But let's forget about the rest of this class for now and start constructing YetAnotherConfigLib...

YetAnotherConfigLib.createBuilder()
        .name(Text.of("Mod Name"))
        .save(MyConfig::save)
        .build()

Here you can see the general syntax for the API beginning to shape, however, this isn't very useful, let's add a category...

YetAnotherConfigLib.createBuilder()
        .title(Text.of("Mod Name"))
        .category(ConfigCategory.createBuilder()
                .name(Text.of("My Category")
                .tooltip(Text.of("This displays when you hover over a category button")) // optional
                .build())
        .save(MyConfig::save)
        .build()

Still, not very useful, but here is where it gets good! Let's forget about what we have so far and just focus on Option

Options

Options are broken down into two main parts, a binding and a controller.

Bindings

Bindings are simple, you bind a property to an interface. This provides essential functionality to query and set values in your config, there are two types of bindings you can use by default, a generic binding and a minecraft binding.

Generic

This is the Binding you will be using most often, it accepts a default value, a getter, and a setter, like this...

Binding.generic(0 /* default value for setting */, () -> this.booleanToggle, newValue -> this.booleanToggle = newValue)

Minecraft

You can also bind an option found in the GameOptions class like this...

Binding.minecraft(Minecraft.getInstance().gameOptions.getAutoJump())

Immutable

There is also an option to make a binding immutable (cannot be changed). This may be useful for labels

Binding.immutable(value)

Controllers

Controllers provide YACL a graphical widget to display and for users to interact with, this functionality has been completely separated from the Option itself so you can define multiple ways of displaying the same datatype.

By default there are only a handful of controllers available, but it is super easy to add another controller (later in the wiki) and covers the basics, booleans, numbers and enums.

BooleanController

A simple controller that displays Text based on the state of the boolean. There are a few pre-defined value formatters: ON_OFF (default), TRUE_FALSE and YES_NO. These can be found in the class.

new BooleanController(option /* provided by builder */, BooleanController.YES_NO_FORMATTER /* default ON_OFF, optional */)

TickBoxController

A controller that displays a tickbox, indicating true or false.

new TickBoxController(option /* provided by builder */)

EnumController

A simple controller that displays Text based on the name of the enum constant (this can be customised). By default, this controller first looks to see if the enum implements NameableEnum, which provides a Text, falling back on Enum#toString()

new EnumController(option /* provided by builder */, enumConstant -> Text.of(enumConstant.toString()) /* optional */)

CyclingListController

Any Iterable can be passed to this controller. It behaves just like EnumController and can be cycled with a click.

new CyclingListController(option /* provided by builder */, List.of("A", "B", "C"), entry -> entry.toLowerCase() /* optional */)

SliderController

There is a slider controller for every common number datatype:

  • IntegerSliderController

  • FloatSliderController

  • DoubleSliderController

  • LongSliderController

These sliders accept a minimum value, a maximum value and an interval (slider increment) and optionally a value formatter. By default, doubles format to two decimal places, floats one, and all four are separated with commas every three digits. (again, can be customized)

In this example, I chose to use an integer but all are the same.

new IntegerSliderController(option /* provided by builder */, 0 /* min */, 10 /* max */, 1 /* interval */)

ActionController

Simply displays some text on the right and executes the ButtonOption action on press. By default, the text reads EXECUTE but this can be customized.

new ActionController(option /* provided by builder */, Text.of("Run") /* optional */)

StringController

A custom text field implementation.

new StringController(option /* provided by builder */)

ColorController

A hex color field with a color preview.

new ColorController(option /* provided by builder */, allowAlpha /* default false */)

LabelController

Renders some Text, should be used with an immutable binding. Allows user to click on styled text or hover like in the chat.

new LabelController(option /* provided by builder */)

Building an option

Here you can see a basic example of an option. Please note that there is a shorthand function for Binding.generic being used (see here)

Option.createBuilder(boolean.class)
        .name(Text.of("My Boolean Option"))
        .tooltip(Text.of("This option displays the basic capabilities of YetAnotherConfigLib")) // optional
        .binding(
                true, // default
                () -> this.booleanToggle, // getter
                newValue -> this.booleanToggle = newValue // setter
         )
        .controller(BooleanController::new)
        .build()

All together!

That's all you need to know to build YetAnotherConfigLib, let's put everything together!

import net.minecraft.client.screen.Screen;

public class MyConfig {
    public boolean booleanToggle = true;
    public int intSlider = 5;

    public void save() { /* save your config! */ }

    public Screen createGui(Screen parent) {
        YetAnotherConfigLib.createBuilder()
                .title(Text.of("Mod Name"))
                .category(ConfigCategory.createBuilder()
                        .name(Text.of("My Category"))
                        .tooltip(Text.of("This displays when you hover over a category button")) // optional
                        .option(Option.createBuilder(boolean.class)
                                .name(Text.of("My Boolean Option"))
                                .tooltip(Text.of("This option displays the basic capabilities of YetAnotherConfigLib")) // optional
                                .binding(
                                        true, // default
                                        () -> this.booleanToggle, // getter
                                        newValue -> this.booleanToggle = newValue // setter
                                 )
                                .controller(BooleanController::new)
                                .build())
                        .build())
                .save(MyConfig::save)
                .build()
                .generateScreen(parent);
    }
}

Make sure you notice the last line where a Screen is generated.

Advanced features

Option Groups

Instead of just adding an option to a category, you can also add groups to a category. Groups act much like subcategories, they are displayed as a separator in the option list. They also don't need to be named and just be used as a spacer. You can also set them to be collapsed by default if there are too many.

ConfigCategory.createBuilder()
        .name(Text.of("My Category"))
        .tooltip(Text.of("This displays when you hover over a category button")) // optional
        .group(OptionGroup.createBuilder()
                .name(Text.of("Option Group"))
                .tooltip(Text.of("Like everything in YACL, you can have tooltips")) // optional
                .collapsed(true) // optional, default false
                .option(Option.createBuilder(boolean.class)
                        .name(Text.of("My Boolean Option"))
                        .tooltip(Text.of("This option displays the basic capabilities of YetAnotherConfigLib")) // optional
                        .binding(
                                true, // default
                                () -> this.booleanToggle, // getter
                                newValue -> this.booleanToggle = newValue // setter
                        )
                        .controller(BooleanController::new)
                        .build())
                .build())
        .build()

So, if we look at this in-game...

Lists

There is another type of option called a ListOption. It works with all controllers and all list types that has a controller type for it. Lists take a hybrid form of option groups and a regular option, and such you add lists with .group(), NOT .option().

You can minimize like a group and add flags like an option, it's both! Lists require an initial value for when users add another entry.

ListOption.createBuilder(String.class)
    .name(Text.of("List Option"))
    .binding(/* gets and sets a List, requires list field to be not final, does not manipulate the list */)
    .controller(StringController::new) // usual controllers, passed to every entry
    .initial("") // when adding a new entry to the list, this is the initial value it has
    .build()

Available Flag

You can mark options as unavailable on build to prevent changing the value.

Option.createBuilder(boolean.class)
        /* option things */
        .available(false)
        .build()

Additionally, you can modify this value after building the option with option.setAvailable(false). You can do this while the user is in the UI and it will dynamically change.

Option Flags

Options can be marked with flags that have an associated runnable that is ran when the option is saved. Can be used to reload chunks, require a restart etc.

There are some builtin flags to use which are listed below:

  • OptionFlag.GAME_RESTART - Opens a Screen asking the user to restart Minecraft

  • OptionFlag.RELOAD_CHUNKS - Reloads the world renderer

  • OptionFlag.WORLD_RENDER_UPDATE - Rebuilds terrain

  • OptionFlag.ASSET_RELOAD - Reloads all assets

Option.createBuilder(boolean.class)
        /* option things */
        .flag(OptionFlag.GAME_RESTART)
        .build()

Please note if you are to use the same custom flag more than once it should be defined as a field or an interface, not in lambda form. This allows YACL to identify them as the same so they are only ran once.

Dynamic Tooltips

You can also make tooltips change based on the value of the option. It is as simple as consuming the value in the builder...

Option.createBuilder(boolean.class)
        .tooltip(value -> Text.of("I now know that my option is set to " + value + "!"))
        .build()

Button "Options"

A thing I noticed while using other config libraries is that it is way too hard to just make a custom button that a user can press next to all the other options, so I made it super easy! This time, you don't need a binding! See here about how to customize the action controller text.

ButtonOption.createBuilder()
        .name(Text.of("Pressable Button"))
        .tooltip(Text.of("This is so easy!")) // optional
        .action((yaclScreen, buttonOption) -> {
            System.out.println("Button has been pressed!");
        })
        .controller(ActionController::new)
        .build()

Placeholder Categories

Placeholder categories can be used in place of a ConfigCategory. Rather than changing the option list to the category's options/groups, it just opens up another Screen.

PlaceholderCategory.createBuilder()
        .name(Text.of("Category Name"))
        .tooltip(Text.of("Tooltip for this category!"))
        .screen((client, parent) -> new MyScreen(parent))
        .build()

Instant Option Application

You can also specify options to instantly apply to their bindings as the user changes it, skipping the Apply Changes button altogether. Note that this does prevent the user from being able to undo their actions and you from using option flags

Option.createBuilder(int.class)
        /* option stuff! */
        .instant(true)
        .build()

Last updated