package com.badlogic.gdx.automation.recorder; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.util.ArrayList; import java.util.Arrays; import java.util.Map; import com.badlogic.gdx.Application.ApplicationType; import com.badlogic.gdx.Gdx; import com.badlogic.gdx.Input; import com.badlogic.gdx.automation.recorder.InputProperty.SyncProperty; import com.badlogic.gdx.automation.recorder.InputProperty.SyncProperty.Accelerometer; import com.badlogic.gdx.automation.recorder.InputProperty.SyncProperty.Button; import com.badlogic.gdx.automation.recorder.InputProperty.SyncProperty.KeyEvent; import com.badlogic.gdx.automation.recorder.InputProperty.SyncProperty.KeyPressed; import com.badlogic.gdx.automation.recorder.InputProperty.SyncProperty.Orientation; import com.badlogic.gdx.automation.recorder.InputProperty.SyncProperty.Pointer; import com.badlogic.gdx.automation.recorder.InputProperty.SyncProperty.PointerEvent; import com.badlogic.gdx.automation.recorder.InputProperty.SyncProperty.Type; import com.badlogic.gdx.automation.recorder.InputProperty.SyncPropertyVisitor; import com.badlogic.gdx.utils.TimeUtils; /** * A snapshot of all real-time values to be read from an input at a certain * time. Especially the {@link #set(InputState, int)} method is useful to take a * snapshot of a configurable set of properties from a given {@link Input} * * @author Lukas Böhm */ public class InputState { private int MAX_POINTERS; private final SyncValueApplier applier; public float accelerometerX; public float accelerometerY; public float accelerometerZ; public int[] x; public int[] y; public int[] deltaX; public int[] deltaY; public boolean[] touched; public boolean justTouched; public boolean button0; public boolean button1; public boolean button2; public final SparseArray<Boolean> pressedKeys; public final ArrayList<EventBufferAccessHelper.KeyEvent> keyEvents; public final ArrayList<EventBufferAccessHelper.PointerEvent> pointerEvents; public float pitch; public float roll; public float azimuth; public final float[] rotationMatrix = new float[16]; public int orientation; public long timeStamp = TimeUtils.millis(); public InputState() { applier = new SyncValueApplier(); pressedKeys = new SparseArray<Boolean>(); keyEvents = new ArrayList<EventBufferAccessHelper.KeyEvent>(); pointerEvents = new ArrayList<EventBufferAccessHelper.PointerEvent>(); } public InputState(int maxPointers) { this(); if (maxPointers > 20) { Gdx.app.log( InputRecorder.LOG_TAG, "Warning: Most of the libGDX backends only use 20 pointers internally. Trying to use " + maxPointers); } this.MAX_POINTERS = maxPointers; x = new int[MAX_POINTERS]; y = new int[MAX_POINTERS]; deltaX = new int[MAX_POINTERS]; deltaY = new int[MAX_POINTERS]; touched = new boolean[MAX_POINTERS]; } public void initialize(int maxPointers) { pressedKeys.clear(); keyEvents.clear(); pointerEvents.clear(); if (maxPointers != MAX_POINTERS) { if (maxPointers > MAX_POINTERS) { if (maxPointers > 20) { Gdx.app.log( InputRecorder.LOG_TAG, "Warning: Most of the libGDX backends only use 20 pointers internally. Trying to use " + maxPointers); } x = new int[maxPointers]; y = new int[maxPointers]; deltaX = new int[maxPointers]; deltaY = new int[maxPointers]; touched = new boolean[maxPointers]; } else { Arrays.fill(x, 0); Arrays.fill(y, 0); Arrays.fill(deltaX, 0); Arrays.fill(deltaY, 0); Arrays.fill(touched, false); } MAX_POINTERS = maxPointers; } } public void set(InputState state, int copyFlags) { if ((copyFlags & Type.BUTTONS.key) != 0) { button0 = state.button0; button1 = state.button1; button2 = state.button2; } if ((copyFlags & Type.KEY_EVENTS.key) != 0) { keyEvents.clear(); keyEvents.addAll(state.keyEvents); } if ((copyFlags & Type.KEYS_PRESSED.key) != 0) { setPressedKeys(state.pressedKeys); } if ((copyFlags & Type.ORIENTATION.key) != 0) { accelerometerX = state.accelerometerX; accelerometerY = state.accelerometerY; accelerometerZ = state.accelerometerZ; pitch = state.pitch; roll = state.roll; azimuth = state.azimuth; orientation = state.orientation; System.arraycopy(state.rotationMatrix, 0, rotationMatrix, 0, 16); } if ((copyFlags & Type.POINTERS.key) != 0) { System.arraycopy(state.x, 0, x, 0, MAX_POINTERS); System.arraycopy(state.y, 0, y, 0, MAX_POINTERS); System.arraycopy(state.deltaX, 0, deltaX, 0, MAX_POINTERS); System.arraycopy(state.deltaY, 0, deltaY, 0, MAX_POINTERS); System.arraycopy(state.touched, 0, touched, 0, MAX_POINTERS); justTouched = state.justTouched; } if ((copyFlags & Type.POINTER_EVENTS.key) != 0) { pointerEvents.clear(); pointerEvents.addAll(state.pointerEvents); } } public void setPressedKeys(SparseArray<Boolean> pressed) { for (Map.Entry<Integer, Boolean> entry : pressedKeys.entrySet()) { entry.setValue(false); } for (Map.Entry<Integer, Boolean> entry : pressed.entrySet()) { pressedKeys.put(entry.getKey(), Boolean.TRUE); } } /** * Simple forward for {@link #set(Input, int, boolean)} using ~0 as the * second argument to indicate that all values are to be read from the given * input. * * @param input * @param updateEvents */ public void setAll(Input input, boolean updateEvents) { set(input, ~0, updateEvents); } /** * Use OR'ed {@link InputProperty.Type#key}s to define properties * * @param input * @param properties * @param updateEvents * whether to force-pull events from their backend-specific * sources before copying them into this InputState. */ public void set(Input input, int properties, boolean updateEvents) { timeStamp = TimeUtils.millis(); // input in milliseconds should be // sufficient if ((InputProperty.SyncProperty.Type.POINTERS.key & properties) != 0) { setX(input); setY(input); setDeltaX(input); setDeltaY(input); setTouched(input); } if ((InputProperty.SyncProperty.Type.BUTTONS.key & properties) != 0) { setButtons(input); } if ((InputProperty.SyncProperty.Type.KEYS_PRESSED.key & properties) != 0) { setPressedKeys(input); } if ((InputProperty.SyncProperty.Type.KEY_EVENTS.key & properties) != 0) { setKeyEvents(input, updateEvents); } if ((InputProperty.SyncProperty.Type.POINTER_EVENTS.key & properties) != 0) { setPointerEvents(input, updateEvents); } if ((InputProperty.SyncProperty.Type.ORIENTATION.key & properties) != 0) { setOrientation(input); } } private void setX(Input input) { for (int i = 0; i < MAX_POINTERS; i++) { x[i] = input.getX(i); } } private void setY(Input input) { for (int i = 0; i < MAX_POINTERS; i++) { y[i] = input.getY(i); } } private void setDeltaX(Input input) { for (int i = 0; i < MAX_POINTERS; i++) { deltaX[i] = input.getDeltaX(i); } } private void setDeltaY(Input input) { for (int i = 0; i < MAX_POINTERS; i++) { deltaY[i] = input.getDeltaY(i); } } private void setTouched(Input input) { for (int i = 0; i < MAX_POINTERS; i++) { touched[i] = input.isTouched(i); } justTouched = input.justTouched(); } private void setButtons(Input input) { button0 = input.isButtonPressed(0); button1 = input.isButtonPressed(1); button2 = input.isButtonPressed(2); } private void setPressedKeys(Input input) { EventBufferAccessHelper.copyPressedKeys(input, pressedKeys); } private void setKeyEvents(Input input, boolean update) { EventBufferAccessHelper.copyKeyEvents(input, keyEvents, update); } private void setPointerEvents(Input input, boolean update) { EventBufferAccessHelper.copyPointerEvents(input, pointerEvents, update); } private void setOrientation(Input input) { if (Gdx.app.getType() == ApplicationType.Android) { while (input instanceof InputProxy) { input = ((InputProxy) input).getProxiedInput(); } try { Class<?> inputClass = input.getClass(); if (Class.forName( "com.badlogic.gdx.backends.android.AndroidInput") .isAssignableFrom(inputClass)) { Method updateOrientation = inputClass.getDeclaredMethod( "updateOrientation", new Class<?>[0]); updateOrientation.setAccessible(true); updateOrientation.invoke(input, new Object[0]); } } catch (ClassNotFoundException e) { throw new IllegalStateException( "Android application without AndroidInput?"); } catch (NoSuchMethodException e) { throw new IllegalStateException("No updateOrientation?"); } catch (SecurityException e) { throw e; } catch (IllegalAccessException e) { throw new IllegalStateException( "Running in a too restrictive environment..."); } catch (IllegalArgumentException e) { throw e; } catch (InvocationTargetException e) { throw new IllegalStateException("updateOrientation:" + e.getLocalizedMessage()); } } accelerometerX = input.getAccelerometerX(); accelerometerY = input.getAccelerometerY(); accelerometerZ = input.getAccelerometerZ(); pitch = input.getPitch(); roll = input.getRoll(); azimuth = input.getAzimuth(); input.getRotationMatrix(rotationMatrix); } /* * Getter methods "inherited" from Input interface */ private void verifyPointer(int pointer) { if (pointer < 0) { throw new IllegalArgumentException( "Did not expect negative pointer indices"); } if (pointer >= MAX_POINTERS) { throw new IllegalArgumentException("Did not expect more than " + MAX_POINTERS + " pointers"); } } public long getTimeStamp() { return timeStamp; } public float getAccelerometerX() { return accelerometerX; } public float getAccelerometerY() { return accelerometerY; } public float getAccelerometerZ() { return accelerometerZ; } public int getX() { return x[0]; } public int getX(int pointer) { verifyPointer(pointer); return x[pointer]; } public int getDeltaX() { return deltaX[0]; } public int getDeltaX(int pointer) { verifyPointer(pointer); return deltaX[pointer]; } public int getY() { return y[0]; } public int getY(int pointer) { verifyPointer(pointer); return y[pointer]; } public int getDeltaY() { return deltaY[0]; } public int getDeltaY(int pointer) { verifyPointer(pointer); return deltaY[pointer]; } public boolean justTouched() { return justTouched; } public boolean isTouched() { return touched[0]; } public boolean isTouched(int pointer) { verifyPointer(pointer); return touched[pointer]; } public boolean isButtonPressed(int button) { switch (button) { case 0: return button0; case 1: return button1; case 2: return button2; default: return false; } } public boolean isKeyPressed(int key) { if (key == Input.Keys.ANY_KEY) { return pressedKeys.size() > 0; } else { return pressedKeys.containsKey(key) && pressedKeys.get(key) != null && pressedKeys.get(key); } } public float getAzimuth() { return azimuth; } public float getPitch() { return pitch; } public float getRoll() { return roll; } public void getRotationMatrix(float[] matrix) { if (matrix.length < 16 && matrix.length >= 9) { matrix[0] = rotationMatrix[0]; matrix[1] = rotationMatrix[1]; matrix[2] = rotationMatrix[2]; matrix[3] = rotationMatrix[4]; matrix[4] = rotationMatrix[5]; matrix[5] = rotationMatrix[6]; matrix[6] = rotationMatrix[8]; matrix[7] = rotationMatrix[9]; matrix[8] = rotationMatrix[10]; } else if (matrix.length >= 16) { System.arraycopy(rotationMatrix, 0, matrix, 0, 16); } } public int getRotation() { return orientation; } /** * Returns the maximum number of pointers that were tracked by this * {@link InputState}. This should correspond to * {@link InputRecorderConfiguration#recordedPointerCount} * * @return the maximum number of pointers stored */ public int getPointerCount() { return MAX_POINTERS; } public void apply(SyncProperty syncValue) { syncValue.accept(applier); } private class SyncValueApplier implements SyncPropertyVisitor { @Override public void visitAccelerometer(Accelerometer accelerometer) { accelerometerX = accelerometer.accelerometerX; accelerometerY = accelerometer.accelerometerY; accelerometerZ = accelerometer.accelerometerZ; } @Override public void visitKeyPressed(KeyPressed keyPressed) { pressedKeys.put(keyPressed.keyCode, Boolean.TRUE); // TODO where are the keys cleared? } @Override public void visitPointerEvent(PointerEvent pointerEvent) { EventBufferAccessHelper.PointerEvent addedEvent = new EventBufferAccessHelper.PointerEvent( pointerEvent); addedEvent.timeStamp = timeStamp; pointerEvents.add(addedEvent); } @Override public void visitKeyEvent(KeyEvent keyEvent) { EventBufferAccessHelper.KeyEvent addedEvent = new EventBufferAccessHelper.KeyEvent( keyEvent); addedEvent.timeStamp = timeStamp; keyEvents.add(addedEvent); } @Override public void visitOrientation(Orientation orientation) { InputState.this.orientation = orientation.orientation; roll = orientation.roll; pitch = orientation.pitch; azimuth = orientation.azimuth; System.arraycopy(orientation.rotationMatrix, 0, InputState.this.rotationMatrix, 0, 16); } @Override public void visitPointer(Pointer pointer) { x[pointer.pointer] = (int) pointer.x; y[pointer.pointer] = (int) pointer.y; deltaX[pointer.pointer] = (int) pointer.deltaX; deltaY[pointer.pointer] = (int) pointer.deltaY; } @Override public void visitButton(Button button) { button0 = button.button0; button1 = button.button1; button2 = button.button2; } } }