From f85c449ed586c7ced780423943e55bfa5abaeb0f Mon Sep 17 00:00:00 2001 From: nea Date: Sat, 22 Oct 2022 00:34:22 +0200 Subject: rudimentary config gui (again) --- .../notenoughupdates/util/config/ConfigHolder.kt | 60 ------ .../notenoughupdates/util/config/IConfigHolder.kt | 75 -------- .../notenoughupdates/util/config/ManagedConfig.kt | 206 +++++++++++++++++++++ .../util/config/ProfileSpecificConfigHolder.kt | 82 -------- .../nea/notenoughupdates/util/data/DataHolder.kt | 60 ++++++ .../nea/notenoughupdates/util/data/IDataHolder.kt | 75 ++++++++ .../util/data/ProfileSpecificDataHolder.kt | 82 ++++++++ 7 files changed, 423 insertions(+), 217 deletions(-) delete mode 100644 src/main/kotlin/moe/nea/notenoughupdates/util/config/ConfigHolder.kt delete mode 100644 src/main/kotlin/moe/nea/notenoughupdates/util/config/IConfigHolder.kt create mode 100644 src/main/kotlin/moe/nea/notenoughupdates/util/config/ManagedConfig.kt delete mode 100644 src/main/kotlin/moe/nea/notenoughupdates/util/config/ProfileSpecificConfigHolder.kt create mode 100644 src/main/kotlin/moe/nea/notenoughupdates/util/data/DataHolder.kt create mode 100644 src/main/kotlin/moe/nea/notenoughupdates/util/data/IDataHolder.kt create mode 100644 src/main/kotlin/moe/nea/notenoughupdates/util/data/ProfileSpecificDataHolder.kt (limited to 'src/main/kotlin/moe/nea/notenoughupdates/util') diff --git a/src/main/kotlin/moe/nea/notenoughupdates/util/config/ConfigHolder.kt b/src/main/kotlin/moe/nea/notenoughupdates/util/config/ConfigHolder.kt deleted file mode 100644 index e8a9649..0000000 --- a/src/main/kotlin/moe/nea/notenoughupdates/util/config/ConfigHolder.kt +++ /dev/null @@ -1,60 +0,0 @@ -package moe.nea.notenoughupdates.util.config - -import java.nio.file.Path -import kotlinx.serialization.KSerializer -import kotlin.io.path.exists -import kotlin.io.path.readText -import kotlin.io.path.writeText -import moe.nea.notenoughupdates.NotEnoughUpdates - -abstract class ConfigHolder( - val serializer: KSerializer, - val name: String, - val default: () -> T -) : IConfigHolder { - - - final override var config: T - private set - - init { - config = readValueOrDefault() - IConfigHolder.putConfig(this::class, this) - } - - private val file: Path get() = NotEnoughUpdates.CONFIG_DIR.resolve("$name.json") - - protected fun readValueOrDefault(): T { - if (file.exists()) - try { - return NotEnoughUpdates.json.decodeFromString( - serializer, - file.readText() - ) - } catch (e: Exception) {/* Expecting IOException and SerializationException, but Kotlin doesn't allow multi catches*/ - IConfigHolder.badLoads.add(name) - NotEnoughUpdates.logger.error( - "Exception during loading of config file $name. This will reset this config.", - e - ) - } - return default() - } - - private fun writeValue(t: T) { - file.writeText(NotEnoughUpdates.json.encodeToString(serializer, t)) - } - - override fun save() { - writeValue(config) - } - - override fun load() { - config = readValueOrDefault() - } - - override fun markDirty() { - IConfigHolder.markDirty(this::class) - } - -} diff --git a/src/main/kotlin/moe/nea/notenoughupdates/util/config/IConfigHolder.kt b/src/main/kotlin/moe/nea/notenoughupdates/util/config/IConfigHolder.kt deleted file mode 100644 index 2acc99d..0000000 --- a/src/main/kotlin/moe/nea/notenoughupdates/util/config/IConfigHolder.kt +++ /dev/null @@ -1,75 +0,0 @@ -package moe.nea.notenoughupdates.util.config - -import java.util.concurrent.CopyOnWriteArrayList -import net.fabricmc.fabric.api.client.event.lifecycle.v1.ClientLifecycleEvents -import kotlin.reflect.KClass -import net.minecraft.client.MinecraftClient -import net.minecraft.server.command.CommandOutput -import net.minecraft.text.Text -import moe.nea.notenoughupdates.NotEnoughUpdates -import moe.nea.notenoughupdates.events.ScreenOpenEvent - -interface IConfigHolder { - companion object { - internal var badLoads: MutableList = CopyOnWriteArrayList() - private val allConfigs: MutableMap>, IConfigHolder<*>> = mutableMapOf() - private val dirty: MutableSet>> = mutableSetOf() - - internal fun , K> putConfig(kClass: KClass, inst: IConfigHolder) { - allConfigs[kClass] = inst - } - - fun , K> markDirty(kClass: KClass) { - if (kClass !in allConfigs) { - NotEnoughUpdates.logger.error("Tried to markDirty '${kClass.qualifiedName}', which isn't registered as 'IConfigHolder'") - return - } - dirty.add(kClass) - } - - private fun performSaves() { - val toSave = dirty.toList().also { - dirty.clear() - } - for (it in toSave) { - val obj = allConfigs[it] - if (obj == null) { - NotEnoughUpdates.logger.error("Tried to save '${it}', which isn't registered as 'ConfigHolder'") - continue - } - obj.save() - } - } - - private fun warnForResetConfigs(player: CommandOutput) { - if (badLoads.isNotEmpty()) { - player.sendMessage( - Text.literal( - "The following configs have been reset: ${badLoads.joinToString(", ")}. " + - "This can be intentional, but probably isn't." - ) - ) - badLoads.clear() - } - } - - fun registerEvents() { - ScreenOpenEvent.subscribe { event -> - performSaves() - val p = MinecraftClient.getInstance().player - if (p != null) { - warnForResetConfigs(p) - } - } - ClientLifecycleEvents.CLIENT_STOPPING.register(ClientLifecycleEvents.ClientStopping { - performSaves() - }) - } - - } - - val config: T - fun save() - fun markDirty() - fun load() -} diff --git a/src/main/kotlin/moe/nea/notenoughupdates/util/config/ManagedConfig.kt b/src/main/kotlin/moe/nea/notenoughupdates/util/config/ManagedConfig.kt new file mode 100644 index 0000000..a149bf2 --- /dev/null +++ b/src/main/kotlin/moe/nea/notenoughupdates/util/config/ManagedConfig.kt @@ -0,0 +1,206 @@ +package moe.nea.notenoughupdates.util.config + +import io.github.cottonmc.cotton.gui.client.CottonClientScreen +import io.github.cottonmc.cotton.gui.client.LibGui +import io.github.cottonmc.cotton.gui.client.LightweightGuiDescription +import io.github.cottonmc.cotton.gui.widget.WButton +import io.github.cottonmc.cotton.gui.widget.WLabel +import io.github.cottonmc.cotton.gui.widget.WToggleButton +import io.github.cottonmc.cotton.gui.widget.WWidget +import io.github.cottonmc.cotton.gui.widget.data.Insets +import kotlinx.serialization.decodeFromString +import kotlinx.serialization.encodeToString +import kotlinx.serialization.json.JsonElement +import kotlinx.serialization.json.JsonObject +import kotlinx.serialization.json.JsonPrimitive +import kotlinx.serialization.json.boolean +import kotlinx.serialization.json.jsonPrimitive +import kotlin.io.path.createDirectories +import kotlin.io.path.readText +import kotlin.io.path.writeText +import kotlin.properties.ReadOnlyProperty +import kotlin.reflect.KProperty +import net.minecraft.client.MinecraftClient +import net.minecraft.text.Text +import moe.nea.notenoughupdates.NotEnoughUpdates +import moe.nea.notenoughupdates.gui.WGridPanelWithPadding +import moe.nea.notenoughupdates.util.ScreenUtil.setScreenLater + +abstract class ManagedConfig(val name: String) { + + class GuiAppender(val width: Int) { + private var row = 0 + internal val panel = WGridPanelWithPadding(verticalPadding = 4, horizontalPadding = 4) + internal val reloadables = mutableListOf<(() -> Unit)>() + fun set(x: Int, y: Int, w: Int, h: Int, widget: WWidget) { + panel.add(widget, x, y, w, h) + } + + + fun onReload(reloadable: () -> Unit) { + reloadables.add(reloadable) + } + + fun skipRows(r: Int) { + row += r + } + + fun appendSplitRow(left: WWidget, right: WWidget) { + val lw = width / 2 + set(0, row, lw, 1, left) + set(lw, row, width - lw, 1, right) + skipRows(1) + } + + fun appendFullRow(widget: WWidget) { + set(0, row, width, 1, widget) + skipRows(1) + } + } + + interface OptionHandler { + fun toJson(element: T): JsonElement? + fun fromJson(element: JsonElement): T + fun emitGuiElements(opt: Option, guiAppender: GuiAppender) + } + + inner class Option internal constructor( + val propertyName: String, + val default: () -> T, + val handler: OptionHandler + ) : ReadOnlyProperty { + + private lateinit var _value: T + private var loaded = false + var value: T + get() { + if (!loaded) + load() + return _value + } + set(value) { + loaded = true + _value = value + } + + override fun getValue(thisRef: Any?, property: KProperty<*>): T { + return value + } + + private fun load() { + if (data.containsKey(propertyName)) { + try { + value = handler.fromJson(data[propertyName]!!) + } catch (e: Exception) { + NotEnoughUpdates.logger.error( + "Exception during loading of config file $name. This will reset this config.", + e + ) + } + } + value = default() + } + + internal fun toJson(): JsonElement? { + return handler.toJson(value) + } + + fun appendToGui(guiapp: GuiAppender) { + handler.emitGuiElements(this, guiapp) + } + } + + val file = NotEnoughUpdates.CONFIG_DIR.resolve("$name.json") + val data: JsonObject by lazy { + try { + NotEnoughUpdates.json.decodeFromString( + file.readText() + ) + } catch (e: Exception) { + NotEnoughUpdates.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(NotEnoughUpdates.json.encodeToString(data)) + } + + + val allOptions = mutableMapOf>() + val sortedOptions = mutableListOf>() + + protected fun option(propertyName: String, default: () -> T, handler: OptionHandler): Option { + if (propertyName in allOptions) error("Cannot register the same name twice") + return Option(propertyName, default, handler).also { + allOptions[propertyName] = it + sortedOptions.add(it) + } + } + + class BooleanHandler(val config: ManagedConfig) : OptionHandler { + override fun toJson(element: Boolean): JsonElement? { + return JsonPrimitive(element) + } + + override fun fromJson(element: JsonElement): Boolean { + return element.jsonPrimitive.boolean + } + + override fun emitGuiElements(opt: Option, guiAppender: GuiAppender) { + guiAppender.appendFullRow( + WToggleButton(Text.translatable("neu.config.${config.name}.${opt.propertyName}")).apply { + guiAppender.onReload { toggle = opt.value } + setOnToggle { + opt.value = it + config.save() + } + } + ) + } + } + + class ClickHandler(val config: ManagedConfig, val runnable: () -> Unit) : OptionHandler { + override fun toJson(element: Unit): JsonElement? { + return null + } + + override fun fromJson(element: JsonElement) {} + + override fun emitGuiElements(opt: Option, guiAppender: GuiAppender) { + guiAppender.appendSplitRow( + WLabel(Text.translatable("neu.config.${config.name}.${opt.propertyName}")), + WButton(Text.translatable("neu.config.${config.name}.${opt.propertyName}")).apply { + setOnClick { + runnable() + } + }, + ) + } + } + + protected fun toggle(propertyName: String, default: () -> Boolean): Option { + return option(propertyName, default, BooleanHandler(this)) + } + + fun showConfigEditor() { + val lwgd = LightweightGuiDescription() + val guiapp = GuiAppender(20) + guiapp.panel.insets = Insets.ROOT_PANEL + sortedOptions.forEach { it.appendToGui(guiapp) } + guiapp.reloadables.forEach { it() } + lwgd.setRootPanel(guiapp.panel) + setScreenLater(CottonClientScreen(lwgd)) + } + + protected fun button(propertyName: String, runnable: () -> Unit): Option { + return option(propertyName, { }, ClickHandler(this, runnable)) + } + +} diff --git a/src/main/kotlin/moe/nea/notenoughupdates/util/config/ProfileSpecificConfigHolder.kt b/src/main/kotlin/moe/nea/notenoughupdates/util/config/ProfileSpecificConfigHolder.kt deleted file mode 100644 index 6a39e0a..0000000 --- a/src/main/kotlin/moe/nea/notenoughupdates/util/config/ProfileSpecificConfigHolder.kt +++ /dev/null @@ -1,82 +0,0 @@ -package moe.nea.notenoughupdates.util.config - -import java.nio.file.Path -import kotlinx.serialization.KSerializer -import kotlin.io.path.createDirectories -import kotlin.io.path.deleteExisting -import kotlin.io.path.exists -import kotlin.io.path.extension -import kotlin.io.path.listDirectoryEntries -import kotlin.io.path.nameWithoutExtension -import kotlin.io.path.readText -import kotlin.io.path.writeText -import moe.nea.notenoughupdates.NotEnoughUpdates -import moe.nea.notenoughupdates.util.SBData - -abstract class ProfileSpecificConfigHolder( - private val configSerializer: KSerializer, - val configName: String, - private val configDefault: () -> S -) : IConfigHolder { - - var allConfigs: MutableMap - - override val config: S? - get() = SBData.profileCuteName?.let { - allConfigs.computeIfAbsent(it) { configDefault() } - } - - init { - allConfigs = readValues() - readValues() - IConfigHolder.putConfig(this::class, this) - } - - private val configDirectory: Path get() = NotEnoughUpdates.CONFIG_DIR.resolve("profiles") - - private fun readValues(): MutableMap { - if (!configDirectory.exists()) { - configDirectory.createDirectories() - } - val profileFiles = configDirectory.listDirectoryEntries() - return profileFiles - .filter { it.extension == "json" } - .mapNotNull { - try { - it.nameWithoutExtension to NotEnoughUpdates.json.decodeFromString(configSerializer, it.readText()) - } catch (e: Exception) { /* Expecting IOException and SerializationException, but Kotlin doesn't allow multi catches*/ - IConfigHolder.badLoads.add(configName) - NotEnoughUpdates.logger.error( - "Exception during loading of profile specific config file $it ($configName). This will reset that profiles config.", - e - ) - null - } - }.toMap().toMutableMap() - } - - override fun save() { - if (!configDirectory.exists()) { - configDirectory.createDirectories() - } - val c = allConfigs - configDirectory.listDirectoryEntries().forEach { - if (it.nameWithoutExtension !in c) { - it.deleteExisting() - } - } - c.forEach { (name, value) -> - val f = configDirectory.resolve("$name.json") - f.writeText(NotEnoughUpdates.json.encodeToString(configSerializer, value)) - } - } - - override fun markDirty() { - IConfigHolder.markDirty(this::class) - } - - override fun load() { - allConfigs = readValues() - } - -} diff --git a/src/main/kotlin/moe/nea/notenoughupdates/util/data/DataHolder.kt b/src/main/kotlin/moe/nea/notenoughupdates/util/data/DataHolder.kt new file mode 100644 index 0000000..6c9d8e8 --- /dev/null +++ b/src/main/kotlin/moe/nea/notenoughupdates/util/data/DataHolder.kt @@ -0,0 +1,60 @@ +package moe.nea.notenoughupdates.util.data + +import java.nio.file.Path +import kotlinx.serialization.KSerializer +import kotlin.io.path.exists +import kotlin.io.path.readText +import kotlin.io.path.writeText +import moe.nea.notenoughupdates.NotEnoughUpdates + +abstract class DataHolder( + val serializer: KSerializer, + val name: String, + val default: () -> T +) : IDataHolder { + + + final override var data: T + private set + + init { + data = readValueOrDefault() + IDataHolder.putDataHolder(this::class, this) + } + + private val file: Path get() = NotEnoughUpdates.CONFIG_DIR.resolve("$name.json") + + protected fun readValueOrDefault(): T { + if (file.exists()) + try { + return NotEnoughUpdates.json.decodeFromString( + serializer, + file.readText() + ) + } catch (e: Exception) {/* Expecting IOException and SerializationException, but Kotlin doesn't allow multi catches*/ + IDataHolder.badLoads.add(name) + NotEnoughUpdates.logger.error( + "Exception during loading of config file $name. This will reset this config.", + e + ) + } + return default() + } + + private fun writeValue(t: T) { + file.writeText(NotEnoughUpdates.json.encodeToString(serializer, t)) + } + + override fun save() { + writeValue(data) + } + + override fun load() { + data = readValueOrDefault() + } + + override fun markDirty() { + IDataHolder.markDirty(this::class) + } + +} diff --git a/src/main/kotlin/moe/nea/notenoughupdates/util/data/IDataHolder.kt b/src/main/kotlin/moe/nea/notenoughupdates/util/data/IDataHolder.kt new file mode 100644 index 0000000..ccbf676 --- /dev/null +++ b/src/main/kotlin/moe/nea/notenoughupdates/util/data/IDataHolder.kt @@ -0,0 +1,75 @@ +package moe.nea.notenoughupdates.util.data + +import java.util.concurrent.CopyOnWriteArrayList +import net.fabricmc.fabric.api.client.event.lifecycle.v1.ClientLifecycleEvents +import kotlin.reflect.KClass +import net.minecraft.client.MinecraftClient +import net.minecraft.server.command.CommandOutput +import net.minecraft.text.Text +import moe.nea.notenoughupdates.NotEnoughUpdates +import moe.nea.notenoughupdates.events.ScreenOpenEvent + +interface IDataHolder { + companion object { + internal var badLoads: MutableList = CopyOnWriteArrayList() + private val allConfigs: MutableMap>, IDataHolder<*>> = mutableMapOf() + private val dirty: MutableSet>> = mutableSetOf() + + internal fun , K> putDataHolder(kClass: KClass, inst: IDataHolder) { + allConfigs[kClass] = inst + } + + fun , K> markDirty(kClass: KClass) { + if (kClass !in allConfigs) { + NotEnoughUpdates.logger.error("Tried to markDirty '${kClass.qualifiedName}', which isn't registered as 'IConfigHolder'") + return + } + dirty.add(kClass) + } + + private fun performSaves() { + val toSave = dirty.toList().also { + dirty.clear() + } + for (it in toSave) { + val obj = allConfigs[it] + if (obj == null) { + NotEnoughUpdates.logger.error("Tried to save '${it}', which isn't registered as 'ConfigHolder'") + continue + } + obj.save() + } + } + + private fun warnForResetConfigs(player: CommandOutput) { + if (badLoads.isNotEmpty()) { + player.sendMessage( + Text.literal( + "The following configs have been reset: ${badLoads.joinToString(", ")}. " + + "This can be intentional, but probably isn't." + ) + ) + badLoads.clear() + } + } + + fun registerEvents() { + ScreenOpenEvent.subscribe { event -> + performSaves() + val p = MinecraftClient.getInstance().player + if (p != null) { + warnForResetConfigs(p) + } + } + ClientLifecycleEvents.CLIENT_STOPPING.register(ClientLifecycleEvents.ClientStopping { + performSaves() + }) + } + + } + + val data: T + fun save() + fun markDirty() + fun load() +} diff --git a/src/main/kotlin/moe/nea/notenoughupdates/util/data/ProfileSpecificDataHolder.kt b/src/main/kotlin/moe/nea/notenoughupdates/util/data/ProfileSpecificDataHolder.kt new file mode 100644 index 0000000..a2f78b1 --- /dev/null +++ b/src/main/kotlin/moe/nea/notenoughupdates/util/data/ProfileSpecificDataHolder.kt @@ -0,0 +1,82 @@ +package moe.nea.notenoughupdates.util.data + +import java.nio.file.Path +import kotlinx.serialization.KSerializer +import kotlin.io.path.createDirectories +import kotlin.io.path.deleteExisting +import kotlin.io.path.exists +import kotlin.io.path.extension +import kotlin.io.path.listDirectoryEntries +import kotlin.io.path.nameWithoutExtension +import kotlin.io.path.readText +import kotlin.io.path.writeText +import moe.nea.notenoughupdates.NotEnoughUpdates +import moe.nea.notenoughupdates.util.SBData + +abstract class ProfileSpecificDataHolder( + private val dataSerializer: KSerializer, + val configName: String, + private val configDefault: () -> S +) : IDataHolder { + + var allConfigs: MutableMap + + override val data: S? + get() = SBData.profileCuteName?.let { + allConfigs.computeIfAbsent(it) { configDefault() } + } + + init { + allConfigs = readValues() + readValues() + IDataHolder.putDataHolder(this::class, this) + } + + private val configDirectory: Path get() = NotEnoughUpdates.CONFIG_DIR.resolve("profiles").resolve(configName) + + private fun readValues(): MutableMap { + if (!configDirectory.exists()) { + configDirectory.createDirectories() + } + val profileFiles = configDirectory.listDirectoryEntries() + return profileFiles + .filter { it.extension == "json" } + .mapNotNull { + try { + it.nameWithoutExtension to NotEnoughUpdates.json.decodeFromString(dataSerializer, it.readText()) + } catch (e: Exception) { /* Expecting IOException and SerializationException, but Kotlin doesn't allow multi catches*/ + IDataHolder.badLoads.add(configName) + NotEnoughUpdates.logger.error( + "Exception during loading of profile specific config file $it ($configName). This will reset that profiles config.", + e + ) + null + } + }.toMap().toMutableMap() + } + + override fun save() { + if (!configDirectory.exists()) { + configDirectory.createDirectories() + } + val c = allConfigs + configDirectory.listDirectoryEntries().forEach { + if (it.nameWithoutExtension !in c) { + it.deleteExisting() + } + } + c.forEach { (name, value) -> + val f = configDirectory.resolve("$name.json") + f.writeText(NotEnoughUpdates.json.encodeToString(dataSerializer, value)) + } + } + + override fun markDirty() { + IDataHolder.markDirty(this::class) + } + + override fun load() { + allConfigs = readValues() + } + +} -- cgit