Add better key binding support

This commit is contained in:
nea
2023-08-25 14:18:43 +02:00
parent a79452c254
commit 7842319416
10 changed files with 262 additions and 26 deletions

View File

@@ -50,6 +50,7 @@ Priority 3:
- bits? - bits?
- collection? - collection?
- pretty graphs! - pretty graphs!
- Maintain scroll percentage in vanilla guis when reinitializing
- and much more that i will add as i go along - and much more that i will add as i go along

View File

@@ -0,0 +1,53 @@
package moe.nea.firmament.mixins;
import moe.nea.firmament.gui.config.ManagedConfig;
import moe.nea.firmament.keybindings.FirmamentKeyBindings;
import net.minecraft.client.MinecraftClient;
import net.minecraft.client.gui.screen.option.ControlsListWidget;
import net.minecraft.client.gui.widget.ButtonWidget;
import net.minecraft.client.option.KeyBinding;
import net.minecraft.text.Text;
import org.spongepowered.asm.mixin.Final;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Mutable;
import org.spongepowered.asm.mixin.Shadow;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.ModifyArg;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
@Mixin(ControlsListWidget.KeyBindingEntry.class)
public class MixinKeybindsScreen {
@Mutable
@Shadow
@Final
private ButtonWidget editButton;
@Shadow
@Final
private KeyBinding binding;
@Shadow
@Final
private ButtonWidget resetButton;
@ModifyArg(method = "<init>", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/gui/widget/ButtonWidget;builder(Lnet/minecraft/text/Text;Lnet/minecraft/client/gui/widget/ButtonWidget$PressAction;)Lnet/minecraft/client/gui/widget/ButtonWidget$Builder;"))
public ButtonWidget.PressAction onInit(ButtonWidget.PressAction action) {
ManagedConfig config = FirmamentKeyBindings.INSTANCE.getKeyBindings().get(binding);
if (config == null) return action;
return button -> {
config.showConfigEditor(MinecraftClient.getInstance().currentScreen);
};
}
@Inject(method = "update", at = @At("HEAD"), cancellable = true)
public void onUpdate(CallbackInfo ci) {
ManagedConfig config = FirmamentKeyBindings.INSTANCE.getKeyBindings().get(binding);
if (config == null) return;
resetButton.active = false;
editButton.setMessage(Text.translatable("firmament.keybinding.external"));
ci.cancel();
}
}

View File

@@ -12,7 +12,6 @@ import moe.nea.firmament.Firmament
import moe.nea.firmament.features.chat.ChatLinks import moe.nea.firmament.features.chat.ChatLinks
import moe.nea.firmament.features.debug.DebugView import moe.nea.firmament.features.debug.DebugView
import moe.nea.firmament.features.debug.DeveloperFeatures import moe.nea.firmament.features.debug.DeveloperFeatures
import moe.nea.firmament.features.fishing.FishingWarning
import moe.nea.firmament.features.fixes.Fixes import moe.nea.firmament.features.fixes.Fixes
import moe.nea.firmament.features.inventory.CraftingOverlay import moe.nea.firmament.features.inventory.CraftingOverlay
import moe.nea.firmament.features.inventory.SaveCursorPosition import moe.nea.firmament.features.inventory.SaveCursorPosition
@@ -54,6 +53,7 @@ object FeatureManager : DataHolder<FeatureManager.Config>(serializer(), "feature
loadFeature(DeveloperFeatures) loadFeature(DeveloperFeatures)
loadFeature(DebugView) loadFeature(DebugView)
} }
allFeatures.forEach { it.config }
hasAutoloaded = true hasAutoloaded = true
} }
} }

View File

@@ -8,17 +8,17 @@ package moe.nea.firmament.features.inventory
import kotlinx.serialization.Serializable import kotlinx.serialization.Serializable
import kotlinx.serialization.serializer import kotlinx.serialization.serializer
import net.minecraft.client.gui.DrawContext
import net.minecraft.entity.player.PlayerInventory
import moe.nea.firmament.events.HandledScreenKeyPressedEvent import moe.nea.firmament.events.HandledScreenKeyPressedEvent
import moe.nea.firmament.events.IsSlotProtectedEvent import moe.nea.firmament.events.IsSlotProtectedEvent
import moe.nea.firmament.events.SlotRenderEvents import moe.nea.firmament.events.SlotRenderEvents
import moe.nea.firmament.features.FirmamentFeature import moe.nea.firmament.features.FirmamentFeature
import moe.nea.firmament.keybindings.FirmamentKeyBindings import moe.nea.firmament.gui.config.ManagedConfig
import moe.nea.firmament.mixins.accessor.AccessorHandledScreen import moe.nea.firmament.mixins.accessor.AccessorHandledScreen
import moe.nea.firmament.util.CommonSoundEffects import moe.nea.firmament.util.CommonSoundEffects
import moe.nea.firmament.util.MC import moe.nea.firmament.util.MC
import moe.nea.firmament.util.data.ProfileSpecificDataHolder import moe.nea.firmament.util.data.ProfileSpecificDataHolder
import net.minecraft.entity.player.PlayerInventory
import org.lwjgl.glfw.GLFW
object SlotLocking : FirmamentFeature { object SlotLocking : FirmamentFeature {
override val identifier: String override val identifier: String
@@ -29,13 +29,19 @@ object SlotLocking : FirmamentFeature {
val lockedSlots: MutableSet<Int> = mutableSetOf(), val lockedSlots: MutableSet<Int> = mutableSetOf(),
) )
object TConfig : ManagedConfig(identifier) {
val lock by keyBinding("lock") { GLFW.GLFW_KEY_L }
}
override val config: TConfig
get() = TConfig
object DConfig : ProfileSpecificDataHolder<Data>(serializer(), "locked-slots", ::Data) object DConfig : ProfileSpecificDataHolder<Data>(serializer(), "locked-slots", ::Data)
val keyBinding by FirmamentKeyBindings::SLOT_LOCKING
val lockedSlots get() = DConfig.data?.lockedSlots val lockedSlots get() = DConfig.data?.lockedSlots
override fun onLoad() { override fun onLoad() {
HandledScreenKeyPressedEvent.subscribe { HandledScreenKeyPressedEvent.subscribe {
if (!it.matches(keyBinding)) return@subscribe if (!it.matches(TConfig.lock)) return@subscribe
val inventory = MC.handledScreen ?: return@subscribe val inventory = MC.handledScreen ?: return@subscribe
inventory as AccessorHandledScreen inventory as AccessorHandledScreen

View File

@@ -0,0 +1,113 @@
package moe.nea.firmament.gui.config
import io.github.cottonmc.cotton.gui.widget.WButton
import io.github.cottonmc.cotton.gui.widget.data.InputResult
import kotlinx.serialization.json.Json
import kotlinx.serialization.json.JsonElement
import kotlinx.serialization.json.decodeFromJsonElement
import kotlinx.serialization.json.encodeToJsonElement
import moe.nea.firmament.keybindings.FirmamentKeyBindings
import moe.nea.firmament.keybindings.SavedKeyBinding
import net.minecraft.client.util.InputUtil
import net.minecraft.text.Text
import net.minecraft.util.Formatting
import org.lwjgl.glfw.GLFW
class KeyBindingHandler(name: String, managedConfig: ManagedConfig) : ManagedConfig.OptionHandler<SavedKeyBinding> {
init {
FirmamentKeyBindings.registerKeyBinding(name, managedConfig)
}
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 updateButton: (() -> Unit)? = null
val button = object : WButton() {
override fun onKeyPressed(ch: Int, key: Int, modifiers: Int): InputResult {
if (!editing) {
return super.onKeyPressed(ch, key, modifiers)
}
if (ch == GLFW.GLFW_KEY_ESCAPE) {
lastPressedNonModifier = 0
editing = false
lastPressed = 0
updateButton!!()
return InputResult.PROCESSED
}
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
lastPressed = 0
lastPressedNonModifier = 0
}
updateButton!!()
return InputResult.PROCESSED
}
override fun onFocusLost() {
super.onFocusLost()
lastPressedNonModifier = 0
editing = false
lastPressed = 0
updateButton!!()
}
override fun onKeyReleased(ch: Int, key: Int, modifiers: Int): InputResult {
if (!editing)
return super.onKeyReleased(ch, key, modifiers)
if (lastPressedNonModifier == ch || (lastPressedNonModifier == 0 && ch == lastPressed)) {
opt.value = SavedKeyBinding(
ch, modifiers
)
editing = false
lastPressed = 0
lastPressedNonModifier = 0
}
updateButton!!()
return InputResult.PROCESSED
}
}
fun updateLabel() {
val stroke = Text.literal("")
if (opt.value.shift) {
stroke.append("SHIFT + ") // TODO: translations?
}
if (opt.value.alt) {
stroke.append("ALT + ")
}
if (opt.value.ctrl) {
stroke.append("CTRL + ")
}
stroke.append(InputUtil.Type.KEYSYM.createFromCode(opt.value.keyCode).localizedText)
if (editing)
stroke.styled { it.withColor(Formatting.YELLOW) }
button.setLabel(stroke)
}
updateButton = ::updateLabel
updateButton()
button.setOnClick {
editing = true
button.requestFocus()
updateButton()
}
guiAppender.appendLabeledRow(opt.labelText, button)
}
}

View File

@@ -14,21 +14,22 @@ import io.github.cottonmc.cotton.gui.widget.WLabel
import io.github.cottonmc.cotton.gui.widget.data.Axis import io.github.cottonmc.cotton.gui.widget.data.Axis
import io.github.cottonmc.cotton.gui.widget.data.Insets import io.github.cottonmc.cotton.gui.widget.data.Insets
import io.github.cottonmc.cotton.gui.widget.data.VerticalAlignment import io.github.cottonmc.cotton.gui.widget.data.VerticalAlignment
import moe.nea.jarvis.api.Point
import kotlinx.serialization.decodeFromString import kotlinx.serialization.decodeFromString
import kotlinx.serialization.encodeToString import kotlinx.serialization.encodeToString
import kotlinx.serialization.json.JsonElement import kotlinx.serialization.json.JsonElement
import kotlinx.serialization.json.JsonObject import kotlinx.serialization.json.JsonObject
import moe.nea.firmament.Firmament
import moe.nea.firmament.gui.WTightScrollPanel
import moe.nea.firmament.keybindings.SavedKeyBinding
import moe.nea.firmament.util.MC
import moe.nea.firmament.util.ScreenUtil.setScreenLater
import moe.nea.jarvis.api.Point
import net.minecraft.client.gui.screen.Screen
import net.minecraft.text.Text
import kotlin.io.path.createDirectories import kotlin.io.path.createDirectories
import kotlin.io.path.readText import kotlin.io.path.readText
import kotlin.io.path.writeText import kotlin.io.path.writeText
import kotlin.time.Duration 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.WTightScrollPanel
import moe.nea.firmament.util.MC
import moe.nea.firmament.util.ScreenUtil.setScreenLater
abstract class ManagedConfig(override val name: String) : ManagedConfigElement() { abstract class ManagedConfig(override val name: String) : ManagedConfigElement() {
@@ -106,6 +107,18 @@ abstract class ManagedConfig(override val name: String) : ManagedConfigElement()
}, HudMetaHandler(this, label, width, height)) }, HudMetaHandler(this, label, width, height))
} }
protected fun keyBinding(
propertyName: String,
default: () -> Int,
): ManagedOption<SavedKeyBinding> = keyBindingWithDefaultModifiers(propertyName) { SavedKeyBinding(default()) }
protected fun keyBindingWithDefaultModifiers(
propertyName: String,
default: () -> SavedKeyBinding,
): ManagedOption<SavedKeyBinding> {
return option(propertyName, default, KeyBindingHandler("firmament.config.${name}.${propertyName}", this))
}
protected fun integer( protected fun integer(
propertyName: String, propertyName: String,
min: Int, min: Int,
@@ -137,8 +150,12 @@ abstract class ManagedConfig(override val name: String) : ManagedConfigElement()
guiapp.appendFullRow(WBox(Axis.HORIZONTAL).also { guiapp.appendFullRow(WBox(Axis.HORIZONTAL).also {
it.add(WButton(Text.literal("")).also { it.add(WButton(Text.literal("")).also {
it.setOnClick { it.setOnClick {
if (parent != null) {
setScreenLater(parent)
} else {
AllConfigsGui.showAllGuis() AllConfigsGui.showAllGuis()
} }
}
}) })
it.add(WLabel(Text.translatable("firmament.config.${name}")).also { it.add(WLabel(Text.translatable("firmament.config.${name}")).also {
it.verticalAlignment = VerticalAlignment.CENTER it.verticalAlignment = VerticalAlignment.CENTER

View File

@@ -7,17 +7,24 @@
package moe.nea.firmament.keybindings package moe.nea.firmament.keybindings
import net.fabricmc.fabric.api.client.keybinding.v1.KeyBindingHelper import net.fabricmc.fabric.api.client.keybinding.v1.KeyBindingHelper
import org.lwjgl.glfw.GLFW
import net.minecraft.client.option.KeyBinding import net.minecraft.client.option.KeyBinding
import net.minecraft.client.util.InputUtil import net.minecraft.client.util.InputUtil
import moe.nea.firmament.features.inventory.SlotLocking
import moe.nea.firmament.gui.config.ManagedConfig
object FirmamentKeyBindings { object FirmamentKeyBindings {
val SLOT_LOCKING = KeyBindingHelper.registerKeyBinding( fun registerKeyBinding(name: String, config: ManagedConfig) {
val vanillaKeyBinding = KeyBindingHelper.registerKeyBinding(
KeyBinding( KeyBinding(
"firmament.key.slotlocking", name,
InputUtil.Type.KEYSYM, InputUtil.Type.KEYSYM,
GLFW.GLFW_KEY_L, -1,
"firmament.key.category" "firmament.key.category"
) )
) )
keyBindings[vanillaKeyBinding] = config
}
val keyBindings = mutableMapOf<KeyBinding, ManagedConfig>()
} }

View File

@@ -0,0 +1,36 @@
package moe.nea.firmament.keybindings
import kotlinx.serialization.Serializable
import org.lwjgl.glfw.GLFW
@Serializable
data class SavedKeyBinding(
val keyCode: Int,
val shift: Boolean = false,
val ctrl: Boolean = false,
val alt: Boolean = false,
) : IKeyBinding {
constructor(keyCode: Int, mods: Triple<Boolean, Boolean, Boolean>) : this(
keyCode,
mods.first && keyCode != GLFW.GLFW_KEY_LEFT_SHIFT && keyCode != GLFW.GLFW_KEY_RIGHT_SHIFT,
mods.second && keyCode != GLFW.GLFW_KEY_LEFT_CONTROL && keyCode != GLFW.GLFW_KEY_RIGHT_CONTROL,
mods.third && keyCode != GLFW.GLFW_KEY_LEFT_ALT && keyCode != GLFW.GLFW_KEY_RIGHT_ALT,
)
constructor(keyCode: Int, mods: Int) : this(keyCode, getMods(mods))
companion object {
fun getMods(modifiers: Int): Triple<Boolean, Boolean, Boolean> {
return Triple(
modifiers and GLFW.GLFW_MOD_SHIFT != 0,
modifiers and GLFW.GLFW_MOD_CONTROL != 0,
modifiers and GLFW.GLFW_MOD_ALT != 0
)
}
}
override fun matches(keyCode: Int, scanCode: Int, modifiers: Int): Boolean {
return keyCode == this.keyCode && getMods(modifiers) == Triple(shift, ctrl, alt)
}
}

View File

@@ -6,10 +6,10 @@
package moe.nea.firmament.util package moe.nea.firmament.util
import moe.nea.firmament.Firmament
import net.fabricmc.fabric.api.client.event.lifecycle.v1.ClientTickEvents import net.fabricmc.fabric.api.client.event.lifecycle.v1.ClientTickEvents
import net.minecraft.client.MinecraftClient import net.minecraft.client.MinecraftClient
import net.minecraft.client.gui.screen.Screen import net.minecraft.client.gui.screen.Screen
import moe.nea.firmament.Firmament
object ScreenUtil { object ScreenUtil {
init { init {
@@ -29,10 +29,10 @@ object ScreenUtil {
private var nextOpenedGui: Screen? = null private var nextOpenedGui: Screen? = null
fun setScreenLater(nextScreen: Screen) { fun setScreenLater(nextScreen: Screen?) {
val nog = nextOpenedGui val nog = nextOpenedGui
if (nog != null) { if (nog != null) {
Firmament.logger.warn("Setting screen ${nextScreen::class.qualifiedName} to be opened later, but ${nog::class.qualifiedName} is already queued.") Firmament.logger.warn("Setting screen ${if (nextScreen == null) "null" else nextScreen::class.qualifiedName} to be opened later, but ${nog::class.qualifiedName} is already queued.")
return return
} }
nextOpenedGui = nextScreen nextOpenedGui = nextScreen

View File

@@ -79,6 +79,9 @@
"firmament.config.chat-links.allowed-hosts": "Allowed Image Hosts", "firmament.config.chat-links.allowed-hosts": "Allowed Image Hosts",
"firmament.config.chat-links.position": "Chat Image Preview", "firmament.config.chat-links.position": "Chat Image Preview",
"firmament.hud.edit": "Edit %s", "firmament.hud.edit": "Edit %s",
"firmament.keybinding.external": "External",
"firmament.config.slot-locking": "Slot Locking",
"firmament.config.slot-locking.lock": "Lock Slot",
"firmament.config.custom-skyblock-textures": "Custom SkyBlock Item Textures", "firmament.config.custom-skyblock-textures": "Custom SkyBlock Item Textures",
"firmament.config.custom-skyblock-textures.cache-duration": "Model Cache Duration", "firmament.config.custom-skyblock-textures.cache-duration": "Model Cache Duration",
"firmament.config.custom-skyblock-textures.enabled": "Enable Custom Item Textures", "firmament.config.custom-skyblock-textures.enabled": "Enable Custom Item Textures",