package moe.nea.firmament.compat.yacl import com.google.auto.service.AutoService import dev.isxander.yacl3.api.Binding import dev.isxander.yacl3.api.ButtonOption import dev.isxander.yacl3.api.ConfigCategory import dev.isxander.yacl3.api.LabelOption import dev.isxander.yacl3.api.Option import dev.isxander.yacl3.api.OptionDescription import dev.isxander.yacl3.api.OptionGroup import dev.isxander.yacl3.api.YetAnotherConfigLib import dev.isxander.yacl3.api.controller.ControllerBuilder import dev.isxander.yacl3.api.controller.DoubleSliderControllerBuilder import dev.isxander.yacl3.api.controller.EnumControllerBuilder import dev.isxander.yacl3.api.controller.IntegerSliderControllerBuilder import dev.isxander.yacl3.api.controller.StringControllerBuilder import dev.isxander.yacl3.api.controller.TickBoxControllerBuilder import dev.isxander.yacl3.api.controller.ValueFormatter import dev.isxander.yacl3.gui.YACLScreen import dev.isxander.yacl3.gui.tab.ListHolderWidget import kotlin.time.Duration import kotlin.time.Duration.Companion.seconds import kotlin.time.DurationUnit import net.minecraft.client.gui.Element import net.minecraft.client.gui.screen.Screen import net.minecraft.text.Text import moe.nea.firmament.gui.config.BooleanHandler import moe.nea.firmament.gui.config.ChoiceHandler import moe.nea.firmament.gui.config.ClickHandler import moe.nea.firmament.gui.config.DurationHandler import moe.nea.firmament.gui.config.EnumRenderer import moe.nea.firmament.gui.config.FirmamentConfigScreenProvider import moe.nea.firmament.gui.config.HudMeta import moe.nea.firmament.gui.config.HudMetaHandler import moe.nea.firmament.gui.config.IntegerHandler import moe.nea.firmament.gui.config.KeyBindingHandler import moe.nea.firmament.gui.config.ManagedConfig import moe.nea.firmament.gui.config.ManagedOption import moe.nea.firmament.gui.config.StringHandler import moe.nea.firmament.keybindings.SavedKeyBinding import moe.nea.firmament.util.FirmFormatters @AutoService(FirmamentConfigScreenProvider::class) class YaclIntegration : FirmamentConfigScreenProvider { fun buildCategories() = ManagedConfig.Category.entries .map(::buildCategory) private fun buildCategory(category: ManagedConfig.Category): ConfigCategory { return ConfigCategory.createBuilder() .name(category.labelText) .also { categoryB -> category.configs.forEach { categoryB.group( OptionGroup.createBuilder() .name(it.labelText) .options(buildOptions(it.sortedOptions)) .build()) } } .build() } fun buildOptions(options: List>): Collection> = options.map { buildOption(it) } private fun buildOption(managedOption: ManagedOption): Option<*> { val handler = managedOption.handler val binding = Binding.generic(managedOption.default(), managedOption::value, { managedOption.value = it; managedOption.element.save() }) fun createDefaultBinding(function: (Option) -> ControllerBuilder): Option.Builder { return Option.createBuilder() .name(managedOption.labelText) .description(OptionDescription.of(managedOption.labelDescription)) .binding(binding as Binding) .controller { function(it) } } when (handler) { is ClickHandler -> return ButtonOption.createBuilder() .name(managedOption.labelText) .action { t, u -> handler.runnable() } .build() is HudMetaHandler -> return ButtonOption.createBuilder() .name(managedOption.labelText) .action { t, u -> handler.openEditor(managedOption as ManagedOption, t) } .build() is ChoiceHandler<*> -> return createDefaultBinding { createChoiceBinding(handler as ChoiceHandler<*>, managedOption as ManagedOption<*>, it as Option<*>) }.build() is BooleanHandler -> return createDefaultBinding(TickBoxControllerBuilder::create).build() is StringHandler -> return createDefaultBinding(StringControllerBuilder::create).build() is IntegerHandler -> return createDefaultBinding { IntegerSliderControllerBuilder.create(it).range(handler.min, handler.max).step(1) }.build() is DurationHandler -> return Option.createBuilder() .name(managedOption.labelText) .binding((binding as Binding).xmap({ it.toDouble(DurationUnit.SECONDS) }, { it.seconds })) .controller { DoubleSliderControllerBuilder.create(it) .formatValue { Text.literal(FirmFormatters.formatTimespan(it.seconds)) } .step(0.1) .range(handler.min.toDouble(DurationUnit.SECONDS), handler.max.toDouble(DurationUnit.SECONDS)) } .build() is KeyBindingHandler -> return createDefaultBinding { KeybindingBuilder(it, managedOption as ManagedOption) }.build() else -> return LabelOption.create(Text.literal("This option is currently unhandled for this config menu. Please report this as a bug.")) } } private enum class Sacrifice {} private fun createChoiceBinding( handler: ChoiceHandler<*>, managedOption: ManagedOption<*>, option: Option<*> ): ControllerBuilder { val b = EnumControllerBuilder.create(option as Option) b.enumClass(handler.enumClass as Class) /** * This is a function with E to avoid realizing the Sacrifice outside of a `X` wrapper. */ fun > makeValueFormatter(): ValueFormatter { return ValueFormatter { (handler.renderer as EnumRenderer).getName(managedOption as ManagedOption, it) } } b.formatValue(makeValueFormatter()) return b as ControllerBuilder } fun buildConfig(): YetAnotherConfigLib { return YetAnotherConfigLib.createBuilder() .title(Text.literal("Firmament")) .categories(buildCategories()) .build() } override val key: String get() = "yacl" override fun open(parent: Screen?): Screen { return object : YACLScreen(buildConfig(), parent) { override fun setFocused(focused: Element?) { if (this.focused is KeybindingWidget && focused is ListHolderWidget<*> ) { return } super.setFocused(focused) } override fun shouldCloseOnEsc(): Boolean { if (focused is KeybindingWidget) { return false } return super.shouldCloseOnEsc() } } } }