feat: MoulConfig config gui

This commit is contained in:
Linnea Gräf
2024-11-12 17:02:08 +01:00
parent 9763a4caa4
commit b774daef5b
14 changed files with 691 additions and 22 deletions

View File

@@ -0,0 +1,337 @@
package moe.nea.firmament.compat.moulconfig
import com.google.auto.service.AutoService
import io.github.notenoughupdates.moulconfig.Config
import io.github.notenoughupdates.moulconfig.common.IMinecraft
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 kotlin.time.Duration
import kotlin.time.Duration.Companion.seconds
import kotlin.time.DurationUnit
import net.minecraft.client.gui.screen.Screen
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.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<Class<out ManagedConfig.OptionHandler<*>>, ((ManagedConfig.OptionHandler<*>, ManagedOption<*>, accordionId: Int, configObject: Config) -> ProcessedEditableOptionFirm<*>)> =
mutableMapOf()
fun <T : Any, H : ManagedConfig.OptionHandler<T>> register(
handlerClass: Class<H>,
transform: (H, ManagedOption<T>, accordionId: Int, configObject: Config) -> ProcessedEditableOptionFirm<T>
) {
handlers[handlerClass] =
transform as ((ManagedConfig.OptionHandler<*>, ManagedOption<*>, accordionId: Int, configObject: Config) -> ProcessedEditableOptionFirm<*>)
}
fun <T : Any> getHandler(
option: ManagedOption<T>,
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<T>
}
class CustomSliderEditor<T>(
option: ProcessedOption,
setter: GetSetter<T>,
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<Boolean>(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<String>(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<Unit>(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<HudMeta>(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<Duration>(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<Int>(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<SavedKeyBinding>(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
}
}
val categories = ManagedConfig.Category.entries.map {
val options = mutableListOf<ProcessedOptionFirm>()
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
}
}

View File

@@ -0,0 +1,47 @@
package moe.nea.firmament.compat.moulconfig
import io.github.notenoughupdates.moulconfig.gui.editors.GuiOptionEditorAccordion
import io.github.notenoughupdates.moulconfig.processor.ProcessedCategory
import io.github.notenoughupdates.moulconfig.processor.ProcessedOption
import moe.nea.firmament.gui.config.ManagedConfig
class ProcessedCategoryFirm(
val category: ManagedConfig.Category,
private val options: List<ProcessedOptionFirm>
) : ProcessedCategory {
val accordions = options.filter { it.editor is GuiOptionEditorAccordion }
.associateBy { (it.editor as GuiOptionEditorAccordion).accordionId }
init {
for (option in options) {
option.category = this
}
}
override fun getDebugDeclarationLocation(): String? {
return "FirmamentCategory.${category.name}"
}
override fun getDisplayName(): String {
return category.labelText.string
}
override fun getDescription(): String {
return "Missing description" // TODO: add description
}
override fun getIdentifier(): String {
return category.name
}
override fun getParentCategoryId(): String? {
return null
}
override fun getOptions(): List<ProcessedOption> {
return options
}
override fun getAccordionAnchors(): Map<Int, ProcessedOption> {
return accordions
}
}

View File

@@ -0,0 +1,27 @@
package moe.nea.firmament.compat.moulconfig
import io.github.notenoughupdates.moulconfig.Config
import moe.nea.firmament.gui.config.ManagedOption
abstract class ProcessedEditableOptionFirm<T : Any>(
val managedOption: ManagedOption<T>,
categoryAccordionId: Int,
configObject: Config,
) : ProcessedOptionFirm(categoryAccordionId, configObject) {
val managedConfig = managedOption.element
override fun getDebugDeclarationLocation(): String {
return "FirmamentOption:${managedConfig.name}:${managedOption.propertyName}"
}
override fun getName(): String {
return managedOption.labelText.string
}
override fun getDescription(): String {
return "Missing description" // TODO: add description
}
override fun explicitNotifyChange() {
managedConfig.save()
}
}

View File

@@ -0,0 +1,39 @@
package moe.nea.firmament.compat.moulconfig
import io.github.notenoughupdates.moulconfig.Config
import io.github.notenoughupdates.moulconfig.annotations.SearchTag
import io.github.notenoughupdates.moulconfig.gui.GuiOptionEditor
import io.github.notenoughupdates.moulconfig.processor.ProcessedCategory
import io.github.notenoughupdates.moulconfig.processor.ProcessedOption
abstract class ProcessedOptionFirm(
private val accordionId: Int,
private val config: Config
) : ProcessedOption {
lateinit var category: ProcessedCategoryFirm
override fun getAccordionId(): Int {
return accordionId
}
protected abstract fun createEditor(): GuiOptionEditor
val editorInstance by lazy { createEditor() }
override fun getSearchTags(): Array<SearchTag> {
return emptyArray()
}
override fun getEditor(): GuiOptionEditor {
return editorInstance
}
override fun getCategory(): ProcessedCategory {
return category
}
override fun getConfig(): Config {
return config
}
override fun explicitNotifyChange() {
}
}