All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.badlogic.gdx.backends.iosrobovm.IOSMini2DxInput Maven / Gradle / Ivy

There is a newer version: 1.9.10
Show newest version
/*******************************************************************************
 * Copyright 2011 See LIBGDX_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.mini2Dx.ios.IOSMini2DxConfig;
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;

/**
 * Based on LibGDX's IOSInput class
 */
public class IOSMini2DxInput 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);

	IOSMini2DxGame app;
	IOSMini2DxConfig 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 IOSMini2DxInput(IOSMini2DxGame 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);
		}
	}

	@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 - 2024 Weber Informatics LLC | Privacy Policy