package moe.nea.firmament.compat.moulconfig import com.google.auto.service.AutoService import io.github.notenoughupdates.moulconfig.Config import io.github.notenoughupdates.moulconfig.DescriptionRendereringBehaviour import io.github.notenoughupdates.moulconfig.Social import io.github.notenoughupdates.moulconfig.common.IMinecraft import io.github.notenoughupdates.moulconfig.common.MyResourceLocation import io.github.notenoughupdates.moulconfig.gui.GuiComponent import io.github.notenoughupdates.moulconfig.gui.GuiElementWrapper import io.github.notenoughupdates.moulconfig.gui.GuiOptionEditor import io.github.notenoughupdates.moulconfig.gui.HorizontalAlign import io.github.notenoughupdates.moulconfig.gui.MoulConfigEditor import io.github.notenoughupdates.moulconfig.gui.VerticalAlign import io.github.notenoughupdates.moulconfig.gui.component.AlignComponent import io.github.notenoughupdates.moulconfig.gui.component.RowComponent import io.github.notenoughupdates.moulconfig.gui.component.SliderComponent import io.github.notenoughupdates.moulconfig.gui.component.TextComponent import io.github.notenoughupdates.moulconfig.gui.editors.ComponentEditor import io.github.notenoughupdates.moulconfig.gui.editors.GuiOptionEditorAccordion import io.github.notenoughupdates.moulconfig.gui.editors.GuiOptionEditorBoolean import io.github.notenoughupdates.moulconfig.gui.editors.GuiOptionEditorButton import io.github.notenoughupdates.moulconfig.gui.editors.GuiOptionEditorText import io.github.notenoughupdates.moulconfig.observer.GetSetter import io.github.notenoughupdates.moulconfig.processor.ProcessedCategory import io.github.notenoughupdates.moulconfig.processor.ProcessedOption import java.lang.reflect.Type import java.net.URI import kotlin.time.Duration import kotlin.time.Duration.Companion.seconds import kotlin.time.DurationUnit import net.minecraft.client.gui.screen.Screen import net.minecraft.util.Identifier import net.minecraft.util.Util import moe.nea.firmament.Firmament import moe.nea.firmament.gui.config.BooleanHandler import moe.nea.firmament.gui.config.ClickHandler import moe.nea.firmament.gui.config.DurationHandler 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.gui.toMoulConfig import moe.nea.firmament.keybindings.SavedKeyBinding import moe.nea.firmament.util.ErrorUtil import moe.nea.firmament.util.FirmFormatters import moe.nea.firmament.util.MC import moe.nea.firmament.util.MoulConfigUtils.xmap @AutoService(FirmamentConfigScreenProvider::class) class MCConfigEditorIntegration : FirmamentConfigScreenProvider { override val key: String get() = "moulconfig" val handlers: MutableMap>, ((ManagedConfig.OptionHandler<*>, ManagedOption<*>, accordionId: Int, configObject: Config) -> ProcessedEditableOptionFirm<*>)> = mutableMapOf() fun > register( handlerClass: Class, transform: (H, ManagedOption, accordionId: Int, configObject: Config) -> ProcessedEditableOptionFirm ) { handlers[handlerClass] = transform as ((ManagedConfig.OptionHandler<*>, ManagedOption<*>, accordionId: Int, configObject: Config) -> ProcessedEditableOptionFirm<*>) } fun getHandler( option: ManagedOption, accordionId: Int, configObject: Config ): ProcessedEditableOptionFirm<*> { val transform = handlers[option.handler.javaClass] ?: error("Could not transform ${option.handler}") // TODO: replace with soft error and an error config element return transform.invoke(option.handler, option, accordionId, configObject) as ProcessedEditableOptionFirm } class CustomSliderEditor( option: ProcessedOption, setter: GetSetter, fromT: (T) -> Float, toT: (Float) -> T, minValue: T, maxValue: T, minStep: Float, formatter: (T) -> String, ) : ComponentEditor(option) { override fun getDelegate(): GuiComponent { return delegateI } val mappedSetter = setter.xmap(fromT, toT) private val delegateI by lazy { wrapComponent(RowComponent( AlignComponent( TextComponent( IMinecraft.instance.defaultFontRenderer, { formatter(setter.get()) }, 25, TextComponent.TextAlignment.CENTER, false, false ), GetSetter.constant(HorizontalAlign.CENTER), GetSetter.constant(VerticalAlign.CENTER) ), SliderComponent( mappedSetter, fromT(minValue), fromT(maxValue), minStep, 40 ) )) } } init { register(BooleanHandler::class.java) { handler, option, categoryAccordionId, configObject -> object : ProcessedEditableOptionFirm(option, categoryAccordionId, configObject) { override fun createEditor(): GuiOptionEditor { return GuiOptionEditorBoolean(this, -1, configObject) } override fun get(): Any { return managedOption.value } override fun getType(): Type { return Boolean::class.java } override fun set(value: Any?): Boolean { managedOption.value = value as Boolean return true } } } register(StringHandler::class.java) { handler, option, categoryAccordionId, configObject -> object : ProcessedEditableOptionFirm(option, categoryAccordionId, configObject) { override fun createEditor(): GuiOptionEditor { return GuiOptionEditorText(this) } override fun get(): Any { return managedOption.value } override fun getType(): Type { return String::class.java } override fun set(value: Any?): Boolean { managedOption.value = value as String return true } } } register(ClickHandler::class.java) { handler, option, categoryAccordionId, configObject -> object : ProcessedEditableOptionFirm(option, categoryAccordionId, configObject) { override fun createEditor(): GuiOptionEditor { return GuiOptionEditorButton(this, -1, "Click", configObject) } override fun get(): Any { return Runnable { handler.runnable() } } override fun getType(): Type { return Runnable::class.java } override fun set(value: Any?): Boolean { ErrorUtil.softError("Trying to set a buttons data") return false } } } register(HudMetaHandler::class.java) { handler, option, categoryAccordionId, configObject -> object : ProcessedEditableOptionFirm(option, categoryAccordionId, configObject) { override fun createEditor(): GuiOptionEditor { return GuiOptionEditorButton(this, -1, "Edit HUD", configObject) } override fun get(): Any { return Runnable { handler.openEditor(option, MC.screen!!) } } override fun getType(): Type { return Runnable::class.java } override fun set(value: Any?): Boolean { ErrorUtil.softError("Trying to assign to a hud meta") return false } } } register(DurationHandler::class.java) { handler, option, categoryAccordionId, configObject -> object : ProcessedEditableOptionFirm(option, categoryAccordionId, configObject) { override fun createEditor(): GuiOptionEditor { return CustomSliderEditor( this, option, { it.toDouble(DurationUnit.SECONDS).toFloat() }, { it.toDouble().seconds }, handler.min, handler.max, 0.1F, FirmFormatters::formatTimespan ) } override fun get(): Any { ErrorUtil.softError("Getting on a slider component") return Unit } override fun getType(): Type { return Nothing::class.java } override fun set(value: Any?): Boolean { ErrorUtil.softError("Setting on a slider component") return false } } } register(IntegerHandler::class.java) { handler, option, categoryAccordionId, configObject -> object : ProcessedEditableOptionFirm(option, categoryAccordionId, configObject) { override fun createEditor(): GuiOptionEditor { return CustomSliderEditor( this, option, { it.toFloat() }, { it.toInt() }, handler.min, handler.max, 1F, Integer::toString ) } override fun get(): Any { ErrorUtil.softError("Getting on a slider component") return Unit } override fun getType(): Type { return Nothing::class.java } override fun set(value: Any?): Boolean { ErrorUtil.softError("Setting on a slider component") return false } } } register(KeyBindingHandler::class.java) { handler, option, categoryAccordionId, configObject -> object : ProcessedEditableOptionFirm(option, categoryAccordionId, configObject) { override fun createEditor(): GuiOptionEditor { return object : ComponentEditor(this) { val button = wrapComponent(handler.createButtonComponent(option)) override fun getDelegate(): GuiComponent { return button } } } override fun get(): Any { ErrorUtil.softError("Getting on a keybinding") return Unit } override fun getType(): Type { return Nothing::class.java } override fun set(value: Any?): Boolean { ErrorUtil.softError("Setting on a keybinding") return false } } } } override fun open(parent: Screen?): Screen { val configObject = object : Config() { override fun saveNow() { ManagedConfig.allManagedConfigs.getAll().forEach { it.save() } } override fun shouldAutoFocusSearchbar(): Boolean { return true } override fun getTitle(): String { return "Firmament" } @Deprecated("Deprecated in java") override fun executeRunnable(runnableId: Int) { if (runnableId >= 0) ErrorUtil.softError("Executed runnable $runnableId") } override fun getDescriptionBehaviour(option: ProcessedOption?): DescriptionRendereringBehaviour { return DescriptionRendereringBehaviour.EXPAND_PANEL } fun mkSocial(name: String, identifier: Identifier, link: String) = object : Social() { override fun onClick() { Util.getOperatingSystem().open(URI(link)) } override fun getTooltip(): List { return listOf(name) } override fun getIcon(): MyResourceLocation { return identifier.toMoulConfig() } } private val socials = listOf( mkSocial("Discord", Firmament.identifier("textures/socials/discord.png"), Firmament.modContainer.metadata.contact.get("discord").get()), mkSocial("Source Code", Firmament.identifier("textures/socials/git.png"), Firmament.modContainer.metadata.contact.get("sources").get()), mkSocial("Modrinth", Firmament.identifier("textures/socials/modrinth.png"), Firmament.modContainer.metadata.contact.get("modrinth").get()), ) override fun getSocials(): List { return socials } } val categories = ManagedConfig.Category.entries.map { val options = mutableListOf() var nextAccordionId = 720 it.configs.forEach { config -> val categoryAccordionId = nextAccordionId++ options.add(object : ProcessedOptionFirm(-1, configObject) { override fun getDebugDeclarationLocation(): String { return "FirmamentConfig:${config.name}" } override fun getName(): String { return config.labelText.string } override fun getDescription(): String { return "Missing description" } override fun get(): Any { return Unit } override fun getType(): Type { return Unit.javaClass } override fun set(value: Any?): Boolean { return false } override fun createEditor(): GuiOptionEditor { return GuiOptionEditorAccordion(this, categoryAccordionId) } }) config.allOptions.forEach { (key, option) -> val processedOption = getHandler(option, categoryAccordionId, configObject) options.add(processedOption) } } return@map ProcessedCategoryFirm(it, options) } val editor = MoulConfigEditor(ProcessedCategory.collect(categories), configObject) return GuiElementWrapper(editor) // TODO : add parent support } }