Add better key binding support
This commit is contained in:
1
TODO.txt
1
TODO.txt
@@ -50,6 +50,7 @@ Priority 3:
|
||||
- bits?
|
||||
- collection?
|
||||
- pretty graphs!
|
||||
- Maintain scroll percentage in vanilla guis when reinitializing
|
||||
|
||||
|
||||
- and much more that i will add as i go along
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -12,7 +12,6 @@ import moe.nea.firmament.Firmament
|
||||
import moe.nea.firmament.features.chat.ChatLinks
|
||||
import moe.nea.firmament.features.debug.DebugView
|
||||
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.inventory.CraftingOverlay
|
||||
import moe.nea.firmament.features.inventory.SaveCursorPosition
|
||||
@@ -54,6 +53,7 @@ object FeatureManager : DataHolder<FeatureManager.Config>(serializer(), "feature
|
||||
loadFeature(DeveloperFeatures)
|
||||
loadFeature(DebugView)
|
||||
}
|
||||
allFeatures.forEach { it.config }
|
||||
hasAutoloaded = true
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,17 +8,17 @@ package moe.nea.firmament.features.inventory
|
||||
|
||||
import kotlinx.serialization.Serializable
|
||||
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.IsSlotProtectedEvent
|
||||
import moe.nea.firmament.events.SlotRenderEvents
|
||||
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.util.CommonSoundEffects
|
||||
import moe.nea.firmament.util.MC
|
||||
import moe.nea.firmament.util.data.ProfileSpecificDataHolder
|
||||
import net.minecraft.entity.player.PlayerInventory
|
||||
import org.lwjgl.glfw.GLFW
|
||||
|
||||
object SlotLocking : FirmamentFeature {
|
||||
override val identifier: String
|
||||
@@ -29,13 +29,19 @@ object SlotLocking : FirmamentFeature {
|
||||
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)
|
||||
|
||||
val keyBinding by FirmamentKeyBindings::SLOT_LOCKING
|
||||
val lockedSlots get() = DConfig.data?.lockedSlots
|
||||
override fun onLoad() {
|
||||
HandledScreenKeyPressedEvent.subscribe {
|
||||
if (!it.matches(keyBinding)) return@subscribe
|
||||
if (!it.matches(TConfig.lock)) return@subscribe
|
||||
val inventory = MC.handledScreen ?: return@subscribe
|
||||
inventory as AccessorHandledScreen
|
||||
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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.Insets
|
||||
import io.github.cottonmc.cotton.gui.widget.data.VerticalAlignment
|
||||
import moe.nea.jarvis.api.Point
|
||||
import kotlinx.serialization.decodeFromString
|
||||
import kotlinx.serialization.encodeToString
|
||||
import kotlinx.serialization.json.JsonElement
|
||||
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.readText
|
||||
import kotlin.io.path.writeText
|
||||
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() {
|
||||
|
||||
@@ -106,6 +107,18 @@ abstract class ManagedConfig(override val name: String) : ManagedConfigElement()
|
||||
}, 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(
|
||||
propertyName: String,
|
||||
min: Int,
|
||||
@@ -125,7 +138,7 @@ abstract class ManagedConfig(override val name: String) : ManagedConfigElement()
|
||||
|
||||
|
||||
fun reloadGui() {
|
||||
latestGuiAppender?.reloadables?.forEach {it() }
|
||||
latestGuiAppender?.reloadables?.forEach { it() }
|
||||
}
|
||||
|
||||
fun getConfigEditor(parent: Screen? = null): CottonClientScreen {
|
||||
@@ -137,8 +150,12 @@ abstract class ManagedConfig(override val name: String) : ManagedConfigElement()
|
||||
guiapp.appendFullRow(WBox(Axis.HORIZONTAL).also {
|
||||
it.add(WButton(Text.literal("←")).also {
|
||||
it.setOnClick {
|
||||
if (parent != null) {
|
||||
setScreenLater(parent)
|
||||
} else {
|
||||
AllConfigsGui.showAllGuis()
|
||||
}
|
||||
}
|
||||
})
|
||||
it.add(WLabel(Text.translatable("firmament.config.${name}")).also {
|
||||
it.verticalAlignment = VerticalAlignment.CENTER
|
||||
|
||||
@@ -7,17 +7,24 @@
|
||||
package moe.nea.firmament.keybindings
|
||||
|
||||
import net.fabricmc.fabric.api.client.keybinding.v1.KeyBindingHelper
|
||||
import org.lwjgl.glfw.GLFW
|
||||
import net.minecraft.client.option.KeyBinding
|
||||
import net.minecraft.client.util.InputUtil
|
||||
import moe.nea.firmament.features.inventory.SlotLocking
|
||||
import moe.nea.firmament.gui.config.ManagedConfig
|
||||
|
||||
object FirmamentKeyBindings {
|
||||
val SLOT_LOCKING = KeyBindingHelper.registerKeyBinding(
|
||||
fun registerKeyBinding(name: String, config: ManagedConfig) {
|
||||
val vanillaKeyBinding = KeyBindingHelper.registerKeyBinding(
|
||||
KeyBinding(
|
||||
"firmament.key.slotlocking",
|
||||
name,
|
||||
InputUtil.Type.KEYSYM,
|
||||
GLFW.GLFW_KEY_L,
|
||||
-1,
|
||||
"firmament.key.category"
|
||||
)
|
||||
)
|
||||
keyBindings[vanillaKeyBinding] = config
|
||||
}
|
||||
|
||||
val keyBindings = mutableMapOf<KeyBinding, ManagedConfig>()
|
||||
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
}
|
||||
@@ -6,10 +6,10 @@
|
||||
|
||||
package moe.nea.firmament.util
|
||||
|
||||
import moe.nea.firmament.Firmament
|
||||
import net.fabricmc.fabric.api.client.event.lifecycle.v1.ClientTickEvents
|
||||
import net.minecraft.client.MinecraftClient
|
||||
import net.minecraft.client.gui.screen.Screen
|
||||
import moe.nea.firmament.Firmament
|
||||
|
||||
object ScreenUtil {
|
||||
init {
|
||||
@@ -29,10 +29,10 @@ object ScreenUtil {
|
||||
|
||||
private var nextOpenedGui: Screen? = null
|
||||
|
||||
fun setScreenLater(nextScreen: Screen) {
|
||||
fun setScreenLater(nextScreen: Screen?) {
|
||||
val nog = nextOpenedGui
|
||||
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
|
||||
}
|
||||
nextOpenedGui = nextScreen
|
||||
|
||||
@@ -79,6 +79,9 @@
|
||||
"firmament.config.chat-links.allowed-hosts": "Allowed Image Hosts",
|
||||
"firmament.config.chat-links.position": "Chat Image Preview",
|
||||
"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.cache-duration": "Model Cache Duration",
|
||||
"firmament.config.custom-skyblock-textures.enabled": "Enable Custom Item Textures",
|
||||
|
||||
Reference in New Issue
Block a user