aboutsummaryrefslogtreecommitdiff
path: root/src/main/kotlin/gui/config
diff options
context:
space:
mode:
authorLinnea Gräf <nea@nea.moe>2024-08-28 19:04:24 +0200
committerLinnea Gräf <nea@nea.moe>2024-08-28 19:04:24 +0200
commitd2f240ff0ca0d27f417f837e706c781a98c31311 (patch)
tree0db7aff6cc14deaf36eed83889d59fd6b3a6f599 /src/main/kotlin/gui/config
parenta6906308163aa3b2d18fa1dc1aa71ac9bbcc83ab (diff)
downloadFirmament-d2f240ff0ca0d27f417f837e706c781a98c31311.tar.gz
Firmament-d2f240ff0ca0d27f417f837e706c781a98c31311.tar.bz2
Firmament-d2f240ff0ca0d27f417f837e706c781a98c31311.zip
Refactor source layout
Introduce compat source sets and move all kotlin sources to the main directory [no changelog]
Diffstat (limited to 'src/main/kotlin/gui/config')
-rw-r--r--src/main/kotlin/gui/config/AllConfigsGui.kt46
-rw-r--r--src/main/kotlin/gui/config/BooleanHandler.kt37
-rw-r--r--src/main/kotlin/gui/config/ClickHandler.kt24
-rw-r--r--src/main/kotlin/gui/config/DurationHandler.kt58
-rw-r--r--src/main/kotlin/gui/config/GuiAppender.kt40
-rw-r--r--src/main/kotlin/gui/config/HudMetaHandler.kt39
-rw-r--r--src/main/kotlin/gui/config/IntegerHandler.kt54
-rw-r--r--src/main/kotlin/gui/config/JAnyHud.kt48
-rw-r--r--src/main/kotlin/gui/config/KeyBindingHandler.kt149
-rw-r--r--src/main/kotlin/gui/config/ManagedConfig.kt181
-rw-r--r--src/main/kotlin/gui/config/ManagedConfigElement.kt8
-rw-r--r--src/main/kotlin/gui/config/ManagedOption.kt62
-rw-r--r--src/main/kotlin/gui/config/StringHandler.kt36
13 files changed, 782 insertions, 0 deletions
diff --git a/src/main/kotlin/gui/config/AllConfigsGui.kt b/src/main/kotlin/gui/config/AllConfigsGui.kt
new file mode 100644
index 0000000..4f7731c
--- /dev/null
+++ b/src/main/kotlin/gui/config/AllConfigsGui.kt
@@ -0,0 +1,46 @@
+
+
+package moe.nea.firmament.gui.config
+
+import io.github.notenoughupdates.moulconfig.observer.ObservableList
+import io.github.notenoughupdates.moulconfig.xml.Bind
+import net.minecraft.client.gui.screen.Screen
+import net.minecraft.text.Text
+import moe.nea.firmament.features.FeatureManager
+import moe.nea.firmament.repo.RepoManager
+import moe.nea.firmament.util.MC
+import moe.nea.firmament.util.MoulConfigUtils
+import moe.nea.firmament.util.ScreenUtil.setScreenLater
+
+object AllConfigsGui {
+
+ val allConfigs
+ get() = listOf(
+ RepoManager.Config
+ ) + FeatureManager.allFeatures.mapNotNull { it.config }
+
+ fun <T> List<T>.toObservableList(): ObservableList<T> = ObservableList(this)
+
+ class MainMapping(val allConfigs: List<ManagedConfig>) {
+ @get:Bind("configs")
+ val configs = allConfigs.map { EntryMapping(it) }.toObservableList()
+
+ class EntryMapping(val config: ManagedConfig) {
+ @Bind
+ fun name() = Text.translatable("firmament.config.${config.name}").string
+
+ @Bind
+ fun openEditor() {
+ config.showConfigEditor(MC.screen)
+ }
+ }
+ }
+
+ fun makeScreen(parent: Screen? = null): Screen {
+ return MoulConfigUtils.loadScreen("config/main", MainMapping(allConfigs), parent)
+ }
+
+ fun showAllGuis() {
+ setScreenLater(makeScreen())
+ }
+}
diff --git a/src/main/kotlin/gui/config/BooleanHandler.kt b/src/main/kotlin/gui/config/BooleanHandler.kt
new file mode 100644
index 0000000..8592777
--- /dev/null
+++ b/src/main/kotlin/gui/config/BooleanHandler.kt
@@ -0,0 +1,37 @@
+
+
+package moe.nea.firmament.gui.config
+
+import io.github.notenoughupdates.moulconfig.gui.component.CenterComponent
+import io.github.notenoughupdates.moulconfig.gui.component.SwitchComponent
+import io.github.notenoughupdates.moulconfig.observer.GetSetter
+import kotlinx.serialization.json.JsonElement
+import kotlinx.serialization.json.JsonPrimitive
+import kotlinx.serialization.json.boolean
+import kotlinx.serialization.json.jsonPrimitive
+
+class BooleanHandler(val config: ManagedConfig) : ManagedConfig.OptionHandler<Boolean> {
+ override fun toJson(element: Boolean): JsonElement? {
+ return JsonPrimitive(element)
+ }
+
+ override fun fromJson(element: JsonElement): Boolean {
+ return element.jsonPrimitive.boolean
+ }
+
+ override fun emitGuiElements(opt: ManagedOption<Boolean>, guiAppender: GuiAppender) {
+ guiAppender.appendLabeledRow(
+ opt.labelText,
+ CenterComponent(SwitchComponent(object : GetSetter<Boolean> {
+ override fun get(): Boolean {
+ return opt.get()
+ }
+
+ override fun set(newValue: Boolean) {
+ opt.set(newValue)
+ config.save()
+ }
+ }, 200)
+ ))
+ }
+}
diff --git a/src/main/kotlin/gui/config/ClickHandler.kt b/src/main/kotlin/gui/config/ClickHandler.kt
new file mode 100644
index 0000000..fa1c621
--- /dev/null
+++ b/src/main/kotlin/gui/config/ClickHandler.kt
@@ -0,0 +1,24 @@
+
+
+package moe.nea.firmament.gui.config
+
+import io.github.notenoughupdates.moulconfig.gui.component.TextComponent
+import kotlinx.serialization.json.JsonElement
+import moe.nea.firmament.gui.FirmButtonComponent
+
+class ClickHandler(val config: ManagedConfig, val runnable: () -> Unit) : ManagedConfig.OptionHandler<Unit> {
+ override fun toJson(element: Unit): JsonElement? {
+ return null
+ }
+
+ override fun fromJson(element: JsonElement) {}
+
+ override fun emitGuiElements(opt: ManagedOption<Unit>, guiAppender: GuiAppender) {
+ guiAppender.appendLabeledRow(
+ opt.labelText,
+ FirmButtonComponent(
+ TextComponent(opt.labelText.string),
+ action = runnable),
+ )
+ }
+}
diff --git a/src/main/kotlin/gui/config/DurationHandler.kt b/src/main/kotlin/gui/config/DurationHandler.kt
new file mode 100644
index 0000000..8d485b1
--- /dev/null
+++ b/src/main/kotlin/gui/config/DurationHandler.kt
@@ -0,0 +1,58 @@
+
+
+package moe.nea.firmament.gui.config
+
+import io.github.notenoughupdates.moulconfig.common.IMinecraft
+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.observer.GetSetter
+import kotlinx.serialization.json.JsonElement
+import kotlinx.serialization.json.JsonPrimitive
+import kotlinx.serialization.json.jsonPrimitive
+import kotlinx.serialization.json.long
+import kotlin.time.Duration
+import kotlin.time.DurationUnit
+import kotlin.time.toDuration
+import net.minecraft.text.Text
+import moe.nea.firmament.util.FirmFormatters
+
+class DurationHandler(val config: ManagedConfig, val min: Duration, val max: Duration) :
+ ManagedConfig.OptionHandler<Duration> {
+ override fun toJson(element: Duration): JsonElement? {
+ return JsonPrimitive(element.inWholeMilliseconds)
+ }
+
+ override fun fromJson(element: JsonElement): Duration {
+ return element.jsonPrimitive.long.toDuration(DurationUnit.MILLISECONDS)
+ }
+
+ override fun emitGuiElements(opt: ManagedOption<Duration>, guiAppender: GuiAppender) {
+ guiAppender.appendLabeledRow(
+ opt.labelText,
+ RowComponent(
+ TextComponent(IMinecraft.instance.defaultFontRenderer,
+ { FirmFormatters.formatTimespan(opt.value) },
+ 40,
+ TextComponent.TextAlignment.CENTER,
+ true,
+ false),
+ SliderComponent(
+ object : GetSetter<Float> {
+ override fun get(): Float {
+ return opt.value.toDouble(DurationUnit.SECONDS).toFloat()
+ }
+
+ override fun set(newValue: Float) {
+ opt.value = newValue.toDouble().toDuration(DurationUnit.SECONDS)
+ }
+ },
+ min.toDouble(DurationUnit.SECONDS).toFloat(),
+ max.toDouble(DurationUnit.SECONDS).toFloat(),
+ 0.1F,
+ 130
+ )
+ ))
+ }
+
+}
diff --git a/src/main/kotlin/gui/config/GuiAppender.kt b/src/main/kotlin/gui/config/GuiAppender.kt
new file mode 100644
index 0000000..329319d
--- /dev/null
+++ b/src/main/kotlin/gui/config/GuiAppender.kt
@@ -0,0 +1,40 @@
+
+
+package moe.nea.firmament.gui.config
+
+import io.github.notenoughupdates.moulconfig.gui.GuiComponent
+import io.github.notenoughupdates.moulconfig.gui.component.RowComponent
+import io.github.notenoughupdates.moulconfig.gui.component.TextComponent
+import io.github.notenoughupdates.moulconfig.observer.GetSetter
+import net.minecraft.client.gui.screen.Screen
+import net.minecraft.text.Text
+import moe.nea.firmament.gui.FixedComponent
+
+class GuiAppender(val width: Int, val screenAccessor: () -> Screen) {
+ val panel = mutableListOf<GuiComponent>()
+ internal val reloadables = mutableListOf<(() -> Unit)>()
+
+ fun onReload(reloadable: () -> Unit) {
+ reloadables.add(reloadable)
+ }
+
+ fun appendLabeledRow(label: Text, right: GuiComponent) {
+ appendSplitRow(
+ TextComponent(label.string),
+ right
+ )
+ }
+
+ fun appendSplitRow(left: GuiComponent, right: GuiComponent) {
+ // TODO: make this more dynamic
+ // i could just make a component that allows for using half the available size
+ appendFullRow(RowComponent(
+ FixedComponent(GetSetter.constant(width / 2), null, left),
+ FixedComponent(GetSetter.constant(width / 2), null, right),
+ ))
+ }
+
+ fun appendFullRow(widget: GuiComponent) {
+ panel.add(widget)
+ }
+}
diff --git a/src/main/kotlin/gui/config/HudMetaHandler.kt b/src/main/kotlin/gui/config/HudMetaHandler.kt
new file mode 100644
index 0000000..35c9d51
--- /dev/null
+++ b/src/main/kotlin/gui/config/HudMetaHandler.kt
@@ -0,0 +1,39 @@
+
+
+package moe.nea.firmament.gui.config
+
+import io.github.notenoughupdates.moulconfig.gui.component.TextComponent
+import kotlinx.serialization.json.Json
+import kotlinx.serialization.json.JsonElement
+import kotlinx.serialization.json.decodeFromJsonElement
+import kotlinx.serialization.json.encodeToJsonElement
+import net.minecraft.text.MutableText
+import net.minecraft.text.Text
+import moe.nea.firmament.gui.FirmButtonComponent
+import moe.nea.firmament.jarvis.JarvisIntegration
+import moe.nea.firmament.util.MC
+
+class HudMetaHandler(val config: ManagedConfig, val label: MutableText, val width: Int, val height: Int) :
+ ManagedConfig.OptionHandler<HudMeta> {
+ override fun toJson(element: HudMeta): JsonElement? {
+ return Json.encodeToJsonElement(element.position)
+ }
+
+ override fun fromJson(element: JsonElement): HudMeta {
+ return HudMeta(Json.decodeFromJsonElement(element), label, width, height)
+ }
+
+ override fun emitGuiElements(opt: ManagedOption<HudMeta>, guiAppender: GuiAppender) {
+ guiAppender.appendLabeledRow(
+ opt.labelText,
+ FirmButtonComponent(
+ TextComponent(
+ Text.stringifiedTranslatable("firmament.hud.edit", label).string),
+ ) {
+ MC.screen = JarvisIntegration.jarvis.getHudEditor(
+ guiAppender.screenAccessor.invoke(),
+ listOf(opt.value)
+ )
+ })
+ }
+}
diff --git a/src/main/kotlin/gui/config/IntegerHandler.kt b/src/main/kotlin/gui/config/IntegerHandler.kt
new file mode 100644
index 0000000..31ce90f
--- /dev/null
+++ b/src/main/kotlin/gui/config/IntegerHandler.kt
@@ -0,0 +1,54 @@
+
+
+package moe.nea.firmament.gui.config
+
+import io.github.notenoughupdates.moulconfig.common.IMinecraft
+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.observer.GetSetter
+import kotlinx.serialization.json.JsonElement
+import kotlinx.serialization.json.JsonPrimitive
+import kotlinx.serialization.json.int
+import kotlinx.serialization.json.jsonPrimitive
+import moe.nea.firmament.util.FirmFormatters
+
+class IntegerHandler(val config: ManagedConfig, val min: Int, val max: Int) : ManagedConfig.OptionHandler<Int> {
+ override fun toJson(element: Int): JsonElement? {
+ return JsonPrimitive(element)
+ }
+
+ override fun fromJson(element: JsonElement): Int {
+ return element.jsonPrimitive.int
+ }
+
+ override fun emitGuiElements(opt: ManagedOption<Int>, guiAppender: GuiAppender) {
+ guiAppender.appendLabeledRow(
+ opt.labelText,
+ RowComponent(
+ TextComponent(IMinecraft.instance.defaultFontRenderer,
+ { FirmFormatters.formatCommas(opt.value, 0) },
+ 40,
+ TextComponent.TextAlignment.CENTER,
+ true,
+ false),
+ SliderComponent(
+ object : GetSetter<Float> {
+ override fun get(): Float {
+ return opt.value.toFloat()
+ }
+
+ override fun set(newValue: Float) {
+ opt.value = newValue.toInt()
+ }
+ },
+ min.toFloat(),
+ max.toFloat(),
+ 0.1F,
+ 130
+ )
+ ))
+
+ }
+
+}
diff --git a/src/main/kotlin/gui/config/JAnyHud.kt b/src/main/kotlin/gui/config/JAnyHud.kt
new file mode 100644
index 0000000..35c4eb2
--- /dev/null
+++ b/src/main/kotlin/gui/config/JAnyHud.kt
@@ -0,0 +1,48 @@
+
+
+package moe.nea.firmament.gui.config
+
+import moe.nea.jarvis.api.JarvisHud
+import moe.nea.jarvis.api.JarvisScalable
+import kotlinx.serialization.Serializable
+import net.minecraft.text.Text
+
+@Serializable
+data class HudPosition(
+ var x: Double,
+ var y: Double,
+ var scale: Float,
+)
+
+
+data class HudMeta(
+ val position: HudPosition,
+ private val label: Text,
+ private val width: Int,
+ private val height: Int,
+) : JarvisScalable, JarvisHud {
+ override fun getX(): Double = position.x
+
+ override fun setX(newX: Double) {
+ position.x = newX
+ }
+
+ override fun getY(): Double = position.y
+
+ override fun setY(newY: Double) {
+ position.y = newY
+ }
+
+ override fun getLabel(): Text = label
+
+ override fun getWidth(): Int = width
+
+ override fun getHeight(): Int = height
+
+ override fun getScale(): Float = position.scale
+
+ override fun setScale(newScale: Float) {
+ position.scale = newScale
+ }
+
+}
diff --git a/src/main/kotlin/gui/config/KeyBindingHandler.kt b/src/main/kotlin/gui/config/KeyBindingHandler.kt
new file mode 100644
index 0000000..c389cc9
--- /dev/null
+++ b/src/main/kotlin/gui/config/KeyBindingHandler.kt
@@ -0,0 +1,149 @@
+
+
+package moe.nea.firmament.gui.config
+
+import io.github.notenoughupdates.moulconfig.common.IMinecraft
+import io.github.notenoughupdates.moulconfig.common.MyResourceLocation
+import io.github.notenoughupdates.moulconfig.deps.libninepatch.NinePatch
+import io.github.notenoughupdates.moulconfig.gui.GuiImmediateContext
+import io.github.notenoughupdates.moulconfig.gui.KeyboardEvent
+import io.github.notenoughupdates.moulconfig.gui.component.TextComponent
+import org.lwjgl.glfw.GLFW
+import kotlinx.serialization.json.Json
+import kotlinx.serialization.json.JsonElement
+import kotlinx.serialization.json.decodeFromJsonElement
+import kotlinx.serialization.json.encodeToJsonElement
+import net.minecraft.text.Text
+import net.minecraft.util.Formatting
+import moe.nea.firmament.gui.FirmButtonComponent
+import moe.nea.firmament.keybindings.FirmamentKeyBindings
+import moe.nea.firmament.keybindings.SavedKeyBinding
+
+class KeyBindingHandler(val name: String, val managedConfig: ManagedConfig) :
+ ManagedConfig.OptionHandler<SavedKeyBinding> {
+
+ override fun initOption(opt: ManagedOption<SavedKeyBinding>) {
+ FirmamentKeyBindings.registerKeyBinding(name, opt)
+ }
+
+ override fun toJson(element: SavedKeyBinding): JsonElement? {
+ return Json.encodeToJsonElement(element)
+ }
+
+ override fun fromJson(element: JsonElement): SavedKeyBinding {
+ return Json.decodeFromJsonElement(element)
+ }
+
+ override fun emitGuiElements(opt: ManagedOption<SavedKeyBinding>, guiAppender: GuiAppender) {
+ var editing = false
+ var lastPressed = 0
+ var lastPressedNonModifier = 0
+ var label: String = ""
+ var button: FirmButtonComponent? = null
+ fun updateLabel() {
+ var stroke = opt.value.format()
+ if (editing) {
+ stroke = Text.literal("")
+ val (shift, alt, ctrl) = SavedKeyBinding.getMods(SavedKeyBinding.getModInt())
+ if (shift) {
+ stroke.append("SHIFT + ")
+ }
+ if (alt) {
+ stroke.append("ALT + ")
+ }
+ if (ctrl) {
+ stroke.append("CTRL + ")
+ }
+ stroke.append("???")
+ stroke.styled { it.withColor(Formatting.YELLOW) }
+ }
+ label = (stroke).string
+ managedConfig.save()
+ }
+ button = object : FirmButtonComponent(
+ TextComponent(
+ IMinecraft.instance.defaultFontRenderer,
+ { label },
+ 130,
+ TextComponent.TextAlignment.LEFT,
+ false,
+ false
+ ), action = {
+ if (editing) {
+ button!!.blur()
+ } else {
+ editing = true
+ button!!.requestFocus()
+ updateLabel()
+ }
+ }) {
+ override fun keyboardEvent(event: KeyboardEvent, context: GuiImmediateContext): Boolean {
+ if (event is KeyboardEvent.KeyPressed) {
+ return if (event.pressed) onKeyPressed(event.keycode, SavedKeyBinding.getModInt())
+ else onKeyReleased(event.keycode, SavedKeyBinding.getModInt())
+ }
+ return super.keyboardEvent(event, context)
+ }
+
+ override fun getBackground(context: GuiImmediateContext): NinePatch<MyResourceLocation> {
+ if (editing) return activeBg
+ return super.getBackground(context)
+ }
+
+ fun onKeyPressed(ch: Int, modifiers: Int): Boolean {
+ if (!editing) {
+ return false
+ }
+ if (ch == GLFW.GLFW_KEY_ESCAPE) {
+ lastPressedNonModifier = 0
+ editing = false
+ lastPressed = 0
+ opt.value = SavedKeyBinding(GLFW.GLFW_KEY_UNKNOWN)
+ updateLabel()
+ blur()
+ return true
+ }
+ if (ch == GLFW.GLFW_KEY_LEFT_SHIFT || ch == GLFW.GLFW_KEY_RIGHT_SHIFT
+ || ch == GLFW.GLFW_KEY_LEFT_ALT || ch == GLFW.GLFW_KEY_RIGHT_ALT
+ || ch == GLFW.GLFW_KEY_LEFT_CONTROL || ch == GLFW.GLFW_KEY_RIGHT_CONTROL
+ ) {
+ lastPressed = ch
+ } else {
+ opt.value = SavedKeyBinding(
+ ch, modifiers
+ )
+ editing = false
+ blur()
+ lastPressed = 0
+ lastPressedNonModifier = 0
+ }
+ updateLabel()
+ return true
+ }
+
+ override fun onLostFocus() {
+ lastPressedNonModifier = 0
+ editing = false
+ lastPressed = 0
+ updateLabel()
+ }
+
+ fun onKeyReleased(ch: Int, modifiers: Int): Boolean {
+ if (!editing)
+ return false
+ if (lastPressedNonModifier == ch || (lastPressedNonModifier == 0 && ch == lastPressed)) {
+ opt.value = SavedKeyBinding(ch, modifiers)
+ editing = false
+ blur()
+ lastPressed = 0
+ lastPressedNonModifier = 0
+ }
+ updateLabel()
+ return true
+ }
+ }
+ updateLabel()
+ guiAppender.appendLabeledRow(opt.labelText, button)
+ }
+
+}
diff --git a/src/main/kotlin/gui/config/ManagedConfig.kt b/src/main/kotlin/gui/config/ManagedConfig.kt
new file mode 100644
index 0000000..aa6e3c8
--- /dev/null
+++ b/src/main/kotlin/gui/config/ManagedConfig.kt
@@ -0,0 +1,181 @@
+
+
+package moe.nea.firmament.gui.config
+
+import io.github.notenoughupdates.moulconfig.gui.CloseEventListener
+import io.github.notenoughupdates.moulconfig.gui.GuiComponentWrapper
+import io.github.notenoughupdates.moulconfig.gui.GuiContext
+import io.github.notenoughupdates.moulconfig.gui.component.CenterComponent
+import io.github.notenoughupdates.moulconfig.gui.component.ColumnComponent
+import io.github.notenoughupdates.moulconfig.gui.component.PanelComponent
+import io.github.notenoughupdates.moulconfig.gui.component.RowComponent
+import io.github.notenoughupdates.moulconfig.gui.component.ScrollPanelComponent
+import io.github.notenoughupdates.moulconfig.gui.component.TextComponent
+import moe.nea.jarvis.api.Point
+import org.lwjgl.glfw.GLFW
+import kotlinx.serialization.encodeToString
+import kotlinx.serialization.json.JsonElement
+import kotlinx.serialization.json.JsonObject
+import kotlin.io.path.createDirectories
+import kotlin.io.path.readText
+import kotlin.io.path.writeText
+import kotlin.time.Duration
+import net.minecraft.client.gui.screen.Screen
+import net.minecraft.text.Text
+import moe.nea.firmament.Firmament
+import moe.nea.firmament.gui.FirmButtonComponent
+import moe.nea.firmament.keybindings.SavedKeyBinding
+import moe.nea.firmament.util.ScreenUtil.setScreenLater
+
+abstract class ManagedConfig(override val name: String) : ManagedConfigElement() {
+
+ interface OptionHandler<T : Any> {
+ fun initOption(opt: ManagedOption<T>) {}
+ fun toJson(element: T): JsonElement?
+ fun fromJson(element: JsonElement): T
+ fun emitGuiElements(opt: ManagedOption<T>, guiAppender: GuiAppender)
+ }
+
+ val file = Firmament.CONFIG_DIR.resolve("$name.json")
+ val data: JsonObject by lazy {
+ try {
+ Firmament.json.decodeFromString(
+ file.readText()
+ )
+ } catch (e: Exception) {
+ Firmament.logger.info("Could not read config $name. Loading empty config.")
+ JsonObject(mutableMapOf())
+ }
+ }
+
+ fun save() {
+ val data = JsonObject(allOptions.mapNotNull { (key, value) ->
+ value.toJson()?.let {
+ key to it
+ }
+ }.toMap())
+ file.parent.createDirectories()
+ file.writeText(Firmament.json.encodeToString(data))
+ }
+
+
+ val allOptions = mutableMapOf<String, ManagedOption<*>>()
+ val sortedOptions = mutableListOf<ManagedOption<*>>()
+
+ private var latestGuiAppender: GuiAppender? = null
+
+ protected fun <T : Any> option(
+ propertyName: String,
+ default: () -> T,
+ handler: OptionHandler<T>
+ ): ManagedOption<T> {
+ if (propertyName in allOptions) error("Cannot register the same name twice")
+ return ManagedOption(this, propertyName, default, handler).also {
+ it.handler.initOption(it)
+ it.load(data)
+ allOptions[propertyName] = it
+ sortedOptions.add(it)
+ }
+ }
+
+ protected fun toggle(propertyName: String, default: () -> Boolean): ManagedOption<Boolean> {
+ return option(propertyName, default, BooleanHandler(this))
+ }
+
+ protected fun duration(
+ propertyName: String,
+ min: Duration,
+ max: Duration,
+ default: () -> Duration,
+ ): ManagedOption<Duration> {
+ return option(propertyName, default, DurationHandler(this, min, max))
+ }
+
+
+ protected fun position(
+ propertyName: String,
+ width: Int,
+ height: Int,
+ default: () -> Point,
+ ): ManagedOption<HudMeta> {
+ val label = Text.translatable("firmament.config.${name}.${propertyName}")
+ return option(propertyName, {
+ val p = default()
+ HudMeta(HudPosition(p.x, p.y, 1F), label, width, height)
+ }, HudMetaHandler(this, label, width, height))
+ }
+
+ protected fun keyBinding(
+ propertyName: String,
+ default: () -> Int,
+ ): ManagedOption<SavedKeyBinding> = keyBindingWithOutDefaultModifiers(propertyName) { SavedKeyBinding(default()) }
+
+ protected fun keyBindingWithOutDefaultModifiers(
+ propertyName: String,
+ default: () -> SavedKeyBinding,
+ ): ManagedOption<SavedKeyBinding> {
+ return option(propertyName, default, KeyBindingHandler("firmament.config.${name}.${propertyName}", this))
+ }
+
+ protected fun keyBindingWithDefaultUnbound(
+ propertyName: String,
+ ): ManagedOption<SavedKeyBinding> {
+ return keyBindingWithOutDefaultModifiers(propertyName) { SavedKeyBinding(GLFW.GLFW_KEY_UNKNOWN) }
+ }
+
+ protected fun integer(
+ propertyName: String,
+ min: Int,
+ max: Int,
+ default: () -> Int,
+ ): ManagedOption<Int> {
+ return option(propertyName, default, IntegerHandler(this, min, max))
+ }
+
+ protected fun button(propertyName: String, runnable: () -> Unit): ManagedOption<Unit> {
+ return option(propertyName, { }, ClickHandler(this, runnable))
+ }
+
+ protected fun string(propertyName: String, default: () -> String): ManagedOption<String> {
+ return option(propertyName, default, StringHandler(this))
+ }
+
+
+ fun reloadGui() {
+ latestGuiAppender?.reloadables?.forEach { it() }
+ }
+
+ val labelText = Text.translatable("firmament.config.${name}")
+
+ fun getConfigEditor(parent: Screen? = null): Screen {
+ var screen: Screen? = null
+ val guiapp = GuiAppender(400) { requireNotNull(screen) { "Screen Accessor called too early" } }
+ latestGuiAppender = guiapp
+ guiapp.appendFullRow(RowComponent(
+ FirmButtonComponent(TextComponent("←")) {
+ if (parent != null) {
+ save()
+ setScreenLater(parent)
+ } else {
+ AllConfigsGui.showAllGuis()
+ }
+ }
+ ))
+ sortedOptions.forEach { it.appendToGui(guiapp) }
+ guiapp.reloadables.forEach { it() }
+ val component = CenterComponent(PanelComponent(ScrollPanelComponent(400, 300, ColumnComponent(guiapp.panel)), 10, PanelComponent.DefaultBackgroundRenderer.VANILLA))
+ screen = object : GuiComponentWrapper(GuiContext(component)) {
+ override fun close() {
+ if (context.onBeforeClose() == CloseEventListener.CloseAction.NO_OBJECTIONS_TO_CLOSE) {
+ client!!.setScreen(parent)
+ }
+ }
+ }
+ return screen
+ }
+
+ fun showConfigEditor(parent: Screen? = null) {
+ setScreenLater(getConfigEditor(parent))
+ }
+
+}
diff --git a/src/main/kotlin/gui/config/ManagedConfigElement.kt b/src/main/kotlin/gui/config/ManagedConfigElement.kt
new file mode 100644
index 0000000..28cd6b8
--- /dev/null
+++ b/src/main/kotlin/gui/config/ManagedConfigElement.kt
@@ -0,0 +1,8 @@
+
+
+package moe.nea.firmament.gui.config
+
+abstract class ManagedConfigElement {
+ abstract val name: String
+
+}
diff --git a/src/main/kotlin/gui/config/ManagedOption.kt b/src/main/kotlin/gui/config/ManagedOption.kt
new file mode 100644
index 0000000..b7264e8
--- /dev/null
+++ b/src/main/kotlin/gui/config/ManagedOption.kt
@@ -0,0 +1,62 @@
+
+
+package moe.nea.firmament.gui.config
+
+import io.github.notenoughupdates.moulconfig.observer.GetSetter
+import kotlinx.serialization.json.JsonElement
+import kotlinx.serialization.json.JsonObject
+import kotlin.properties.ReadWriteProperty
+import kotlin.reflect.KProperty
+import net.minecraft.text.Text
+import moe.nea.firmament.Firmament
+
+class ManagedOption<T : Any>(
+ val element: ManagedConfigElement,
+ val propertyName: String,
+ val default: () -> T,
+ val handler: ManagedConfig.OptionHandler<T>
+) : ReadWriteProperty<Any?, T>, GetSetter<T> {
+ override fun set(newValue: T) {
+ this.value = newValue
+ }
+
+ override fun get(): T {
+ return this.value
+ }
+
+ val rawLabelText = "firmament.config.${element.name}.${propertyName}"
+ val labelText = Text.translatable(rawLabelText)
+
+ lateinit var value: T
+
+ override fun setValue(thisRef: Any?, property: KProperty<*>, value: T) {
+ this.value = value
+ }
+
+ override fun getValue(thisRef: Any?, property: KProperty<*>): T {
+ return value
+ }
+
+ fun load(root: JsonElement) {
+ if (root is JsonObject && root.containsKey(propertyName)) {
+ try {
+ value = handler.fromJson(root[propertyName]!!)
+ return
+ } catch (e: Exception) {
+ Firmament.logger.error(
+ "Exception during loading of config file ${element.name}. This will reset this config.",
+ e
+ )
+ }
+ }
+ value = default()
+ }
+
+ fun toJson(): JsonElement? {
+ return handler.toJson(value)
+ }
+
+ fun appendToGui(guiapp: GuiAppender) {
+ handler.emitGuiElements(this, guiapp)
+ }
+}
diff --git a/src/main/kotlin/gui/config/StringHandler.kt b/src/main/kotlin/gui/config/StringHandler.kt
new file mode 100644
index 0000000..a326abb
--- /dev/null
+++ b/src/main/kotlin/gui/config/StringHandler.kt
@@ -0,0 +1,36 @@
+
+
+package moe.nea.firmament.gui.config
+
+import io.github.notenoughupdates.moulconfig.gui.component.TextFieldComponent
+import io.github.notenoughupdates.moulconfig.observer.GetSetter
+import kotlinx.serialization.json.JsonElement
+import kotlinx.serialization.json.JsonPrimitive
+import kotlinx.serialization.json.jsonPrimitive
+import net.minecraft.text.Text
+
+class StringHandler(val config: ManagedConfig) : ManagedConfig.OptionHandler<String> {
+ override fun toJson(element: String): JsonElement? {
+ return JsonPrimitive(element)
+ }
+
+ override fun fromJson(element: JsonElement): String {
+ return element.jsonPrimitive.content
+ }
+
+ override fun emitGuiElements(opt: ManagedOption<String>, guiAppender: GuiAppender) {
+ guiAppender.appendLabeledRow(
+ opt.labelText,
+ TextFieldComponent(
+ object : GetSetter<String> by opt {
+ override fun set(newValue: String) {
+ opt.set(newValue)
+ config.save()
+ }
+ },
+ 130,
+ suggestion = Text.translatableWithFallback(opt.rawLabelText + ".hint", "").string
+ ),
+ )
+ }
+}