/*******************************************************************************
* 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 com.badlogic.gdx.graphics.Pixmap;
import org.robovm.cocoatouch.coregraphics.CGPoint;
import org.robovm.cocoatouch.foundation.NSArray;
import org.robovm.cocoatouch.foundation.NSSet;
import org.robovm.cocoatouch.uikit.UIAcceleration;
import org.robovm.cocoatouch.uikit.UIAccelerometer;
import org.robovm.cocoatouch.uikit.UIAccelerometerDelegate;
import org.robovm.cocoatouch.uikit.UIAlertView;
import org.robovm.cocoatouch.uikit.UIAlertViewDelegate;
import org.robovm.cocoatouch.uikit.UIAlertViewStyle;
import org.robovm.cocoatouch.uikit.UIApplication;
import org.robovm.cocoatouch.uikit.UIEvent;
import org.robovm.cocoatouch.uikit.UIInterfaceOrientation;
import org.robovm.cocoatouch.uikit.UITextField;
import org.robovm.cocoatouch.uikit.UITouch;
import org.robovm.cocoatouch.uikit.UITouchPhase;
import org.robovm.cocoatouch.uikit.UIView;
import org.robovm.objc.ObjCClass;
import com.badlogic.gdx.Input;
import com.badlogic.gdx.InputProcessor;
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;
static {
// HACK: Make sure UITouch has been registered
ObjCClass.getByType(UITouch.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<TouchEvent> touchEventPool = new Pool<TouchEvent>() {
@Override
protected TouchEvent newObject() {
return new TouchEvent();
}
};
Array<TouchEvent> touchEvents = new Array<TouchEvent>();
TouchEvent currentEvent = null;
float[] acceleration = new float[3];
InputProcessor inputProcessor = null;
// We need to hold on to the reference to this delegate or else its
// ObjC peer will get released when the Java peer is GCed.
UIAccelerometerDelegate accelerometerDelegate;
public IOSInput(IOSApplication app) {
this.app = app;
this.config = app.config;
}
void setupPeripherals() {
setupAccelerometer();
setupCompass();
}
private void setupCompass () {
if(config.useCompass) {
// FIXME implement compass
}
}
private void setupAccelerometer() {
if(config.useAccelerometer) {
accelerometerDelegate = new UIAccelerometerDelegate.Adapter() {
@Override
public void didAccelerate(UIAccelerometer accelerometer, UIAcceleration values) {
float x = (float)values.getX() * 10;
float y = (float)values.getY() * 10;
float z = (float)values.getZ() * 10;
UIInterfaceOrientation orientation = app.graphics.viewController != null
? app.graphics.viewController.getInterfaceOrientation()
: UIApplication.getSharedApplication().getStatusBarOrientation();
acceleration[0] = -x;
acceleration[1] = -y;
acceleration[2] = -z;
}
};
UIAccelerometer.getSharedAccelerometer().setDelegate(accelerometerDelegate);
UIAccelerometer.getSharedAccelerometer().setUpdateInterval(config.accelerometerUpdate);
}
}
@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() {
// FIXME implement this
return 0;
}
@Override
public float getPitch() {
// FIXME implement this
return 0;
}
@Override
public float getRoll() {
// FIXME implement this
return 0;
}
@Override
public void getRotationMatrix(float[] matrix) {
// FIXME implement this
}
@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() {
return touchDown[0] != 0;
}
@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 void getTextInput(TextInputListener listener, String title, String text) {
final UIAlertView uiAlertView = buildUIAlertView(listener, title, text, null);
uiAlertView.show();
}
// 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 UIAlertViewDelegate.Adapter() {
@Override
public void clicked (UIAlertView view, int 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 getPlaceholderTextInput(TextInputListener listener, String title, String placeholder) {
final UIAlertView uiAlertView = buildUIAlertView(listener, title, null, placeholder);
uiAlertView.show();
}
@Override
public void setOnscreenKeyboardVisible(boolean visible) {
}
@Override
public void vibrate(int milliseconds) {
// FIXME implement this
}
@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 void setCatchMenuKey(boolean catchMenu) {
}
@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;
// FIXME implement this (not sure if possible)
// if(peripheral == Peripheral.OnscreenKeyboard) return true;
// FIXME implement this
// if(peripheral == Peripheral.Compass) return true;
return false;
}
@Override
public int getRotation() {
UIInterfaceOrientation orientation = app.graphics.viewController != null
? app.graphics.viewController.getInterfaceOrientation()
: UIApplication.getSharedApplication().getStatusBarOrientation();
// we measure orientation counter clockwise, just like on Android
if(orientation == UIInterfaceOrientation.Portrait) return 0;
if(orientation == UIInterfaceOrientation.LandscapeLeft) return 270;
if(orientation == UIInterfaceOrientation.PortraitUpsideDown) return 180;
if(orientation == UIInterfaceOrientation.LandscapeRight) return 90;
return 0;
}
@Override
public Orientation getNativeOrientation() {
return Orientation.Portrait;
}
@Override
public void setCursorCatched(boolean catched) {
}
@Override
public boolean isCursorCatched() {
return false;
}
@Override
public void setCursorPosition(int x, int y) {
}
@Override
public void setCursorImage(Pixmap pixmap, int xHotspot, int yHotspot) {
}
public void touchDown(NSSet touches, UIEvent event) {
toTouchEvents(touches, event);
}
public void touchUp(NSSet touches, UIEvent event) {
toTouchEvents(touches, event);
}
public void touchMoved(NSSet touches, UIEvent event) {
toTouchEvents(touches, event);
}
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 void toTouchEvents(NSSet touches, UIEvent uiEvent) {
for (UITouch touch : (NSSet<UITouch>) touches) {
CGPoint loc = touch.getLocation(touch.getView());
synchronized(touchEvents) {
TouchEvent event = touchEventPool.obtain();
event.x = (int)(loc.x() * app.displayScaleFactor);
event.y = (int)(loc.y() * app.displayScaleFactor);
event.phase = touch.getPhase();
event.timestamp = (long)(touch.getTimestamp() * 1000000000);
touchEvents.add(event);
if(touch.getPhase() == UITouchPhase.Began) {
event.pointer = getFreePointer();
touchDown[event.pointer] = (int) touch.getHandle();
touchX[event.pointer] = event.x;
touchY[event.pointer] = event.y;
deltaX[event.pointer] = 0;
deltaY[event.pointer] = 0;
numTouched++;
}
if(touch.getPhase() == UITouchPhase.Moved ||
touch.getPhase() == 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(touch.getPhase() == UITouchPhase.Cancelled ||
touch.getPhase() == 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;
}
}