feat: Add macro editing UI
This commit is contained in:
@@ -10,6 +10,7 @@ import moe.nea.firmament.events.WorldKeyboardEvent
|
||||
import moe.nea.firmament.keybindings.SavedKeyBinding
|
||||
import moe.nea.firmament.util.MC
|
||||
import moe.nea.firmament.util.TimeMark
|
||||
import moe.nea.firmament.util.tr
|
||||
|
||||
object ComboProcessor {
|
||||
|
||||
@@ -22,18 +23,13 @@ object ComboProcessor {
|
||||
var isInputting = false
|
||||
var lastInput = TimeMark.farPast()
|
||||
val breadCrumbs = mutableListOf<SavedKeyBinding>()
|
||||
// TODO: keep breadcrumbs
|
||||
|
||||
|
||||
init {
|
||||
val f = SavedKeyBinding(InputUtil.GLFW_KEY_F)
|
||||
val one = SavedKeyBinding(InputUtil.GLFW_KEY_1)
|
||||
val two = SavedKeyBinding(InputUtil.GLFW_KEY_2)
|
||||
setActions(
|
||||
listOf(
|
||||
ComboKeyAction(CommandAction("wardrobe"), listOf(f, one)),
|
||||
ComboKeyAction(CommandAction("equipment"), listOf(f, two)),
|
||||
)
|
||||
MacroData.DConfig.data.comboActions
|
||||
)
|
||||
}
|
||||
|
||||
@@ -68,10 +64,24 @@ object ComboProcessor {
|
||||
0F
|
||||
)
|
||||
val breadCrumbText = breadCrumbs.joinToString(" > ")
|
||||
event.context.drawText(MC.font, breadCrumbText, 0, 0, -1, true)
|
||||
event.context.drawText(
|
||||
MC.font,
|
||||
tr("firmament.combo.active", "Current Combo: ").append(breadCrumbText),
|
||||
0,
|
||||
0,
|
||||
-1,
|
||||
true
|
||||
)
|
||||
event.context.matrices.translate(0F, MC.font.fontHeight + 2F, 0F)
|
||||
for ((key, value) in activeTrie.nodes) {
|
||||
event.context.drawText(MC.font, Text.literal("$breadCrumbText > $key: ").append(value.label), 0, 0, -1, true)
|
||||
event.context.drawText(
|
||||
MC.font,
|
||||
Text.literal("$breadCrumbText > $key: ").append(value.label),
|
||||
0,
|
||||
0,
|
||||
-1,
|
||||
true
|
||||
)
|
||||
event.context.matrices.translate(0F, MC.font.fontHeight + 1F, 0F)
|
||||
}
|
||||
event.context.matrices.pop()
|
||||
|
||||
@@ -1,14 +1,19 @@
|
||||
package moe.nea.firmament.features.macros
|
||||
|
||||
import kotlinx.serialization.SerialName
|
||||
import kotlinx.serialization.Serializable
|
||||
import net.minecraft.text.Text
|
||||
import moe.nea.firmament.util.MC
|
||||
|
||||
interface HotkeyAction {
|
||||
@Serializable
|
||||
sealed interface HotkeyAction {
|
||||
// TODO: execute
|
||||
val label: Text
|
||||
fun execute()
|
||||
}
|
||||
|
||||
@Serializable
|
||||
@SerialName("command")
|
||||
data class CommandAction(val command: String) : HotkeyAction {
|
||||
override val label: Text
|
||||
get() = Text.literal("/$command")
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
package moe.nea.firmament.features.macros
|
||||
|
||||
import kotlinx.serialization.Serializable
|
||||
import net.minecraft.text.Text
|
||||
import moe.nea.firmament.keybindings.SavedKeyBinding
|
||||
import moe.nea.firmament.util.ErrorUtil
|
||||
|
||||
sealed interface KeyComboTrie {
|
||||
val label: Text
|
||||
@@ -13,28 +15,37 @@ sealed interface KeyComboTrie {
|
||||
val root = Branch(mutableMapOf())
|
||||
for (combo in combos) {
|
||||
var p = root
|
||||
require(combo.keys.isNotEmpty())
|
||||
if (combo.keys.isEmpty()) {
|
||||
ErrorUtil.softUserError("Key Combo for ${combo.action.label.string} is empty")
|
||||
continue
|
||||
}
|
||||
for ((index, key) in combo.keys.withIndex()) {
|
||||
val m = (p.nodes as MutableMap)
|
||||
if (index == combo.keys.lastIndex) {
|
||||
if (key in m)
|
||||
error("Overlapping actions found for ${combo.keys} (another action ${m[key]} already exists).")
|
||||
if (key in m) {
|
||||
ErrorUtil.softUserError("Overlapping actions found for ${combo.keys.joinToString(" > ")} (another action ${m[key]} already exists).")
|
||||
break
|
||||
}
|
||||
|
||||
m[key] = Leaf(combo.action)
|
||||
} else {
|
||||
val c = m.getOrPut(key) { Branch(mutableMapOf()) }
|
||||
if (c !is Branch)
|
||||
error("Overlapping actions found for ${combo.keys} (final node exists at index $index) through another action already")
|
||||
if (c !is Branch) {
|
||||
ErrorUtil.softUserError("Overlapping actions found for ${combo.keys} (final node exists at index $index) through another action already")
|
||||
break
|
||||
} else {
|
||||
p = c
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return root
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Serializable
|
||||
data class ComboKeyAction(
|
||||
val action: HotkeyAction,
|
||||
val keys: List<SavedKeyBinding>,
|
||||
|
||||
11
src/main/kotlin/features/macros/MacroData.kt
Normal file
11
src/main/kotlin/features/macros/MacroData.kt
Normal file
@@ -0,0 +1,11 @@
|
||||
package moe.nea.firmament.features.macros
|
||||
|
||||
import kotlinx.serialization.Serializable
|
||||
import moe.nea.firmament.util.data.DataHolder
|
||||
|
||||
@Serializable
|
||||
data class MacroData(
|
||||
var comboActions: List<ComboKeyAction> = listOf(),
|
||||
){
|
||||
object DConfig : DataHolder<MacroData>(kotlinx.serialization.serializer(), "macros", ::MacroData)
|
||||
}
|
||||
161
src/main/kotlin/features/macros/MacroUI.kt
Normal file
161
src/main/kotlin/features/macros/MacroUI.kt
Normal file
@@ -0,0 +1,161 @@
|
||||
package moe.nea.firmament.features.macros
|
||||
|
||||
import io.github.notenoughupdates.moulconfig.gui.CloseEventListener
|
||||
import io.github.notenoughupdates.moulconfig.observer.ObservableList
|
||||
import io.github.notenoughupdates.moulconfig.xml.Bind
|
||||
import moe.nea.firmament.annotations.Subscribe
|
||||
import moe.nea.firmament.commands.thenExecute
|
||||
import moe.nea.firmament.events.CommandEvent
|
||||
import moe.nea.firmament.gui.config.AllConfigsGui.toObservableList
|
||||
import moe.nea.firmament.gui.config.KeyBindingStateManager
|
||||
import moe.nea.firmament.keybindings.SavedKeyBinding
|
||||
import moe.nea.firmament.util.MC
|
||||
import moe.nea.firmament.util.MoulConfigUtils
|
||||
import moe.nea.firmament.util.ScreenUtil
|
||||
|
||||
class MacroUI {
|
||||
|
||||
|
||||
companion object {
|
||||
@Subscribe
|
||||
fun onCommands(event: CommandEvent.SubCommand) {
|
||||
// TODO: add button in config
|
||||
event.subcommand("macros") {
|
||||
thenExecute {
|
||||
ScreenUtil.setScreenLater(MoulConfigUtils.loadScreen("config/macros/index", MacroUI(), null))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@field:Bind("combos")
|
||||
val combos = Combos()
|
||||
|
||||
class Combos {
|
||||
@field:Bind("actions")
|
||||
val actions: ObservableList<ActionEditor> = ObservableList(
|
||||
MacroData.DConfig.data.comboActions.mapTo(mutableListOf()) {
|
||||
ActionEditor(it, this)
|
||||
}
|
||||
)
|
||||
|
||||
var dontSave = false
|
||||
|
||||
@Bind
|
||||
fun beforeClose(): CloseEventListener.CloseAction {
|
||||
if (!dontSave)
|
||||
save()
|
||||
return CloseEventListener.CloseAction.NO_OBJECTIONS_TO_CLOSE
|
||||
}
|
||||
|
||||
@Bind
|
||||
fun addCommand() {
|
||||
actions.add(
|
||||
ActionEditor(
|
||||
ComboKeyAction(
|
||||
CommandAction("ac Hello from a Firmament Hotkey"),
|
||||
listOf()
|
||||
),
|
||||
this
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
@Bind
|
||||
fun discard() {
|
||||
dontSave = true
|
||||
MC.screen?.close()
|
||||
}
|
||||
|
||||
@Bind
|
||||
fun saveAndClose() {
|
||||
save()
|
||||
MC.screen?.close()
|
||||
}
|
||||
|
||||
@Bind
|
||||
fun save() {
|
||||
MacroData.DConfig.data.comboActions = actions.map { it.asSaveable() }
|
||||
MacroData.DConfig.markDirty()
|
||||
ComboProcessor.setActions(MacroData.DConfig.data.comboActions) // TODO: automatically reload those from the config on startup
|
||||
}
|
||||
}
|
||||
|
||||
class KeyBindingEditor(var binding: SavedKeyBinding, val parent: ActionEditor) {
|
||||
val sm = KeyBindingStateManager(
|
||||
{ binding },
|
||||
{ binding = it },
|
||||
::blur,
|
||||
::requestFocus
|
||||
)
|
||||
|
||||
@field:Bind
|
||||
val button = sm.createButton()
|
||||
|
||||
init {
|
||||
sm.updateLabel()
|
||||
}
|
||||
|
||||
fun blur() {
|
||||
button.blur()
|
||||
}
|
||||
|
||||
@Bind
|
||||
fun delete() {
|
||||
parent.combo.removeIf { it === this }
|
||||
parent.combo.update()
|
||||
}
|
||||
|
||||
fun requestFocus() {
|
||||
button.requestFocus()
|
||||
}
|
||||
}
|
||||
|
||||
class ActionEditor(val action: ComboKeyAction, val parent: MacroUI.Combos) {
|
||||
fun asSaveable(): ComboKeyAction {
|
||||
return ComboKeyAction(
|
||||
CommandAction(command),
|
||||
combo.map { it.binding }
|
||||
)
|
||||
}
|
||||
|
||||
@field:Bind("command")
|
||||
var command: String = (action.action as CommandAction).command
|
||||
|
||||
@field:Bind("combo")
|
||||
val combo = action.keys.map { KeyBindingEditor(it, this) }.toObservableList()
|
||||
|
||||
@Bind
|
||||
fun formattedCombo() =
|
||||
combo.joinToString(" > ") { it.binding.toString() }
|
||||
|
||||
@Bind
|
||||
fun addStep() {
|
||||
combo.add(KeyBindingEditor(SavedKeyBinding.unbound(), this))
|
||||
}
|
||||
|
||||
@Bind
|
||||
fun back() {
|
||||
MC.screen?.close()
|
||||
}
|
||||
|
||||
@Bind
|
||||
fun delete() {
|
||||
parent.actions.removeIf { it === this }
|
||||
parent.actions.update()
|
||||
}
|
||||
@Bind
|
||||
fun edit() {
|
||||
MC.screen = MoulConfigUtils.loadScreen("config/macros/editor", this, MC.screen)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun <T> ObservableList<T>.setAll(ts: Collection<T>) {
|
||||
val observer = this.observer
|
||||
this.clear()
|
||||
this.addAll(ts)
|
||||
this.observer = observer
|
||||
this.update()
|
||||
}
|
||||
@@ -40,34 +40,7 @@ class KeyBindingHandler(val name: String, val managedConfig: ManagedConfig) :
|
||||
{ button.blur() },
|
||||
{ button.requestFocus() }
|
||||
)
|
||||
button = object : FirmButtonComponent(
|
||||
TextComponent(
|
||||
IMinecraft.instance.defaultFontRenderer,
|
||||
{ sm.label.string },
|
||||
130,
|
||||
TextComponent.TextAlignment.LEFT,
|
||||
false,
|
||||
false
|
||||
), action = {
|
||||
sm.onClick()
|
||||
}) {
|
||||
override fun keyboardEvent(event: KeyboardEvent, context: GuiImmediateContext): Boolean {
|
||||
if (event is KeyboardEvent.KeyPressed) {
|
||||
return sm.keyboardEvent(event.keycode, event.pressed)
|
||||
}
|
||||
return super.keyboardEvent(event, context)
|
||||
}
|
||||
|
||||
override fun getBackground(context: GuiImmediateContext): NinePatch<MyResourceLocation> {
|
||||
if (sm.editing) return activeBg
|
||||
return super.getBackground(context)
|
||||
}
|
||||
|
||||
|
||||
override fun onLostFocus() {
|
||||
sm.onLostFocus()
|
||||
}
|
||||
}
|
||||
button = sm.createButton()
|
||||
sm.updateLabel()
|
||||
return button
|
||||
}
|
||||
|
||||
@@ -1,8 +1,15 @@
|
||||
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 net.minecraft.text.Text
|
||||
import net.minecraft.util.Formatting
|
||||
import moe.nea.firmament.gui.FirmButtonComponent
|
||||
import moe.nea.firmament.keybindings.SavedKeyBinding
|
||||
|
||||
class KeyBindingStateManager(
|
||||
@@ -51,9 +58,11 @@ class KeyBindingStateManager(
|
||||
) {
|
||||
lastPressed = ch
|
||||
} else {
|
||||
setValue(SavedKeyBinding(
|
||||
setValue(
|
||||
SavedKeyBinding(
|
||||
ch, modifiers
|
||||
))
|
||||
)
|
||||
)
|
||||
editing = false
|
||||
blur()
|
||||
lastPressed = 0
|
||||
@@ -104,5 +113,34 @@ class KeyBindingStateManager(
|
||||
label = stroke
|
||||
}
|
||||
|
||||
fun createButton(): FirmButtonComponent {
|
||||
return object : FirmButtonComponent(
|
||||
TextComponent(
|
||||
IMinecraft.instance.defaultFontRenderer,
|
||||
{ this@KeyBindingStateManager.label.string },
|
||||
130,
|
||||
TextComponent.TextAlignment.LEFT,
|
||||
false,
|
||||
false
|
||||
), action = {
|
||||
this@KeyBindingStateManager.onClick()
|
||||
}) {
|
||||
override fun keyboardEvent(event: KeyboardEvent, context: GuiImmediateContext): Boolean {
|
||||
if (event is KeyboardEvent.KeyPressed) {
|
||||
return this@KeyBindingStateManager.keyboardEvent(event.keycode, event.pressed)
|
||||
}
|
||||
return super.keyboardEvent(event, context)
|
||||
}
|
||||
|
||||
override fun getBackground(context: GuiImmediateContext): NinePatch<MyResourceLocation> {
|
||||
if (this@KeyBindingStateManager.editing) return activeBg
|
||||
return super.getBackground(context)
|
||||
}
|
||||
|
||||
|
||||
override fun onLostFocus() {
|
||||
this@KeyBindingStateManager.onLostFocus()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -57,6 +57,8 @@ data class SavedKeyBinding(
|
||||
fun isShiftDown() = InputUtil.isKeyPressed(h, GLFW.GLFW_KEY_LEFT_SHIFT)
|
||||
|| InputUtil.isKeyPressed(h, GLFW.GLFW_KEY_RIGHT_SHIFT)
|
||||
|
||||
fun unbound(): SavedKeyBinding =
|
||||
SavedKeyBinding(GLFW.GLFW_KEY_UNKNOWN)
|
||||
}
|
||||
|
||||
fun isPressed(atLeast: Boolean = false): Boolean {
|
||||
|
||||
@@ -75,4 +75,7 @@ object ErrorUtil {
|
||||
return nullable
|
||||
}
|
||||
|
||||
fun softUserError(string: String) {
|
||||
MC.sendChat(tr("frimanet.usererror", "Firmament encountered a user caused error: $string"))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package moe.nea.firmament.util
|
||||
|
||||
import io.github.moulberry.repo.data.Coordinate
|
||||
import io.github.notenoughupdates.moulconfig.gui.GuiComponentWrapper
|
||||
import java.util.concurrent.ConcurrentLinkedQueue
|
||||
import kotlin.jvm.optionals.getOrNull
|
||||
import net.minecraft.client.MinecraftClient
|
||||
@@ -126,6 +127,8 @@ object MC {
|
||||
}
|
||||
private set
|
||||
|
||||
val currentMoulConfigContext
|
||||
get() = (screen as? GuiComponentWrapper)?.context
|
||||
|
||||
fun openUrl(uri: String) {
|
||||
Util.getOperatingSystem().open(uri)
|
||||
|
||||
@@ -2,6 +2,7 @@ package moe.nea.firmament.util
|
||||
|
||||
object TestUtil {
|
||||
inline fun <T> unlessTesting(block: () -> T): T? = if (isInTest) null else block()
|
||||
@JvmField
|
||||
val isInTest =
|
||||
Thread.currentThread().stackTrace.any {
|
||||
it.className.startsWith("org.junit.") || it.className.startsWith("io.kotest.")
|
||||
|
||||
@@ -0,0 +1,56 @@
|
||||
<?xml version="1.0" encoding="UTF-8" ?>
|
||||
<Root xmlns="http://notenoughupdates.org/moulconfig" xmlns:firm="http://firmament.nea.moe/moulconfig"
|
||||
>
|
||||
<Panel background="TRANSPARENT" insets="10">
|
||||
<Column>
|
||||
<Meta beforeClose="@beforeClose"/>
|
||||
<ScrollPanel width="380" height="300">
|
||||
<Align horizontal="CENTER">
|
||||
<Array data="@actions">
|
||||
<!-- evenBackground="#8B8B8B" oddBackground="#C6C6C6" -->
|
||||
<Panel background="TRANSPARENT" insets="3">
|
||||
<Panel background="VANILLA" insets="6">
|
||||
<Column>
|
||||
<Row>
|
||||
<Text text="@command" width="280"/>
|
||||
</Row>
|
||||
<Row>
|
||||
<Text text="@formattedCombo" width="250"/>
|
||||
<Align horizontal="RIGHT">
|
||||
<Row>
|
||||
<firm:Button onClick="@edit">
|
||||
<Text text="Edit"/>
|
||||
</firm:Button>
|
||||
<Spacer width="12"/>
|
||||
<firm:Button onClick="@delete">
|
||||
<Text text="Delete"/>
|
||||
</firm:Button>
|
||||
</Row>
|
||||
</Align>
|
||||
</Row>
|
||||
</Column>
|
||||
</Panel>
|
||||
|
||||
</Panel>
|
||||
</Array>
|
||||
</Align>
|
||||
</ScrollPanel>
|
||||
<Align horizontal="RIGHT">
|
||||
<Row>
|
||||
<firm:Button onClick="@discard">
|
||||
<Text text="Discard Changes"/>
|
||||
</firm:Button>
|
||||
<firm:Button onClick="@saveAndClose">
|
||||
<Text text="Save & Close"/>
|
||||
</firm:Button>
|
||||
<firm:Button onClick="@save">
|
||||
<Text text="Save"/>
|
||||
</firm:Button>
|
||||
<firm:Button onClick="@addCommand">
|
||||
<Text text="Add Combo Command"/>
|
||||
</firm:Button>
|
||||
</Row>
|
||||
</Align>
|
||||
</Column>
|
||||
</Panel>
|
||||
</Root>
|
||||
@@ -0,0 +1,42 @@
|
||||
<?xml version="1.0" encoding="UTF-8" ?>
|
||||
<Root xmlns="http://notenoughupdates.org/moulconfig" xmlns:firm="http://firmament.nea.moe/moulconfig"
|
||||
>
|
||||
<Center>
|
||||
<Panel background="VANILLA" insets="10">
|
||||
<Column>
|
||||
<Row>
|
||||
<firm:Button onClick="@back">
|
||||
<Text text="←"/>
|
||||
</firm:Button>
|
||||
<Text text="Editing command macro"/>
|
||||
</Row>
|
||||
<Row>
|
||||
<Text text="Command: /"/>
|
||||
<Align horizontal="RIGHT">
|
||||
<TextField value="@command" width="200"/>
|
||||
</Align>
|
||||
</Row>
|
||||
<Row>
|
||||
<Text text="Key Combo:"/>
|
||||
<Align horizontal="RIGHT">
|
||||
<firm:Button onClick="@addStep">
|
||||
<Text text="+"/>
|
||||
</firm:Button>
|
||||
</Align>
|
||||
</Row>
|
||||
<Array data="@combo">
|
||||
<Row>
|
||||
<firm:Fixed width="160">
|
||||
<Indirect value="@button"/>
|
||||
</firm:Fixed>
|
||||
<Align horizontal="RIGHT">
|
||||
<firm:Button onClick="@delete">
|
||||
<Text text="Delete"/>
|
||||
</firm:Button>
|
||||
</Align>
|
||||
</Row>
|
||||
</Array>
|
||||
</Column>
|
||||
</Panel>
|
||||
</Center>
|
||||
</Root>
|
||||
@@ -0,0 +1,16 @@
|
||||
<?xml version="1.0" encoding="UTF-8" ?>
|
||||
<Root xmlns="http://notenoughupdates.org/moulconfig"
|
||||
xmlns:firm="http://firmament.nea.moe/moulconfig">
|
||||
<Center>
|
||||
<Tabs>
|
||||
<Tab>
|
||||
<Tab.Header>
|
||||
<Text text="Combo Macros"/>
|
||||
</Tab.Header>
|
||||
<Tab.Body>
|
||||
<Fragment value="firmament:gui/config/macros/combos.xml" bind="@combos"/>
|
||||
</Tab.Body>
|
||||
</Tab>
|
||||
</Tabs>
|
||||
</Center>
|
||||
</Root>
|
||||
Reference in New Issue
Block a user