aboutsummaryrefslogtreecommitdiff
path: root/src/main/kotlin/gui
diff options
context:
space:
mode:
Diffstat (limited to 'src/main/kotlin/gui')
-rw-r--r--src/main/kotlin/gui/BarComponent.kt125
-rw-r--r--src/main/kotlin/gui/FirmButtonComponent.kt81
-rw-r--r--src/main/kotlin/gui/FirmHoverComponent.kt59
-rw-r--r--src/main/kotlin/gui/FixedComponent.kt38
-rw-r--r--src/main/kotlin/gui/ImageComponent.kt33
-rw-r--r--src/main/kotlin/gui/TickComponent.kt18
-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
-rw-r--r--src/main/kotlin/gui/entity/EntityModifier.kt9
-rw-r--r--src/main/kotlin/gui/entity/EntityRenderer.kt197
-rw-r--r--src/main/kotlin/gui/entity/EntityWidget.kt35
-rw-r--r--src/main/kotlin/gui/entity/FakeWorld.kt488
-rw-r--r--src/main/kotlin/gui/entity/GuiPlayer.kt54
-rw-r--r--src/main/kotlin/gui/entity/ModifyAge.kt25
-rw-r--r--src/main/kotlin/gui/entity/ModifyCharged.kt14
-rw-r--r--src/main/kotlin/gui/entity/ModifyEquipment.kt55
-rw-r--r--src/main/kotlin/gui/entity/ModifyHorse.kt61
-rw-r--r--src/main/kotlin/gui/entity/ModifyInvisible.kt13
-rw-r--r--src/main/kotlin/gui/entity/ModifyName.kt14
-rw-r--r--src/main/kotlin/gui/entity/ModifyPlayerSkin.kt47
-rw-r--r--src/main/kotlin/gui/entity/ModifyRiding.kt15
-rw-r--r--src/main/kotlin/gui/entity/ModifyWither.kt20
-rw-r--r--src/main/kotlin/gui/hud/MoulConfigHud.kt66
34 files changed, 2249 insertions, 0 deletions
diff --git a/src/main/kotlin/gui/BarComponent.kt b/src/main/kotlin/gui/BarComponent.kt
new file mode 100644
index 0000000..8ef0753
--- /dev/null
+++ b/src/main/kotlin/gui/BarComponent.kt
@@ -0,0 +1,125 @@
+
+package moe.nea.firmament.gui
+
+import com.mojang.blaze3d.systems.RenderSystem
+import io.github.notenoughupdates.moulconfig.common.MyResourceLocation
+import io.github.notenoughupdates.moulconfig.common.RenderContext
+import io.github.notenoughupdates.moulconfig.gui.GuiComponent
+import io.github.notenoughupdates.moulconfig.gui.GuiImmediateContext
+import io.github.notenoughupdates.moulconfig.observer.GetSetter
+import io.github.notenoughupdates.moulconfig.platform.ModernRenderContext
+import me.shedaniel.math.Color
+import net.minecraft.client.gui.DrawContext
+import net.minecraft.util.Identifier
+import moe.nea.firmament.Firmament
+
+class BarComponent(
+ val progress: GetSetter<Double>, val total: GetSetter<Double>,
+ val fillColor: Color,
+ val emptyColor: Color,
+) : GuiComponent() {
+ override fun getWidth(): Int {
+ return 80
+ }
+
+ override fun getHeight(): Int {
+ return 8
+ }
+
+ data class Texture(
+ val identifier: Identifier,
+ val u1: Float, val v1: Float,
+ val u2: Float, val v2: Float,
+ ) {
+ fun draw(context: DrawContext, x: Int, y: Int, width: Int, height: Int, color: Color) {
+ context.drawTexturedQuad(
+ identifier,
+ x, y, x + width, x + height, 0,
+ u1, u2, v1, v2,
+ color.red / 255F,
+ color.green / 255F,
+ color.blue / 255F,
+ color.alpha / 255F,
+ )
+ }
+ }
+
+ companion object {
+ val resource = Firmament.identifier("textures/gui/bar.png")
+ val left = Texture(resource, 0 / 64F, 0 / 64F, 4 / 64F, 8 / 64F)
+ val middle = Texture(resource, 4 / 64F, 0 / 64F, 8 / 64F, 8 / 64F)
+ val right = Texture(resource, 8 / 64F, 0 / 64F, 12 / 64F, 8 / 64F)
+ val segmentOverlay = Texture(resource, 12 / 64F, 0 / 64F, 15 / 64F, 8 / 64F)
+ }
+
+ private fun drawSection(
+ context: DrawContext,
+ texture: Texture,
+ x: Int,
+ y: Int,
+ width: Int,
+ sectionStart: Double,
+ sectionEnd: Double
+ ) {
+ if (sectionEnd < progress.get() && width == 4) {
+ texture.draw(context, x, y, 4, 8, fillColor)
+ return
+ }
+ if (sectionStart > progress.get() && width == 4) {
+ texture.draw(context, x, y, 4, 8, emptyColor)
+ return
+ }
+ val increasePerPixel = (sectionEnd - sectionStart) / width
+ var valueAtPixel = sectionStart
+ for (i in (0 until width)) {
+ val newTex =
+ Texture(texture.identifier, texture.u1 + i / 64F, texture.v1, texture.u1 + (i + 1) / 64F, texture.v2)
+ newTex.draw(
+ context, x + i, y, 1, 8,
+ if (valueAtPixel < progress.get()) fillColor else emptyColor
+ )
+ valueAtPixel += increasePerPixel
+ }
+ }
+
+ override fun render(context: GuiImmediateContext) {
+ val renderContext = (context.renderContext as ModernRenderContext).drawContext
+ var i = 0
+ val x = 0
+ val y = 0
+ while (i < context.width - 4) {
+ drawSection(
+ renderContext,
+ if (i == 0) left else middle,
+ x + i, y,
+ (context.width - (i + 4)).coerceAtMost(4),
+ i * total.get() / context.width, (i + 4) * total.get() / context.width
+ )
+ i += 4
+ }
+ drawSection(
+ renderContext,
+ right,
+ x + context.width - 4,
+ y,
+ 4,
+ (context.width - 4) * total.get() / context.width,
+ total.get()
+ )
+ RenderSystem.setShaderColor(1F, 1F, 1F, 1F)
+
+ }
+
+}
+
+fun Identifier.toMoulConfig(): MyResourceLocation {
+ return MyResourceLocation(this.namespace, this.path)
+}
+
+fun RenderContext.color(color: Color) {
+ color(color.red, color.green, color.blue, color.alpha)
+}
+
+fun RenderContext.color(red: Int, green: Int, blue: Int, alpha: Int) {
+ color(red / 255f, green / 255f, blue / 255f, alpha / 255f)
+}
diff --git a/src/main/kotlin/gui/FirmButtonComponent.kt b/src/main/kotlin/gui/FirmButtonComponent.kt
new file mode 100644
index 0000000..82e5b05
--- /dev/null
+++ b/src/main/kotlin/gui/FirmButtonComponent.kt
@@ -0,0 +1,81 @@
+
+package moe.nea.firmament.gui
+
+import io.github.notenoughupdates.moulconfig.common.MyResourceLocation
+import io.github.notenoughupdates.moulconfig.deps.libninepatch.NinePatch
+import io.github.notenoughupdates.moulconfig.gui.GuiComponent
+import io.github.notenoughupdates.moulconfig.gui.GuiImmediateContext
+import io.github.notenoughupdates.moulconfig.gui.MouseEvent
+import io.github.notenoughupdates.moulconfig.gui.component.PanelComponent
+import io.github.notenoughupdates.moulconfig.observer.GetSetter
+
+
+open class FirmButtonComponent(
+ child: GuiComponent,
+ val isEnabled: GetSetter<Boolean> = GetSetter.constant(true),
+ val noBackground: Boolean = false,
+ val action: Runnable,
+) : PanelComponent(child, if (noBackground) 0 else 2, DefaultBackgroundRenderer.TRANSPARENT) {
+
+ /* TODO: make use of vanillas built in nine slicer */
+ val hoveredBg =
+ NinePatch.builder(MyResourceLocation("minecraft", "textures/gui/sprites/widget/button_highlighted.png"))
+ .cornerSize(5)
+ .cornerUv(5 / 200F, 5 / 20F)
+ .mode(NinePatch.Mode.STRETCHING)
+ .build()
+ val unhoveredBg = NinePatch.builder(MyResourceLocation("minecraft", "textures/gui/sprites/widget/button.png"))
+ .cornerSize(5)
+ .cornerUv(5 / 200F, 5 / 20F)
+ .mode(NinePatch.Mode.STRETCHING)
+ .build()
+ val disabledBg =
+ NinePatch.builder(MyResourceLocation("minecraft", "textures/gui/sprites/widget/button_disabled.png"))
+ .cornerSize(5)
+ .cornerUv(5 / 200F, 5 / 20F)
+ .mode(NinePatch.Mode.STRETCHING)
+ .build()
+ val activeBg = NinePatch.builder(MyResourceLocation("firmament", "textures/gui/sprites/widget/button_active.png"))
+ .cornerSize(5)
+ .cornerUv(5 / 200F, 5 / 20F)
+ .mode(NinePatch.Mode.STRETCHING)
+ .build()
+ var isClicking = false
+ override fun mouseEvent(mouseEvent: MouseEvent, context: GuiImmediateContext): Boolean {
+ if (!isEnabled.get()) return false
+ if (isClicking) {
+ if (mouseEvent is MouseEvent.Click && !mouseEvent.mouseState && mouseEvent.mouseButton == 0) {
+ isClicking = false
+ if (context.isHovered) {
+ action.run()
+ }
+ return true
+ }
+ }
+ if (!context.isHovered) return false
+ if (mouseEvent !is MouseEvent.Click) return false
+ if (mouseEvent.mouseState && mouseEvent.mouseButton == 0) {
+ requestFocus()
+ isClicking = true
+ return true
+ }
+ return false
+ }
+
+ open fun getBackground(context: GuiImmediateContext): NinePatch<MyResourceLocation> =
+ if (!isEnabled.get()) disabledBg
+ else if (context.isHovered || isClicking) hoveredBg
+ else unhoveredBg
+
+ override fun render(context: GuiImmediateContext) {
+ context.renderContext.pushMatrix()
+ if (!noBackground)
+ context.renderContext.drawNinePatch(
+ getBackground(context),
+ 0f, 0f, context.width, context.height
+ )
+ context.renderContext.translate(insets.toFloat(), insets.toFloat(), 0f)
+ element.render(getChildContext(context))
+ context.renderContext.popMatrix()
+ }
+}
diff --git a/src/main/kotlin/gui/FirmHoverComponent.kt b/src/main/kotlin/gui/FirmHoverComponent.kt
new file mode 100644
index 0000000..b1792ce
--- /dev/null
+++ b/src/main/kotlin/gui/FirmHoverComponent.kt
@@ -0,0 +1,59 @@
+package moe.nea.firmament.gui
+
+import io.github.notenoughupdates.moulconfig.gui.GuiComponent
+import io.github.notenoughupdates.moulconfig.gui.GuiImmediateContext
+import io.github.notenoughupdates.moulconfig.gui.KeyboardEvent
+import io.github.notenoughupdates.moulconfig.gui.MouseEvent
+import java.util.function.BiFunction
+import java.util.function.Supplier
+import kotlin.time.Duration
+import moe.nea.firmament.util.TimeMark
+
+class FirmHoverComponent(
+ val child: GuiComponent,
+ val hoverLines: Supplier<List<String>>,
+ val hoverDelay: Duration,
+) : GuiComponent() {
+ override fun getWidth(): Int {
+ return child.width
+ }
+
+ override fun getHeight(): Int {
+ return child.height
+ }
+
+ override fun <T : Any?> foldChildren(
+ initial: T,
+ visitor: BiFunction<GuiComponent, T, T>
+ ): T {
+ return visitor.apply(child, initial)
+ }
+
+ override fun render(context: GuiImmediateContext) {
+ if (context.isHovered && (permaHover || lastMouseMove.passedTime() > hoverDelay)) {
+ context.renderContext.scheduleDrawTooltip(hoverLines.get())
+ permaHover = true
+ } else {
+ permaHover = false
+ }
+ if (!context.isHovered) {
+ lastMouseMove = TimeMark.now()
+ }
+ child.render(context)
+
+ }
+
+ var permaHover = false
+ var lastMouseMove = TimeMark.farPast()
+
+ override fun mouseEvent(mouseEvent: MouseEvent, context: GuiImmediateContext): Boolean {
+ if (mouseEvent is MouseEvent.Move) {
+ lastMouseMove = TimeMark.now()
+ }
+ return child.mouseEvent(mouseEvent, context)
+ }
+
+ override fun keyboardEvent(event: KeyboardEvent, context: GuiImmediateContext): Boolean {
+ return child.keyboardEvent(event, context)
+ }
+}
diff --git a/src/main/kotlin/gui/FixedComponent.kt b/src/main/kotlin/gui/FixedComponent.kt
new file mode 100644
index 0000000..ae1da2d
--- /dev/null
+++ b/src/main/kotlin/gui/FixedComponent.kt
@@ -0,0 +1,38 @@
+
+package moe.nea.firmament.gui
+
+import io.github.notenoughupdates.moulconfig.gui.GuiComponent
+import io.github.notenoughupdates.moulconfig.gui.GuiImmediateContext
+import io.github.notenoughupdates.moulconfig.gui.KeyboardEvent
+import io.github.notenoughupdates.moulconfig.gui.MouseEvent
+import io.github.notenoughupdates.moulconfig.observer.GetSetter
+import java.util.function.BiFunction
+
+class FixedComponent(
+ val fixedWidth: GetSetter<Int>?,
+ val fixedHeight: GetSetter<Int>?,
+ val component: GuiComponent,
+) : GuiComponent() {
+ override fun getWidth(): Int = fixedWidth?.get() ?: component.width
+
+ override fun getHeight(): Int = fixedHeight?.get() ?: component.height
+
+ override fun <T : Any?> foldChildren(initial: T, visitor: BiFunction<GuiComponent, T, T>): T {
+ return visitor.apply(component, initial)
+ }
+
+ fun fixContext(context: GuiImmediateContext): GuiImmediateContext =
+ context.translated(0, 0, width, height)
+
+ override fun render(context: GuiImmediateContext) {
+ component.render(fixContext(context))
+ }
+
+ override fun mouseEvent(mouseEvent: MouseEvent, context: GuiImmediateContext): Boolean {
+ return component.mouseEvent(mouseEvent, fixContext(context))
+ }
+
+ override fun keyboardEvent(event: KeyboardEvent, context: GuiImmediateContext): Boolean {
+ return component.keyboardEvent(event, fixContext(context))
+ }
+}
diff --git a/src/main/kotlin/gui/ImageComponent.kt b/src/main/kotlin/gui/ImageComponent.kt
new file mode 100644
index 0000000..bba7dee
--- /dev/null
+++ b/src/main/kotlin/gui/ImageComponent.kt
@@ -0,0 +1,33 @@
+package moe.nea.firmament.gui
+
+import io.github.notenoughupdates.moulconfig.common.MyResourceLocation
+import io.github.notenoughupdates.moulconfig.gui.GuiComponent
+import io.github.notenoughupdates.moulconfig.gui.GuiImmediateContext
+import java.util.function.Supplier
+
+class ImageComponent(
+ private val width: Int,
+ private val height: Int,
+ val resourceLocation: Supplier<MyResourceLocation>,
+ val u1: Float,
+ val u2: Float,
+ val v1: Float,
+ val v2: Float,
+) : GuiComponent() {
+ override fun getWidth(): Int {
+ return width
+ }
+
+ override fun getHeight(): Int {
+ return height
+ }
+
+ override fun render(context: GuiImmediateContext) {
+ context.renderContext.bindTexture(resourceLocation.get())
+ context.renderContext.drawTexturedRect(
+ 0f, 0f,
+ context.width.toFloat(), context.height.toFloat(),
+ u1, v1, u2, v2
+ )
+ }
+}
diff --git a/src/main/kotlin/gui/TickComponent.kt b/src/main/kotlin/gui/TickComponent.kt
new file mode 100644
index 0000000..d1879b1
--- /dev/null
+++ b/src/main/kotlin/gui/TickComponent.kt
@@ -0,0 +1,18 @@
+package moe.nea.firmament.gui
+
+import io.github.notenoughupdates.moulconfig.gui.GuiComponent
+import io.github.notenoughupdates.moulconfig.gui.GuiImmediateContext
+
+class TickComponent(val onTick: Runnable) : GuiComponent() {
+ override fun getWidth(): Int {
+ return 0
+ }
+
+ override fun getHeight(): Int {
+ return 0
+ }
+
+ override fun render(context: GuiImmediateContext) {
+ onTick.run()
+ }
+}
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) {