diff options
author | isXander <xander@isxander.dev> | 2024-04-14 23:19:21 +0100 |
---|---|---|
committer | isXander <xander@isxander.dev> | 2024-04-14 23:19:21 +0100 |
commit | 97bbc5a3d91ed57e55796777bbfc117ff28e2221 (patch) | |
tree | 3b9d17cb271a7676149d9d62bcbbe32bc72d4f9c /src | |
parent | 26aec79e10025ff3427ceb47602156ebd670b2ac (diff) | |
download | YetAnotherConfigLib-97bbc5a3d91ed57e55796777bbfc117ff28e2221.tar.gz YetAnotherConfigLib-97bbc5a3d91ed57e55796777bbfc117ff28e2221.tar.bz2 YetAnotherConfigLib-97bbc5a3d91ed57e55796777bbfc117ff28e2221.zip |
Add Kotlin DSL
Diffstat (limited to 'src')
8 files changed, 718 insertions, 0 deletions
diff --git a/src/main/java/dev/isxander/yacl3/api/ConfigCategory.java b/src/main/java/dev/isxander/yacl3/api/ConfigCategory.java index b3d68fc..41f3ca9 100644 --- a/src/main/java/dev/isxander/yacl3/api/ConfigCategory.java +++ b/src/main/java/dev/isxander/yacl3/api/ConfigCategory.java @@ -125,6 +125,13 @@ public interface ConfigCategory { Builder groups(@NotNull Collection<OptionGroup> groups); /** + * Fetches the builder for the root group of the category. + * This is the group that has no header and options are added through {@link Builder#option(Option)}. + * In its default implementation, this builder is severely limited and a lot of methods are unsupported. + */ + OptionGroup.Builder rootGroupBuilder(); + + /** * Sets the tooltip to be used by the category. * Can be invoked twice to append more lines. * No need to wrap the text yourself, the gui does this itself. diff --git a/src/main/java/dev/isxander/yacl3/impl/ConfigCategoryImpl.java b/src/main/java/dev/isxander/yacl3/impl/ConfigCategoryImpl.java index 400abf6..c73d647 100644 --- a/src/main/java/dev/isxander/yacl3/impl/ConfigCategoryImpl.java +++ b/src/main/java/dev/isxander/yacl3/impl/ConfigCategoryImpl.java @@ -46,6 +46,8 @@ public final class ConfigCategoryImpl implements ConfigCategory { private Component name; private final List<Option<?>> rootOptions = new ArrayList<>(); + private final RootGroupBuilder rootGroupBuilder = new RootGroupBuilder(); + private final List<OptionGroup> groups = new ArrayList<>(); private final List<Component> tooltipLines = new ArrayList<>(); @@ -107,6 +109,11 @@ public final class ConfigCategoryImpl implements ConfigCategory { } @Override + public OptionGroup.Builder rootGroupBuilder() { + return rootGroupBuilder; + } + + @Override public ConfigCategory build() { Validate.notNull(name, "`name` must not be null to build `ConfigCategory`"); @@ -130,5 +137,39 @@ public final class ConfigCategoryImpl implements ConfigCategory { return new ConfigCategoryImpl(name, ImmutableList.copyOf(combinedGroups), concatenatedTooltip); } + + private class RootGroupBuilder implements OptionGroup.Builder { + @Override + public OptionGroup.Builder name(@NotNull Component name) { + throw new UnsupportedOperationException("Cannot set name of root group!"); + } + + @Override + public OptionGroup.Builder description(@NotNull OptionDescription description) { + throw new UnsupportedOperationException("Cannot set name of root group!"); + } + + @Override + public OptionGroup.Builder option(@NotNull Option<?> option) { + ConfigCategoryImpl.BuilderImpl.this.option(option); + return this; + } + + @Override + public OptionGroup.Builder options(@NotNull Collection<? extends Option<?>> options) { + ConfigCategoryImpl.BuilderImpl.this.options(options); + return this; + } + + @Override + public OptionGroup.Builder collapsed(boolean collapsible) { + throw new UnsupportedOperationException("Cannot set collapsible of root group!"); + } + + @Override + public OptionGroup build() { + throw new UnsupportedOperationException("Cannot build root group!"); + } + } } } diff --git a/src/main/kotlin/dev/isxander/yacl3/dsl/API.kt b/src/main/kotlin/dev/isxander/yacl3/dsl/API.kt new file mode 100644 index 0000000..87778e5 --- /dev/null +++ b/src/main/kotlin/dev/isxander/yacl3/dsl/API.kt @@ -0,0 +1,105 @@ +package dev.isxander.yacl3.dsl + +import dev.isxander.yacl3.api.* +import net.minecraft.network.chat.Component + +interface YACLDsl { + val namespaceKey: String + + val categories: YACLDslReference + + fun title(component: Component) + fun title(block: () -> Component) + + fun category(id: String, block: CategoryDsl.() -> Unit): ConfigCategory + + fun save(block: () -> Unit) +} + +interface OptionAddableDsl { + fun <T : Any> option(id: String, block: OptionDsl<T>.() -> Unit): Option<T> +} + +interface CategoryDsl : OptionAddableDsl { + val groups: CategoryDslReference + val options: GroupDslReference + + fun group(id: String, block: GroupDsl.() -> Unit): OptionGroup + + fun name(component: Component) + fun name(block: () -> Component) + + fun tooltip(vararg component: Component) + fun tooltipBuilder(block: TooltipBuilderDsl.() -> Unit) + fun useDefaultTooltip(lines: Int = 1) +} + +interface GroupDsl : OptionAddableDsl { + val options: GroupDslReference + + fun name(component: Component) + fun name(block: () -> Component) + + fun descriptionBuilder(block: OptionDescription.Builder.() -> Unit) + fun description(description: OptionDescription) + fun useDefaultDescription(lines: Int = 1) +} + +interface OptionDsl<T> : Option.Builder<T> { + val option: FutureValue<Option<T>> + + fun OptionDescription.Builder.addDefaultDescription(lines: Int? = null) +} + +interface TooltipBuilderDsl { + fun text(component: Component) + fun text(block: () -> Component) + + operator fun Component.unaryPlus() + + class Delegate(private val tooltipFunction: (Component) -> Unit) : TooltipBuilderDsl { + override fun text(component: Component) { + tooltipFunction(component) + } + + override fun text(block: () -> Component) { + text(block()) + } + + override fun Component.unaryPlus() { + text(this) + } + } +} + +interface YACLDslReference : Reference<CategoryDslReference> { + fun get(): YetAnotherConfigLib? + + val isBuilt: Boolean + + fun registering(block: CategoryDsl.() -> Unit): RegisterableDelegateProvider<CategoryDsl, ConfigCategory> +} + +interface CategoryDslReference : Reference<GroupDslReference> { + fun get(): ConfigCategory? + + val root: GroupDslReference + + val isBuilt: Boolean + + fun registering(block: GroupDsl.() -> Unit): RegisterableDelegateProvider<GroupDsl, OptionGroup> +} + +interface GroupDslReference { + fun get(): OptionGroup? + + operator fun <T> get(id: String): FutureValue<Option<T>> + + val isBuilt: Boolean + + fun <T : Any> registering(block: OptionDsl<T>.() -> Unit): RegisterableDelegateProvider<OptionDsl<T>, Option<T>> +} + + + + diff --git a/src/main/kotlin/dev/isxander/yacl3/dsl/Extensions.kt b/src/main/kotlin/dev/isxander/yacl3/dsl/Extensions.kt new file mode 100644 index 0000000..4b93f5f --- /dev/null +++ b/src/main/kotlin/dev/isxander/yacl3/dsl/Extensions.kt @@ -0,0 +1,36 @@ +package dev.isxander.yacl3.dsl + +import dev.isxander.yacl3.api.Option +import dev.isxander.yacl3.api.OptionDescription +import dev.isxander.yacl3.api.OptionGroup +import dev.isxander.yacl3.api.controller.ControllerBuilder +import net.minecraft.network.chat.Component +import kotlin.reflect.KMutableProperty0 + +fun <T : Any> Option.Builder<T>.binding(property: KMutableProperty0<T>, default: T) { + binding(default, { property.get() }, { property.set(it) }) +} + +fun <T : Any> Option.Builder<T>.descriptionBuilder(block: OptionDescription.Builder.(T) -> Unit) { + description { OptionDescription.createBuilder().apply { block(it) }.build() } +} + +fun Option.Builder<*>.descriptionBuilderConst(block: OptionDescription.Builder.() -> Unit) { + description(OptionDescription.createBuilder().apply(block).build()) +} + +fun Option.Builder<*>.available(block: () -> Boolean) { + available(block()) +} + +fun OptionDescription.Builder.text(block: () -> Component) { + text(block()) +} + +fun OptionGroup.Builder.descriptionBuilder(block: OptionDescription.Builder.() -> Unit) { + description(OptionDescription.createBuilder().apply(block).build()) +} + +fun <T, B : ControllerBuilder<T>> Option.Builder<T>.controller(builder: (Option<T>) -> B, block: B.() -> Unit = {}) { + controller { builder(it).apply(block) } +} diff --git a/src/main/kotlin/dev/isxander/yacl3/dsl/Util.kt b/src/main/kotlin/dev/isxander/yacl3/dsl/Util.kt new file mode 100644 index 0000000..819365c --- /dev/null +++ b/src/main/kotlin/dev/isxander/yacl3/dsl/Util.kt @@ -0,0 +1,110 @@ +package dev.isxander.yacl3.dsl + +import dev.isxander.yacl3.api.Option +import kotlin.properties.ReadOnlyProperty +import kotlin.reflect.KProperty + +interface FutureValue<T> { + fun onReady(block: (T) -> Unit) + fun <R> map(block: (T) -> R): FutureValue<R> + fun <R> flatMap(block: (T) -> FutureValue<R>): FutureValue<R> + fun getOrNull(): T? + fun getOrThrow(): T = getOrNull() ?: error("Value not ready yet!") + + open class Impl<T>(default: T? = null) : FutureValue<T> { + var value: T? = default + set(value) { + field = value + while (taskQueue.isNotEmpty()) { + taskQueue.removeFirst()(value!!) + } + } + private val taskQueue = ArrayDeque<(T) -> Unit>() + + override fun onReady(block: (T) -> Unit) { + if (value != null) block(value!!) + else taskQueue.add(block) + } + + override fun <R> map(block: (T) -> R): FutureValue<R> { + val future = Impl<R>() + onReady { + future.value = block(it) + } + return future + } + + override fun <R> flatMap(block: (T) -> FutureValue<R>): FutureValue<R> { + val future = Impl<R>() + onReady { + block(it).onReady { inner -> + future.value = inner + } + } + return future + } + + override fun getOrNull(): T? = value + } +} + +interface Reference<T> : ReadOnlyProperty<Any?, FutureValue<T>> { + operator fun get(id: String): FutureValue<T> + + override fun getValue(thisRef: Any?, property: KProperty<*>): FutureValue<T> { + return get(property.name) + } + + operator fun invoke(name: String? = null, block: (T) -> Unit): ReadOnlyProperty<Any?, FutureValue<T>> { + return ReadOnlyProperty { thisRef, property -> + val future = get(name ?: property.name) + future.onReady(block) + future + } + } +} + + +operator fun <T> FutureValue<out Reference<T>>.get(id: String): FutureValue<T> { + val future = FutureValue.Impl<FutureValue<T>>() + onReady { + future.value = it[id] + } + return future.flatten() +} + +fun FutureValue<GroupDslReference>.getOption(id: String): FutureValue<Option<*>> { + val future = FutureValue.Impl<FutureValue<Option<*>>>() + onReady { + future.value = it.get<Any?>(id) as FutureValue<Option<*>> + } + return future.flatten() +} + + +private fun <T> FutureValue<FutureValue<T>>.flatten(): FutureValue<T> { + val future = FutureValue.Impl<T>() + onReady { outer -> + outer.onReady { inner -> + future.value = inner + } + } + return future +} + +class RegisterableDelegateProvider<Dsl, Return>( + private val registerFunction: (String, Dsl.() -> Unit) -> Return, + private val action: Dsl.() -> Unit +) { + operator fun provideDelegate(thisRef: Any?, property: KProperty<*>): ExistingDelegateProvider<Return> { + return ExistingDelegateProvider(registerFunction(property.name, action)) + } +} + +class ExistingDelegateProvider<Return>( + private val delegate: Return +) { + operator fun getValue(thisRef: Any?, property: KProperty<*>): Return { + return delegate + } +} diff --git a/src/main/kotlin/dev/isxander/yacl3/dsl/YetAnotherConfigLibDsl.kt b/src/main/kotlin/dev/isxander/yacl3/dsl/YetAnotherConfigLibDsl.kt new file mode 100644 index 0000000..8c10cfd --- /dev/null +++ b/src/main/kotlin/dev/isxander/yacl3/dsl/YetAnotherConfigLibDsl.kt @@ -0,0 +1,283 @@ +package dev.isxander.yacl3.dsl + +import dev.isxander.yacl3.api.* +import net.minecraft.locale.Language +import net.minecraft.network.chat.Component + +fun YetAnotherConfigLib(namespace: String, block: YACLDsl.() -> Unit): YetAnotherConfigLib { + val context = YACLDslContext(namespace) + context.block() + return context.build() +} + +class YACLDslContext( + private val namespace: String, + private val builder: YetAnotherConfigLib.Builder = YetAnotherConfigLib.createBuilder() +) : YACLDsl { + private val categoryMap = LinkedHashMap<String, YACLDslCategoryContext>() + private val categoryDslReferenceMap = mutableMapOf<String, FutureValue.Impl<CategoryDslReference>>() + + override val namespaceKey = "yacl3.config.$namespace" + + private var used = false + private var built: YetAnotherConfigLib? = null + + private var saveFunction: () -> Unit = {} + + override val categories = object : YACLDslReference { + override fun get(): YetAnotherConfigLib? = built + + override operator fun get(id: String): FutureValue<CategoryDslReference> = + FutureValue.Impl(categoryMap[id]?.groups).also { categoryDslReferenceMap[id] = it } + + override fun registering(block: CategoryDsl.() -> Unit): RegisterableDelegateProvider<CategoryDsl, ConfigCategory> { + return RegisterableDelegateProvider({ id, configuration -> category(id, configuration) }, block) + } + + override val isBuilt: Boolean + get() = built != null + } + + init { + title(Component.translatable("$namespaceKey.title")) + } + + override fun title(component: Component) { + builder.title(component) + } + + override fun title(block: () -> Component) { + title(block()) + } + + override fun category(id: String, block: CategoryDsl.() -> Unit): ConfigCategory { + val context = YACLDslCategoryContext(id, this) + context.block() + categoryMap[id] = context + categoryDslReferenceMap[id]?.value = context.groups + + val built = context.build() + builder.category(built) + + return built + } + + override fun save(block: () -> Unit) { + val oldSaveFunction = saveFunction + saveFunction = { // allows stacking of save functions + oldSaveFunction() + block() + } + } + + fun build(): YetAnotherConfigLib { + if (used) error("Cannot use the same DSL context twice!") + used = true + + builder.save(saveFunction) + + return builder.build().also { built = it } + } +} + +class YACLDslCategoryContext( + private val id: String, + private val root: YACLDslContext, + private val builder: ConfigCategory.Builder = ConfigCategory.createBuilder(), +) : CategoryDsl { + private val groupMap = LinkedHashMap<String, YACLDslGroupContext>() + private val groupDslReferenceMap = mutableMapOf<String, FutureValue.Impl<GroupDslReference>>() + val categoryKey = "${root.namespaceKey}.$id" + + private var built: ConfigCategory? = null + + private val rootGroup: YACLDslGroupContext = YACLDslGroupContext(id, this, builder.rootGroupBuilder(), root = true) + + override val groups = object : CategoryDslReference { + override fun get(): ConfigCategory? = built + + override fun get(id: String): FutureValue<GroupDslReference> = + FutureValue.Impl(groupMap[id]?.options).also { groupDslReferenceMap[id] = it } + + override val root: GroupDslReference + get() = rootGroup.options + + override fun registering(block: GroupDsl.() -> Unit): RegisterableDelegateProvider<GroupDsl, OptionGroup> { + return RegisterableDelegateProvider({ id, configuration -> group(id, configuration) }, block) + } + + override val isBuilt: Boolean + get() = built != null + + } + + override val options = rootGroup.options + + init { + builder.name(Component.translatable("$categoryKey.title")) + } + + override fun name(component: Component) { + builder.name(component) + } + + override fun name(block: () -> Component) { + name(block()) + } + + override fun group(id: String, block: GroupDsl.() -> Unit): OptionGroup { + val context = YACLDslGroupContext(id, this) + context.block() + groupMap[id] = context + groupDslReferenceMap[id]?.value = context.options + + return context.build().also { + builder.group(it) + } + } + + override fun <T : Any> option(id: String, block: OptionDsl<T>.() -> Unit): Option<T> = + rootGroup.option(id, block) + + override fun tooltip(vararg component: Component) { + builder.tooltip(*component) + } + + override fun tooltipBuilder(block: TooltipBuilderDsl.() -> Unit) { + val builder = TooltipBuilderDsl.Delegate { builder.tooltip(it) } + builder.block() + } + + override fun useDefaultTooltip(lines: Int) { + if (lines == 1) { + builder.tooltip(Component.translatable("$categoryKey.tooltip")) + } else for (i in 1..lines) { + builder.tooltip(Component.translatable("$categoryKey.tooltip.$i")) + } + } + + fun build(): ConfigCategory { + return builder.build().also { built = it } + } +} + +class YACLDslGroupContext( + private val id: String, + private val category: YACLDslCategoryContext, + private val builder: OptionGroup.Builder = OptionGroup.createBuilder(), + private val root: Boolean = false, +) : GroupDsl { + private val optionMap = LinkedHashMap<String, YACLDslOptionContext<*>>() + private val optionDslReferenceMap = mutableMapOf<String, FutureValue.Impl<Option<*>>>() + val groupKey = "${category.categoryKey}.$id" + private var built: OptionGroup? = null + + override val options = object : GroupDslReference { + override fun get(): OptionGroup? = built + + override fun <T> get(id: String): FutureValue<Option<T>> = + FutureValue.Impl(optionMap[id]).flatMap { it.option as FutureValue<Option<T>> }.also { optionDslReferenceMap[id] = it as FutureValue.Impl<Option<*>> } + + override fun <T : Any> registering(block: OptionDsl<T>.() -> Unit): RegisterableDelegateProvider<OptionDsl<T>, Option<T>> { + return RegisterableDelegateProvider({ id, configuration -> option(id, configuration) }, block) + } + + override val isBuilt: Boolean + get() = built != null + + } + + override fun name(component: Component) { + builder.name(component) + } + + override fun name(block: () -> Component) { + name(block()) + } + + override fun descriptionBuilder(block: OptionDescription.Builder.() -> Unit) { + builder.description(OptionDescription.createBuilder().apply(block).build()) + } + + override fun description(description: OptionDescription) { + builder.description(description) + } + + init { + if (!root) { + builder.name(Component.translatable("$groupKey.name")) + } + } + + override fun <T : Any> option(id: String, block: OptionDsl<T>.() -> Unit): Option<T> { + val context = YACLDslOptionContext<T>(id, this) + context.block() + optionMap[id] = context + + return context.build().also { + optionDslReferenceMap[id]?.value = it + builder.option(it) + } + } + + override fun useDefaultDescription(lines: Int) { + descriptionBuilder { + if (lines == 1) { + text(Component.translatable("$groupKey.description")) + } else for (i in 1..lines) { + text(Component.translatable("$groupKey.description.$i")) + } + } + } + + fun build(): OptionGroup { + return builder.build().also { built = it } + } +} + +class YACLDslOptionContext<T : Any>( + private val id: String, + private val group: YACLDslGroupContext, + private val builder: Option.Builder<T> = Option.createBuilder() +) : Option.Builder<T> by builder, OptionDsl<T> { + val optionKey = "${group.groupKey}.$id" + private var built: Option<T>? = null + + private val taskQueue = ArrayDeque<(Option<T>) -> Unit>() + override val option = FutureValue.Impl<Option<T>>() + + init { + name(Component.translatable("$optionKey.name")) + } + + override fun OptionDescription.Builder.addDefaultDescription(lines: Int?) { + if (lines != null) { + if (lines == 1) { + text(Component.translatable("$optionKey.description")) + } else for (i in 1..lines) { + text(Component.translatable("$optionKey.description.$i")) + } + } else { + // loop until we find a key that doesn't exist + var i = 1 + while (i < 100) { + val key = "$optionKey.description.$i" + if (Language.getInstance().has(key)) { + text(Component.translatable(key)) + } + + i++ + } + } + } + + override fun build(): Option<T> { + return builder.build().also { + built = it + option.value = it + while (taskQueue.isNotEmpty()) { + taskQueue.removeFirst()(it) + } + } + } +} diff --git a/src/testmod/java/dev/isxander/yacl3/test/GuiTest.java b/src/testmod/java/dev/isxander/yacl3/test/GuiTest.java index 3ddfce6..cdc1285 100644 --- a/src/testmod/java/dev/isxander/yacl3/test/GuiTest.java +++ b/src/testmod/java/dev/isxander/yacl3/test/GuiTest.java @@ -49,6 +49,12 @@ public class GuiTest { Minecraft.getInstance().setScreen(AutogenConfigTest.INSTANCE.generateGui().generateScreen(screen)); }) .build()) + .option(ButtonOption.createBuilder() + .name(Component.literal("Kotlin DSL Test")) + .action((screen, opt) -> { + Minecraft.getInstance().setScreen(DslTestKt.kotlinDslGui(screen)); + }) + .build()) .group(OptionGroup.createBuilder() .name(Component.literal("Wiki")) .option(ButtonOption.createBuilder() diff --git a/src/testmod/kotlin/dev/isxander/yacl3/test/DslTest.kt b/src/testmod/kotlin/dev/isxander/yacl3/test/DslTest.kt new file mode 100644 index 0000000..2efd9a4 --- /dev/null +++ b/src/testmod/kotlin/dev/isxander/yacl3/test/DslTest.kt @@ -0,0 +1,130 @@ +package dev.isxander.yacl3.test + +import dev.isxander.yacl3.api.OptionFlag +import dev.isxander.yacl3.api.controller.BooleanControllerBuilder +import dev.isxander.yacl3.api.controller.IntegerSliderControllerBuilder +import dev.isxander.yacl3.dsl.* +import net.minecraft.client.gui.screens.Screen +import net.minecraft.network.chat.Component +import net.minecraft.resources.ResourceLocation + +object Foo { + var bar = true + var baz = 0 +} + +fun kotlinDslGui(parent: Screen?) = YetAnotherConfigLib("namespace") { + // default title with translation key: + // `yacl3.config.namespace.title` + /* NO CODE REQUIRED */ + + // or set the title + title(Component.literal("A cool title")) + + + // usual save function + save { + // run your save function! + } + + // get access to an option from the very root of the dsl! + categories["testCategory"]["testGroup"].getOption("testOption").onReady { + // do something with it + } + + val testCategory by categories.registering { + // default name with translation key: + // `yacl3.config.namespace.testCategory.testGroup.name` + /* NO CODE REQUIRED */ + + // or set the name + name { Component.literal("A cool category") } + + // custom tooltip + tooltipBuilder { + // add a line like this + +Component.translatable("somecustomkey") + + // or like this + text(Component.translatable("somecustomkey")) + + // or like this + text { Component.translatable("somecustomkey") } + } + + // you can declare things with strings + group("testGroup") { + // default name with translation key: + // `yacl3.config.namespace.testCategory.testGroup.name` + /* NO CODE REQUIRED */ + + // or set the name + name { Component.literal("A cool group") } + + + // custom description builder: + descriptionBuilder { + // blah blah blah + } + + // default description with translation key: + // `yacl3.config.namespace.testCategory.testGroup.description.1-5` + // not compatible with custom description builder + useDefaultDescription(lines = 5) + + // you can define opts/groups/categories using this delegate syntax + val testOption by options.registering { // type is automatically inferred from binding + // default name with translation key: + // `yacl3.config.namespace.testCategory.testGroup.testOption.name` + /* NO CODE REQUIRED */ + + // custom description builder: + descriptionBuilder { value -> // changes the desc based on the current value + // description with translation key: + // `yacl3.config.namespace.testCategory.testGroup.testOption.description.1-5` + addDefaultDescription(lines = 5) + + text { Component.translatable("somecustomkey") } + webpImage(ResourceLocation("namespace", "image.png")) + } + + // KProperties are cool! + binding(Foo::bar, Foo.bar) + + // you can access other options like this! + // `options` field is from the enclosing group dsl + listener { opt, newVal -> + options.get<Int>("otherTestOption").onReady { it.setAvailable(newVal) } + } + + // or even get an access to them before creation + options.get<Int>("otherTestOption").onReady { + // do something with it + } + + // you can set available with a block + available { true } + + // regular controller stuff + // this will be DSLed at some point + controller(BooleanControllerBuilder::create) { + // blah blah blah + } + + // flags as usual + flag(OptionFlag.ASSET_RELOAD) + } + + val otherTestOption by options.registering { // type is automatically inferred from binding + controller(IntegerSliderControllerBuilder::create) { + range(0, 100) + step(5) + } + + binding(Foo::baz, Foo.baz) + + // blah blah blah other stuff + } + } + } +}.generateScreen(parent) |