Common config gui

This commit is contained in:
nea
2023-05-24 02:29:20 +02:00
parent 5ff50799b6
commit 5984383d2c
19 changed files with 262 additions and 295 deletions

View File

@@ -1,94 +0,0 @@
package moe.nea.firmament.gui
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.WTextField
import io.github.cottonmc.cotton.gui.widget.WToggleButton
import io.github.cottonmc.cotton.gui.widget.data.HorizontalAlignment
import io.github.cottonmc.cotton.gui.widget.data.Insets
import io.github.cottonmc.cotton.gui.widget.data.VerticalAlignment
import moe.nea.firmament.Firmament
import moe.nea.firmament.util.data.DataHolder
import net.minecraft.text.Text
import kotlin.reflect.KMutableProperty1
class ConfigGui<K>(val holder: DataHolder<K>, val build: ConfigGui<K>.() -> Unit) : LightweightGuiDescription() {
private val root = WGridPanelWithPadding(verticalPadding = 4)
private val reloadables = mutableListOf<(() -> Unit)>()
init {
setRootPanel(root)
root.insets = Insets.ROOT_PANEL
build()
reload()
}
fun title(text: Text) {
if (col != 0) {
Firmament.logger.warn("Set title not at the top of the ConfigGui")
}
val label = WLabel(text)
label.verticalAlignment = VerticalAlignment.TOP
label.horizontalAlignment = HorizontalAlignment.CENTER
root.add(label, 0, col, 11, 1)
col++
}
private fun label(text: Text) {
val label = WLabel(text)
label.verticalAlignment = VerticalAlignment.CENTER
root.add(label, 0, col, 5, 1)
}
fun toggle(text: Text, prop: KMutableProperty1<K, Boolean>) {
val toggle = WToggleButton(text)
reloadables.add { toggle.toggle = prop.get(holder.data) }
toggle.setOnToggle {
prop.set(holder.data, true)
holder.markDirty()
}
root.add(toggle, 5, col, 6, 1)
label(text)
col++
}
fun button(text: Text, buttonText: Text, runnable: () -> Unit) {
val button = WButton(buttonText)
button.setOnClick {
runnable.invoke()
}
root.add(button, 5, col, 6, 1)
label(text)
col++
}
fun textfield(
text: Text,
background: Text,
prop: KMutableProperty1<K, String>,
maxLength: Int = 255
) {
val textfield = WTextField(background)
textfield.isEditable = true
reloadables.add {
textfield.text = prop.get(holder.data)
}
textfield.maxLength = maxLength
textfield.setChangedListener {
prop.set(holder.data, it)
holder.markDirty()
}
root.add(textfield, 5, col, 6, 11)
label(text)
col++
}
fun reload() {
reloadables.forEach { it.invoke() }
}
private var col = 0
}

View File

@@ -1,33 +0,0 @@
package moe.nea.firmament.gui
import io.github.cottonmc.cotton.gui.widget.WPanelWithInsets
import io.github.cottonmc.cotton.gui.widget.WWidget
import io.github.cottonmc.cotton.gui.widget.data.Insets
class WGridPanelWithPadding(
val grid: Int = 18,
val verticalPadding: Int = 0,
val horizontalPadding: Int = 0,
) : WPanelWithInsets() {
private inline val vertOffset get() = grid + verticalPadding
private inline val horiOffset get() = grid + horizontalPadding
fun add(w: WWidget, x: Int, y: Int, width: Int = 1, height: Int = 1) {
children.add(w)
w.parent = this
w.setLocation(x * horiOffset + insets.left, y * vertOffset + insets.top)
if (w.canResize())
w.setSize(
grid + (horiOffset * (width - 1)),
grid + (vertOffset * (height - 1)),
)
expandToFit(w, insets)
}
override fun setInsets(insets: Insets): WGridPanelWithPadding {
super.setInsets(insets)
return this
}
}

View File

@@ -0,0 +1,42 @@
package moe.nea.firmament.gui.config
import io.github.cottonmc.cotton.gui.client.BackgroundPainter
import io.github.cottonmc.cotton.gui.client.CottonClientScreen
import io.github.cottonmc.cotton.gui.client.LightweightGuiDescription
import io.github.cottonmc.cotton.gui.widget.WButton
import io.github.cottonmc.cotton.gui.widget.WGridPanel
import io.github.cottonmc.cotton.gui.widget.WLabel
import io.github.cottonmc.cotton.gui.widget.WListPanel
import io.github.cottonmc.cotton.gui.widget.data.Insets
import net.minecraft.text.Text
import moe.nea.firmament.features.FeatureManager
import moe.nea.firmament.repo.RepoManager
import moe.nea.firmament.util.ScreenUtil.setScreenLater
object AllConfigsGui {
fun showAllGuis() {
val lwgd = LightweightGuiDescription()
var screen: CottonClientScreen? = null
lwgd.setRootPanel(WListPanel(
listOf(
RepoManager.Config
) + FeatureManager.allFeatures.mapNotNull { it.config }, ::WGridPanel
) { config, panel ->
panel.insets = Insets.ROOT_PANEL
panel.backgroundPainter = BackgroundPainter.VANILLA
panel.add(WLabel(Text.translatable("firmament.config.${config.name}")), 0, 0, 10, 1)
panel.add(WButton(Text.translatable("firmanent.config.edit")).also {
it.setOnClick {
config.showConfigEditor(screen)
}
}, 0, 1, 10, 1)
println("Panel size: ${panel.width} ${panel.height}")
}.also {
it.setListItemHeight(52)
it.setSize(10 * 18 + 14 + 16, 300)
})
screen = CottonClientScreen(lwgd)
setScreenLater(screen)
}
}

View File

@@ -0,0 +1,33 @@
package moe.nea.firmament.gui.config
import io.github.cottonmc.cotton.gui.widget.WLabel
import io.github.cottonmc.cotton.gui.widget.WToggleButton
import io.github.cottonmc.cotton.gui.widget.data.VerticalAlignment
import kotlinx.serialization.json.JsonElement
import kotlinx.serialization.json.JsonPrimitive
import kotlinx.serialization.json.boolean
import kotlinx.serialization.json.jsonPrimitive
import net.minecraft.text.Text
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: ManagedConfig.Option<Boolean>, guiAppender: GuiAppender) {
guiAppender.appendLabeledRow(
opt.labelText,
WToggleButton(opt.labelText).apply {
guiAppender.onReload { toggle = opt.value }
setOnToggle {
opt.value = it
config.save()
}
}
)
}
}

View File

@@ -0,0 +1,25 @@
package moe.nea.firmament.gui.config
import io.github.cottonmc.cotton.gui.widget.WButton
import io.github.cottonmc.cotton.gui.widget.WLabel
import kotlinx.serialization.json.JsonElement
import net.minecraft.text.Text
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: ManagedConfig.Option<Unit>, guiAppender: GuiAppender) {
guiAppender.appendLabeledRow(
Text.translatable("firmament.config.${config.name}.${opt.propertyName}"),
WButton(Text.translatable("firmament.config.${config.name}.${opt.propertyName}")).apply {
setOnClick {
runnable()
}
},
)
}
}

View File

@@ -0,0 +1,44 @@
package moe.nea.firmament.gui.config
import io.github.cottonmc.cotton.gui.widget.WGridPanel
import io.github.cottonmc.cotton.gui.widget.WLabel
import io.github.cottonmc.cotton.gui.widget.WWidget
import io.github.cottonmc.cotton.gui.widget.data.VerticalAlignment
import net.minecraft.text.Text
class GuiAppender(val width: Int) {
private var row = 0
internal val panel = WGridPanel().also { it.setGaps(4, 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 appendLabeledRow(label: Text, right: WWidget) {
appendSplitRow(
WLabel(label).setVerticalAlignment(VerticalAlignment.CENTER),
right
)
}
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)
}
}

View File

@@ -0,0 +1,153 @@
package moe.nea.firmament.gui.config
import io.github.cottonmc.cotton.gui.client.CottonClientScreen
import io.github.cottonmc.cotton.gui.client.LightweightGuiDescription
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 kotlin.io.path.createDirectories
import kotlin.io.path.readText
import kotlin.io.path.writeText
import kotlin.properties.ReadWriteProperty
import kotlin.reflect.KProperty
import net.minecraft.client.gui.screen.Screen
import net.minecraft.text.Text
import moe.nea.firmament.Firmament
import moe.nea.firmament.util.MC
import moe.nea.firmament.util.ScreenUtil.setScreenLater
abstract class ManagedConfig(val name: String) {
interface OptionHandler<T : Any> {
fun toJson(element: T): JsonElement?
fun fromJson(element: JsonElement): T
fun emitGuiElements(opt: Option<T>, guiAppender: GuiAppender)
}
inner class Option<T : Any> internal constructor(
val config: ManagedConfig,
val propertyName: String,
val default: () -> T,
val handler: OptionHandler<T>
) : ReadWriteProperty<Any?, T> {
val rawLabelText = "firmament.config.${config.name}.${propertyName}"
val labelText = Text.translatable(rawLabelText)
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 setValue(thisRef: Any?, property: KProperty<*>, value: T) {
this.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) {
Firmament.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 = 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, Option<*>>()
val sortedOptions = mutableListOf<Option<*>>()
private var latestGuiAppender: GuiAppender? = null
protected fun <T : Any> option(propertyName: String, default: () -> T, handler: OptionHandler<T>): Option<T> {
if (propertyName in allOptions) error("Cannot register the same name twice")
return Option(this, propertyName, default, handler).also {
allOptions[propertyName] = it
sortedOptions.add(it)
}
}
protected fun toggle(propertyName: String, default: () -> Boolean): Option<Boolean> {
return option(propertyName, default, BooleanHandler(this))
}
protected fun button(propertyName: String, runnable: () -> Unit): Option<Unit> {
return option(propertyName, { }, ClickHandler(this, runnable))
}
protected fun string(propertyName: String, default: () -> String): Option<String> {
return option(propertyName, default, StringHandler(this))
}
fun reloadGui() {
latestGuiAppender?.reloadables?.forEach {it() }
}
fun showConfigEditor(parent: Screen? = null) {
val lwgd = LightweightGuiDescription()
val guiapp = GuiAppender(20)
latestGuiAppender = guiapp
guiapp.panel.insets = Insets.ROOT_PANEL
sortedOptions.forEach { it.appendToGui(guiapp) }
guiapp.reloadables.forEach { it() }
lwgd.setRootPanel(guiapp.panel)
setScreenLater(object : CottonClientScreen(lwgd) {
override fun close() {
latestGuiAppender = null
MC.screen = parent
}
})
}
}

View File

@@ -0,0 +1,32 @@
package moe.nea.firmament.gui.config
import io.github.cottonmc.cotton.gui.widget.WLabel
import io.github.cottonmc.cotton.gui.widget.WTextField
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.toString()
}
override fun emitGuiElements(opt: ManagedConfig.Option<String>, guiAppender: GuiAppender) {
guiAppender.appendLabeledRow(
opt.labelText,
WTextField(opt.labelText).apply {
suggestion = Text.translatableWithFallback(opt.rawLabelText + ".hint", "")
guiAppender.onReload { text = opt.value }
setChangedListener {
opt.value = it
config.save()
}
}
)
}
}

View File

@@ -1,36 +0,0 @@
package moe.nea.firmament.gui
import net.minecraft.text.Text
import moe.nea.firmament.repo.RepoManager
fun repoGui(): ConfigGui<RepoManager.Config> {
return ConfigGui(RepoManager) {
title(Text.translatable("firmament.gui.repo.title"))
toggle(Text.translatable("firmament.gui.repo.autoupdate"), RepoManager.Config::autoUpdate)
textfield(
Text.translatable("firmament.gui.repo.username"),
Text.translatable("firmament.gui.repo.hint.username"),
RepoManager.Config::user,
maxLength = 255
)
textfield(
Text.translatable("firmament.gui.repo.reponame"),
Text.translatable("firmament.gui.repo.hint.reponame"),
RepoManager.Config::repo
)
textfield(
Text.translatable("firmament.gui.repo.branch"),
Text.translatable("firmament.gui.repo.hint.branch"),
RepoManager.Config::branch
)
button(
Text.translatable("firmament.gui.repo.reset.label"),
Text.translatable("firmament.gui.repo.reset"),
) {
RepoManager.data.user = "NotEnoughUpdates"
RepoManager.data.repo = "NotEnoughUpdates-REPO"
RepoManager.data.branch = "dangerous"
reload()
}
}
}