aboutsummaryrefslogtreecommitdiff
path: root/src/main/kotlin/keybindings/GenericInputButton.kt
blob: 4a4aabae9deff003bf3d6d7630162b787981db58 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
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())
	}

}