input handler tests, fix modifier key action

This commit is contained in:
Bixilon 2023-06-29 21:36:35 +02:00
parent 745d982fa0
commit 1c6f1b4b9a
No known key found for this signature in database
GPG Key ID: 5CAD791931B09AC4
8 changed files with 210 additions and 9 deletions

View File

@ -14,10 +14,15 @@
package de.bixilon.minosoft.gui.rendering.input.key.manager
import de.bixilon.kutil.time.TimeUtil.millis
import de.bixilon.minosoft.config.key.KeyActions
import de.bixilon.minosoft.config.key.KeyBinding
import de.bixilon.minosoft.config.key.KeyCodes
import de.bixilon.minosoft.gui.rendering.input.InputHandler
import de.bixilon.minosoft.gui.rendering.input.key.manager.InputTestUtil.create
import de.bixilon.minosoft.gui.rendering.input.key.manager.InputTestUtil.simulate
import de.bixilon.minosoft.gui.rendering.system.window.CursorModes
import de.bixilon.minosoft.gui.rendering.system.window.KeyChangeTypes
import org.testng.Assert
import org.testng.Assert.assertFalse
import org.testng.Assert.assertTrue
import org.testng.annotations.Test
@ -76,4 +81,75 @@ class InputManagerTest {
input.simulate(KeyCodes.KEY_1, KeyChangeTypes.PRESS)
assertTrue(input.getLastPressed(KeyCodes.KEY_1) - time < 10)
}
fun `check char invoke`() {
val input = create()
var pressed: Int? = null
input.handler.handler = object : InputHandler {
override fun onCharPress(char: Int): Boolean {
pressed = char
return true
}
}
input.simulate(123)
Assert.assertEquals(pressed, 123)
}
fun `don't skip next char if just pushed and popped`() {
val input = create()
var pressed: Int? = null
val handler1 = object : InputHandler {
override fun onCharPress(char: Int): Boolean {
pressed = char
return true
}
}
val handler2 = object : InputHandler {
override fun onCharPress(char: Int): Boolean {
pressed = char + 5000
return true
}
}
input.handler.handler = handler1
input.handler.handler = null
input.handler.handler = handler2
input.simulate(123)
Assert.assertEquals(pressed, 5123)
}
fun `don't skip next char if just pushed and popped and opened via key binding`() {
val input = create()
var pressed: Int? = null
val handler1 = object : InputHandler {
override fun onCharPress(char: Int): Boolean {
pressed = char
return true
}
}
val handler2 = object : InputHandler {
override fun onCharPress(char: Int): Boolean {
pressed = char + 5000
return true
}
}
input.bindings.register(InputTestUtil.dummy, KeyBinding(mapOf(KeyActions.PRESS to setOf(KeyCodes.KEY_1)))) { input.handler.handler = handler1 }
input.handler.handler = handler1
input.handler.handler = null
input.handler.handler = handler2
input.simulate(123)
Assert.assertEquals(pressed, 5123)
}
fun `check correct set of cursor mode`() {
val input = create()
input.context.window.cursorMode = CursorModes.DISABLED
input.handler.handler = InputTestUtil.Handler
Assert.assertEquals(input.context.window.cursorMode, CursorModes.NORMAL)
input.handler.handler = null
Assert.assertEquals(input.context.window.cursorMode, CursorModes.DISABLED)
}
}

View File

@ -19,11 +19,13 @@ import de.bixilon.minosoft.config.key.KeyCodes
import de.bixilon.minosoft.config.profile.profiles.controls.ControlsProfile
import de.bixilon.minosoft.data.registries.identified.Namespaces
import de.bixilon.minosoft.data.registries.identified.ResourceLocation
import de.bixilon.minosoft.gui.rendering.RenderContext
import de.bixilon.minosoft.gui.rendering.input.InputHandler
import de.bixilon.minosoft.gui.rendering.input.key.manager.binding.BindingsManager
import de.bixilon.minosoft.gui.rendering.input.key.manager.binding.actions.bindingsPressed
import de.bixilon.minosoft.gui.rendering.input.key.manager.binding.actions.keysPressed
import de.bixilon.minosoft.gui.rendering.system.window.KeyChangeTypes
import de.bixilon.minosoft.gui.rendering.system.window.dummy.DummyWindow
import de.bixilon.minosoft.test.IT
import de.bixilon.minosoft.util.KUtil.set
import it.unimi.dsi.fastutil.objects.Object2LongOpenHashMap
@ -32,11 +34,15 @@ object InputTestUtil {
private val profile = BindingsManager::class.java.getDeclaredField("profile").apply { isAccessible = true }
private val bindings = BindingsManager::class.java.getDeclaredField("bindings").apply { isAccessible = true }
private val onKey = InputManager::class.java.getDeclaredMethod("onKey", KeyCodes::class.java, KeyChangeTypes::class.java).apply { isAccessible = true }
private val onChar = InputManager::class.java.getDeclaredMethod("onChar", Int::class.java).apply { isAccessible = true }
private val times = InputManager::class.java.getDeclaredField("times").apply { isAccessible = true }
fun create(): InputManager {
val manager = IT.OBJENESIS.newInstance(InputManager::class.java)
val context = IT.OBJENESIS.newInstance(RenderContext::class.java)
context::window.forceSet(DummyWindow())
manager::context.forceSet(context)
val bindings = IT.OBJENESIS.newInstance(BindingsManager::class.java)
bindings::input.forceSet(manager)
@ -60,6 +66,10 @@ object InputTestUtil {
onKey.invoke(this, code, change)
}
fun InputManager.simulate(char: Int) {
onChar.invoke(this, char)
}
object Handler : InputHandler
val dummy = Namespaces.minosoft("dummy")

View File

@ -16,6 +16,8 @@ package de.bixilon.minosoft.gui.rendering.input.key.manager.binding
import de.bixilon.minosoft.config.key.KeyActions
import de.bixilon.minosoft.config.key.KeyBinding
import de.bixilon.minosoft.config.key.KeyCodes
import de.bixilon.minosoft.gui.rendering.input.InputHandler
import de.bixilon.minosoft.gui.rendering.input.key.manager.InputTestUtil
import de.bixilon.minosoft.gui.rendering.input.key.manager.InputTestUtil.create
import de.bixilon.minosoft.gui.rendering.input.key.manager.InputTestUtil.dummy
import de.bixilon.minosoft.gui.rendering.input.key.manager.InputTestUtil.simulate
@ -93,5 +95,97 @@ class BindingManagerTest {
assertTrue(input.bindings.isDown(dummy))
}
// TODO: input handler test
fun `ignore if consumer is set`() {
val input = create()
var invoked = 0
input.bindings.register(dummy, KeyBinding(mapOf(KeyActions.PRESS to setOf(KeyCodes.KEY_1)))) { invoked++ }
input.handler.handler = InputTestUtil.Handler
input.simulate(KeyCodes.KEY_1, KeyChangeTypes.PRESS)
assertEquals(invoked, 0)
}
fun `consumer set and ignore that`() {
val input = create()
var invoked = 0
input.bindings.register(dummy, KeyBinding(mapOf(KeyActions.PRESS to setOf(KeyCodes.KEY_1)), ignoreConsumer = true)) { invoked++ }
input.handler.handler = InputTestUtil.Handler
input.simulate(KeyCodes.KEY_1, KeyChangeTypes.PRESS)
assertEquals(invoked, 1)
}
fun `skip next char if opened via keybinding`() {
val input = create()
var pressed: Int? = null
val handler = object : InputHandler {
override fun onCharPress(char: Int): Boolean {
pressed = char
return true
}
}
input.bindings.register(dummy, KeyBinding(mapOf(KeyActions.PRESS to setOf(KeyCodes.KEY_1)))) { input.handler.handler = handler }
input.simulate(KeyCodes.KEY_1, KeyChangeTypes.PRESS)
assertNull(pressed)
input.simulate(10)
assertNull(pressed)
input.simulate(12)
assertEquals(pressed, 12)
}
fun `don't skip unprintable skip next char if opened via keybinding`() {
val input = create()
var pressed: Int? = null
val handler = object : InputHandler {
override fun onCharPress(char: Int): Boolean {
pressed = char
return true
}
}
input.bindings.register(dummy, KeyBinding(mapOf(KeyActions.PRESS to setOf(KeyCodes.KEY_ESCAPE)))) { input.handler.handler = handler }
input.simulate(KeyCodes.KEY_ESCAPE, KeyChangeTypes.PRESS)
assertNull(pressed)
input.simulate(10)
assertEquals(pressed, 10)
}
fun `unpress key combination if handler was set`() {
val input = create()
var state = false
input.bindings.register(dummy, KeyBinding(mapOf(KeyActions.CHANGE to setOf(KeyCodes.KEY_1)))) { state = it }
input.simulate(KeyCodes.KEY_1, KeyChangeTypes.PRESS)
assertTrue(state)
input.handler.handler = InputTestUtil.Handler
assertFalse(state)
}
fun `press key before modifier`() {
val input = create()
var pressed = 0
input.bindings.register(dummy, KeyBinding(mapOf(KeyActions.PRESS to setOf(KeyCodes.KEY_1), KeyActions.MODIFIER to setOf(KeyCodes.KEY_2)))) { pressed++ }
input.simulate(KeyCodes.KEY_1, KeyChangeTypes.PRESS)
assertEquals(pressed, 0)
input.simulate(KeyCodes.KEY_2, KeyChangeTypes.PRESS)
assertEquals(pressed, 0)
input.simulate(KeyCodes.KEY_2, KeyChangeTypes.RELEASE)
assertEquals(pressed, 0)
}
fun `press modifier before key`() {
val input = create()
var pressed = 0
input.bindings.register(dummy, KeyBinding(mapOf(KeyActions.PRESS to setOf(KeyCodes.KEY_1), KeyActions.MODIFIER to setOf(KeyCodes.KEY_2)))) { pressed++ }
input.simulate(KeyCodes.KEY_2, KeyChangeTypes.PRESS)
assertEquals(pressed, 0)
input.simulate(KeyCodes.KEY_1, KeyChangeTypes.PRESS)
assertEquals(pressed, 1)
input.simulate(KeyCodes.KEY_2, KeyChangeTypes.RELEASE)
assertEquals(pressed, 1)
}
}

View File

@ -261,7 +261,7 @@ class Modifier {
assertFalse(state.satisfied)
}
fun `multiple keys, not all pressed`() {
fun `multiple keys, one pressed`() {
val state = KeyBindingFilterState(true)
KeyActionFilter.Modifier.check(
state, setOf(KeyCodes.KEY_0, KeyCodes.KEY_1, KeyCodes.KEY_2), input(), name,
@ -271,7 +271,7 @@ class Modifier {
0L,
)
assertFalse(state.satisfied)
assertTrue(state.satisfied)
}
fun `multiple keys, all pressed`() {

View File

@ -175,5 +175,9 @@ enum class KeyCodes {
KEY_CODE_MAP = keyCodeMap
}
fun KeyCodes.isPrintable(): Boolean {
return this.ordinal in KEY_SPACE.ordinal..KEY_WORLD_2.ordinal
}
}
}

View File

@ -15,6 +15,7 @@ package de.bixilon.minosoft.gui.rendering.input.key.manager
import de.bixilon.kotlinglm.vec2.Vec2
import de.bixilon.minosoft.config.key.KeyCodes
import de.bixilon.minosoft.config.key.KeyCodes.Companion.isPrintable
import de.bixilon.minosoft.gui.rendering.input.InputHandler
import de.bixilon.minosoft.gui.rendering.system.window.CursorModes
import de.bixilon.minosoft.gui.rendering.system.window.KeyChangeTypes
@ -28,7 +29,12 @@ class InputHandlerManager(
if (field == value) {
return
}
val previous = field
field = value
if ((previous == null) == (value == null)) return
if (previous == null) {
skipMouse = true
}
if (value == null) {
disable()
@ -86,4 +92,16 @@ class InputHandlerManager(
private fun disable() {
context.window.cursorMode = CursorModes.DISABLED
}
fun checkSkip(code: KeyCodes, pressed: Boolean, previous: InputHandler?) {
val next = handler
if (next == null || previous == next) return
if (pressed) {
this.skipKey = true
}
if (code.isPrintable()) {
this.skipChar = true
}
}
}

View File

@ -94,11 +94,14 @@ class InputManager(
this.pressed -= code
}
val handler = this.handler.handler
bindings.onKey(code, pressed, millis)
if (pressed) {
times[code] = millis
}
this.handler.checkSkip(code, pressed, handler)
}
private fun onChar(char: Int) {

View File

@ -59,15 +59,11 @@ interface KeyActionFilter {
object Modifier : KeyActionFilter {
override fun check(filter: KeyBindingFilterState, codes: Set<KeyCodes>, input: InputManager, name: ResourceLocation, state: KeyBindingState, code: KeyCodes, pressed: Boolean, millis: Long) {
if (code !in codes) {
filter.satisfied = false
return
}
if (codes.size == 1) { // optimize if (as most) key has just one modifier key
if (pressed) return
if (!pressed) {
filter.satisfied = false
return
}
if (code in codes) return
if (input.areKeysDown(codes)) return
filter.satisfied = false