diff options
Diffstat (limited to 'src/main/kotlin/keybindings')
| -rw-r--r-- | src/main/kotlin/keybindings/FirmamentKeyBindings.kt | 17 | ||||
| -rw-r--r-- | src/main/kotlin/keybindings/FirmamentKeyboardState.kt | 24 | ||||
| -rw-r--r-- | src/main/kotlin/keybindings/GenericInputButton.kt | 332 | ||||
| -rw-r--r-- | src/main/kotlin/keybindings/IKeyBinding.kt | 29 | ||||
| -rw-r--r-- | src/main/kotlin/keybindings/SavedKeyBinding.kt | 139 |
5 files changed, 408 insertions, 133 deletions
diff --git a/src/main/kotlin/keybindings/FirmamentKeyBindings.kt b/src/main/kotlin/keybindings/FirmamentKeyBindings.kt index 59b131a..63b7232 100644 --- a/src/main/kotlin/keybindings/FirmamentKeyBindings.kt +++ b/src/main/kotlin/keybindings/FirmamentKeyBindings.kt @@ -1,18 +1,23 @@ package moe.nea.firmament.keybindings import net.fabricmc.fabric.api.client.keybinding.v1.KeyBindingHelper -import net.minecraft.client.option.KeyBinding -import net.minecraft.client.util.InputUtil +import net.minecraft.client.KeyMapping +import com.mojang.blaze3d.platform.InputConstants +import moe.nea.firmament.Firmament import moe.nea.firmament.gui.config.ManagedOption import moe.nea.firmament.util.TestUtil +import moe.nea.firmament.util.data.ManagedConfig object FirmamentKeyBindings { + val cats = mutableMapOf<ManagedConfig.Category, KeyMapping.Category>() + + fun registerKeyBinding(name: String, config: ManagedOption<SavedKeyBinding>) { - val vanillaKeyBinding = KeyBinding( + val vanillaKeyBinding = KeyMapping( name, - InputUtil.Type.KEYSYM, + InputConstants.Type.KEYSYM, -1, - "firmament.key.category" + cats.computeIfAbsent(config.element.category) { KeyMapping.Category(Firmament.identifier(it.name.lowercase())) } ) if (!TestUtil.isInTest) { KeyBindingHelper.registerKeyBinding(vanillaKeyBinding) @@ -20,6 +25,6 @@ object FirmamentKeyBindings { keyBindings[vanillaKeyBinding] = config } - val keyBindings = mutableMapOf<KeyBinding, ManagedOption<SavedKeyBinding>>() + val keyBindings = mutableMapOf<KeyMapping, ManagedOption<SavedKeyBinding>>() } diff --git a/src/main/kotlin/keybindings/FirmamentKeyboardState.kt b/src/main/kotlin/keybindings/FirmamentKeyboardState.kt new file mode 100644 index 0000000..da94a44 --- /dev/null +++ b/src/main/kotlin/keybindings/FirmamentKeyboardState.kt @@ -0,0 +1,24 @@ +package moe.nea.firmament.keybindings + +import java.util.BitSet +import org.lwjgl.glfw.GLFW +import net.minecraft.client.input.KeyEvent + +object FirmamentKeyboardState { + + private val pressedScancodes = BitSet() + + @Synchronized + fun isScancodeDown(scancode: Int): Boolean { + // TODO: maintain a record of keycodes that were pressed for this scanCode to check if they are still held + return pressedScancodes.get(scancode) + } + + @Synchronized + fun maintainState(keyInput: KeyEvent, action: Int) { + when (action) { + GLFW.GLFW_PRESS -> pressedScancodes.set(keyInput.scancode) + GLFW.GLFW_RELEASE -> pressedScancodes.clear(keyInput.scancode) + } + } +} diff --git a/src/main/kotlin/keybindings/GenericInputButton.kt b/src/main/kotlin/keybindings/GenericInputButton.kt new file mode 100644 index 0000000..4a4aaba --- /dev/null +++ b/src/main/kotlin/keybindings/GenericInputButton.kt @@ -0,0 +1,332 @@ +package moe.nea.firmament.keybindings + +import org.lwjgl.glfw.GLFW +import kotlinx.serialization.KSerializer +import kotlinx.serialization.Serializable +import kotlinx.serialization.descriptors.SerialDescriptor +import kotlinx.serialization.encoding.Decoder +import kotlinx.serialization.encoding.Encoder +import kotlinx.serialization.json.JsonElement +import kotlinx.serialization.json.JsonNull +import kotlinx.serialization.json.JsonObject +import kotlinx.serialization.json.JsonPrimitive +import kotlinx.serialization.json.buildJsonObject +import kotlinx.serialization.json.int +import kotlinx.serialization.json.put +import net.minecraft.client.Minecraft +import net.minecraft.client.input.MouseButtonEvent +import net.minecraft.client.input.InputWithModifiers +import net.minecraft.client.input.KeyEvent +import net.minecraft.client.input.MouseButtonInfo +import com.mojang.blaze3d.platform.InputConstants +import com.mojang.blaze3d.platform.MacosUtil +import net.minecraft.network.chat.Component +import moe.nea.firmament.util.MC +import moe.nea.firmament.util.mc.InitLevel + +@Serializable(with = GenericInputButton.Serializer::class) +sealed interface GenericInputButton { + + object Serializer : KSerializer<GenericInputButton> { + override val descriptor: SerialDescriptor + get() = SerialDescriptor("Firmament:GenericInputButton", JsonElement.serializer().descriptor) + + override fun serialize( + encoder: Encoder, + value: GenericInputButton + ) { + JsonElement.serializer().serialize( + encoder, + when (value) { + is KeyCodeButton -> buildJsonObject { put("keyCode", value.keyCode) } + is MouseButton -> buildJsonObject { put("mouse", value.mouseButton) } + is ScanCodeButton -> buildJsonObject { put("scanCode", value.scanCode) } + Unbound -> JsonNull + }) + } + + override fun deserialize(decoder: Decoder): GenericInputButton { + val element = JsonElement.serializer().deserialize(decoder) + if (element is JsonNull) + return Unbound + require(element is JsonObject) + (element["keyCode"] as? JsonPrimitive)?.let { + return KeyCodeButton(it.int) + } + (element["mouse"] as? JsonPrimitive)?.let { + return MouseButton(it.int) + } + (element["scanCode"] as? JsonPrimitive)?.let { + return ScanCodeButton(it.int) + } + error("Could not parse GenericInputButton: $element") + } + } + + companion object { + + fun of(event: KeyEvent) = ofKeyAndScan(event.input(), event.scancode) + fun escape() = ofKeyCode(GLFW.GLFW_KEY_ESCAPE) + fun ofKeyCode(keyCode: Int): GenericInputButton = KeyCodeButton(keyCode) + fun ofScanCode(scanCode: Int): GenericInputButton = ScanCodeButton(scanCode) + fun ofScanCodeFromKeyCode(keyCode: Int): GenericInputButton = ScanCodeButton(GLFW.glfwGetKeyScancode(keyCode)) + fun unbound(): GenericInputButton = Unbound + fun mouse(mouseButton: Int): GenericInputButton = MouseButton(mouseButton) + fun ofKeyAndScan(keyCode: Int, scanCode: Int): GenericInputButton { + if (keyCode == GLFW.GLFW_KEY_UNKNOWN) + return ofScanCode(scanCode) + return ofKeyCode(keyCode) // TODO: should i always upgrade to a scanCode? + } + } + + data object Unbound : GenericInputButton { + override fun toInputKey(): InputConstants.Key { + return InputConstants.UNKNOWN + } + + override fun isBound(): Boolean { + return false + } + + override fun isPressed(): Boolean { + return false + } + } + + data class MouseButton( + val mouseButton: Int + ) : GenericInputButton { + override fun toInputKey(): InputConstants.Key { + return InputConstants.Type.MOUSE.getOrCreate(mouseButton) + } + + override fun isPressed(): Boolean { + return GLFW.glfwGetMouseButton(MC.window.handle(), mouseButton) == GLFW.GLFW_PRESS + } + } + + data class KeyCodeButton( + val keyCode: Int + ) : GenericInputButton { + override fun toInputKey(): InputConstants.Key { + return InputConstants.Type.KEYSYM.getOrCreate(keyCode) + } + + override fun isPressed(): Boolean { + return InputConstants.isKeyDown(MC.window, keyCode) + } + + override fun isCtrl(): Boolean { + return keyCode in InputModifiers.controlKeys + } + + override fun isAlt(): Boolean { + return keyCode in InputModifiers.altKeys + } + + override fun isShift(): Boolean { + return keyCode in InputModifiers.shiftKeys + } + + override fun isSuper(): Boolean { + return keyCode in InputModifiers.superKeys + } + } + + data class ScanCodeButton( + val scanCode: Int + ) : GenericInputButton { + override fun toInputKey(): InputConstants.Key { + return InputConstants.Type.SCANCODE.getOrCreate(scanCode) + } + + override fun isPressed(): Boolean { + return FirmamentKeyboardState.isScancodeDown(scanCode) + } + } + + fun isBound() = true + + fun isModifier() = isCtrl() || isAlt() || isSuper() || isShift() + fun isCtrl() = false + fun isAlt() = false + fun isSuper() = false + fun isShift() = false + + fun toInputKey(): InputConstants.Key + fun format(): Component = + if (InitLevel.isAtLeast(InitLevel.RENDER_INIT)) { + toInputKey().displayName + } else { + Component.nullToEmpty(toString()) + } + + fun matches(inputAction: GenericInputAction) = inputAction.matches(this) + fun isPressed(): Boolean +} + +sealed interface GenericInputAction { + fun matches(inputButton: GenericInputButton): Boolean + + data class MouseInput( + val mouseButton: Int + ) : GenericInputAction { + override fun matches(inputButton: GenericInputButton): Boolean { + return inputButton is GenericInputButton.MouseButton && inputButton.mouseButton == mouseButton + } + } + + data class KeyboardInput( + val keyCode: Int, + val scanCode: Int, + ) : GenericInputAction { + override fun matches(inputButton: GenericInputButton): Boolean { + return when (inputButton) { + is GenericInputButton.KeyCodeButton -> inputButton.keyCode == keyCode + is GenericInputButton.ScanCodeButton -> inputButton.scanCode == scanCode + else -> false + } + } + } + + companion object { + @JvmStatic + fun mouse(mouseButton: Int): GenericInputAction = MouseInput(mouseButton) + + @JvmStatic + fun mouse(click: MouseButtonEvent): GenericInputAction = mouse(click.button()) + + @JvmStatic + fun of(input: net.minecraft.client.input.MouseButtonInfo): GenericInputAction = mouse(input.button) + @JvmStatic + fun of(input: KeyEvent): GenericInputAction = key(input.input(), input.scancode) + + @JvmStatic + fun key(keyCode: Int, scanCode: Int): GenericInputAction = KeyboardInput(keyCode, scanCode) + } +} + +@Serializable +data class InputModifiers( + val modifiers: Int +) { + companion object { + @JvmStatic + fun current(): InputModifiers { + val h = MC.window + val ctrl = if (MacosUtil.IS_MACOS) { + InputConstants.isKeyDown(h, GLFW.GLFW_KEY_LEFT_SUPER) + || InputConstants.isKeyDown(h, GLFW.GLFW_KEY_RIGHT_SUPER) + } else InputConstants.isKeyDown(h, GLFW.GLFW_KEY_LEFT_CONTROL) + || InputConstants.isKeyDown(h, GLFW.GLFW_KEY_RIGHT_CONTROL) + val shift = InputConstants.isKeyDown(h, GLFW.GLFW_KEY_LEFT_SHIFT) || InputConstants.isKeyDown( + h, + GLFW.GLFW_KEY_RIGHT_SHIFT + ) + val alt = InputConstants.isKeyDown(h, GLFW.GLFW_KEY_LEFT_ALT) + || InputConstants.isKeyDown(h, GLFW.GLFW_KEY_RIGHT_ALT) + val `super` = InputConstants.isKeyDown(h, GLFW.GLFW_KEY_LEFT_SUPER) + || InputConstants.isKeyDown(h, GLFW.GLFW_KEY_RIGHT_SUPER) + return of( + ctrl = ctrl, + shift = shift, + alt = alt, + `super` = `super`, + ) + } + + + val superKeys = listOf(GLFW.GLFW_KEY_LEFT_SUPER, GLFW.GLFW_KEY_RIGHT_SUPER) + val controlKeys = if (MacosUtil.IS_MACOS) { + listOf(GLFW.GLFW_KEY_LEFT_SUPER, GLFW.GLFW_KEY_RIGHT_SUPER) + } else { + listOf(GLFW.GLFW_KEY_LEFT_CONTROL, GLFW.GLFW_KEY_RIGHT_CONTROL) + } + val shiftKeys = listOf(GLFW.GLFW_KEY_LEFT_SHIFT, GLFW.GLFW_KEY_RIGHT_SHIFT) + val altKeys = listOf(GLFW.GLFW_KEY_LEFT_ALT, GLFW.GLFW_KEY_RIGHT_ALT) + + fun of( + vararg useNamedArgs: Boolean, + ctrl: Boolean = false, + shift: Boolean = false, + alt: Boolean = false, + `super`: Boolean = false + ): InputModifiers { + require(useNamedArgs.isEmpty()) + return InputModifiers( + (if (ctrl) GLFW.GLFW_MOD_CONTROL else 0) + or (if (shift) GLFW.GLFW_MOD_SHIFT else 0) + or (if (alt) GLFW.GLFW_MOD_ALT else 0) + or (if (`super`) GLFW.GLFW_MOD_SUPER else 0) + ) + } + + fun ofKeyCodes(vararg keys: Int): InputModifiers { + var mods = 0 + for (key in keys) { + if (key in superKeys) + mods = mods or GLFW.GLFW_MOD_SUPER + if (key in controlKeys) + mods = mods or GLFW.GLFW_MOD_CONTROL + if (key in altKeys) + mods = mods or GLFW.GLFW_MOD_ALT + if (key in shiftKeys) + mods = mods or GLFW.GLFW_MOD_SHIFT + } + return of(mods) + } + + @JvmStatic + fun of(modifiers: Int) = InputModifiers(modifiers) + + @JvmStatic + fun of(input: InputWithModifiers) = InputModifiers(input.modifiers()) + + fun none(): InputModifiers { + return InputModifiers(0) + } + + fun ofKey(button: GenericInputButton): InputModifiers { + return when (button) { + is GenericInputButton.KeyCodeButton -> ofKeyCodes(button.keyCode) + else -> none() + } + } + } + + fun isAtLeast(other: InputModifiers): Boolean { + return this.modifiers and other.modifiers == this.modifiers + } + + fun without(other: InputModifiers): InputModifiers { + return InputModifiers(this.modifiers and other.modifiers.inv()) + } + + fun isEmpty() = modifiers == 0 + + fun getFlag(flag: Int) = modifiers and flag != 0 + val ctrl get() = getFlag(GLFW.GLFW_MOD_CONTROL) // TODO: consult someone on control vs command again + val shift get() = getFlag(GLFW.GLFW_MOD_SHIFT) + val alt get() = getFlag(GLFW.GLFW_MOD_ALT) + val `super` get() = getFlag(GLFW.GLFW_MOD_SUPER) + + override fun toString(): String { + return listOfNotNull( + if (ctrl) "CTRL" else null, + if (shift) "SHIFT" else null, + if (alt) "ALT" else null, + if (`super`) "SUPER" else null, + ).joinToString(" + ") + } + + fun matches(other: InputModifiers, atLeast: Boolean): Boolean { + if (atLeast) + return isAtLeast(other) + return this == other + } + + fun format(): Component { // TODO: translation for mods + return Component.nullToEmpty(toString()) + } + +} diff --git a/src/main/kotlin/keybindings/IKeyBinding.kt b/src/main/kotlin/keybindings/IKeyBinding.kt deleted file mode 100644 index 1975361..0000000 --- a/src/main/kotlin/keybindings/IKeyBinding.kt +++ /dev/null @@ -1,29 +0,0 @@ - - -package moe.nea.firmament.keybindings - -import net.minecraft.client.option.KeyBinding - -interface IKeyBinding { - fun matches(keyCode: Int, scanCode: Int, modifiers: Int): Boolean - - fun withModifiers(wantedModifiers: Int): IKeyBinding { - val old = this - return object : IKeyBinding { - override fun matches(keyCode: Int, scanCode: Int, modifiers: Int): Boolean { - return old.matches(keyCode, scanCode, modifiers) && (modifiers and wantedModifiers) == wantedModifiers - } - } - } - - companion object { - fun minecraft(keyBinding: KeyBinding) = object : IKeyBinding { - override fun matches(keyCode: Int, scanCode: Int, modifiers: Int) = - keyBinding.matchesKey(keyCode, scanCode) - } - - fun ofKeyCode(wantedKeyCode: Int) = object : IKeyBinding { - override fun matches(keyCode: Int, scanCode: Int, modifiers: Int): Boolean = keyCode == wantedKeyCode - } - } -} diff --git a/src/main/kotlin/keybindings/SavedKeyBinding.kt b/src/main/kotlin/keybindings/SavedKeyBinding.kt index 5bca87e..91d88ca 100644 --- a/src/main/kotlin/keybindings/SavedKeyBinding.kt +++ b/src/main/kotlin/keybindings/SavedKeyBinding.kt @@ -1,106 +1,49 @@ - - package moe.nea.firmament.keybindings -import org.lwjgl.glfw.GLFW import kotlinx.serialization.Serializable -import net.minecraft.client.MinecraftClient -import net.minecraft.client.util.InputUtil -import net.minecraft.text.Text -import moe.nea.firmament.util.MC +import net.minecraft.network.chat.Component @Serializable data class SavedKeyBinding( - val keyCode: Int, - val shift: Boolean = false, - val ctrl: Boolean = false, - val alt: Boolean = false, -) : IKeyBinding { - val isBound: Boolean get() = keyCode != GLFW.GLFW_KEY_UNKNOWN - - 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, - ) - } - - fun getModInt(): Int { - val h = MC.window.handle - val ctrl = if (MinecraftClient.IS_SYSTEM_MAC) { - InputUtil.isKeyPressed(h, GLFW.GLFW_KEY_LEFT_SUPER) - || InputUtil.isKeyPressed(h, GLFW.GLFW_KEY_RIGHT_SUPER) - } else InputUtil.isKeyPressed(h, GLFW.GLFW_KEY_LEFT_CONTROL) - || InputUtil.isKeyPressed(h, GLFW.GLFW_KEY_RIGHT_CONTROL) - val shift = isShiftDown() - val alt = InputUtil.isKeyPressed(h, GLFW.GLFW_KEY_LEFT_ALT) - || InputUtil.isKeyPressed(h, GLFW.GLFW_KEY_RIGHT_ALT) - var mods = 0 - if (ctrl) mods = mods or GLFW.GLFW_MOD_CONTROL - if (shift) mods = mods or GLFW.GLFW_MOD_SHIFT - if (alt) mods = mods or GLFW.GLFW_MOD_ALT - return mods - } - - private val h get() = MC.window.handle - fun isShiftDown() = InputUtil.isKeyPressed(h, GLFW.GLFW_KEY_LEFT_SHIFT) - || InputUtil.isKeyPressed(h, GLFW.GLFW_KEY_RIGHT_SHIFT) - - } - - fun isPressed(atLeast: Boolean = false): Boolean { - if (!isBound) return false - val h = MC.window.handle - if (!InputUtil.isKeyPressed(h, keyCode)) return false - - val ctrl = if (MinecraftClient.IS_SYSTEM_MAC) { - InputUtil.isKeyPressed(h, GLFW.GLFW_KEY_LEFT_SUPER) - || InputUtil.isKeyPressed(h, GLFW.GLFW_KEY_RIGHT_SUPER) - } else InputUtil.isKeyPressed(h, GLFW.GLFW_KEY_LEFT_CONTROL) - || InputUtil.isKeyPressed(h, GLFW.GLFW_KEY_RIGHT_CONTROL) - val shift = isShiftDown() - val alt = InputUtil.isKeyPressed(h, GLFW.GLFW_KEY_LEFT_ALT) - || InputUtil.isKeyPressed(h, GLFW.GLFW_KEY_RIGHT_ALT) - if (atLeast) - return (ctrl >= this.ctrl) && - (alt >= this.alt) && - (shift >= this.shift) - - return (ctrl == this.ctrl) && - (alt == this.alt) && - (shift == this.shift) - } - - override fun matches(keyCode: Int, scanCode: Int, modifiers: Int): Boolean { - if (this.keyCode == GLFW.GLFW_KEY_UNKNOWN) return false - return keyCode == this.keyCode && getMods(modifiers) == Triple(shift, ctrl, alt) - } - - fun format(): Text { - val stroke = Text.literal("") - if (ctrl) { - stroke.append("CTRL + ") - } - if (alt) { - stroke.append("ALT + ") - } - if (shift) { - stroke.append("SHIFT + ") // TODO: translations? - } - - stroke.append(InputUtil.Type.KEYSYM.createFromCode(keyCode).localizedText) - return stroke - } + val button: GenericInputButton, + val modifiers: InputModifiers, +) { + companion object { + fun isShiftDown() = InputModifiers.current().shift + + fun unbound(): SavedKeyBinding = withoutMods(GenericInputButton.unbound()) + fun withoutMods(input: GenericInputButton) = SavedKeyBinding(input, InputModifiers.none()) + fun keyWithoutMods(keyCode: Int): SavedKeyBinding = withoutMods(GenericInputButton.ofKeyCode(keyCode)) + fun keyWithMods(keyCode: Int, mods: InputModifiers) = + SavedKeyBinding(GenericInputButton.ofKeyCode(keyCode), mods) + } + + fun isPressed(atLeast: Boolean = false): Boolean { + if (!button.isPressed()) + return false + val mods = InputModifiers.current() + .without(InputModifiers.ofKey(button)) + return mods.matches(this.modifiers, atLeast) + } + + override fun toString(): String { + return format().string + } + + fun format(): Component { + val stroke = Component.empty() + if (!modifiers.isEmpty()) { + stroke.append(modifiers.format()) + stroke.append(" + ") + } + stroke.append(button.format()) + return stroke + } + + val isBound: Boolean get() = button.isBound() + fun matches(action: GenericInputAction, inputModifiers: InputModifiers, atLeast: Boolean = false): Boolean { + return action.matches(button) && this.modifiers.matches(inputModifiers, atLeast) + } } + |
