aboutsummaryrefslogtreecommitdiff
path: root/src/main/kotlin/keybindings
diff options
context:
space:
mode:
Diffstat (limited to 'src/main/kotlin/keybindings')
-rw-r--r--src/main/kotlin/keybindings/FirmamentKeyBindings.kt17
-rw-r--r--src/main/kotlin/keybindings/FirmamentKeyboardState.kt24
-rw-r--r--src/main/kotlin/keybindings/GenericInputButton.kt332
-rw-r--r--src/main/kotlin/keybindings/IKeyBinding.kt29
-rw-r--r--src/main/kotlin/keybindings/SavedKeyBinding.kt139
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)
+ }
}
+