From 305718e163f91802a4bc1c1ed6540febb2ce204e Mon Sep 17 00:00:00 2001 From: isXander Date: Tue, 11 Jun 2024 23:13:49 +0100 Subject: codec config and rewritten kotlin dsl --- src/main/kotlin/dev/isxander/yacl3/dsl/API.kt | 183 +++++++++---- .../kotlin/dev/isxander/yacl3/dsl/Controllers.kt | 119 ++++++++ .../kotlin/dev/isxander/yacl3/dsl/Extensions.kt | 59 +++- src/main/kotlin/dev/isxander/yacl3/dsl/Impl.kt | 298 +++++++++++++++++++++ src/main/kotlin/dev/isxander/yacl3/dsl/Util.kt | 103 +------ .../isxander/yacl3/dsl/YetAnotherConfigLibDsl.kt | 283 ------------------- 6 files changed, 610 insertions(+), 435 deletions(-) create mode 100644 src/main/kotlin/dev/isxander/yacl3/dsl/Controllers.kt create mode 100644 src/main/kotlin/dev/isxander/yacl3/dsl/Impl.kt delete mode 100644 src/main/kotlin/dev/isxander/yacl3/dsl/YetAnotherConfigLibDsl.kt (limited to 'src/main/kotlin/dev') diff --git a/src/main/kotlin/dev/isxander/yacl3/dsl/API.kt b/src/main/kotlin/dev/isxander/yacl3/dsl/API.kt index 87778e5..cd2c483 100644 --- a/src/main/kotlin/dev/isxander/yacl3/dsl/API.kt +++ b/src/main/kotlin/dev/isxander/yacl3/dsl/API.kt @@ -1,63 +1,154 @@ package dev.isxander.yacl3.dsl import dev.isxander.yacl3.api.* +import net.minecraft.network.chat.CommonComponents import net.minecraft.network.chat.Component +import java.util.concurrent.CompletableFuture +import kotlin.properties.ReadOnlyProperty -interface YACLDsl { - val namespaceKey: String +interface Buildable { + val built: CompletableFuture - val categories: YACLDslReference + fun build(): T +} - fun title(component: Component) - fun title(block: () -> Component) +fun CompletableFuture.onReady(block: (T) -> Unit) = + this.whenComplete { result, _ -> result?.let(block) } - fun category(id: String, block: CategoryDsl.() -> Unit): ConfigCategory +operator fun CompletableFuture>.get(id: String): CompletableFuture = + thenCompose { it[id] } - fun save(block: () -> Unit) +typealias FutureOption = CompletableFuture> + +fun CompletableFuture.futureRef(id: String): FutureOption = + thenCompose { it.futureRef(id) } + +fun YetAnotherConfigLib(id: String, block: RootDsl.() -> Unit) = + RootDslImpl(id).apply(block).build() + +interface ParentRegistrar { + fun register(id: String, registrant: T): T + + fun register(id: String, block: DSL.() -> Unit): T + + /** Registers a registrant via delegation - if id is not provided, the delegated property name is used */ + fun registering(id: String? = null, block: DSL.() -> Unit): RegisterableActionDelegateProvider + + /** Creates a delegated future reference to a registrant that may or may not exist yet */ + val futureRef: ReadOnlyProperty> + + /** Creates a future reference to a registrant that may or may not exist yet */ + fun futureRef(id: String): CompletableFuture + + /** Gets a registrant with the id, if it exists */ + fun ref(id: String): T? + + /** Creates a delegated property that returns a registrant with a matching id, or null if it does not exist at the time of calling */ + val ref: ReadOnlyProperty + + operator fun get(id: String): CompletableFuture } -interface OptionAddableDsl { - fun option(id: String, block: OptionDsl.() -> Unit): Option +interface OptionRegistrar { + /** Registers an option that has already been built. */ + fun > register(id: String, option: OPT): OPT + + /** Registers a regular option */ + fun register(id: String, block: OptionDsl.() -> Unit): Option + + /** Registers a regular option via delegation */ + fun registering(id: String? = null, block: OptionDsl.() -> Unit): RegisterableActionDelegateProvider, Option> + + fun futureRef(id: String): CompletableFuture> + fun futureRef(): RegisterableDelegateProvider>> + + fun ref(id: String? = null): ReadOnlyProperty?> + + + fun registerLabel(id: String): LabelOption + val registeringLabel: RegisterableDelegateProvider + + fun registerLabel(id: String, text: Component): LabelOption + + fun registerLabel(id: String, builder: TextLineBuilderDsl.() -> Unit): LabelOption + + fun registerButton(id: String, block: ButtonOptionDsl.() -> Unit): ButtonOption + fun registeringButton(id: String? = null, block: ButtonOptionDsl.() -> Unit): RegisterableActionDelegateProvider } -interface CategoryDsl : OptionAddableDsl { - val groups: CategoryDslReference - val options: GroupDslReference +typealias CategoryRegistrar = ParentRegistrar +typealias GroupRegistrar = ParentRegistrar + +interface RootDsl { + val rootKey: String + val rootId: String + val thisRoot: CompletableFuture - fun group(id: String, block: GroupDsl.() -> Unit): OptionGroup + val categories: CategoryRegistrar + + fun title(component: Component) + fun title(block: () -> Component) + + fun screenInit(block: () -> Unit) + fun save(block: () -> Unit) +} + +interface CategoryDsl : Buildable { + val categoryKey: String + val categoryId: String + val thisCategory: CompletableFuture + + val groups: GroupRegistrar + val rootOptions: OptionRegistrar fun name(component: Component) fun name(block: () -> Component) fun tooltip(vararg component: Component) - fun tooltipBuilder(block: TooltipBuilderDsl.() -> Unit) - fun useDefaultTooltip(lines: Int = 1) + fun tooltip(block: TextLineBuilderDsl.() -> Unit) } -interface GroupDsl : OptionAddableDsl { - val options: GroupDslReference +interface GroupDsl : Buildable { + val groupKey: String + val groupId: String + val thisGroup: CompletableFuture + + val options: OptionRegistrar fun name(component: Component) fun name(block: () -> Component) - fun descriptionBuilder(block: OptionDescription.Builder.() -> Unit) fun description(description: OptionDescription) - fun useDefaultDescription(lines: Int = 1) + fun descriptionBuilder(block: OptionDescription.Builder.() -> Unit) + fun OptionDescription.Builder.addDefaultText(lines: Int? = null) = + addDefaultText("$groupKey.description", lines) +} + +interface OptionDsl : Option.Builder, Buildable> { + val optionKey: String + val optionId: String + val thisOption: CompletableFuture> + + fun OptionDescription.Builder.addDefaultText(lines: Int? = null) = + addDefaultText("$optionKey.description", lines) } -interface OptionDsl : Option.Builder { - val option: FutureValue> +interface ButtonOptionDsl : ButtonOption.Builder, Buildable { + val optionKey: String + val optionId: String + val thisOption: CompletableFuture - fun OptionDescription.Builder.addDefaultDescription(lines: Int? = null) + fun OptionDescription.Builder.addDefaultText(lines: Int? = null) = + addDefaultText("$optionKey.description", lines) } -interface TooltipBuilderDsl { +interface TextLineBuilderDsl { fun text(component: Component) fun text(block: () -> Component) operator fun Component.unaryPlus() - class Delegate(private val tooltipFunction: (Component) -> Unit) : TooltipBuilderDsl { + class Delegate(private val tooltipFunction: (Component) -> Unit) : TextLineBuilderDsl { override fun text(component: Component) { tooltipFunction(component) } @@ -70,36 +161,18 @@ interface TooltipBuilderDsl { text(this) } } -} - -interface YACLDslReference : Reference { - fun get(): YetAnotherConfigLib? - - val isBuilt: Boolean - - fun registering(block: CategoryDsl.() -> Unit): RegisterableDelegateProvider -} -interface CategoryDslReference : Reference { - fun get(): ConfigCategory? - - val root: GroupDslReference - - val isBuilt: Boolean - - fun registering(block: GroupDsl.() -> Unit): RegisterableDelegateProvider -} - -interface GroupDslReference { - fun get(): OptionGroup? - - operator fun get(id: String): FutureValue> - - val isBuilt: Boolean - - fun registering(block: OptionDsl.() -> Unit): RegisterableDelegateProvider, Option> + companion object { + fun createText(block: TextLineBuilderDsl.() -> Unit): Component { + val text = Component.empty() + var first = true + val builder = Delegate { + if (!first) text.append(CommonComponents.NEW_LINE) + text.append(it) + first = false + } + block(builder) + return text + } + } } - - - - diff --git a/src/main/kotlin/dev/isxander/yacl3/dsl/Controllers.kt b/src/main/kotlin/dev/isxander/yacl3/dsl/Controllers.kt new file mode 100644 index 0000000..12bb9e0 --- /dev/null +++ b/src/main/kotlin/dev/isxander/yacl3/dsl/Controllers.kt @@ -0,0 +1,119 @@ +package dev.isxander.yacl3.dsl + +import dev.isxander.yacl3.api.Option +import dev.isxander.yacl3.api.controller.* +import net.minecraft.world.item.Item +import java.awt.Color + +typealias ControllerBuilderFactory = (Option) -> ControllerBuilder + +fun tickBox(): ControllerBuilderFactory = { option -> + TickBoxControllerBuilder.create(option) +} + +fun textSwitch(formatter: ValueFormatter? = null): ControllerBuilderFactory = { option -> + BooleanControllerBuilder.create(option).apply { + formatter?.let { formatValue(it) } + } +} + +fun slider(range: IntRange, step: Int = 1, formatter: ValueFormatter? = null): ControllerBuilderFactory = { option -> + IntegerSliderControllerBuilder.create(option).apply { + range(range.first, range.last) + step(step) + formatter?.let { formatValue(it) } + } +} + +fun slider(range: LongRange, step: Long = 1, formatter: ValueFormatter? = null): ControllerBuilderFactory = { option -> + LongSliderControllerBuilder.create(option).apply { + range(range.first, range.last) + step(step) + formatter?.let { formatValue(it) } + } +} + +fun slider(range: ClosedRange, step: Float = 1f, formatter: ValueFormatter? = null): ControllerBuilderFactory = { option -> + FloatSliderControllerBuilder.create(option).apply { + range(range.start, range.endInclusive) + step(step) + formatter?.let { formatValue(it) } + } +} + +fun slider(range: ClosedRange, step: Double = 1.0, formatter: ValueFormatter? = null): ControllerBuilderFactory = { option -> + DoubleSliderControllerBuilder.create(option).apply { + range(range.start, range.endInclusive) + step(step) + formatter?.let { formatValue(it) } + } +} + +fun stringField(): ControllerBuilderFactory = { option -> + StringControllerBuilder.create(option) +} + +fun numberField(min: Int? = null, max: Int? = null, formatter: ValueFormatter? = null): ControllerBuilderFactory = { option -> + IntegerFieldControllerBuilder.create(option).apply { + min?.let { min(it) } + max?.let { max(it) } + formatter?.let { formatValue(it) } + } +} + +fun numberField(min: Long? = null, max: Long? = null, formatter: ValueFormatter? = null): ControllerBuilderFactory = { option -> + LongFieldControllerBuilder.create(option).apply { + min?.let { min(it) } + max?.let { max(it) } + formatter?.let { formatValue(it) } + } +} + +fun numberField(min: Float? = null, max: Float? = null, formatter: ValueFormatter? = null): ControllerBuilderFactory = { option -> + FloatFieldControllerBuilder.create(option).apply { + min?.let { min(it) } + max?.let { max(it) } + formatter?.let { formatValue(it) } + } +} + +fun numberField(min: Double? = null, max: Double? = null, formatter: ValueFormatter? = null): ControllerBuilderFactory = { option -> + DoubleFieldControllerBuilder.create(option).apply { + min?.let { min(it) } + max?.let { max(it) } + formatter?.let { formatValue(it) } + } +} + +fun colorPicker(allowAlpha: Boolean = false): ControllerBuilderFactory = { option -> + ColorControllerBuilder.create(option).apply { + allowAlpha(allowAlpha) + } +} + +fun cyclingList(values: Iterable, formatter: ValueFormatter? = null): ControllerBuilderFactory = { option -> + CyclingListControllerBuilder.create(option).apply { + values(values) + formatter?.let { formatValue(it) } + } +} + +fun > enumSwitch(enumClass: Class, formatter: ValueFormatter? = null): ControllerBuilderFactory = { option -> + EnumControllerBuilder.create(option).apply { + enumClass(enumClass) + formatter?.let { formatValue(it) } + } +} + +inline fun > enumSwitch(formatter: ValueFormatter? = null): ControllerBuilderFactory = + enumSwitch(T::class.java, formatter) + +fun > enumDropdown(formatter: ValueFormatter? = null): ControllerBuilderFactory = { option -> + EnumDropdownControllerBuilder.create(option).apply { + formatter?.let { formatValue(it) } + } +} + +fun minecraftItem(): ControllerBuilderFactory = { option -> + ItemControllerBuilder.create(option) +} diff --git a/src/main/kotlin/dev/isxander/yacl3/dsl/Extensions.kt b/src/main/kotlin/dev/isxander/yacl3/dsl/Extensions.kt index 4b93f5f..7349850 100644 --- a/src/main/kotlin/dev/isxander/yacl3/dsl/Extensions.kt +++ b/src/main/kotlin/dev/isxander/yacl3/dsl/Extensions.kt @@ -1,9 +1,12 @@ package dev.isxander.yacl3.dsl +import dev.isxander.yacl3.api.Binding +import dev.isxander.yacl3.api.ButtonOption 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.locale.Language import net.minecraft.network.chat.Component import kotlin.reflect.KMutableProperty0 @@ -11,14 +14,62 @@ fun Option.Builder.binding(property: KMutableProperty0, default: binding(default, { property.get() }, { property.set(it) }) } -fun Option.Builder.descriptionBuilder(block: OptionDescription.Builder.(T) -> Unit) { +var Option.Builder.controller: ControllerBuilderFactory + get() = throw UnsupportedOperationException() + set(value) { + controller(value) + } + +var Option.Builder.binding: Binding + get() = throw UnsupportedOperationException() + set(value) { + binding(value) + } + +var Option.Builder<*>.available: Boolean + get() = throw UnsupportedOperationException() + set(value) { + available(value) + } + +fun Option.Builder.descriptionBuilderDyn(block: OptionDescription.Builder.(value: T) -> Unit) { description { OptionDescription.createBuilder().apply { block(it) }.build() } } -fun Option.Builder<*>.descriptionBuilderConst(block: OptionDescription.Builder.() -> Unit) { +fun Option.Builder<*>.descriptionBuilder(block: OptionDescription.Builder.() -> Unit) { description(OptionDescription.createBuilder().apply(block).build()) } +fun ButtonOption.Builder.descriptionBuilder(block: OptionDescription.Builder.() -> Unit) { + description(OptionDescription.createBuilder().apply(block).build()) +} + +fun OptionGroup.Builder.descriptionBuilder(block: OptionDescription.Builder.() -> Unit) { + description(OptionDescription.createBuilder().apply(block).build()) +} + +fun OptionDescription.Builder.addDefaultText(prefix: String, lines: Int? = null) { + if (lines != null) { + if (lines == 1) { + text(Component.translatable(prefix)) + } else for (i in 1..lines) { + text(Component.translatable("$prefix.$i")) + } + } else { + // loop until we find a key that doesn't exist + var i = 1 + while (i < 100) { + val key = "$prefix.$i" + if (!Language.getInstance().has(key)) { + break + } + text(Component.translatable(key)) + + i++ + } + } +} + fun Option.Builder<*>.available(block: () -> Boolean) { available(block()) } @@ -27,10 +78,6 @@ fun OptionDescription.Builder.text(block: () -> Component) { text(block()) } -fun OptionGroup.Builder.descriptionBuilder(block: OptionDescription.Builder.() -> Unit) { - description(OptionDescription.createBuilder().apply(block).build()) -} - fun > Option.Builder.controller(builder: (Option) -> B, block: B.() -> Unit = {}) { controller { builder(it).apply(block) } } diff --git a/src/main/kotlin/dev/isxander/yacl3/dsl/Impl.kt b/src/main/kotlin/dev/isxander/yacl3/dsl/Impl.kt new file mode 100644 index 0000000..ab0d1a2 --- /dev/null +++ b/src/main/kotlin/dev/isxander/yacl3/dsl/Impl.kt @@ -0,0 +1,298 @@ +package dev.isxander.yacl3.dsl + +import dev.isxander.yacl3.api.* +import dev.isxander.yacl3.impl.utils.YACLConstants +import net.minecraft.network.chat.Component +import org.slf4j.Logger +import java.util.concurrent.CompletableFuture +import kotlin.properties.ReadOnlyProperty + +private val LOGGER: Logger = YACLConstants.LOGGER + +class ParentRegistrarImpl, INNER>( + private val adder: (registrant: T, id: String) -> Unit, + private val dslFactory: (id: String) -> DSL, + private val getter: (id: String) -> CompletableFuture, + private val innerGetter: (id: String) -> CompletableFuture, +) : ParentRegistrar { + override fun register(id: String, registrant: T) = + adder(registrant, id).let { registrant } + + override fun register(id: String, block: DSL.() -> Unit): T = + register(id, dslFactory(id).apply(block).build()) + + override fun registering(id: String?, block: DSL.() -> Unit) = + RegisterableActionDelegateProvider(this::register, block, id) + + override fun futureRef(id: String): CompletableFuture = getter(id) + + override val futureRef: ReadOnlyProperty> + get() = ReadOnlyProperty { _, property -> futureRef(property.name) } + + override fun ref(id: String): T? = + futureRef(id).getNow(null) + + override val ref: ReadOnlyProperty + get() = ReadOnlyProperty { _, property -> ref(property.name) } + + override fun get(id: String): CompletableFuture = innerGetter(id) +} + +class RootDslImpl( + override val rootId: String +) : RootDsl, Buildable { + override val rootKey: String = "yacl3.config.$rootId" + + override val thisRoot = CompletableFuture() + + override val built = thisRoot + private val builder = YetAnotherConfigLib.createBuilder() + + private val categoryFutures = mutableMapOf>() + private fun createFuture(id: String) = categoryFutures.computeIfAbsent(id) { CompletableFuture() } + + init { + builder.title(Component.translatable("$rootKey.title")) + } + + override val categories: CategoryRegistrar = ParentRegistrarImpl( + { category, _ -> builder.category(category) }, + { id -> CategoryDslImpl(id, this) + .also { createFuture(id).complete(it) } + }, + { id -> createFuture(id).thenCompose { it.built } }, + { id -> createFuture(id).thenApply { it.groups } }, + ) + + override fun title(component: Component) { + builder.title(component) + } + + override fun title(block: () -> Component) = title(block()) + + override fun screenInit(block: () -> Unit) { + builder.screenInit { block() } + } + + override fun save(block: () -> Unit) { + builder.save { block() } + } + + override fun build(): YetAnotherConfigLib = + builder.build().also { + thisRoot.complete(it) + checkUnresolvedFutures() + } + + private fun checkUnresolvedFutures() { + categoryFutures.filterValues { !it.isDone } + .forEach { LOGGER.error("Future category ${it.key} was referenced but was never built.") } + } +} + +class CategoryDslImpl( + override val categoryId: String, + private val parent: RootDsl, +) : CategoryDsl { + override val categoryKey = "${parent.rootKey}.category.$categoryId" + + override val thisCategory = CompletableFuture() + + override val built = thisCategory + private val builder = ConfigCategory.createBuilder() + + private val groupFutures = mutableMapOf>() + private fun createGroupFuture(id: String) = groupFutures.computeIfAbsent(id) { CompletableFuture() } + + private val rootOptFutures = mutableMapOf>>() + private fun createRootOptFuture(id: String) = rootOptFutures.computeIfAbsent(id) { CompletableFuture() } + + init { + builder.name(Component.translatable(categoryKey)) + } + + override val groups: GroupRegistrar = ParentRegistrarImpl( + { group, _ -> builder.group(group) }, + { id -> GroupDslImpl(id, this) + .also { createGroupFuture(id).complete(it) } + }, + { id -> createGroupFuture(id).thenCompose { it.built } }, + { id -> createGroupFuture(id).thenApply { it.options } }, + ) + + override val rootOptions: OptionRegistrar = OptionRegistrarImpl( + { option, id -> builder.option(option).also { createRootOptFuture(id).complete(option) } }, + { id -> createRootOptFuture(id) }, + "$categoryKey.root", + ) + + override fun name(component: Component) { + builder.name(component) + } + + override fun name(block: () -> Component) = name(block()) + + override fun tooltip(block: TextLineBuilderDsl.() -> Unit) { + builder.tooltip(TextLineBuilderDsl.createText(block)) + } + + override fun tooltip(vararg component: Component) = tooltip { + component.forEach { +it } + } + + override fun build(): ConfigCategory = + builder.build().also { + thisCategory.complete(it) + checkUnresolvedFutures() + } + + private fun checkUnresolvedFutures() { + groupFutures.filterValues { !it.isDone } + .forEach { LOGGER.error("Future group $categoryId/${it.key} was referenced but was never built.") } + rootOptFutures.filterValues { !it.isDone } + .forEach { LOGGER.error("Future option $categoryId/root/${it.key} was referenced but was never built.") } + } +} + +class GroupDslImpl( + override val groupId: String, + private val parent: CategoryDsl, +) : GroupDsl { + override val groupKey = "${parent.categoryKey}.group.$groupId" + + override val thisGroup = CompletableFuture() + + override val built = thisGroup + private val builder = OptionGroup.createBuilder() + + private val optionFutures = mutableMapOf>>() + private fun createOptionFuture(id: String) = optionFutures.computeIfAbsent(id) { CompletableFuture() } + + init { + builder.name(Component.translatable(groupKey)) + } + + override val options: OptionRegistrar = OptionRegistrarImpl( + { option, id -> builder.option(option).also { createOptionFuture(id).complete(option) } }, + { id -> createOptionFuture(id) }, + groupKey, + ) + + override fun name(component: Component) { + builder.name(component) + } + + override fun name(block: () -> Component) = name(block()) + + override fun description(description: OptionDescription) { + builder.description(description) + } + + override fun descriptionBuilder(block: OptionDescription.Builder.() -> Unit) { + builder.description(OptionDescription.createBuilder().apply(block).build()) + } + + override fun OptionDescription.Builder.addDefaultText(lines: Int?) { + addDefaultText("$groupKey.description", lines) + } + + override fun build(): OptionGroup = + builder.build().also { + thisGroup.complete(it) + checkUnresolvedFutures() + } + + private fun checkUnresolvedFutures() { + optionFutures.filterValues { !it.isDone } + .forEach { LOGGER.error("Future option ${parent.categoryId}/$groupId/${it.key} was referenced but was never built.") } + } +} + +class OptionRegistrarImpl( + private val adder: (registrant: Option<*>, id: String) -> Unit, + private val getter: (id: String) -> CompletableFuture>, + private val groupKey: String, +) : OptionRegistrar { + override fun > register(id: String, option: OPT): OPT = + adder(option, id).let { option } + + override fun register(id: String, block: OptionDsl.() -> Unit): Option = + register(id, OptionDslImpl(id, groupKey).apply(block).build()) + + override fun registering( + id: String?, + block: OptionDsl.() -> Unit + ) = RegisterableActionDelegateProvider(this::register, block, id) + + @Suppress("UNCHECKED_CAST") + override fun futureRef(id: String): CompletableFuture> = + getter(id) as CompletableFuture> + + override fun futureRef() = + RegisterableDelegateProvider({ this.futureRef(it) }, null) + + override fun ref(id: String?) = ReadOnlyProperty?> { _, property -> + futureRef(id ?: property.name).getNow(null) + } + + override fun registerLabel(id: String): LabelOption = + register(id, LabelOption.create(Component.translatable("$groupKey.label.$id"))) + + override val registeringLabel = RegisterableDelegateProvider(this::registerLabel, null) + + override fun registerLabel(id: String, text: Component): LabelOption = + register(id, LabelOption.create(text)) + + override fun registerLabel(id: String, builder: TextLineBuilderDsl.() -> Unit): LabelOption = + registerLabel(id, TextLineBuilderDsl.createText(builder)) + + override fun registerButton(id: String, block: ButtonOptionDsl.() -> Unit): ButtonOption = + register(id, ButtonOptionDslImpl(id, groupKey).apply(block).build()) + + override fun registeringButton( + id: String?, + block: ButtonOptionDsl.() -> Unit + ) = RegisterableActionDelegateProvider(this::registerButton, block, id) +} + +class OptionDslImpl( + override val optionId: String, + groupKey: String, + private val builder: Option.Builder = Option.createBuilder(), +) : OptionDsl, Option.Builder by builder { + override val optionKey = "$groupKey.option.$optionId" + + override val thisOption = CompletableFuture>() + override val built = thisOption + + init { + builder.name(Component.translatable(optionKey)) + } + + override fun OptionDescription.Builder.addDefaultText(lines: Int?) = + addDefaultText(prefix = "$optionKey.description", lines = lines) + + override fun build(): Option = + builder.build().also { thisOption.complete(it) } +} + +class ButtonOptionDslImpl( + override val optionId: String, + groupKey: String, + private val builder: ButtonOption.Builder = ButtonOption.createBuilder(), +) : ButtonOptionDsl, ButtonOption.Builder by builder { + override val optionKey = "$groupKey.option.$optionId" + + override val thisOption = CompletableFuture() + override val built = thisOption + + init { + builder.name(Component.translatable(optionKey)) + } + + override fun OptionDescription.Builder.addDefaultText(lines: Int?) = + addDefaultText(prefix = "$optionKey.description", lines = lines) + + override fun build(): ButtonOption = + builder.build().also { thisOption.complete(it) } +} diff --git a/src/main/kotlin/dev/isxander/yacl3/dsl/Util.kt b/src/main/kotlin/dev/isxander/yacl3/dsl/Util.kt index 819365c..87337aa 100644 --- a/src/main/kotlin/dev/isxander/yacl3/dsl/Util.kt +++ b/src/main/kotlin/dev/isxander/yacl3/dsl/Util.kt @@ -1,110 +1,31 @@ package dev.isxander.yacl3.dsl -import dev.isxander.yacl3.api.Option import kotlin.properties.ReadOnlyProperty import kotlin.reflect.KProperty -interface FutureValue { - fun onReady(block: (T) -> Unit) - fun map(block: (T) -> R): FutureValue - fun flatMap(block: (T) -> FutureValue): FutureValue - fun getOrNull(): T? - fun getOrThrow(): T = getOrNull() ?: error("Value not ready yet!") - - open class Impl(default: T? = null) : FutureValue { - 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 map(block: (T) -> R): FutureValue { - val future = Impl() - onReady { - future.value = block(it) - } - return future - } - - override fun flatMap(block: (T) -> FutureValue): FutureValue { - val future = Impl() - onReady { - block(it).onReady { inner -> - future.value = inner - } - } - return future - } - - override fun getOrNull(): T? = value - } -} - -interface Reference : ReadOnlyProperty> { - operator fun get(id: String): FutureValue - - override fun getValue(thisRef: Any?, property: KProperty<*>): FutureValue { - return get(property.name) - } - - operator fun invoke(name: String? = null, block: (T) -> Unit): ReadOnlyProperty> { - return ReadOnlyProperty { thisRef, property -> - val future = get(name ?: property.name) - future.onReady(block) - future - } - } -} - - -operator fun FutureValue>.get(id: String): FutureValue { - val future = FutureValue.Impl>() - onReady { - future.value = it[id] - } - return future.flatten() -} - -fun FutureValue.getOption(id: String): FutureValue> { - val future = FutureValue.Impl>>() - onReady { - future.value = it.get(id) as FutureValue> - } - return future.flatten() -} - - -private fun FutureValue>.flatten(): FutureValue { - val future = FutureValue.Impl() - onReady { outer -> - outer.onReady { inner -> - future.value = inner - } +class RegisterableDelegateProvider( + private val registerFunction: (id: String) -> R, + private val id: String?, +) { + operator fun provideDelegate(thisRef: Any?, property: KProperty<*>): ExistingDelegateProvider { + return ExistingDelegateProvider(registerFunction(id ?: property.name)) } - return future } -class RegisterableDelegateProvider( +class RegisterableActionDelegateProvider( private val registerFunction: (String, Dsl.() -> Unit) -> Return, - private val action: Dsl.() -> Unit + private val action: Dsl.() -> Unit, + private val name: String? ) { operator fun provideDelegate(thisRef: Any?, property: KProperty<*>): ExistingDelegateProvider { - return ExistingDelegateProvider(registerFunction(property.name, action)) + return ExistingDelegateProvider(registerFunction(name ?: property.name, action)) } } class ExistingDelegateProvider( private val delegate: Return -) { - operator fun getValue(thisRef: Any?, property: KProperty<*>): Return { +) : ReadOnlyProperty { + override 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 deleted file mode 100644 index 8c10cfd..0000000 --- a/src/main/kotlin/dev/isxander/yacl3/dsl/YetAnotherConfigLibDsl.kt +++ /dev/null @@ -1,283 +0,0 @@ -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() - private val categoryDslReferenceMap = mutableMapOf>() - - 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 = - FutureValue.Impl(categoryMap[id]?.groups).also { categoryDslReferenceMap[id] = it } - - override fun registering(block: CategoryDsl.() -> Unit): RegisterableDelegateProvider { - 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() - private val groupDslReferenceMap = mutableMapOf>() - 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 = - FutureValue.Impl(groupMap[id]?.options).also { groupDslReferenceMap[id] = it } - - override val root: GroupDslReference - get() = rootGroup.options - - override fun registering(block: GroupDsl.() -> Unit): RegisterableDelegateProvider { - 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 option(id: String, block: OptionDsl.() -> Unit): Option = - 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>() - private val optionDslReferenceMap = mutableMapOf>>() - val groupKey = "${category.categoryKey}.$id" - private var built: OptionGroup? = null - - override val options = object : GroupDslReference { - override fun get(): OptionGroup? = built - - override fun get(id: String): FutureValue> = - FutureValue.Impl(optionMap[id]).flatMap { it.option as FutureValue> }.also { optionDslReferenceMap[id] = it as FutureValue.Impl> } - - override fun registering(block: OptionDsl.() -> Unit): RegisterableDelegateProvider, Option> { - 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 option(id: String, block: OptionDsl.() -> Unit): Option { - val context = YACLDslOptionContext(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( - private val id: String, - private val group: YACLDslGroupContext, - private val builder: Option.Builder = Option.createBuilder() -) : Option.Builder by builder, OptionDsl { - val optionKey = "${group.groupKey}.$id" - private var built: Option? = null - - private val taskQueue = ArrayDeque<(Option) -> Unit>() - override val option = FutureValue.Impl>() - - 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 { - return builder.build().also { - built = it - option.value = it - while (taskQueue.isNotEmpty()) { - taskQueue.removeFirst()(it) - } - } - } -} -- cgit