com.badlogic.gdx.backends.iosrobovm.IOSInput Maven / Gradle / Ivy
/*******************************************************************************
* Copyright 2011 See AUTHORS file.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
******************************************************************************/
package com.badlogic.gdx.backends.iosrobovm;
import org.robovm.apple.audiotoolbox.AudioServices;
import org.robovm.apple.coregraphics.CGPoint;
import org.robovm.apple.coregraphics.CGRect;
import org.robovm.apple.foundation.NSExtensions;
import org.robovm.apple.foundation.NSObject;
import org.robovm.apple.foundation.NSRange;
import org.robovm.apple.uikit.UIAlertView;
import org.robovm.apple.uikit.UIAlertViewDelegate;
import org.robovm.apple.uikit.UIAlertViewDelegateAdapter;
import org.robovm.apple.uikit.UIAlertViewStyle;
import org.robovm.apple.uikit.UIDevice;
import org.robovm.apple.uikit.UIKeyboardType;
import org.robovm.apple.uikit.UIReturnKeyType;
import org.robovm.apple.uikit.UITextAutocapitalizationType;
import org.robovm.apple.uikit.UITextAutocorrectionType;
import org.robovm.apple.uikit.UITextField;
import org.robovm.apple.uikit.UITextFieldDelegate;
import org.robovm.apple.uikit.UITextFieldDelegateAdapter;
import org.robovm.apple.uikit.UITextSpellCheckingType;
import org.robovm.apple.uikit.UITouch;
import org.robovm.apple.uikit.UITouchPhase;
import org.robovm.objc.annotation.Method;
import org.robovm.rt.VM;
import org.robovm.rt.bro.NativeObject;
import org.robovm.rt.bro.annotation.MachineSizedUInt;
import org.robovm.rt.bro.annotation.Pointer;
import com.badlogic.gdx.Gdx;
import com.badlogic.gdx.Input;
import com.badlogic.gdx.InputProcessor;
import com.badlogic.gdx.backends.iosrobovm.custom.UIAcceleration;
import com.badlogic.gdx.backends.iosrobovm.custom.UIAccelerometer;
import com.badlogic.gdx.backends.iosrobovm.custom.UIAccelerometerDelegate;
import com.badlogic.gdx.backends.iosrobovm.custom.UIAccelerometerDelegateAdapter;
import com.badlogic.gdx.utils.Array;
import com.badlogic.gdx.utils.GdxRuntimeException;
import com.badlogic.gdx.utils.Pool;
public class IOSInput implements Input {
static final int MAX_TOUCHES = 20;
private static class NSObjectWrapper {
private static final long HANDLE_OFFSET;
static {
try {
HANDLE_OFFSET = VM.getInstanceFieldOffset(VM.getFieldAddress(NativeObject.class.getDeclaredField("handle")));
} catch (Throwable t) {
throw new Error(t);
}
}
private final T instance;
public NSObjectWrapper (Class cls) {
instance = VM.allocateObject(cls);
}
public T wrap (long handle) {
VM.setLong(VM.getObjectAddress(instance) + HANDLE_OFFSET, handle);
return instance;
}
}
private static final NSObjectWrapper UI_TOUCH_WRAPPER = new NSObjectWrapper(UITouch.class);
static final NSObjectWrapper UI_ACCELERATION_WRAPPER = new NSObjectWrapper(UIAcceleration.class);
IOSApplication app;
IOSApplicationConfiguration config;
int[] deltaX = new int[MAX_TOUCHES];
int[] deltaY = new int[MAX_TOUCHES];
int[] touchX = new int[MAX_TOUCHES];
int[] touchY = new int[MAX_TOUCHES];
// we store the pointer to the UITouch struct here, or 0
long[] touchDown = new long[MAX_TOUCHES];
int numTouched = 0;
boolean justTouched = false;
Pool touchEventPool = new Pool() {
@Override
protected TouchEvent newObject () {
return new TouchEvent();
}
};
Array touchEvents = new Array();
TouchEvent currentEvent = null;
float[] acceleration = new float[3];
float[] rotation = new float[3];
float[] R = new float[9];
InputProcessor inputProcessor = null;
boolean hasVibrator;
//CMMotionManager motionManager;
UIAccelerometerDelegate accelerometerDelegate;
boolean compassSupported;
boolean keyboardCloseOnReturn;
public IOSInput (IOSApplication app) {
this.app = app;
this.config = app.config;
this.keyboardCloseOnReturn = app.config.keyboardCloseOnReturn;
}
void setupPeripherals () {
//motionManager = new CMMotionManager();
setupAccelerometer();
setupCompass();
UIDevice device = UIDevice.getCurrentDevice();
if (device.getModel().equalsIgnoreCase("iphone")) hasVibrator = true;
}
private void setupCompass () {
if (config.useCompass) {
//setupMagnetometer();
}
}
private void setupAccelerometer () {
if (config.useAccelerometer) {
accelerometerDelegate = new UIAccelerometerDelegateAdapter() {
@Method(selector = "accelerometer:didAccelerate:")
public void didAccelerate (UIAccelerometer accelerometer, @Pointer long valuesPtr) {
UIAcceleration values = UI_ACCELERATION_WRAPPER.wrap(valuesPtr);
float x = (float)values.getX() * 10;
float y = (float)values.getY() * 10;
float z = (float)values.getZ() * 10;
acceleration[0] = -x;
acceleration[1] = -y;
acceleration[2] = -z;
}
};
UIAccelerometer.getSharedAccelerometer().setDelegate(accelerometerDelegate);
UIAccelerometer.getSharedAccelerometer().setUpdateInterval(config.accelerometerUpdate);
}
}
// need to retain a reference so GC doesn't get right of the
// object passed to the native thread
// VoidBlock2 accelVoid = null;
// private void setupAccelerometer () {
// if (config.useAccelerometer) {
// motionManager.setAccelerometerUpdateInterval(config.accelerometerUpdate);
// accelVoid = new VoidBlock2() {
// @Override
// public void invoke(CMAccelerometerData accelData, NSError error) {
// updateAccelerometer(accelData);
// }
// };
// motionManager.startAccelerometerUpdates(new NSOperationQueue(), accelVoid);
// }
// }
// need to retain a reference so GC doesn't get right of the
// object passed to the native thread
// VoidBlock2 magnetVoid = null;
// private void setupMagnetometer () {
// if (motionManager.isMagnetometerAvailable() && config.useCompass) compassSupported = true;
// else return;
// motionManager.setMagnetometerUpdateInterval(config.magnetometerUpdate);
// magnetVoid = new VoidBlock2() {
// @Override
// public void invoke(CMMagnetometerData magnetData, NSError error) {
// updateRotation(magnetData);
// }
// };
// motionManager.startMagnetometerUpdates(new NSOperationQueue(), magnetVoid);
// }
// private void updateAccelerometer (CMAccelerometerData data) {
// float x = (float) data.getAcceleration().x() * 10f;
// float y = (float) data.getAcceleration().y() * 10f;
// float z = (float) data.getAcceleration().z() * 10f;
// acceleration[0] = -x;
// acceleration[1] = -y;
// acceleration[2] = -z;
// }
//
// private void updateRotation (CMMagnetometerData data) {
// final float eX = (float) data.getMagneticField().x();
// final float eY = (float) data.getMagneticField().y();
// final float eZ = (float) data.getMagneticField().z();
//
// float gX = acceleration[0];
// float gY = acceleration[1];
// float gZ = acceleration[2];
//
// float cX = eY * gZ - eZ * gY;
// float cY = eZ * gX - eX * gZ;
// float cZ = eX * gY - eY * gX;
//
// final float normal = (float) Math.sqrt(cX * cX + cY * cY + cZ * cZ);
// final float invertC = 1.0f / normal;
// cX *= invertC;
// cY *= invertC;
// cZ *= invertC;
// final float invertG = 1.0f / (float) Math.sqrt(gX * gX + gY * gY + gZ * gZ);
// gX *= invertG;
// gY *= invertG;
// gZ *= invertG;
// final float mX = gY * cZ - gZ * cY;
// final float mY = gZ * cX - gX * cZ;
// final float mZ = gX * cY - gY * cX;
//
// R[0] = cX; R[1] = cY; R[2] = cZ;
// R[3] = mX; R[4] = mY; R[5] = mZ;
// R[6] = gX; R[7] = gY; R[8] = gZ;
//
// rotation[0] = (float) Math.atan2(R[1], R[4]) * MathUtils.radDeg;
// rotation[1] = (float) Math.asin(-R[7]) * MathUtils.radDeg;
// rotation[2] = (float) Math.atan2(-R[6], R[8]) * MathUtils.radDeg;
// }
@Override
public float getAccelerometerX () {
return acceleration[0];
}
@Override
public float getAccelerometerY () {
return acceleration[1];
}
@Override
public float getAccelerometerZ () {
return acceleration[2];
}
@Override
public float getAzimuth () {
if (!compassSupported) return 0;
return rotation[0];
}
@Override
public float getPitch () {
if (!compassSupported) return 0;
return rotation[1];
}
@Override
public float getRoll () {
if (!compassSupported) return 0;
return rotation[2];
}
@Override
public void getRotationMatrix (float[] matrix) {
if (matrix.length != 9) return;
//TODO implement when azimuth is fixed
}
@Override
public int getX () {
return touchX[0];
}
@Override
public int getX (int pointer) {
return touchX[pointer];
}
@Override
public int getDeltaX () {
return deltaX[0];
}
@Override
public int getDeltaX (int pointer) {
return deltaX[pointer];
}
@Override
public int getY () {
return touchY[0];
}
@Override
public int getY (int pointer) {
return touchY[pointer];
}
@Override
public int getDeltaY () {
return deltaY[0];
}
@Override
public int getDeltaY (int pointer) {
return deltaY[pointer];
}
@Override
public boolean isTouched () {
for (int pointer = 0; pointer < MAX_TOUCHES; pointer++) {
if (touchDown[pointer] != 0) {
return true;
}
}
return false;
}
@Override
public boolean justTouched () {
return justTouched;
}
@Override
public boolean isTouched (int pointer) {
return touchDown[pointer] != 0;
}
@Override
public boolean isButtonPressed (int button) {
return button == Buttons.LEFT && numTouched > 0;
}
@Override
public boolean isKeyPressed (int key) {
return false;
}
@Override
public boolean isKeyJustPressed (int key) {
return false;
}
@Override
public void getTextInput(TextInputListener listener, String title, String text, String hint) {
buildUIAlertView(listener, title, text, hint).show();
}
// hack for software keyboard support
// uses a hidden textfield to capture input
// see: http://www.badlogicgames.com/forum/viewtopic.php?f=17&t=11788
private class HiddenTextField extends UITextField {
public HiddenTextField (CGRect frame) {
super(frame);
setKeyboardType(UIKeyboardType.Default);
setReturnKeyType(UIReturnKeyType.Done);
setAutocapitalizationType(UITextAutocapitalizationType.None);
setAutocorrectionType(UITextAutocorrectionType.No);
setSpellCheckingType(UITextSpellCheckingType.No);
setHidden(true);
}
@Override
public void deleteBackward () {
app.input.inputProcessor.keyTyped((char)8);
super.deleteBackward();
Gdx.graphics.requestRendering();
}
}
private UITextField textfield = null;
private final UITextFieldDelegate textDelegate = new UITextFieldDelegateAdapter() {
@Override
public boolean shouldChangeCharacters (UITextField textField, NSRange range, String string) {
for (int i = 0; i < range.getLength(); i++) {
app.input.inputProcessor.keyTyped((char)8);
}
if (string.isEmpty()) {
if (range.getLength() > 0) Gdx.graphics.requestRendering();
return false;
}
char[] chars = new char[string.length()];
string.getChars(0, string.length(), chars, 0);
for (int i = 0; i < chars.length; i++) {
app.input.inputProcessor.keyTyped(chars[i]);
}
Gdx.graphics.requestRendering();
return true;
}
@Override
public boolean shouldEndEditing (UITextField textField) {
// Text field needs to have at least one symbol - so we can use backspace
textField.setText("x");
Gdx.graphics.requestRendering();
return true;
}
@Override
public boolean shouldReturn (UITextField textField) {
if (keyboardCloseOnReturn) setOnscreenKeyboardVisible(false);
app.input.inputProcessor.keyDown(Keys.ENTER);
app.input.inputProcessor.keyTyped((char)13);
Gdx.graphics.requestRendering();
return false;
}
};
@Override
public void setOnscreenKeyboardVisible (boolean visible) {
if (textfield == null) createDefaultTextField();
if (visible) {
textfield.becomeFirstResponder();
textfield.setDelegate(textDelegate);
} else {
textfield.resignFirstResponder();
}
}
/**
* Set the keyboard to close when the UITextField return key is pressed
* @param shouldClose Whether or not the keyboard should clsoe on return key press
*/
public void setKeyboardCloseOnReturnKey (boolean shouldClose) {
keyboardCloseOnReturn = shouldClose;
}
public UITextField getKeyboardTextField () {
if (textfield == null) createDefaultTextField();
return textfield;
}
private void createDefaultTextField () {
textfield = new UITextField(new CGRect(10, 10, 100, 50));
//Parameters
// Setting parameters
textfield.setKeyboardType(UIKeyboardType.Default);
textfield.setReturnKeyType(UIReturnKeyType.Done);
textfield.setAutocapitalizationType(UITextAutocapitalizationType.None);
textfield.setAutocorrectionType(UITextAutocorrectionType.No);
textfield.setSpellCheckingType(UITextSpellCheckingType.No);
textfield.setHidden(true);
// Text field needs to have at least one symbol - so we can use backspace
textfield.setText("x");
app.getUIViewController().getView().addSubview(textfield);
}
// Issue 773 indicates this may solve a premature GC issue
UIAlertViewDelegate delegate;
/** Builds an {@link UIAlertView} with an added {@link UITextField} for inputting text.
* @param listener Text input listener
* @param title Dialog title
* @param text Text for text field
* @return UiAlertView */
private UIAlertView buildUIAlertView (final TextInputListener listener, String title, String text, String placeholder) {
delegate = new UIAlertViewDelegateAdapter() {
@Override
public void clicked (UIAlertView view, long clicked) {
if (clicked == 0) {
// user clicked "Cancel" button
listener.canceled();
} else if (clicked == 1) {
// user clicked "Ok" button
UITextField textField = view.getTextField(0);
listener.input(textField.getText());
}
delegate = null;
}
@Override
public void cancel (UIAlertView view) {
listener.canceled();
delegate = null;
}
};
// build the view
final UIAlertView uiAlertView = new UIAlertView();
uiAlertView.setTitle(title);
uiAlertView.addButton("Cancel");
uiAlertView.addButton("Ok");
uiAlertView.setAlertViewStyle(UIAlertViewStyle.PlainTextInput);
uiAlertView.setDelegate(delegate);
UITextField textField = uiAlertView.getTextField(0);
textField.setPlaceholder(placeholder);
textField.setText(text);
return uiAlertView;
}
@Override
public void vibrate (int milliseconds) {
AudioServices.playSystemSound(4095);
}
@Override
public void vibrate (long[] pattern, int repeat) {
// FIXME implement this
}
@Override
public void cancelVibrate () {
// FIXME implement this
}
@Override
public long getCurrentEventTime () {
return currentEvent.timestamp;
}
@Override
public void setCatchBackKey (boolean catchBack) {
}
@Override
public boolean isCatchBackKey () {
return false;
}
@Override
public void setCatchMenuKey (boolean catchMenu) {
}
@Override
public boolean isCatchMenuKey() {
return false;
}
@Override
public void setInputProcessor (InputProcessor processor) {
this.inputProcessor = processor;
}
@Override
public InputProcessor getInputProcessor () {
return inputProcessor;
}
@Override
public boolean isPeripheralAvailable (Peripheral peripheral) {
if (peripheral == Peripheral.Accelerometer && config.useAccelerometer) return true;
if (peripheral == Peripheral.MultitouchScreen) return true;
if (peripheral == Peripheral.Vibrator) return hasVibrator;
if (peripheral == Peripheral.Compass) return compassSupported;
// if(peripheral == Peripheral.OnscreenKeyboard) return true;
return false;
}
@Override
public int getRotation () {
// we measure orientation counter clockwise, just like on Android
switch (app.uiApp.getStatusBarOrientation()) {
case LandscapeLeft:
return 270;
case PortraitUpsideDown:
return 180;
case LandscapeRight:
return 90;
case Portrait:
default:
return 0;
}
}
@Override
public Orientation getNativeOrientation () {
switch (app.uiApp.getStatusBarOrientation()) {
case LandscapeLeft:
case LandscapeRight:
return Orientation.Landscape;
default:
return Orientation.Portrait;
}
}
@Override
public void setCursorCatched (boolean catched) {
}
@Override
public boolean isCursorCatched () {
return false;
}
@Override
public void setCursorPosition (int x, int y) {
}
protected void onTouch (long touches) {
toTouchEvents(touches);
Gdx.graphics.requestRendering();
}
void processEvents () {
synchronized (touchEvents) {
justTouched = false;
for (TouchEvent event : touchEvents) {
currentEvent = event;
switch (event.phase) {
case Began:
if (inputProcessor != null) inputProcessor.touchDown(event.x, event.y, event.pointer, Buttons.LEFT);
if (numTouched == 1) justTouched = true;
break;
case Cancelled:
case Ended:
if (inputProcessor != null) inputProcessor.touchUp(event.x, event.y, event.pointer, Buttons.LEFT);
break;
case Moved:
case Stationary:
if (inputProcessor != null) inputProcessor.touchDragged(event.x, event.y, event.pointer);
break;
}
}
touchEventPool.freeAll(touchEvents);
touchEvents.clear();
}
}
private int getFreePointer () {
for (int i = 0; i < touchDown.length; i++) {
if (touchDown[i] == 0) return i;
}
throw new GdxRuntimeException("Couldn't find free pointer id!");
}
private int findPointer (UITouch touch) {
long ptr = touch.getHandle();
for (int i = 0; i < touchDown.length; i++) {
if (touchDown[i] == ptr) return i;
}
throw new GdxRuntimeException("Couldn't find pointer id for touch event!");
}
private static class NSSetExtensions extends NSExtensions {
@Method(selector = "allObjects")
public static native @Pointer long allObjects (@Pointer long thiz);
}
private static class NSArrayExtensions extends NSExtensions {
@Method(selector = "objectAtIndex:")
public static native @Pointer long objectAtIndex$ (@Pointer long thiz, @MachineSizedUInt long index);
@Method(selector = "count")
public static native @MachineSizedUInt long count (@Pointer long thiz);
}
private void toTouchEvents (long touches) {
long array = NSSetExtensions.allObjects(touches);
int length = (int)NSArrayExtensions.count(array);
for (int i = 0; i < length; i++) {
long touchHandle = NSArrayExtensions.objectAtIndex$(array, i);
UITouch touch = UI_TOUCH_WRAPPER.wrap(touchHandle);
final int locX, locY;
// Get and map the location to our drawing space
{
CGPoint loc = touch.getLocationInView(touch.getWindow());
final CGRect bounds = app.getCachedBounds();
locX = (int)(loc.getX() * app.displayScaleFactor - bounds.getMinX());
locY = (int)(loc.getY() * app.displayScaleFactor - bounds.getMinY());
// app.debug("IOSInput","pos= "+loc+" bounds= "+bounds+" x= "+locX+" locY= "+locY);
}
synchronized (touchEvents) {
UITouchPhase phase = touch.getPhase();
TouchEvent event = touchEventPool.obtain();
event.x = locX;
event.y = locY;
event.phase = phase;
event.timestamp = (long)(touch.getTimestamp() * 1000000000);
touchEvents.add(event);
if (phase == UITouchPhase.Began) {
event.pointer = getFreePointer();
touchDown[event.pointer] = touch.getHandle();
touchX[event.pointer] = event.x;
touchY[event.pointer] = event.y;
deltaX[event.pointer] = 0;
deltaY[event.pointer] = 0;
numTouched++;
}
if (phase == UITouchPhase.Moved || phase == UITouchPhase.Stationary) {
event.pointer = findPointer(touch);
deltaX[event.pointer] = event.x - touchX[event.pointer];
deltaY[event.pointer] = event.y - touchY[event.pointer];
touchX[event.pointer] = event.x;
touchY[event.pointer] = event.y;
}
if (phase == UITouchPhase.Cancelled || phase == UITouchPhase.Ended) {
event.pointer = findPointer(touch);
touchDown[event.pointer] = 0;
touchX[event.pointer] = event.x;
touchY[event.pointer] = event.y;
deltaX[event.pointer] = 0;
deltaY[event.pointer] = 0;
numTouched--;
}
}
}
}
static class TouchEvent {
UITouchPhase phase;
long timestamp;
int x, y;
int pointer;
}
@Override
public float getGyroscopeX() {
// TODO Auto-generated method stub
return 0;
}
@Override
public float getGyroscopeY() {
// TODO Auto-generated method stub
return 0;
}
@Override
public float getGyroscopeZ() {
// TODO Auto-generated method stub
return 0;
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy