(mSurface), handler));
}
}
-
+
@Override
public void onSurfaceCreated(GL10 gl, EGLConfig config)
{
- if (LOG_GAMELIFECYCLEHANDLER)
+ if (LOG_GAMELIFECYCLEHANDLER)
{
Log.i("GameLifecycleHandler", "onSurfaceCreated");
}
NativeExports.onSurfaceCreated();
}
-
+
@Override
public void onSurfaceChanged(GL10 gl, int width, int height)
{
- if (LOG_GAMELIFECYCLEHANDLER)
+ if (LOG_GAMELIFECYCLEHANDLER)
{
Log.i("GameLifecycleHandler", "onSurfaceChanged");
}
diff --git a/Android/src/emu/project64/game/GameMenuHandler.java b/Android/src/emu/project64/game/GameMenuHandler.java
index 8cc95962c..ec42f9c85 100644
--- a/Android/src/emu/project64/game/GameMenuHandler.java
+++ b/Android/src/emu/project64/game/GameMenuHandler.java
@@ -146,7 +146,7 @@ public class GameMenuHandler implements PopupMenu.OnMenuItemClickListener, Popup
break;
case R.id.menuItem_settings:
Intent SettingsIntent = new Intent(mActivity, SettingsActivity.class);
- mActivity.startActivity( SettingsIntent );
+ mActivity.startActivityForResult( SettingsIntent, GameLifecycleHandler.RC_SETTINGS );
return true;
}
return false;
diff --git a/Android/src/emu/project64/game/GameOverlay.java b/Android/src/emu/project64/game/GameOverlay.java
index 2c8839fb2..5423ad404 100644
--- a/Android/src/emu/project64/game/GameOverlay.java
+++ b/Android/src/emu/project64/game/GameOverlay.java
@@ -44,7 +44,7 @@ public class GameOverlay extends View implements TouchController.OnStateChangedL
requestFocus();
}
- public void initialize( VisibleTouchMap touchMap, boolean drawingEnabled, boolean fpsEnabled, boolean joystickAnimated )
+ public void initialize( VisibleTouchMap touchMap, boolean drawingEnabled, boolean joystickAnimated )
{
mTouchMap = touchMap;
mDrawingEnabled = drawingEnabled;
diff --git a/Android/src/emu/project64/hack/MogaHack.java b/Android/src/emu/project64/hack/MogaHack.java
new file mode 100644
index 000000000..ded933dd6
--- /dev/null
+++ b/Android/src/emu/project64/hack/MogaHack.java
@@ -0,0 +1,110 @@
+/****************************************************************************
+* *
+* Project64 - A Nintendo 64 emulator. *
+* http://www.pj64-emu.com/ *
+* Copyright (C) 2016 Project64. All rights reserved. *
+* Copyright (C) 2013 Paul Lamb
+* *
+* License: *
+* GNU/GPLv2 http://www.gnu.org/licenses/gpl-2.0.html *
+* *
+****************************************************************************/
+package emu.project64.hack;
+
+import java.util.List;
+
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.ResolveInfo;
+import android.content.pm.ServiceInfo;
+import android.util.Log;
+
+import com.bda.controller.Controller;
+import com.bda.controller.IControllerService;
+
+import emu.project64.AndroidDevice;
+
+/**
+ * Temporary hack for crash in MOGA library on Lollipop. This hack can be removed once MOGA fixes
+ * their library. The actual issue is caused by the use of implicit service intents, which are
+ * illegal in Lollipop, as seen in the logcat message below.
+ *
+ *
+ * {@code Service Intent must be explicit: Intent { act=com.bda.controller.IControllerService } }
+ *
+ *
+ * @see MOGA developer site
+ * @see
+ * Discussion on explicit intents
+ */
+public class MogaHack
+{
+ public static void init( Controller controller, Context context )
+ {
+ if( AndroidDevice.IS_LOLLIPOP )
+ {
+ boolean mIsBound = false;
+ java.lang.reflect.Field fIsBound = null;
+ android.content.ServiceConnection mServiceConnection = null;
+ java.lang.reflect.Field fServiceConnection = null;
+ try
+ {
+ Class> cMogaController = controller.getClass();
+ fIsBound = cMogaController.getDeclaredField( "mIsBound" );
+ fIsBound.setAccessible( true );
+ mIsBound = fIsBound.getBoolean( controller );
+ fServiceConnection = cMogaController.getDeclaredField( "mServiceConnection" );
+ fServiceConnection.setAccessible( true );
+ mServiceConnection = ( android.content.ServiceConnection ) fServiceConnection.get( controller );
+ }
+ catch( NoSuchFieldException e )
+ {
+ Log.e( "MogaHack", "MOGA Lollipop Hack NoSuchFieldException (get)", e );
+ }
+ catch( IllegalAccessException e )
+ {
+ Log.e( "MogaHack", "MOGA Lollipop Hack IllegalAccessException (get)", e );
+ }
+ catch( IllegalArgumentException e )
+ {
+ Log.e( "MogaHack", "MOGA Lollipop Hack IllegalArgumentException (get)", e );
+ }
+ if( ( !mIsBound ) && ( mServiceConnection != null ) )
+ {
+ // Convert implicit intent to explicit intent, see http://stackoverflow.com/a/26318757
+ Intent intent = new Intent( IControllerService.class.getName() );
+ List resolveInfos = context.getPackageManager().queryIntentServices( intent, 0 );
+ if( resolveInfos == null || resolveInfos.size() != 1 )
+ {
+ Log.e( "MogaHack", "Somebody is trying to intercept our intent. Disabling MOGA controller for security." );
+ return;
+ }
+ ServiceInfo serviceInfo = resolveInfos.get( 0 ).serviceInfo;
+ String packageName = serviceInfo.packageName;
+ String className = serviceInfo.name;
+ intent.setComponent( new ComponentName( packageName, className ) );
+
+ // Start the service explicitly
+ context.startService( intent );
+ context.bindService( intent, mServiceConnection, 1 );
+ try
+ {
+ fIsBound.setBoolean( controller, true );
+ }
+ catch( IllegalAccessException e )
+ {
+ Log.e( "MogaHack", "MOGA Lollipop Hack IllegalAccessException (set)", e );
+ }
+ catch( IllegalArgumentException e )
+ {
+ Log.e( "MogaHack", "MOGA Lollipop Hack IllegalArgumentException (set)", e );
+ }
+ }
+ }
+ else
+ {
+ controller.init();
+ }
+ }
+}
diff --git a/Android/src/emu/project64/input/AbstractController.java b/Android/src/emu/project64/input/AbstractController.java
index 74233a0d8..988762c42 100644
--- a/Android/src/emu/project64/input/AbstractController.java
+++ b/Android/src/emu/project64/input/AbstractController.java
@@ -12,6 +12,7 @@ package emu.project64.input;
import java.util.ArrayList;
+import android.util.Log;
import emu.project64.jni.NativeInput;
/**
@@ -32,19 +33,21 @@ import emu.project64.jni.NativeInput;
* from the subclass. For best performance, subclasses should only call notifyChanged() when the
* input state has actually changed, and should bundle the protected field modifications before
* calling notifyChanged(). For example,
- *
+ *
*
* {@code
* buttons[0] = true; notifyChanged(); buttons[1] = false; notifyChanged(); // Inefficient
* buttons[0] = true; buttons[1] = false; notifyChanged(); // Better
* }
*
- *
+ *
* @see PeripheralController
* @see TouchController
*/
public abstract class AbstractController
{
+ protected final static boolean LOG_CONTROLLER = false;
+
/**
* A small class that encapsulates controller state.
*/
@@ -52,58 +55,58 @@ public abstract class AbstractController
{
/** The pressed state of each controller button. */
public boolean[] buttons = new boolean[NUM_N64_BUTTONS];
-
+
/** The fractional value of the analog-x axis, between -1 and 1, inclusive. */
public float axisFractionX = 0;
-
+
/** The fractional value of the analog-y axis, between -1 and 1, inclusive. */
public float axisFractionY = 0;
}
-
+
// Constants must match EButton listing in plugin.h! (input-sdl plug-in)
-
+
/** N64 button: dpad-right. */
public static final int DPD_R = 0;
-
+
/** N64 button: dpad-left. */
public static final int DPD_L = 1;
-
+
/** N64 button: dpad-down. */
public static final int DPD_D = 2;
-
+
/** N64 button: dpad-up. */
public static final int DPD_U = 3;
-
+
/** N64 button: start. */
public static final int START = 4;
-
+
/** N64 button: trigger-z. */
public static final int BTN_Z = 5;
-
+
/** N64 button: b. */
public static final int BTN_B = 6;
-
+
/** N64 button: a. */
public static final int BTN_A = 7;
-
+
/** N64 button: cpad-right. */
public static final int CPD_R = 8;
-
+
/** N64 button: cpad-left. */
public static final int CPD_L = 9;
-
+
/** N64 button: cpad-down. */
public static final int CPD_D = 10;
-
+
/** N64 button: cpad-up. */
public static final int CPD_U = 11;
-
+
/** N64 button: shoulder-r. */
public static final int BTN_R = 12;
-
+
/** N64 button: shoulder-l. */
public static final int BTN_L = 13;
-
+
/** N64 button: reserved-1. */
public static final int BTN_RESERVED1 = 14;
@@ -112,19 +115,19 @@ public abstract class AbstractController
/** Total number of N64 buttons. */
public static final int NUM_N64_BUTTONS = 16;
-
+
/** The state of all four player controllers. */
private static final ArrayList sStates = new ArrayList();
-
+
/** The state of this controller. */
protected State mState;
-
+
/** The player number, between 1 and 4, inclusive. */
protected int mPlayerNumber = 1;
-
+
/** The factor by which the axis fractions are scaled before going to the core. */
private static final float AXIS_SCALE = 80;
-
+
static
{
sStates.add( new State() );
@@ -132,7 +135,7 @@ public abstract class AbstractController
sStates.add( new State() );
sStates.add( new State() );
}
-
+
/**
* Instantiates a new abstract controller.
*/
@@ -140,30 +143,35 @@ public abstract class AbstractController
{
mState = sStates.get( 0 );
}
-
+
/**
* Notifies the core that the N64 controller state has changed.
*/
protected void notifyChanged()
{
+
int axisX = Math.round( AXIS_SCALE * mState.axisFractionX );
int axisY = Math.round( AXIS_SCALE * mState.axisFractionY );
+ if (LOG_CONTROLLER)
+ {
+ Log.i("Controller", "notifyChanged: axisX=" + axisX + " axisY=" + axisY);
+ }
NativeInput.setState( mPlayerNumber - 1, mState.buttons, axisX, axisY );
}
-
+
/**
* Gets the player number.
- *
+ *
* @return The player number, between 1 and 4, inclusive.
*/
public int getPlayerNumber()
{
return mPlayerNumber;
}
-
+
/**
* Sets the player number.
- *
+ *
* @param player The new player number, between 1 and 4, inclusive.
*/
public void setPlayerNumber( int player )
diff --git a/Android/src/emu/project64/input/PeripheralController.java b/Android/src/emu/project64/input/PeripheralController.java
new file mode 100644
index 000000000..558ea1e5e
--- /dev/null
+++ b/Android/src/emu/project64/input/PeripheralController.java
@@ -0,0 +1,173 @@
+/****************************************************************************
+* *
+* Project 64 - A Nintendo 64 emulator. *
+* http://www.pj64-emu.com/ *
+* Copyright (C) 2016 Project64. All rights reserved. *
+* Copyright (C) 2013 Paul Lamb, littleguy77 *
+* *
+* License: *
+* GNU/GPLv2 http://www.gnu.org/licenses/gpl-2.0.html *
+* *
+****************************************************************************/
+package emu.project64.input;
+
+import java.util.ArrayList;
+
+import emu.project64.input.map.InputMap;
+import emu.project64.input.provider.AbstractProvider;
+import emu.project64.util.Utility;
+import android.annotation.TargetApi;
+import android.util.FloatMath;
+import android.util.Log;
+import android.view.InputDevice;
+import android.view.KeyEvent;
+
+/**
+ * A class for generating N64 controller commands from peripheral hardware (gamepads, joysticks,
+ * keyboards, mice, etc.).
+ */
+public class PeripheralController extends AbstractController implements
+ AbstractProvider.OnInputListener
+{
+ /** The map from input codes to commands. */
+ private final InputMap mInputMap;
+
+ /** The analog deadzone, between 0 and 1, inclusive. */
+ private final float mDeadzoneFraction;
+
+ /** The analog sensitivity, the amount by which to scale stick values, nominally 1. */
+ private final float mSensitivityFraction;
+
+ /** The user input providers. */
+ private final ArrayList mProviders;
+
+ /** The positive analog-x strength, between 0 and 1, inclusive. */
+ private float mStrengthXpos;
+
+ /** The negative analog-x strength, between 0 and 1, inclusive. */
+ private float mStrengthXneg;
+
+ /** The positive analog-y strength, between 0 and 1, inclusive. */
+ private float mStrengthYpos;
+
+ /** The negative analogy-y strength, between 0 and 1, inclusive. */
+ private float mStrengthYneg;
+
+ /**
+ * Instantiates a new peripheral controller.
+ *
+ * @param player The player number, between 1 and 4, inclusive.
+ * @param inputMap The map from input codes to commands.
+ * @param inputDeadzone The analog deadzone in percent.
+ * @param inputSensitivity The analog sensitivity in percent.
+ * @param providers The user input providers. Null elements are safe.
+ */
+ public PeripheralController( int player, InputMap inputMap, int inputDeadzone, int inputSensitivity, AbstractProvider... providers )
+ {
+ setPlayerNumber( player );
+
+ // Assign the maps
+ mInputMap = inputMap;
+ mDeadzoneFraction = ( (float) inputDeadzone ) / 100f;
+ mSensitivityFraction = ( (float) inputSensitivity ) / 100f;
+
+ // Assign the non-null input providers
+ mProviders = new ArrayList();
+ for( AbstractProvider provider : providers )
+ {
+ if( provider != null )
+ {
+ mProviders.add( provider );
+ provider.registerListener( this );
+ }
+ }
+ }
+
+ @TargetApi( 16 )
+ @Override
+ public void onInput( int inputCode, float strength, int hardwareId )
+ {
+ // Apply user changes to the controller state
+ apply( inputCode, strength );
+
+ // Notify the core that controller state has changed
+ notifyChanged();
+ }
+
+ @Override
+ public void onInput( int[] inputCodes, float[] strengths, int hardwareId )
+ {
+ // Apply user changes to the controller state
+ for( int i = 0; i < inputCodes.length; i++ )
+ apply( inputCodes[i], strengths[i] );
+
+ // Notify the core that controller state has changed
+ notifyChanged();
+ }
+
+ /**
+ * Apply user input to the N64 controller state.
+ *
+ * @param inputCode The universal input code that was dispatched.
+ * @param strength The input strength, between 0 and 1, inclusive.
+ *
+ * @return True, if controller state changed.
+ */
+ private boolean apply( int inputCode, float strength )
+ {
+ boolean keyDown = strength > AbstractProvider.STRENGTH_THRESHOLD;
+ int n64Index = mInputMap.get( inputCode );
+
+ if( n64Index >= 0 && n64Index < NUM_N64_BUTTONS )
+ {
+ mState.buttons[n64Index] = keyDown;
+ return true;
+ }
+ else if( n64Index < InputMap.NUM_N64_CONTROLS )
+ {
+ switch( n64Index )
+ {
+ case InputMap.AXIS_R:
+ mStrengthXpos = strength;
+ break;
+ case InputMap.AXIS_L:
+ mStrengthXneg = strength;
+ break;
+ case InputMap.AXIS_D:
+ mStrengthYneg = strength;
+ break;
+ case InputMap.AXIS_U:
+ mStrengthYpos = strength;
+ break;
+ default:
+ return false;
+ }
+
+ // Calculate the net position of the analog stick
+ float rawX = mSensitivityFraction * ( mStrengthXpos - mStrengthXneg );
+ float rawY = mSensitivityFraction * ( mStrengthYpos - mStrengthYneg );
+ float magnitude = (float) Math.sqrt( ( rawX * rawX ) + ( rawY * rawY ) );
+
+ // Update controller state
+ if( magnitude > mDeadzoneFraction )
+ {
+ // Normalize the vector
+ float normalizedX = rawX / magnitude;
+ float normalizedY = rawY / magnitude;
+
+ // Rescale strength to account for deadzone
+ magnitude = ( magnitude - mDeadzoneFraction ) / ( 1f - mDeadzoneFraction );
+ magnitude = Utility.clamp( magnitude, 0f, 1f );
+ mState.axisFractionX = normalizedX * magnitude;
+ mState.axisFractionY = normalizedY * magnitude;
+ }
+ else
+ {
+ // In the deadzone
+ mState.axisFractionX = 0;
+ mState.axisFractionY = 0;
+ }
+ }
+ return false;
+ }
+}
diff --git a/Android/src/emu/project64/input/TouchController.java b/Android/src/emu/project64/input/TouchController.java
index 97dc12f29..466aeb810 100644
--- a/Android/src/emu/project64/input/TouchController.java
+++ b/Android/src/emu/project64/input/TouchController.java
@@ -26,7 +26,6 @@ import android.view.View.OnTouchListener;
/**
* A class for generating N64 controller commands from a touchscreen.
*/
-@SuppressWarnings("deprecation")
public class TouchController extends AbstractController implements OnTouchListener
{
public interface OnStateChangedListener
diff --git a/Android/src/emu/project64/input/map/InputMap.java b/Android/src/emu/project64/input/map/InputMap.java
new file mode 100644
index 000000000..816c13648
--- /dev/null
+++ b/Android/src/emu/project64/input/map/InputMap.java
@@ -0,0 +1,135 @@
+/****************************************************************************
+* *
+* Project64 - A Nintendo 64 emulator. *
+* http://www.pj64-emu.com/ *
+* Copyright (C) 2012 Project64. All rights reserved. *
+* *
+* License: *
+* GNU/GPLv2 http://www.gnu.org/licenses/gpl-2.0.html *
+* *
+****************************************************************************/
+package emu.project64.input.map;
+
+import emu.project64.input.AbstractController;
+import emu.project64.input.provider.AbstractProvider;
+
+/**
+ * A class for mapping arbitrary user inputs to N64 buttons/axes.
+ *
+ * @see AbstractProvider
+ * @see PeripheralController
+ * @see ControllerProfileActivity
+ */
+public class InputMap extends SerializableMap
+{
+ /** Map flag: Input code is not mapped. */
+ public static final int UNMAPPED = -1;
+
+ /** Map offset: N64 non-button controls. */
+ public static final int OFFSET_EXTRAS = AbstractController.NUM_N64_BUTTONS;
+
+ /** N64 control: analog-right. */
+ public static final int AXIS_R = OFFSET_EXTRAS;
+
+ /** N64 control: analog-left. */
+ public static final int AXIS_L = OFFSET_EXTRAS + 1;
+
+ /** N64 control: analog-down. */
+ public static final int AXIS_D = OFFSET_EXTRAS + 2;
+
+ /** N64 control: analog-up. */
+ public static final int AXIS_U = OFFSET_EXTRAS + 3;
+
+ /** Total number of N64 controls. */
+ public static final int NUM_N64_CONTROLS = OFFSET_EXTRAS + 4;
+ /** Total number of mappable controls/functions. */
+ public static final int NUM_MAPPABLES = NUM_N64_CONTROLS;
+
+ public InputMap( String serializedMap )
+ {
+ super( serializedMap );
+ }
+
+ /**
+ * Gets the command mapped to a given input code.
+ *
+ * @param inputCode The standardized input code.
+ *
+ * @return The command the code is mapped to, or UNMAPPED.
+ *
+ * @see AbstractProvider
+ * @see InputMap#UNMAPPED
+ */
+ public int get( int inputCode )
+ {
+ return mMap.get( inputCode, UNMAPPED );
+ }
+
+ /**
+ * Maps an input code to a command.
+ *
+ * @param inputCode The standardized input code to be mapped.
+ * @param command The index to the N64/Mupen command.
+ */
+ public void map( int inputCode, int command )
+ {
+ // Map the input if a valid index was given
+ if( command >= 0 && command < NUM_MAPPABLES && inputCode != 0 )
+ {
+ if( inputCode < 0 )
+ {
+ // If an analog input is mapped, it should be the only thing mapped to this command
+ unmapCommand( command );
+ }
+ else
+ {
+ // If a digital input is mapped, no analog inputs can be mapped to this command
+ for( int i = mMap.size() - 1; i >= 0; i-- )
+ {
+ if( mMap.valueAt( i ) == command && mMap.keyAt( i ) < 0 )
+ mMap.removeAt( i );
+ }
+ }
+ mMap.put( inputCode, command );
+ }
+ }
+
+ /**
+ * Unmaps a command.
+ *
+ * @param command The index to the command.
+ */
+ public void unmapCommand( int command )
+ {
+ // Remove any matching key-value pairs (count down to accommodate removal)
+ for( int i = mMap.size() - 1; i >= 0; i-- )
+ {
+ if( mMap.valueAt( i ) == command )
+ mMap.removeAt( i );
+ }
+ }
+
+ /**
+ * Checks if a command is mapped to at least one input code.
+ *
+ * @param command The index to the command.
+ *
+ * @return True, if the mapping exists.
+ */
+ public boolean isMapped( int command )
+ {
+ return mMap.indexOfValue( command ) >= 0;
+ }
+ public String getMappedCodeInfo( int command )
+ {
+ String result = "";
+ for( int i = 0; i < mMap.size(); i++ )
+ {
+ if( mMap.valueAt( i ) == command )
+ {
+ result += AbstractProvider.getInputName( mMap.keyAt( i ) ) + "\n";
+ }
+ }
+ return result.trim();
+ }
+}
diff --git a/Android/src/emu/project64/input/map/TouchMap.java b/Android/src/emu/project64/input/map/TouchMap.java
index aa12fbbdf..17df147b9 100644
--- a/Android/src/emu/project64/input/map/TouchMap.java
+++ b/Android/src/emu/project64/input/map/TouchMap.java
@@ -33,7 +33,6 @@ import android.util.SparseArray;
* @see TouchController
*/
@SuppressLint("FloatMath")
-@SuppressWarnings("deprecation")
public class TouchMap
{
/** Map flag: Touch location is not mapped. */
diff --git a/Android/src/emu/project64/input/map/VisibleTouchMap.java b/Android/src/emu/project64/input/map/VisibleTouchMap.java
index 4a9df40fc..d3c19c036 100644
--- a/Android/src/emu/project64/input/map/VisibleTouchMap.java
+++ b/Android/src/emu/project64/input/map/VisibleTouchMap.java
@@ -30,34 +30,7 @@ import android.util.Log;
* @see GameOverlay
*/
public class VisibleTouchMap extends TouchMap
-{
- /** FPS frame image. */
- private Image mFpsFrame;
-
- /** X-coordinate of the FPS frame, in percent. */
- private int mFpsFrameX;
-
- /** Y-coordinate of the FPS frame, in percent. */
- private int mFpsFrameY;
-
- /** X-coordinate of the FPS text centroid, in percent. */
- private int mFpsTextX;
-
- /** Y-coordinate of the FPS text centroid, in percent. */
- private int mFpsTextY;
-
- /** The current FPS value. */
- private int mFpsValue;
-
- /** The minimum size of the FPS indicator in pixels. */
- private float mFpsMinPixels;
-
- /** The minimum size to scale the FPS indicator. */
- private float mFpsMinScale;
-
- /** True if the FPS indicator should be drawn. */
- private boolean mFpsEnabled;
-
+{
/** The factor to scale images by. */
private float mScalingFactor = 1.0f;
@@ -71,20 +44,14 @@ public class VisibleTouchMap extends TouchMap
private int mReferenceHeight = 0;
/** The last width passed to {@link #resize(int, int, DisplayMetrics)}. */
- private int cacheWidth = 0;
+ private static int cacheWidth = 0;
/** The last height passed to {@link #resize(int, int, DisplayMetrics)}. */
- private int cacheHeight = 0;
+ private static int cacheHeight = 0;
/** The last height passed to {@link #resize(int, int, DisplayMetrics)}. */
private DisplayMetrics cacheMetrics;
- /** The set of images representing the FPS string. */
- private final CopyOnWriteArrayList mFpsDigits;
-
- /** The set of images representing the numerals 0, 1, 2, ..., 9. */
- private final Image[] mNumerals;
-
/** Auto-hold overlay images. */
public final Image[] autoHoldImages;
@@ -102,8 +69,6 @@ public class VisibleTouchMap extends TouchMap
public VisibleTouchMap( Resources resources )
{
super( resources );
- mFpsDigits = new CopyOnWriteArrayList();
- mNumerals = new Image[10];
autoHoldImages = new Image[NUM_N64_PSEUDOBUTTONS];
autoHoldX = new int[NUM_N64_PSEUDOBUTTONS];
autoHoldY = new int[NUM_N64_PSEUDOBUTTONS];
@@ -118,13 +83,6 @@ public class VisibleTouchMap extends TouchMap
public void clear()
{
super.clear();
- mFpsFrame = null;
- mFpsFrameX = mFpsFrameY = 0;
- mFpsTextX = mFpsTextY = 50;
- mFpsValue = 0;
- mFpsDigits.clear();
- for( int i = 0; i < mNumerals.length; i++ )
- mNumerals[i] = null;
for( int i = 0; i < autoHoldImages.length; i++ )
autoHoldImages[i] = null;
for( int i = 0; i < autoHoldX.length; i++ )
@@ -195,25 +153,6 @@ public class VisibleTouchMap extends TouchMap
autoHoldImages[i].fitPercent( autoHoldX[i], autoHoldY[i], w, h );
}
}
-
- // Compute FPS frame location
- float fpsScale = scale;
- if( mFpsMinScale > scale )
- fpsScale = mFpsMinScale;
- if( mFpsFrame != null )
- {
- mFpsFrame.setScale( fpsScale );
- mFpsFrame.fitPercent( mFpsFrameX, mFpsFrameY, w, h );
- }
- for( int i = 0; i < mNumerals.length; i++ )
- {
- if( mNumerals[i] != null )
- mNumerals[i].setScale( fpsScale );
- }
-
- // Compute the FPS digit locations
- refreshFpsImages();
- refreshFpsPositions();
}
/**
@@ -267,25 +206,6 @@ public class VisibleTouchMap extends TouchMap
}
}
- /**
- * Draws the FPS indicator.
- *
- * @param canvas The canvas on which to draw.
- */
- public void drawFps( Canvas canvas )
- {
- if( canvas == null )
- return;
-
- // Redraw the FPS indicator
- if( mFpsFrame != null )
- mFpsFrame.draw( canvas );
-
- // Draw each digit of the FPS number
- for( Image digit : mFpsDigits )
- digit.draw( canvas );
- }
-
/**
* Updates the analog stick assets to reflect a new position.
*
@@ -318,32 +238,6 @@ public class VisibleTouchMap extends TouchMap
return false;
}
- /**
- * Updates the FPS indicator assets to reflect a new value.
- *
- * @param fps The new FPS value.
- *
- * @return True if the FPS assets changed.
- */
- public boolean updateFps( int fps )
- {
- // Clamp to positive, four digits max [0 - 9999]
- fps = Utility.clamp( fps, 0, 9999 );
-
- // Quick return if user has disabled FPS or it hasn't changed
- if( !mFpsEnabled || mFpsValue == fps )
- return false;
-
- // Store the new value
- mFpsValue = fps;
-
- // Refresh the FPS digits
- refreshFpsImages();
- refreshFpsPositions();
-
- return true;
- }
-
/**
* Updates the auto-hold assets to reflect a new value.
*
@@ -364,73 +258,18 @@ public class VisibleTouchMap extends TouchMap
}
return false;
}
-
- /**
- * Refreshes the images used to draw the FPS string.
- */
- private void refreshFpsImages()
- {
- // Refresh the list of FPS digits
- String fpsString = Integer.toString( mFpsValue );
- mFpsDigits.clear();
- for( int i = 0; i < 4; i++ )
- {
- // Create a new sequence of numeral images
- if( i < fpsString.length() )
- {
- int numeral = SafeMethods.toInt( fpsString.substring( i, i + 1 ), -1 );
- if( numeral > -1 && numeral < 10 )
- {
- // Clone the numeral from the font images and move to next digit
- mFpsDigits.add( new Image( mResources, mNumerals[numeral] ) );
- }
- }
- }
- }
-
- /**
- * Refreshes the positions of the FPS images.
- */
- private void refreshFpsPositions()
- {
- // Compute the centroid of the FPS text
- int x = 0;
- int y = 0;
- if( mFpsFrame != null )
- {
- x = mFpsFrame.x + (int) ( ( mFpsFrame.width * mFpsFrame.scale ) * ( mFpsTextX / 100f ) );
- y = mFpsFrame.y + (int) ( ( mFpsFrame.height * mFpsFrame.scale ) * ( mFpsTextY / 100f ) );
- }
-
- // Compute the width of the FPS text
- int totalWidth = 0;
- for( Image digit : mFpsDigits )
- totalWidth += (int) ( digit.width * digit.scale );
-
- // Compute the starting position of the FPS text
- x -= (int) ( totalWidth / 2f );
-
- // Compute the position of each digit
- for( Image digit : mFpsDigits )
- {
- digit.setPos( x, y - (int) ( digit.hHeight * digit.scale ) );
- x += (int) ( digit.width * digit.scale );
- }
- }
-
+
/**
* Loads all touch map data from the filesystem.
*
* @param skinDir The directory containing the skin.ini and image files.
* @param profile The name of the touchscreen profile.
* @param animated True to load the analog assets in two parts for animation.
- * @param fpsEnabled True to display the FPS indicator.
* @param scale The factor to scale images by.
* @param alpha The opacity of the visible elements.
*/
- public void load( String skinDir, Profile profile, boolean animated, boolean fpsEnabled, float scale, int alpha )
+ public void load( String skinDir, Profile profile, boolean animated, float scale, int alpha )
{
- mFpsEnabled = fpsEnabled;
mScalingFactor = scale;
mTouchscreenTransparency = alpha;
@@ -438,9 +277,6 @@ public class VisibleTouchMap extends TouchMap
ConfigFile skin_ini = new ConfigFile( skinFolder + "/skin.ini" );
mReferenceWidth = SafeMethods.toInt( skin_ini.get( "INFO", "referenceScreenWidth" ), 0 );
mReferenceHeight = SafeMethods.toInt( skin_ini.get( "INFO", "referenceScreenHeight" ), 0 );
- mFpsTextX = SafeMethods.toInt( skin_ini.get( "INFO", "fps-numx" ), 50 );
- mFpsTextY = SafeMethods.toInt( skin_ini.get( "INFO", "fps-numy" ), 50 );
- mFpsMinPixels = SafeMethods.toInt( skin_ini.get( "INFO", "fps-minPixels" ), 0 );
// Scale the assets to the last screensize used
resize( cacheWidth, cacheHeight, cacheMetrics );
@@ -472,10 +308,9 @@ public class VisibleTouchMap extends TouchMap
analogForeImage.setAlpha( mTouchscreenTransparency );
}
- // Load the FPS and autohold images
+ // Load the autohold images
if( profile != null )
{
- loadFpsIndicator( profile );
loadAutoHoldImages( profile, "groupAB-holdA" );
loadAutoHoldImages( profile, "groupAB-holdB" );
loadAutoHoldImages( profile, "groupC-holdCu" );
@@ -489,48 +324,6 @@ public class VisibleTouchMap extends TouchMap
}
}
- /**
- * Loads FPS indicator assets and properties from the filesystem.
- *
- * @param profile The touchscreen profile containing the FPS properties.
- */
- private void loadFpsIndicator( Profile profile )
- {
- int x = profile.getInt( "fps-x", -1 );
- int y = profile.getInt( "fps-y", -1 );
-
- if( x >= 0 && y >= 0 )
- {
- // Position (percentages of the screen dimensions)
- mFpsFrameX = x;
- mFpsFrameY = y;
-
- // Load frame image
- mFpsFrame = new Image( mResources, skinFolder + "/fps.png" );
-
- // Minimum factor the FPS indicator can be scaled by
- mFpsMinScale = mFpsMinPixels / (float) mFpsFrame.width;
-
- // Load numeral images
- String filename = "";
- try
- {
- // Make sure we can load them (they might not even exist)
- for( int i = 0; i < mNumerals.length; i++ )
- {
- filename = skinFolder + "/fps-" + i + ".png";
- mNumerals[i] = new Image( mResources, filename );
- }
- }
- catch( Exception e )
- {
- // Problem, let the user know
- Log.e( "VisibleTouchMap", "Problem loading fps numeral '" + filename
- + "', error message: " + e.getMessage() );
- }
- }
- }
-
/**
* Loads auto-hold assets and properties from the filesystem.
*
diff --git a/Android/src/emu/project64/input/provider/AbstractProvider.java b/Android/src/emu/project64/input/provider/AbstractProvider.java
new file mode 100644
index 000000000..f8ac15f72
--- /dev/null
+++ b/Android/src/emu/project64/input/provider/AbstractProvider.java
@@ -0,0 +1,391 @@
+/****************************************************************************
+* *
+* Project64 - A Nintendo 64 emulator. *
+* http://www.pj64-emu.com/ *
+* Copyright (C) 2016 Project64. All rights reserved. *
+* Copyright (C) 2013 Paul Lamb
+* *
+* License: *
+* GNU/GPLv2 http://www.gnu.org/licenses/gpl-2.0.html *
+* *
+****************************************************************************/
+package emu.project64.input.provider;
+
+import java.util.ArrayList;
+
+import emu.project64.AndroidDevice;
+import tv.ouya.console.api.OuyaController;
+import android.annotation.TargetApi;
+import android.view.InputDevice;
+import android.view.KeyEvent;
+import android.view.MotionEvent;
+
+/**
+ * The base class for transforming arbitrary input data into a common format.
+ *
+ * @see KeyProvider
+ * @see AxisProvider
+ * @see SensorProvider
+ * @see InputMap
+ */
+public abstract class AbstractProvider
+{
+ /** The offset used to construct the hardware ID for a MOGA controller. */
+ private static final int HARDWARE_ID_MOGA_OFFSET = 1000;
+
+ /** The maximum possible hardware ID for a MOGA controller, inclusive. */
+ private static final int HARDWARE_ID_MOGA_MAX = 1010;
+
+ /**
+ * The interface for listening to a provider.
+ */
+ public interface OnInputListener
+ {
+ /**
+ * Called when a single input has been dispatched.
+ *
+ * @param inputCode The universal input code that was dispatched.
+ * @param strength The input strength, between 0 and 1, inclusive.
+ * @param hardwareId The identifier of the source device.
+ */
+ public void onInput( int inputCode, float strength, int hardwareId );
+
+ /**
+ * Called when multiple inputs have been dispatched simultaneously.
+ *
+ * @param inputCodes The universal input codes that were dispatched.
+ * @param strengths The input strengths, between 0 and 1, inclusive.
+ * @param hardwareId The identifier of the source device.
+ */
+ public void onInput( int[] inputCodes, float[] strengths, int hardwareId );
+ }
+
+ /** The strength threshold above which an input is said to be "on". */
+ public static final float STRENGTH_THRESHOLD = 0.5f;
+
+ /** Listener management. */
+ private final ArrayList mPublisher;
+
+ /**
+ * Instantiates a new abstract provider.
+ */
+ protected AbstractProvider()
+ {
+ mPublisher = new ArrayList();
+ }
+
+ /**
+ * Registers a listener to start receiving input notifications.
+ *
+ * @param listener The listener to register. Null values are safe.
+ */
+ public void registerListener( AbstractProvider.OnInputListener listener )
+ {
+ if( ( listener != null ) && !mPublisher.contains( listener ) )
+ {
+ mPublisher.add( listener );
+ }
+ }
+
+ /**
+ * Unregisters a listener to stop receiving input notifications.
+ *
+ * @param listener The listener to unregister. Null values are safe.
+ */
+ public void unregisterListener( AbstractProvider.OnInputListener listener )
+ {
+ if( listener != null )
+ {
+ mPublisher.remove( listener );
+ }
+ }
+
+ /**
+ * Unregisters all listeners.
+ */
+ public void unregisterAllListeners()
+ {
+ mPublisher.clear();
+ }
+
+ /**
+ * Obtains unique hardware id from an input event.
+ *
+ * @param event The event generated by the hardware.
+ *
+ * @return The unique identifier of the hardware.
+ */
+ public static int getHardwareId( KeyEvent event )
+ {
+ // This might be replaced by something else in the future... so we abstract it
+ return event.getDeviceId();
+ }
+
+ /**
+ * Obtains unique hardware id from an input event.
+ *
+ * @param event The event generated by the hardware.
+ *
+ * @return The unique identifier of the hardware.
+ */
+ public static int getHardwareId( MotionEvent event )
+ {
+ // This might be replaced by something else in the future... so we abstract it
+ return event.getDeviceId();
+ }
+
+ /**
+ * Obtains unique hardware id from an input event.
+ *
+ * @param event The event generated by the hardware.
+ *
+ * @return The unique identifier of the hardware.
+ */
+ public static int getHardwareId( com.bda.controller.KeyEvent event )
+ {
+ // This might be replaced by something else in the future... so we abstract it
+ return event.getControllerId() + HARDWARE_ID_MOGA_OFFSET;
+ }
+
+ /**
+ * Obtains unique hardware id from an input event.
+ *
+ * @param event The event generated by the hardware.
+ *
+ * @return The unique identifier of the hardware.
+ */
+ public static int getHardwareId( com.bda.controller.MotionEvent event )
+ {
+ // This might be replaced by something else in the future... so we abstract it
+ return event.getControllerId() + HARDWARE_ID_MOGA_OFFSET;
+ }
+
+ /**
+ * Determines whether the hardware is available. This is a conservative test in that it will
+ * return true if the availability cannot be conclusively determined.
+ *
+ * @param id The unique hardware identifier.
+ *
+ * @return True if the associated hardware is available or indeterminate.
+ */
+ @TargetApi( 9 )
+ public static boolean isHardwareAvailable( int id )
+ {
+ // This might be replaced by something else in the future... so we abstract it
+ if( id > HARDWARE_ID_MOGA_OFFSET && id <= HARDWARE_ID_MOGA_MAX )
+ {
+ return true;
+ }
+ else if( AndroidDevice.IS_GINGERBREAD )
+ {
+ InputDevice device = InputDevice.getDevice( id );
+ return device != null;
+ }
+ else
+ {
+ return true;
+ }
+ }
+
+ /**
+ * Gets the human-readable name of the hardware.
+ *
+ * @param id The unique hardware identifier.
+ *
+ * @return The name of the hardware, or null if hardware not found.
+ */
+ @TargetApi( 9 )
+ public static String getHardwareName( int id )
+ {
+ if( id > HARDWARE_ID_MOGA_OFFSET && id <= HARDWARE_ID_MOGA_MAX )
+ {
+ return "moga-" + ( id - HARDWARE_ID_MOGA_OFFSET );
+ }
+ else if( AndroidDevice.IS_GINGERBREAD )
+ {
+ InputDevice device = InputDevice.getDevice( id );
+ return device == null ? null : device.getName();
+ }
+ else
+ {
+ return null;
+ }
+ }
+
+ /**
+ * Gets the human-readable name of the input.
+ *
+ * @param inputCode The universal input code.
+ *
+ * @return The name of the input.
+ */
+ @SuppressWarnings( "deprecation" )
+ @TargetApi( 12 )
+ public static String getInputName( int inputCode )
+ {
+ if( inputCode > 0 )
+ {
+ if( AndroidDevice.IS_HONEYCOMB_MR1 )
+ {
+ String name = null;
+ if( inputCode != -1 && AndroidDevice.IS_OUYA_HARDWARE )
+ {
+ if( inputCode == OuyaController.BUTTON_A )
+ name = "OUYA BUTTON_A";
+ else if( inputCode == OuyaController.BUTTON_DPAD_DOWN )
+ name = "OUYA BUTTON_DPAD_DOWN";
+ else if( inputCode == OuyaController.BUTTON_DPAD_LEFT )
+ name = "OUYA BUTTON_DPAD_LEFT";
+ else if( inputCode == OuyaController.BUTTON_DPAD_RIGHT )
+ name = "OUYA BUTTON_DPAD_RIGHT";
+ else if( inputCode == OuyaController.BUTTON_DPAD_UP )
+ name = "OUYA BUTTON_DPAD_UP";
+ else if( inputCode == OuyaController.BUTTON_L1 )
+ name = "OUYA BUTTON_L1";
+ else if( inputCode == OuyaController.BUTTON_L2 )
+ name = "OUYA BUTTON_L2";
+ else if( inputCode == OuyaController.BUTTON_L3 )
+ name = "OUYA BUTTON_L3";
+ else if( inputCode == OuyaController.BUTTON_MENU )
+ name = "OUYA BUTTON_MENU";
+ else if( inputCode == OuyaController.BUTTON_O )
+ name = "OUYA BUTTON_O";
+ else if( inputCode == OuyaController.BUTTON_R1 )
+ name = "OUYA BUTTON_R1";
+ else if( inputCode == OuyaController.BUTTON_R2 )
+ name = "OUYA BUTTON_R2";
+ else if( inputCode == OuyaController.BUTTON_R3 )
+ name = "OUYA BUTTON_R3";
+ else if( inputCode == OuyaController.BUTTON_U )
+ name = "OUYA BUTTON_U";
+ else if( inputCode == OuyaController.BUTTON_Y )
+ name = "OUYA BUTTON_Y";
+ }
+ if( name == null )
+ return KeyEvent.keyCodeToString( inputCode );
+ else
+ return name + " (" + KeyEvent.keyCodeToString( inputCode ) + ")";
+ }
+ else
+ return "KEYCODE_" + inputCode;
+ }
+ else if( inputCode < 0 )
+ {
+ int axis = inputToAxisCode( inputCode );
+ String direction = inputToAxisDirection( inputCode ) ? " (+)" : " (-)";
+ if( AndroidDevice.IS_HONEYCOMB_MR1 )
+ {
+ String name = null;
+ if( axis != -1 && AndroidDevice.IS_OUYA_HARDWARE )
+ {
+ if( axis == OuyaController.AXIS_L2 )
+ name = "OUYA AXIS_L2";
+ else if( axis == OuyaController.AXIS_LS_X )
+ name = "OUYA AXIS_LS_X";
+ else if( axis == OuyaController.AXIS_LS_Y )
+ name = "OUYA AXIS_LS_Y";
+ else if( axis == OuyaController.AXIS_R2 )
+ name = "OUYA AXIS_R2";
+ else if( axis == OuyaController.AXIS_RS_X )
+ name = "OUYA AXIS_RS_X";
+ else if( axis == OuyaController.AXIS_RS_Y )
+ name = "OUYA AXIS_RS_Y";
+ }
+ if( name == null )
+ return MotionEvent.axisToString( axis ) + direction;
+ else
+ return name + " (" + MotionEvent.axisToString( axis ) + ")" + direction;
+ }
+ else
+ return "AXIS_" + axis + direction;
+ }
+ else
+ return "NULL";
+ }
+
+ /**
+ * Gets the human-readable name of the input, appended with strength information.
+ *
+ * @param inputCode The universal input code.
+ * @param strength The input strength, between 0 and 1, inclusive.
+ *
+ * @return The name of the input.
+ */
+ public static String getInputName( int inputCode, float strength )
+ {
+ return getInputName( inputCode ) + ( inputCode == 0
+ ? ""
+ : String.format( " %4.2f", strength ) );
+ }
+
+ /**
+ * Utility for child classes. Converts an Android axis code to a universal input code.
+ *
+ * @param axisCode The Android axis code.
+ * @param positiveDirection Set true for positive Android axis, false for negative Android axis.
+ *
+ * @return The corresponding universal input code.
+ */
+ protected static int axisToInputCode( int axisCode, boolean positiveDirection )
+ {
+ // Axis codes are encoded to negative values (versus buttons which are positive). Axis codes
+ // are bit shifted by one so that the lowest bit can encode axis direction.
+ return -( ( axisCode ) * 2 + ( positiveDirection ? 1 : 2 ) );
+ }
+
+ /**
+ * Utility for child classes. Converts a universal input code to an Android axis code.
+ *
+ * @param inputCode The universal input code.
+ *
+ * @return The corresponding Android axis code.
+ */
+ protected static int inputToAxisCode( int inputCode )
+ {
+ return ( -inputCode - 1 ) / 2;
+ }
+
+ /**
+ * Utility for child classes. Converts a universal input code to an Android axis direction.
+ *
+ * @param inputCode The universal input code.
+ *
+ * @return True if the input code represents positive Android axis direction, false otherwise.
+ */
+ protected static boolean inputToAxisDirection( int inputCode )
+ {
+ return ( ( -inputCode ) % 2 ) == 1;
+ }
+
+ /**
+ * Notifies listeners that a single input was dispatched. Subclasses should invoke this method
+ * to publish their input data.
+ *
+ * @param inputCode The universal input code that was dispatched.
+ * @param strength The input strength, between 0 and 1, inclusive.
+ * @param hardwareId The identifier of the source device.
+ */
+ protected void notifyListeners( int inputCode, float strength, int hardwareId )
+ {
+ for( OnInputListener listener : mPublisher )
+ {
+ listener.onInput( inputCode, strength, hardwareId );
+ }
+ }
+
+ /**
+ * Notifies listeners that multiple inputs were dispatched simultaneously. Subclasses should
+ * invoke this method to publish their input data.
+ *
+ * @param inputCodes The universal input codes that were dispatched.
+ * @param strengths The input strengths, between 0 and 1, inclusive.
+ * @param hardwareId The identifier of the source device.
+ */
+ protected void notifyListeners( int[] inputCodes, float[] strengths, int hardwareId )
+ {
+ for( OnInputListener listener : mPublisher )
+ {
+ listener.onInput( inputCodes.clone(), strengths.clone(), hardwareId );
+ }
+ }
+}
diff --git a/Android/src/emu/project64/input/provider/AxisProvider.java b/Android/src/emu/project64/input/provider/AxisProvider.java
new file mode 100644
index 000000000..424645290
--- /dev/null
+++ b/Android/src/emu/project64/input/provider/AxisProvider.java
@@ -0,0 +1,193 @@
+/****************************************************************************
+* *
+* Project64 - A Nintendo 64 emulator. *
+* http://www.pj64-emu.com/ *
+* Copyright (C) 2016 Project64. All rights reserved. *
+* Copyright (C) 2013 Paul Lamb, littleguy77
+* *
+* License: *
+* GNU/GPLv2 http://www.gnu.org/licenses/gpl-2.0.html *
+* *
+****************************************************************************/
+package emu.project64.input.provider;
+
+import emu.project64.AndroidDevice;
+import emu.project64.input.map.AxisMap;
+import android.annotation.TargetApi;
+import android.view.InputDevice;
+import android.view.InputDevice.MotionRange;
+import android.view.MotionEvent;
+import android.view.View;
+
+/**
+ * A class for transforming Android MotionEvent inputs into a common format.
+ */
+public class AxisProvider extends AbstractProvider
+{
+ /** The input codes to listen for. */
+ private int[] mInputCodes;
+
+ /** The default number of input codes to listen for. */
+ private static final int DEFAULT_NUM_INPUTS = 128;
+
+ /**
+ * Instantiates a new axis provider.
+ */
+ @TargetApi( 12 )
+ public AxisProvider()
+ {
+ // By default, provide data from all possible axes
+ mInputCodes = new int[DEFAULT_NUM_INPUTS];
+ for( int i = 0; i < mInputCodes.length; i++ )
+ mInputCodes[i] = -( i + 1 );
+ }
+
+ /**
+ * Instantiates a new axis provider.
+ *
+ * @param view The view receiving MotionEvent data.
+ */
+ @TargetApi( 12 )
+ public AxisProvider( View view )
+ {
+ this();
+
+ // Connect the input source
+ view.setOnGenericMotionListener( new GenericMotionListener() );
+
+ // Request focus for proper listening
+ view.requestFocus();
+ }
+
+ /**
+ * Restricts listening to a set of universal input codes.
+ *
+ * @param inputCodeFilter The new input codes to listen for.
+ */
+ public void setInputCodeFilter( int[] inputCodeFilter )
+ {
+ mInputCodes = inputCodeFilter.clone();
+ }
+
+ /**
+ * Manually dispatches a MotionEvent through the provider's listening chain.
+ *
+ * @param event The MotionEvent object containing full information about the event.
+ *
+ * @return True if the listener has consumed the event, false otherwise.
+ */
+ public boolean onGenericMotion( MotionEvent event )
+ {
+ if( AndroidDevice.IS_HONEYCOMB_MR1 )
+ return new GenericMotionListener().onGenericMotion( null, event );
+ else
+ return false;
+ }
+
+ /**
+ * Just an indirection class that eliminates some logcat chatter about benign errors. If we make
+ * the parent class implement View.OnGenericMotionListener, then we get logcat error messages
+ * even if we conditionally exclude calls to the class based on API. These errors are not
+ * actually harmful, so the logcat messages are simply a nuisance during debugging.
+ *
+ * For a detailed explanation, see here.
+ */
+ @TargetApi( 12 )
+ public class GenericMotionListener implements View.OnGenericMotionListener
+ {
+ /*
+ * (non-Javadoc)
+ *
+ * @see android.view.View.OnGenericMotionListener#onGenericMotion(android.view.View,
+ * android.view.MotionEvent)
+ */
+ @Override
+ public boolean onGenericMotion( View v, MotionEvent event )
+ {
+ // Ignore motion events from non-joysticks (mice are a problem)
+ if( event.getSource() != InputDevice.SOURCE_JOYSTICK )
+ return false;
+
+ InputDevice device = event.getDevice();
+ AxisMap axisInfo = AxisMap.getMap( device );
+
+ // Read all the requested axes
+ float[] strengths = new float[mInputCodes.length];
+ for( int i = 0; i < mInputCodes.length; i++ )
+ {
+ int inputCode = mInputCodes[i];
+
+ // Compute the axis code from the input code
+ int axisCode = inputToAxisCode( inputCode );
+
+ // Get the analog value using the Android API
+ float strength = event.getAxisValue( axisCode );
+
+ // Modify strength if necessary
+ strength = normalizeStrength( strength, axisInfo, device, axisCode );
+
+ // If the strength points in the correct direction, record it
+ boolean direction1 = inputToAxisDirection( inputCode );
+ boolean direction2 = strength > 0;
+ if( direction1 == direction2 )
+ strengths[i] = Math.abs( strength );
+ else
+ strengths[i] = 0;
+ }
+
+ // Notify listeners about new input data
+ notifyListeners( mInputCodes, strengths, getHardwareId( event ) );
+
+ return true;
+ }
+
+ private float normalizeStrength( float strength, AxisMap axisInfo, InputDevice device,
+ int axisCode )
+ {
+ if( axisInfo != null )
+ {
+ int axisClass = axisInfo.getClass( axisCode );
+
+ if( axisClass == AxisMap.AXIS_CLASS_IGNORED )
+ {
+ // We should ignore this axis
+ strength = 0;
+ }
+ else if( device != null )
+ {
+ // We should normalize this axis
+ MotionRange motionRange = device.getMotionRange( axisCode, InputDevice.SOURCE_JOYSTICK );
+ if( motionRange != null )
+ {
+ switch( axisClass )
+ {
+ case AxisMap.AXIS_CLASS_STICK:
+ // Normalize to [-1,1]
+ strength = ( strength - motionRange.getMin() ) / motionRange.getRange() * 2f - 1f;
+ break;
+ case AxisMap.AXIS_CLASS_TRIGGER:
+ // Normalize to [0,1]
+ strength = ( strength - motionRange.getMin() ) / motionRange.getRange();
+ break;
+ case AxisMap.AXIS_CLASS_N64_USB_STICK:
+ // Normalize to [-1,1]
+ // The Raphnet adapters through v2.x and some other USB adapters assume the N64
+ // controller produces values in the range [-127,127]. However, the official N64 spec
+ // says that raw values of +/- 80 indicate full strength. Therefore we rescale by
+ // multiplying by 127/80 (dividing by 0.63).
+ // http://naesten.dyndns.org:8080/psyq/man/os/osContGetReadData.html
+ // http://raphnet-tech.com/products/gc_n64_usb_adapters/
+ strength = strength / 0.63f;
+ break;
+ case AxisMap.AXIS_CLASS_UNKNOWN:
+ default:
+ // Do nothing
+ }
+ }
+ }
+ }
+ return strength;
+ }
+ }
+}
diff --git a/Android/src/emu/project64/input/provider/KeyProvider.java b/Android/src/emu/project64/input/provider/KeyProvider.java
new file mode 100644
index 000000000..68903d357
--- /dev/null
+++ b/Android/src/emu/project64/input/provider/KeyProvider.java
@@ -0,0 +1,172 @@
+/****************************************************************************
+* *
+* Project64 - A Nintendo 64 emulator. *
+* http://www.pj64-emu.com/ *
+* Copyright (C) 2016 Project64. All rights reserved. *
+* Copyright (C) 2013 Paul Lamb, littleguy77
+* *
+* License: *
+* GNU/GPLv2 http://www.gnu.org/licenses/gpl-2.0.html *
+* *
+****************************************************************************/
+package emu.project64.input.provider;
+
+import java.util.List;
+
+import android.app.AlertDialog.Builder;
+import android.content.DialogInterface;
+import android.view.KeyEvent;
+import android.view.View;
+
+/**
+ * A class for transforming Android KeyEvent inputs into a common format.
+ */
+public class KeyProvider extends AbstractProvider implements View.OnKeyListener,
+ DialogInterface.OnKeyListener
+{
+ /**
+ * The formula for decoding KeyEvent data for specific Android IMEs.
+ */
+ public enum ImeFormula
+ {
+ /** The default decoding formula. */
+ DEFAULT,
+ /** The formula for USB/BT Joystick Center, by Poke64738. */
+ USB_BT_JOYSTICK_CENTER,
+ /** The formula for BT Controller, by droidbean. */
+ BT_CONTROLLER,
+ /** An example decoding formula. */
+ EXAMPLE_IME
+ }
+
+ /** The IME formula for decoding KeyEvent data. */
+ private final ImeFormula mImeFormula;
+
+ /** The list of key codes that should be ignored. */
+ private final List mIgnoredCodes;
+
+ /**
+ * Instantiates a new key provider.
+ *
+ * @param formula The decoding formula to be used.
+ * @param ignoredCodes List of key codes that should be ignored.
+ */
+ public KeyProvider( ImeFormula formula, List ignoredCodes )
+ {
+ // Assign the fields
+ mImeFormula = formula;
+ mIgnoredCodes = ignoredCodes;
+ }
+
+ /**
+ * Instantiates a new key provider.
+ *
+ * @param view The view receiving KeyEvent data.
+ * @param formula The decoding formula to be used.
+ * @param ignoredCodes List of key codes that should be ignored.
+ */
+ public KeyProvider( View view, ImeFormula formula, List ignoredCodes )
+ {
+ this( formula, ignoredCodes );
+
+ // Connect the input source
+ view.setOnKeyListener( this );
+
+ // Request focus for proper listening
+ view.requestFocus();
+ }
+
+ /**
+ * Instantiates a new key provider.
+ *
+ * @param builder The builder for the dialog receiving KeyEvent data.
+ * @param formula The decoding formula to be used.
+ * @param ignoredCodes List of key codes that should be ignored.
+ */
+ public KeyProvider( Builder builder, ImeFormula formula, List ignoredCodes )
+ {
+ this( formula, ignoredCodes );
+
+ // Connect the input source
+ builder.setOnKeyListener( this );
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see android.view.View.OnKeyListener#onKey(android.view.View, int, android.view.KeyEvent)
+ */
+ @Override
+ public boolean onKey( View v, int keyCode, KeyEvent event )
+ {
+ return onKey( keyCode, event );
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see android.content.DialogInterface.OnKeyListener#onKey(android.content.DialogInterface,
+ * int, android.view.KeyEvent)
+ */
+ @Override
+ public boolean onKey( DialogInterface dialog, int keyCode, KeyEvent event )
+ {
+ return onKey( keyCode, event );
+ }
+
+ /**
+ * Manually dispatches a KeyEvent through the provider's listening chain.
+ *
+ * @param keyCode The code for the physical key that was pressed.
+ * @param event The KeyEvent object containing full information about the event.
+ *
+ * @return True if the listener has consumed the event, false otherwise.
+ */
+ public boolean onKey( int keyCode, KeyEvent event )
+ {
+ // Ignore specified key codes
+ if( mIgnoredCodes != null && mIgnoredCodes.contains( keyCode ) )
+ {
+ return false;
+ }
+
+ // Translate input code and analog strength (ranges between 0.0 and 1.0)
+ int inputCode;
+ float strength;
+ if( keyCode <= 0xFF )
+ {
+ // Ordinary key/button changed state
+ inputCode = keyCode;
+ strength = 1;
+ }
+ else
+ {
+ // Analog axis changed state, decode using IME-specific formula
+ switch( mImeFormula )
+ {
+ case DEFAULT:
+ case USB_BT_JOYSTICK_CENTER:
+ case BT_CONTROLLER:
+ default:
+ // Formula defined between paulscode and poke64738
+ inputCode = keyCode / 100;
+ strength = ( (float) keyCode % 100 ) / 64f;
+ break;
+ case EXAMPLE_IME:
+ // Low byte stores input code, high byte stores strength
+ inputCode = keyCode & 0xFF;
+ strength = ( (float) ( keyCode >> 8 ) ) / 0xFF;
+ break;
+ }
+ }
+
+ // Strength is zero when the button/axis is released
+ if( event.getAction() == KeyEvent.ACTION_UP )
+ strength = 0;
+
+ // Notify listeners about new input data
+ notifyListeners( inputCode, strength, getHardwareId( event ) );
+
+ return true;
+ }
+}
diff --git a/Android/src/emu/project64/input/provider/MogaProvider.java b/Android/src/emu/project64/input/provider/MogaProvider.java
new file mode 100644
index 000000000..7d1e13218
--- /dev/null
+++ b/Android/src/emu/project64/input/provider/MogaProvider.java
@@ -0,0 +1,97 @@
+/****************************************************************************
+* *
+* Project64 - A Nintendo 64 emulator. *
+* http://www.pj64-emu.com/ *
+* Copyright (C) 2016 Project64. All rights reserved. *
+* Copyright (C) 2013 Paul Lamb, littleguy77
+* *
+* License: *
+* GNU/GPLv2 http://www.gnu.org/licenses/gpl-2.0.html *
+* *
+****************************************************************************/
+package emu.project64.input.provider;
+
+import android.os.Handler;
+
+import com.bda.controller.Controller;
+import com.bda.controller.ControllerListener;
+import com.bda.controller.KeyEvent;
+import com.bda.controller.MotionEvent;
+import com.bda.controller.StateEvent;
+
+/**
+ * A class for transforming MOGA input events into a common format.
+ */
+public class MogaProvider extends AbstractProvider implements ControllerListener
+{
+ private final Controller mController;
+ private final int[] mInputCodes;
+
+ /**
+ * Instantiates a new MOGA provider.
+ */
+ public MogaProvider( Controller controller )
+ {
+ mController = controller;
+ mController.setListener( this, new Handler() );
+
+ mInputCodes = new int[10];
+ //@formatter:off
+ mInputCodes[0] = axisToInputCode( MotionEvent.AXIS_X, true );
+ mInputCodes[1] = axisToInputCode( MotionEvent.AXIS_X, false );
+ mInputCodes[2] = axisToInputCode( MotionEvent.AXIS_Y, true );
+ mInputCodes[3] = axisToInputCode( MotionEvent.AXIS_Y, false );
+ mInputCodes[4] = axisToInputCode( MotionEvent.AXIS_Z, true );
+ mInputCodes[5] = axisToInputCode( MotionEvent.AXIS_Z, false );
+ mInputCodes[6] = axisToInputCode( MotionEvent.AXIS_RZ, true );
+ mInputCodes[7] = axisToInputCode( MotionEvent.AXIS_RZ, false );
+ mInputCodes[8] = axisToInputCode( MotionEvent.AXIS_LTRIGGER, true );
+ mInputCodes[9] = axisToInputCode( MotionEvent.AXIS_RTRIGGER, true );
+ //@formatter:on
+ }
+
+ @Override
+ public void onKeyEvent( KeyEvent event )
+ {
+ int inputCode = event.getKeyCode();
+ float strength = event.getAction() == KeyEvent.ACTION_DOWN ? 1 : 0;
+ int hardwareId = getHardwareId( event );
+
+ // Notify listeners about new input data
+ notifyListeners( inputCode, strength, hardwareId );
+ }
+
+ @Override
+ public void onMotionEvent( MotionEvent event )
+ {
+ // Read all the requested axes
+ float[] strengths = new float[mInputCodes.length];
+ for( int i = 0; i < mInputCodes.length; i++ )
+ {
+ int inputCode = mInputCodes[i];
+
+ // Compute the axis code from the input code
+ int axisCode = inputToAxisCode( inputCode );
+
+ // Get the analog value using the MOGA API
+ float strength = event.getAxisValue( axisCode );
+
+ // If the strength points in the correct direction, record it
+ boolean direction1 = inputToAxisDirection( inputCode );
+ boolean direction2 = strength > 0;
+ if( direction1 == direction2 )
+ strengths[i] = Math.abs( strength );
+ else
+ strengths[i] = 0;
+ }
+ int hardwareId = getHardwareId( event );
+
+ // Notify listeners about new input data
+ notifyListeners( mInputCodes, strengths, hardwareId );
+ }
+
+ @Override
+ public void onStateEvent( StateEvent arg0 )
+ {
+ }
+}
diff --git a/Android/src/emu/project64/jni/UISettingID.java b/Android/src/emu/project64/jni/UISettingID.java
index 07f04f245..f498e1792 100644
--- a/Android/src/emu/project64/jni/UISettingID.java
+++ b/Android/src/emu/project64/jni/UISettingID.java
@@ -26,6 +26,8 @@ public enum UISettingID
//Controller Config
Controller_ConfigFile,
Controller_CurrentProfile,
+ Controller_Deadzone,
+ Controller_Sensitivity,
;
private int value;
diff --git a/Android/src/emu/project64/profile/ControllerProfileActivity.java b/Android/src/emu/project64/profile/ControllerProfileActivity.java
new file mode 100644
index 000000000..9ad834ae3
--- /dev/null
+++ b/Android/src/emu/project64/profile/ControllerProfileActivity.java
@@ -0,0 +1,510 @@
+/****************************************************************************
+* *
+* Project64 - A Nintendo 64 emulator. *
+* http://www.pj64-emu.com/ *
+* Copyright (C) 2012 Project64. All rights reserved. *
+* *
+* License: *
+* GNU/GPLv2 http://www.gnu.org/licenses/gpl-2.0.html *
+* *
+****************************************************************************/
+package emu.project64.profile;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import emu.project64.AndroidDevice;
+import emu.project64.R;
+import emu.project64.hack.MogaHack;
+import emu.project64.input.AbstractController;
+import emu.project64.input.map.InputMap;
+import emu.project64.input.provider.AbstractProvider;
+import emu.project64.input.provider.AbstractProvider.OnInputListener;
+import emu.project64.input.provider.AxisProvider;
+import emu.project64.input.provider.KeyProvider;
+import emu.project64.input.provider.KeyProvider.ImeFormula;
+import emu.project64.input.provider.MogaProvider;
+import emu.project64.jni.NativeExports;
+import emu.project64.jni.UISettingID;
+import emu.project64.persistent.ConfigFile;
+import emu.project64.persistent.ConfigFile.ConfigSection;
+import android.app.AlertDialog;
+import android.app.AlertDialog.Builder;
+import android.annotation.TargetApi;
+import android.content.Context;
+import android.content.DialogInterface;
+import android.graphics.PorterDuff;
+import android.os.Bundle;
+import android.support.v7.app.ActionBar;
+import android.support.v7.app.AppCompatActivity;
+import android.support.v7.widget.Toolbar;
+import android.text.TextUtils;
+import android.util.DisplayMetrics;
+import android.util.Log;
+import android.view.KeyEvent;
+import android.view.MenuItem;
+import android.view.MotionEvent;
+import android.view.View;
+import android.view.View.OnClickListener;
+import android.view.WindowManager;
+import android.widget.Button;
+import android.widget.EditText;
+import android.widget.FrameLayout;
+import android.widget.TextView;
+
+import com.bda.controller.Controller;
+
+public class ControllerProfileActivity extends AppCompatActivity implements OnInputListener,
+ OnClickListener
+{
+ // Visual settings
+ private static final float UNMAPPED_BUTTON_ALPHA = 0.2f;
+ private static final int UNMAPPED_BUTTON_FILTER = 0x66FFFFFF;
+ private static final int MIN_LAYOUT_WIDTH_DP = 480;
+
+ // Controller profile objects
+ private ConfigFile mConfigFile;
+ private Profile mProfile;
+
+ // Input listening
+ private KeyProvider mKeyProvider;
+ private MogaProvider mMogaProvider;
+ private AxisProvider mAxisProvider;
+ private Controller mMogaController = Controller.getInstance( this );
+
+ // Widgets
+ private final Button[] mN64Buttons = new Button[InputMap.NUM_MAPPABLES];
+ private TextView mFeedbackText;
+
+ @Override
+ public void onCreate( Bundle savedInstanceState )
+ {
+ super.onCreate( savedInstanceState );
+
+ // Initialize MOGA controller API
+ // TODO: Remove hack after MOGA SDK is fixed
+ // mMogaController.init();
+ MogaHack.init( mMogaController, this );
+
+ // Load the profile; fail fast if there are any programmer usage errors
+ String name = NativeExports.UISettingsLoadString(UISettingID.Controller_CurrentProfile.getValue());
+ if( TextUtils.isEmpty( name ) )
+ throw new Error( "Invalid usage: profile name cannot be null or empty" );
+ mConfigFile = new ConfigFile(NativeExports.UISettingsLoadString(UISettingID.Controller_ConfigFile.getValue()));
+ ConfigSection section = mConfigFile.get( name );
+ if( section == null )
+ {
+ //profile not found create it
+ mConfigFile.put(name, "map", "-");
+ section = mConfigFile.get( name );
+ if( section == null )
+ {
+ throw new Error( "Invalid usage: profile name not found in config file" );
+ }
+ }
+ mProfile = new Profile( false, section );
+
+ // Set up input listeners
+ mKeyProvider = new KeyProvider( ImeFormula.DEFAULT, AndroidDevice.getUnmappableKeyCodes() );
+ mKeyProvider.registerListener( this );
+ mMogaProvider = new MogaProvider( mMogaController );
+ mMogaProvider.registerListener( this );
+ if( AndroidDevice.IS_HONEYCOMB_MR1 )
+ {
+ mAxisProvider = new AxisProvider();
+ mAxisProvider.registerListener( this );
+ }
+
+ // Initialize the layout
+ initLayoutDefault();
+
+ // Refresh everything
+ refreshAllButtons();
+ }
+
+ @Override
+ public void onResume()
+ {
+ super.onResume();
+ mMogaController.onResume();
+ }
+
+ @Override
+ public void onPause()
+ {
+ super.onPause();
+ mMogaController.onPause();
+
+ // Lazily persist the profile data; only need to do it on pause
+ mProfile.writeTo( mConfigFile );
+ mConfigFile.save();
+ }
+
+ @Override
+ public void onDestroy()
+ {
+ super.onDestroy();
+ mMogaController.exit();
+ }
+
+ @Override
+ public boolean onOptionsItemSelected(MenuItem item)
+ {
+ switch (item.getItemId())
+ {
+ case android.R.id.home:
+ finish();
+ return true;
+ }
+ return super.onOptionsItemSelected(item);
+ }
+
+ @Override
+ public void onBackPressed()
+ {
+ finish();
+ }
+
+ private void initLayoutDefault()
+ {
+ WindowManager manager = (WindowManager) getSystemService( Context.WINDOW_SERVICE );
+ DisplayMetrics metrics = new DisplayMetrics();
+ manager.getDefaultDisplay().getMetrics( metrics );
+ float scalefactor = (float) DisplayMetrics.DENSITY_DEFAULT / (float) metrics.densityDpi;
+ int widthDp = Math.round( metrics.widthPixels * scalefactor );
+
+ // For narrow screens, use an alternate layout
+ if( widthDp < MIN_LAYOUT_WIDTH_DP )
+ {
+ setContentView( R.layout.controller_profile_activity_port );
+ }
+ else
+ {
+ setContentView( R.layout.controller_profile_activity );
+ }
+
+ // Add the tool bar to the activity (which supports the fancy menu/arrow animation)
+ Toolbar toolbar = (Toolbar) findViewById( R.id.toolbar );
+ toolbar.setTitle( getString(R.string.gamepad_title) );
+ setSupportActionBar( toolbar );
+ ActionBar actionbar = getSupportActionBar();
+
+ if (AndroidDevice.IS_ICE_CREAM_SANDWICH)
+ {
+ actionbar.setHomeButtonEnabled(true);
+ actionbar.setDisplayHomeAsUpEnabled(true);
+ }
+
+ // Initialize and refresh the widgets
+ initWidgets();
+ }
+
+ private void initWidgets()
+ {
+ // Get the text view object
+ mFeedbackText = (TextView) findViewById( R.id.textFeedback );
+ mFeedbackText.setText( "" );
+
+ // Create a button list to simplify highlighting and mapping
+ setupButton( R.id.buttonDR, AbstractController.DPD_R );
+ setupButton( R.id.buttonDL, AbstractController.DPD_L );
+ setupButton( R.id.buttonDD, AbstractController.DPD_D );
+ setupButton( R.id.buttonDU, AbstractController.DPD_U );
+ setupButton( R.id.buttonS, AbstractController.START );
+ setupButton( R.id.buttonZ, AbstractController.BTN_Z );
+ setupButton( R.id.buttonB, AbstractController.BTN_B );
+ setupButton( R.id.buttonA, AbstractController.BTN_A );
+ setupButton( R.id.buttonCR, AbstractController.CPD_R );
+ setupButton( R.id.buttonCL, AbstractController.CPD_L );
+ setupButton( R.id.buttonCD, AbstractController.CPD_D );
+ setupButton( R.id.buttonCU, AbstractController.CPD_U );
+ setupButton( R.id.buttonR, AbstractController.BTN_R );
+ setupButton( R.id.buttonL, AbstractController.BTN_L );
+ setupButton( R.id.buttonAR, InputMap.AXIS_R );
+ setupButton( R.id.buttonAL, InputMap.AXIS_L );
+ setupButton( R.id.buttonAD, InputMap.AXIS_D );
+ setupButton( R.id.buttonAU, InputMap.AXIS_U );
+ }
+
+ private void setupButton( int resId, int index )
+ {
+ mN64Buttons[index] = (Button) findViewById( resId );
+ if( mN64Buttons[index] != null )
+ {
+ mN64Buttons[index].setOnClickListener( this );
+ }
+ }
+
+ @Override
+ public void onClick(View view)
+ {
+ // Handle button clicks in the mapping screen
+ for( int i = 0; i < mN64Buttons.length; i++ )
+ {
+ // Find the button that was pressed
+ if( view.equals( mN64Buttons[i] ) )
+ {
+ // Popup a dialog to listen to input codes from user
+ Button button = (Button) view;
+ popupListener( button.getText(), i );
+ }
+ }
+ }
+
+ private interface PromptInputCodeListener
+ {
+ public void onDialogClosed( int inputCode, int hardwareId, int which );
+ }
+
+ /**
+ * Open a dialog to prompt the user for an input code.
+ *
+ * @param context The activity context.
+ * @param moga The MOGA controller interface.
+ * @param title The title of the dialog.
+ * @param message The message to be shown inside the dialog.
+ * @param neutralButtonText The text to be shown on the neutral button, or null.
+ * @param ignoredKeyCodes The key codes to ignore.
+ * @param listener The listener to process the input code, when provided.
+ */
+ public static void promptInputCode( Context context, Controller moga, CharSequence title, CharSequence message,
+ CharSequence neutralButtonText, final PromptInputCodeListener listener )
+ {
+ final ArrayList providers = new ArrayList();
+
+ // Create a widget to dispatch key/motion event data
+ FrameLayout view = new FrameLayout( context );
+ EditText dummyImeListener = new EditText( context );
+ dummyImeListener.setVisibility( View.INVISIBLE );
+ dummyImeListener.setHeight( 0 );
+ view.addView( dummyImeListener );
+
+ // Set the focus parameters of the view so that it will dispatch events
+ view.setFocusable( true );
+ view.setFocusableInTouchMode( true );
+ view.requestFocus();
+
+ // Create the input event providers
+ providers.add( new KeyProvider( view, ImeFormula.DEFAULT, AndroidDevice.getUnmappableKeyCodes() ) );
+ providers.add( new MogaProvider( moga ) );
+ if( AndroidDevice.IS_HONEYCOMB_MR1 )
+ {
+ providers.add( new AxisProvider( view ) );
+ }
+
+ // Notify the client when the user clicks the dialog's positive button
+ DialogInterface.OnClickListener clickListener = new DialogInterface.OnClickListener()
+ {
+ @Override
+ public void onClick( DialogInterface dialog, int which )
+ {
+ for( AbstractProvider provider : providers )
+ provider.unregisterAllListeners();
+ listener.onDialogClosed( 0, 0, which );
+ }
+ };
+
+ final AlertDialog dialog = new Builder( context ).setTitle( title ).setMessage( message ).setCancelable( false )
+ .setNegativeButton( context.getString( android.R.string.cancel ), clickListener )
+ .setPositiveButton( context.getString( android.R.string.ok ), clickListener )
+ .setNeutralButton( neutralButtonText, clickListener ).setPositiveButton( null, null )
+ .setView( view ).create();
+
+ OnInputListener inputListener = new OnInputListener()
+ {
+ @Override
+ public void onInput( int[] inputCodes, float[] strengths, int hardwareId )
+ {
+ if( inputCodes == null || strengths == null )
+ return;
+
+ // Find the strongest input
+ float maxStrength = 0;
+ int strongestInputCode = 0;
+ for( int i = 0; i < inputCodes.length; i++ )
+ {
+ // Identify the strongest input
+ float strength = strengths[i];
+ if( strength > maxStrength )
+ {
+ maxStrength = strength;
+ strongestInputCode = inputCodes[i];
+ }
+ }
+
+ // Call the overloaded method with the strongest found
+ onInput( strongestInputCode, maxStrength, hardwareId );
+ }
+
+ @Override
+ public void onInput( int inputCode, float strength, int hardwareId )
+ {
+ if( inputCode != 0 && strength > AbstractProvider.STRENGTH_THRESHOLD )
+ {
+ for( AbstractProvider provider : providers )
+ provider.unregisterAllListeners();
+ listener.onDialogClosed( inputCode, hardwareId, DialogInterface.BUTTON_POSITIVE );
+ dialog.dismiss();
+ }
+ }
+ };
+
+ // Connect the upstream event listeners
+ for( AbstractProvider provider : providers )
+ {
+ provider.registerListener( inputListener );
+ }
+
+ // Launch the dialog
+ dialog.show();
+ }
+
+ private void popupListener( CharSequence title, final int index )
+ {
+ final InputMap map = new InputMap( mProfile.get( "map" ) );
+ String message = getString( R.string.inputMapActivity_popupMessage,map.getMappedCodeInfo( index ) );
+ String btnText = getString( R.string.inputMapActivity_popupUnmap );
+
+ PromptInputCodeListener listener = new PromptInputCodeListener()
+ {
+ @Override
+ public void onDialogClosed( int inputCode, int hardwareId, int which )
+ {
+ if( which != DialogInterface.BUTTON_NEGATIVE )
+ {
+ if( which == DialogInterface.BUTTON_POSITIVE )
+ {
+ map.map( inputCode, index );
+ }
+ else
+ {
+ map.unmapCommand( index );
+ }
+ mProfile.put( "map", map.serialize() );
+ refreshAllButtons();
+ }
+
+ // Refresh our MOGA provider since the prompt disconnected it
+ mMogaProvider = new MogaProvider( mMogaController );
+ mMogaProvider.registerListener( ControllerProfileActivity.this );
+ }
+ };
+ promptInputCode( this, mMogaController, title, message, btnText, listener);
+ }
+
+ @Override
+ public boolean onKeyDown( int keyCode, KeyEvent event )
+ {
+ return mKeyProvider.onKey( keyCode, event ) || super.onKeyDown( keyCode, event );
+ }
+
+ @Override
+ public boolean onKeyUp( int keyCode, KeyEvent event )
+ {
+ return mKeyProvider.onKey( keyCode, event ) || super.onKeyUp( keyCode, event );
+ }
+
+ @TargetApi( 12 )
+ @Override
+ public boolean onGenericMotionEvent( MotionEvent event )
+ {
+ if( !AndroidDevice.IS_HONEYCOMB_MR1 )
+ {
+ return false;
+ }
+ return mAxisProvider.onGenericMotion( event ) || super.onGenericMotionEvent( event );
+ }
+
+ @Override
+ public void onInput(int inputCode, float strength, int hardwareId)
+ {
+ refreshButton( inputCode, strength );
+ refreshFeedbackText( inputCode, strength );
+ }
+
+ @Override
+ public void onInput(int[] inputCodes, float[] strengths, int hardwareId)
+ {
+ float maxStrength = AbstractProvider.STRENGTH_THRESHOLD;
+ int strongestInputCode = 0;
+ for( int i = 0; i < inputCodes.length; i++ )
+ {
+ int inputCode = inputCodes[i];
+ float strength = strengths[i];
+
+ // Cache the strongest input
+ if( strength > maxStrength )
+ {
+ maxStrength = strength;
+ strongestInputCode = inputCode;
+ }
+
+ refreshButton( inputCode, strength );
+ }
+ refreshFeedbackText( strongestInputCode, maxStrength );
+ }
+
+ private void refreshFeedbackText( int inputCode, float strength )
+ {
+ // Update the feedback text (not all layouts include this, so check null)
+ if( mFeedbackText != null )
+ {
+ mFeedbackText.setText( strength > AbstractProvider.STRENGTH_THRESHOLD ? AbstractProvider.getInputName( inputCode ) : "" );
+ }
+ }
+
+ private void refreshButton( int inputCode, float strength )
+ {
+ InputMap map = new InputMap( mProfile.get( "map" ) );
+ int command = map.get( inputCode );
+ if( command != InputMap.UNMAPPED )
+ {
+ Button button = mN64Buttons[command];
+ refreshButton( button, strength, true );
+ }
+ }
+
+ @TargetApi( 11 )
+ private void refreshButton( Button button, float strength, boolean isMapped )
+ {
+ if( button != null )
+ {
+ button.setPressed( strength > AbstractProvider.STRENGTH_THRESHOLD );
+
+ // Fade any buttons that aren't mapped
+ if( AndroidDevice.IS_HONEYCOMB )
+ {
+ if( isMapped )
+ {
+ button.setAlpha( 1 );
+ }
+ else
+ {
+ button.setAlpha( UNMAPPED_BUTTON_ALPHA );
+ }
+ }
+ else
+ {
+ // For older APIs try something similar (not quite the same)
+ if( isMapped )
+ {
+ button.getBackground().clearColorFilter();
+ }
+ else
+ {
+ button.getBackground().setColorFilter( UNMAPPED_BUTTON_FILTER, PorterDuff.Mode.MULTIPLY );
+ }
+ button.invalidate();
+ }
+ }
+ }
+
+ private void refreshAllButtons()
+ {
+ final InputMap map = new InputMap( mProfile.get( "map" ) );
+ for( int i = 0; i < mN64Buttons.length; i++ )
+ {
+ refreshButton( mN64Buttons[i], 0, map.isMapped( i ) );
+ }
+ }
+}
diff --git a/Android/src/emu/project64/settings/BaseSettingsFragment.java b/Android/src/emu/project64/settings/BaseSettingsFragment.java
index af576f681..80a41d69a 100644
--- a/Android/src/emu/project64/settings/BaseSettingsFragment.java
+++ b/Android/src/emu/project64/settings/BaseSettingsFragment.java
@@ -26,6 +26,7 @@ package emu.project64.settings;
import emu.project64.R;
import emu.project64.SplashActivity;
import emu.project64.jni.NativeExports;
+import emu.project64.profile.ControllerProfileActivity;
import android.content.DialogInterface;
import android.content.Intent;
import android.os.Bundle;
@@ -38,6 +39,8 @@ import android.support.v7.preference.PreferenceFragmentCompat;
public abstract class BaseSettingsFragment extends PreferenceFragmentCompat
{
+ private static final String DIALOG_FRAGMENT_TAG = "android.support.v7.preference.PreferenceFragment.DIALOG";
+
protected abstract int getXml();
protected abstract int getTitleId();
@@ -68,19 +71,45 @@ public abstract class BaseSettingsFragment extends PreferenceFragmentCompat
@Override
public void onDisplayPreferenceDialog(Preference preference)
{
- /*if (AndroidUtil.isHoneycombOrLater() && preference instanceof MultiSelectListPreference) {
- DialogFragment dialogFragment = MultiSelectListPreferenceDialogFragmentCompat.newInstance(preference.getKey());
+ DialogFragment dialogFragment = null;
+ if (preference instanceof SeekBarPreference)
+ {
+ dialogFragment = SeekBarPreferencePreferenceDialogFragmentCompat.newInstance(preference.getKey());
+ }
+ else if (preference instanceof TwoLinesListPreference)
+ {
+ dialogFragment = TwoLinesListPreferenceDialogFragmentCompat.newInstance(preference.getKey());
+ }
+ else
+ {
+ super.onDisplayPreferenceDialog(preference);
+ }
+
+ if (dialogFragment != null)
+ {
dialogFragment.setTargetFragment(this, 0);
dialogFragment.show(getFragmentManager(), DIALOG_FRAGMENT_TAG);
- return;
- }*/
- super.onDisplayPreferenceDialog(preference);
+ }
}
@Override
public boolean onPreferenceTreeClick(Preference preference)
{
- if (preference.getKey().equals("settings_video"))
+ if (preference.getKey().equals("settings_input"))
+ {
+ loadFragment(new InputFragment());
+ }
+ else if (preference.getKey().equals("settings_touch_screen"))
+ {
+ loadFragment(new TouchScreenFragment());
+ }
+ else if (preference.getKey().equals("settings_gamepad_screen"))
+ {
+ final AppCompatActivity activity = (AppCompatActivity)getActivity();
+ Intent intent = new Intent( activity, ControllerProfileActivity.class );
+ activity.startActivity( intent );
+ }
+ else if (preference.getKey().equals("settings_video"))
{
loadFragment(new VideoFragment());
}
diff --git a/Android/src/emu/project64/settings/GameSettingsFragment.java b/Android/src/emu/project64/settings/GameSettingsFragment.java
index 35c74aaa4..39df4f5cf 100644
--- a/Android/src/emu/project64/settings/GameSettingsFragment.java
+++ b/Android/src/emu/project64/settings/GameSettingsFragment.java
@@ -11,14 +11,6 @@
package emu.project64.settings;
import emu.project64.R;
-import emu.project64.jni.NativeExports;
-import emu.project64.jni.SettingsID;
-import android.content.Intent;
-import android.content.SharedPreferences;
-import android.os.Bundle;
-import android.support.v7.preference.ListPreference;
-import android.support.v7.preference.Preference;
-import android.support.v7.preference.PreferenceManager;
public class GameSettingsFragment extends BaseSettingsFragment
{
diff --git a/Android/src/emu/project64/settings/GamepadScreenFragment.java b/Android/src/emu/project64/settings/GamepadScreenFragment.java
new file mode 100644
index 000000000..d134bfec8
--- /dev/null
+++ b/Android/src/emu/project64/settings/GamepadScreenFragment.java
@@ -0,0 +1,57 @@
+/****************************************************************************
+* *
+* Project64 - A Nintendo 64 emulator. *
+* http://www.pj64-emu.com/ *
+* Copyright (C) 2012 Project64. All rights reserved. *
+* *
+* License: *
+* GNU/GPLv2 http://www.gnu.org/licenses/gpl-2.0.html *
+* *
+****************************************************************************/
+package emu.project64.settings;
+
+import android.os.Bundle;
+import emu.project64.R;
+
+public class GamepadScreenFragment extends BaseSettingsFragment
+{
+ @Override
+ protected int getXml()
+ {
+ return R.xml.setting_gamepad;
+ }
+
+ @Override
+ protected int getTitleId()
+ {
+ return R.string.gamepad_title;
+ }
+
+ @Override
+ public void onCreatePreferences(Bundle bundle, String s)
+ {
+ super.onCreatePreferences(bundle, s);
+
+ /*String profilesDir = AndroidDevice.PACKAGE_DIRECTORY + "/profiles";
+ String touchscreenProfiles_cfg = profilesDir + "/touchscreen.cfg";
+ ConfigFile touchscreenProfiles = new ConfigFile( touchscreenProfiles_cfg );
+ Set layoutsKeySet = touchscreenProfiles.keySet();
+ String[] layouts = layoutsKeySet.toArray(new String[layoutsKeySet.size()]);
+
+ CharSequence[] entries = new CharSequence[layouts.length];
+ String[] entryValues = new String[layouts.length];
+ String[] entrySubtitles = new String[layouts.length];
+
+ for( int i = 0; i < layouts.length; i++ )
+ {
+ entries[i] = layouts[i];
+ entryValues[i] = layouts[i];
+ entrySubtitles[i] = touchscreenProfiles.get(layouts[i]).get("comment");
+ }
+
+ final TwoLinesListPreference listPreference = (TwoLinesListPreference) findPreference("touchscreenLayout");
+ listPreference.setEntries(entries);
+ listPreference.setEntryValues(entryValues);
+ listPreference.setEntriesSubtitles(entrySubtitles);*/
+ }
+}
diff --git a/Android/src/emu/project64/settings/InputFragment.java b/Android/src/emu/project64/settings/InputFragment.java
new file mode 100644
index 000000000..fea3fb8dc
--- /dev/null
+++ b/Android/src/emu/project64/settings/InputFragment.java
@@ -0,0 +1,28 @@
+/****************************************************************************
+* *
+* Project64 - A Nintendo 64 emulator. *
+* http://www.pj64-emu.com/ *
+* Copyright (C) 2012 Project64. All rights reserved. *
+* *
+* License: *
+* GNU/GPLv2 http://www.gnu.org/licenses/gpl-2.0.html *
+* *
+****************************************************************************/
+package emu.project64.settings;
+
+import emu.project64.R;
+
+public class InputFragment extends SettingsFragment
+{
+ @Override
+ protected int getXml()
+ {
+ return R.xml.setting_input;
+ }
+
+ @Override
+ protected int getTitleId()
+ {
+ return R.string.input_screen_title;
+ }
+}
diff --git a/Android/src/emu/project64/settings/SeekBarPreference.java b/Android/src/emu/project64/settings/SeekBarPreference.java
new file mode 100644
index 000000000..978ccd742
--- /dev/null
+++ b/Android/src/emu/project64/settings/SeekBarPreference.java
@@ -0,0 +1,222 @@
+/****************************************************************************
+* *
+* Project64 - A Nintendo 64 emulator. *
+* http://www.pj64-emu.com/ *
+* Copyright (C) 2012 Project64. All rights reserved. *
+* *
+* License: *
+* GNU/GPLv2 http://www.gnu.org/licenses/gpl-2.0.html *
+* *
+****************************************************************************/
+package emu.project64.settings;
+
+import emu.project64.R;
+import android.content.Context;
+import android.content.res.TypedArray;
+import android.support.v7.preference.DialogPreference;
+import android.util.AttributeSet;
+import android.widget.SeekBar;
+
+/**
+ * A type of {@link DialogPreference} that uses a {@link SeekBar} as a means of selecting a desired option.
+ */
+public class SeekBarPreference extends DialogPreference
+{
+ private static final int DEFAULT_VALUE = 50;
+ private static final int DEFAULT_MIN = 0;
+ private static final int DEFAULT_MAX = 100;
+ private static final int DEFAULT_STEP = 10;
+ private static final String DEFAULT_UNITS = "%";
+
+ private int mValue = DEFAULT_VALUE;
+ private int mMinValue = DEFAULT_MIN;
+ private int mMaxValue = DEFAULT_MAX;
+ private int mStepSize = DEFAULT_STEP;
+ private String mUnits = DEFAULT_UNITS;
+
+ /**
+ * Constructor
+ *
+ * @param context The {@link Context} this SeekBarPreference is being used in.
+ * @param attrs A collection of attributes, as found associated with a tag in an XML document.
+ */
+ public SeekBarPreference( Context context, AttributeSet attrs )
+ {
+ super( context, attrs );
+
+ // Get the attributes from the XML file, if provided
+ TypedArray a = context.obtainStyledAttributes( attrs, R.styleable.SeekBarPreference );
+ setMinValue( a.getInteger( R.styleable.SeekBarPreference_minimumValue, DEFAULT_MIN ) );
+ setMaxValue( a.getInteger( R.styleable.SeekBarPreference_maximumValue, DEFAULT_MAX ) );
+ setStepSize( a.getInteger( R.styleable.SeekBarPreference_stepSize, DEFAULT_STEP ) );
+ setUnits( a.getString( R.styleable.SeekBarPreference_units ) );
+ if (getUnits() == null)
+ {
+ setUnits(DEFAULT_UNITS);
+ }
+ a.recycle();
+
+ // Setup the layout
+ setDialogLayoutResource( R.layout.seek_bar_preference );
+ }
+
+ /**
+ * Constructor
+ *
+ * @param context The {@link Context} this SeekBarPreference will be used in.
+ */
+ public SeekBarPreference( Context context )
+ {
+ this( context, null );
+ }
+
+ /**
+ * Sets this SeekBarPreference to a specified value.
+ *
+ * @param value The value to set the SeekBarPreference to.
+ */
+ public void setValue( int value )
+ {
+ mValue = validate( value );
+ if( shouldPersist() )
+ persistInt( mValue );
+ setSummary( getValueString( mValue ) );
+ }
+
+ /**
+ * Sets the minimum value this SeekBarPreference may have.
+ *
+ * @param minValue The minimum value for this SeekBarPreference.
+ */
+ public void setMinValue( int minValue )
+ {
+ mMinValue = minValue;
+ }
+
+ /**
+ * Sets the maximum value this SeekBarPreference may have.
+ *
+ * @param maxValue The maximum value for this SeekBarPreference.
+ */
+ public void setMaxValue( int maxValue )
+ {
+ mMaxValue = maxValue;
+ }
+
+ /**
+ * Sets the size of each increment in this SeekBarPreference.
+ *
+ * @param stepSize The size of each increment.
+ */
+ public void setStepSize( int stepSize )
+ {
+ mStepSize = stepSize;
+ }
+
+ /**
+ * Sets the type of units this SeekBarPreference uses (e.g. "%").
+ *
+ * @param units The unit type for this SeekBarPreference to use.
+ */
+ public void setUnits( String units )
+ {
+ mUnits = units;
+ }
+
+ /**
+ * Gets the currently set value.
+ *
+ * @return The currently set value in this SeekBarPreference.
+ */
+ public int getValue()
+ {
+ return mValue;
+ }
+
+ /**
+ * Gets the currently set minimum value.
+ *
+ * @return The currently set minimum value for this SeekBarPreference.
+ */
+ public int getMinValue()
+ {
+ return mMinValue;
+ }
+
+ /**
+ * Gets the currently set maximum value.
+ *
+ * @return The currently set maximum value for this SeekBarPreference.
+ */
+ public int getMaxValue()
+ {
+ return mMaxValue;
+ }
+
+ /**
+ * Gets the currently set increment step size.
+ *
+ * @return The currently set increment step size for this SeekBarPreference.
+ */
+ public int getStepSize()
+ {
+ return mStepSize;
+ }
+
+ /**
+ * Gets the currently set units.
+ *
+ * @return The currently set unit type this SeekBarPreference uses.
+ */
+ public String getUnits()
+ {
+ return mUnits;
+ }
+
+ /**
+ * Gets the value as a string with units appended.
+ *
+ * @param value The value to use in the string.
+ *
+ * @return The value as a String.
+ */
+ public String getValueString( int value )
+ {
+ return getContext().getString( R.string.seekBarPreference_summary, value, mUnits );
+ }
+
+ @Override
+ protected Object onGetDefaultValue( TypedArray a, int index )
+ {
+ return a.getInteger( index, DEFAULT_VALUE );
+ }
+
+ @Override
+ protected void onSetInitialValue( boolean restorePersistedValue, Object defaultValue )
+ {
+ setValue( restorePersistedValue ? getPersistedInt( mValue ) : (Integer) defaultValue );
+ }
+
+ @Override
+ public void onAttached()
+ {
+ setSummary( getValueString( mValue ) );
+ super.onAttached();
+ }
+
+ public int validate( int value )
+ {
+ // Round to nearest integer multiple of mStepSize
+ int newValue = Math.round( value / (float)getStepSize() ) * getStepSize();
+
+ // Address issues when mStepSize is not an integral factor of mMaxValue
+ // e.g. mMaxValue = 100, mMinValue = 0, mStepSize = 9, progress = 100 --> newValue = 99 (should be 100)
+ // e.g. mMaxValue = 100, mMinValue = 0, mStepSize = 6, progress = 99 --> newValue = 102 (should be 100)
+ if( value == getMinValue() || newValue < getMinValue() )
+ newValue = getMinValue();
+ if( value == getMaxValue() || newValue > getMaxValue() )
+ newValue = getMaxValue();
+
+ return newValue;
+ }
+}
diff --git a/Android/src/emu/project64/settings/SeekBarPreferencePreferenceDialogFragmentCompat.java b/Android/src/emu/project64/settings/SeekBarPreferencePreferenceDialogFragmentCompat.java
new file mode 100644
index 000000000..92af133ae
--- /dev/null
+++ b/Android/src/emu/project64/settings/SeekBarPreferencePreferenceDialogFragmentCompat.java
@@ -0,0 +1,102 @@
+/****************************************************************************
+* *
+* Project64 - A Nintendo 64 emulator. *
+* http://www.pj64-emu.com/ *
+* Copyright (C) 2012 Project64. All rights reserved. *
+* *
+* License: *
+* GNU/GPLv2 http://www.gnu.org/licenses/gpl-2.0.html *
+* *
+****************************************************************************/
+package emu.project64.settings;
+
+import emu.project64.R;
+import android.os.Bundle;
+import android.support.v7.app.AlertDialog;
+import android.support.v7.preference.DialogPreference;
+import android.support.v7.preference.Preference;
+import android.support.v7.preference.PreferenceDialogFragmentCompat;
+import android.view.View;
+import android.widget.SeekBar;
+import android.widget.TextView;
+import android.widget.SeekBar.OnSeekBarChangeListener;
+
+public class SeekBarPreferencePreferenceDialogFragmentCompat extends PreferenceDialogFragmentCompat
+ implements DialogPreference.TargetFragment, OnSeekBarChangeListener
+{
+ private TextView mTextView;
+ private SeekBar mSeekBar;
+
+ public void onDialogClosed(boolean positiveResult)
+ {
+ if( positiveResult )
+ {
+ final SeekBarPreference preference = getSeekBarPreference();
+ int value = mSeekBar.getProgress() + preference.getMinValue();
+ preference.setValue( value );
+ }
+ }
+
+ public static SeekBarPreferencePreferenceDialogFragmentCompat newInstance(String key)
+ {
+ SeekBarPreferencePreferenceDialogFragmentCompat fragment = new SeekBarPreferencePreferenceDialogFragmentCompat();
+ Bundle b = new Bundle(1);
+ b.putString("key", key);
+ fragment.setArguments(b);
+ return fragment;
+ }
+
+ @Override
+ public Preference findPreference(CharSequence charSequence)
+ {
+ return getPreference();
+ }
+
+ private SeekBarPreference getSeekBarPreference()
+ {
+ return (SeekBarPreference)this.getPreference();
+ }
+
+ @Override
+ protected void onBindDialogView(View view)
+ {
+ super.onBindDialogView(view);
+
+ // Grab the widget references
+ mTextView = (TextView) view.findViewById( R.id.textFeedback );
+ mSeekBar = (SeekBar) view.findViewById( R.id.seekbar );
+ }
+
+ protected void onPrepareDialogBuilder(AlertDialog.Builder builder)
+ {
+ super.onPrepareDialogBuilder(builder);
+ final SeekBarPreference preference = getSeekBarPreference();
+
+ // Initialize and refresh the widgets
+ mSeekBar.setMax( preference.getMaxValue() - preference.getMinValue());
+ mSeekBar.setOnSeekBarChangeListener( this );
+ mSeekBar.setProgress( preference.getValue() - preference.getMinValue() );
+ mTextView.setText( preference.getValueString( preference.getValue() ) );
+ }
+
+ @Override
+ public void onStartTrackingTouch( SeekBar seekBar )
+ {
+ }
+
+ @Override
+ public void onStopTrackingTouch( SeekBar seekBar )
+ {
+ }
+
+ @Override
+ public void onProgressChanged( SeekBar seekBar, int progress, boolean fromUser )
+ {
+ final SeekBarPreference preference = getSeekBarPreference();
+
+ int value = preference.validate( progress + preference.getMinValue() );
+ if( value != ( progress + preference.getMinValue() ) )
+ seekBar.setProgress( value - preference.getMinValue() );
+ mTextView.setText( preference.getValueString( value ) );
+ }
+}
diff --git a/Android/src/emu/project64/settings/TouchScreenFragment.java b/Android/src/emu/project64/settings/TouchScreenFragment.java
new file mode 100644
index 000000000..655cac375
--- /dev/null
+++ b/Android/src/emu/project64/settings/TouchScreenFragment.java
@@ -0,0 +1,61 @@
+/****************************************************************************
+* *
+* Project64 - A Nintendo 64 emulator. *
+* http://www.pj64-emu.com/ *
+* Copyright (C) 2012 Project64. All rights reserved. *
+* *
+* License: *
+* GNU/GPLv2 http://www.gnu.org/licenses/gpl-2.0.html *
+* *
+****************************************************************************/
+package emu.project64.settings;
+
+import java.util.Set;
+
+import android.os.Bundle;
+import emu.project64.AndroidDevice;
+import emu.project64.R;
+import emu.project64.persistent.ConfigFile;
+
+public class TouchScreenFragment extends BaseSettingsFragment
+{
+ @Override
+ protected int getXml()
+ {
+ return R.xml.setting_touch_screen;
+ }
+
+ @Override
+ protected int getTitleId()
+ {
+ return R.string.touch_screen_title;
+ }
+
+ @Override
+ public void onCreatePreferences(Bundle bundle, String s)
+ {
+ super.onCreatePreferences(bundle, s);
+
+ String profilesDir = AndroidDevice.PACKAGE_DIRECTORY + "/profiles";
+ String touchscreenProfiles_cfg = profilesDir + "/touchscreen.cfg";
+ ConfigFile touchscreenProfiles = new ConfigFile( touchscreenProfiles_cfg );
+ Set layoutsKeySet = touchscreenProfiles.keySet();
+ String[] layouts = layoutsKeySet.toArray(new String[layoutsKeySet.size()]);
+
+ CharSequence[] entries = new CharSequence[layouts.length];
+ String[] entryValues = new String[layouts.length];
+ String[] entrySubtitles = new String[layouts.length];
+
+ for( int i = 0; i < layouts.length; i++ )
+ {
+ entries[i] = layouts[i];
+ entryValues[i] = layouts[i];
+ entrySubtitles[i] = touchscreenProfiles.get(layouts[i]).get("comment");
+ }
+
+ final TwoLinesListPreference listPreference = (TwoLinesListPreference) findPreference("touchscreenLayout");
+ listPreference.setEntries(entries);
+ listPreference.setEntryValues(entryValues);
+ listPreference.setEntriesSubtitles(entrySubtitles);
+ }
+}
diff --git a/Android/src/emu/project64/settings/TwoLinesListPreference.java b/Android/src/emu/project64/settings/TwoLinesListPreference.java
new file mode 100644
index 000000000..3a94707eb
--- /dev/null
+++ b/Android/src/emu/project64/settings/TwoLinesListPreference.java
@@ -0,0 +1,119 @@
+/****************************************************************************
+* *
+* Project64 - A Nintendo 64 emulator. *
+* http://www.pj64-emu.com/ *
+* Copyright (C) 2012 Project64. All rights reserved. *
+* *
+* License: *
+* GNU/GPLv2 http://www.gnu.org/licenses/gpl-2.0.html *
+* *
+****************************************************************************/
+package emu.project64.settings;
+
+import emu.project64.R;
+import android.content.Context;
+import android.content.res.TypedArray;
+import android.support.v7.preference.ListPreference;
+import android.util.AttributeSet;
+
+public class TwoLinesListPreference extends ListPreference
+{
+ private CharSequence[] mEntriesSubtitles;
+ private int mValueIndex;
+
+ public TwoLinesListPreference(Context context, AttributeSet attrs)
+ {
+ super(context, attrs);
+ TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.TwoLinesListPreference);
+ mEntriesSubtitles = a.getTextArray(R.styleable.TwoLinesListPreference_entriesSubtitles);
+ a.recycle();
+ }
+
+ @Override
+ protected void onSetInitialValue(boolean restoreValue, Object defaultValue)
+ {
+ super.onSetInitialValue(restoreValue, defaultValue);
+
+ mEntriesSubtitles = getEntriesSubtitles();
+ mValueIndex = getValueIndex();
+ }
+
+ @Override
+ public void setValue(String value)
+ {
+ super.setValue(value);
+ mValueIndex = getValueIndex();
+ updateSummary();
+ }
+ /**
+ * Returns the index of the given value (in the entry values array).
+ *
+ * @param value The value whose index should be returned.
+ * @return The index of the value, or -1 if not found.
+ */
+ public int findIndexOfValue(String value)
+ {
+ CharSequence[] EntryValues = getEntryValues();
+ if (value != null && EntryValues != null)
+ {
+ for (int i = EntryValues.length - 1; i >= 0; i--)
+ {
+ if (EntryValues[i].equals(value))
+ {
+ return i;
+ }
+ }
+ }
+ return -1;
+ }
+
+ public int getValueIndex()
+ {
+ return findIndexOfValue(getValue());
+ }
+
+ public CharSequence[] getEntriesSubtitles()
+ {
+ return mEntriesSubtitles;
+ }
+
+ @Override
+ public void setEntries(CharSequence[] Entries)
+ {
+ super.setEntries(Entries);
+ updateSummary();
+ }
+
+ @Override
+ public void setEntryValues(CharSequence[] EntryValues)
+ {
+ super.setEntryValues(EntryValues);
+ mValueIndex = getValueIndex();
+ updateSummary();
+ }
+
+ public void setEntriesSubtitles(CharSequence[] mEntriesSubtitles)
+ {
+ this.mEntriesSubtitles = mEntriesSubtitles;
+ updateSummary();
+ }
+
+ private void updateSummary()
+ {
+ if (mValueIndex < 0)
+ {
+ return;
+ }
+ CharSequence[] Entries = getEntries();
+ String summary = Entries[mValueIndex].toString();
+ if (mEntriesSubtitles != null && mEntriesSubtitles.length > mValueIndex)
+ {
+ String subtitle = mEntriesSubtitles[mValueIndex].toString();
+ if (summary.length() > 0 && subtitle.length() > 0)
+ {
+ summary += " - " + subtitle;
+ }
+ }
+ setSummary( summary );
+ }
+}
\ No newline at end of file
diff --git a/Android/src/emu/project64/settings/TwoLinesListPreferenceDialogFragmentCompat.java b/Android/src/emu/project64/settings/TwoLinesListPreferenceDialogFragmentCompat.java
new file mode 100644
index 000000000..f69d47c3a
--- /dev/null
+++ b/Android/src/emu/project64/settings/TwoLinesListPreferenceDialogFragmentCompat.java
@@ -0,0 +1,133 @@
+/****************************************************************************
+* *
+* Project64 - A Nintendo 64 emulator. *
+* http://www.pj64-emu.com/ *
+* Copyright (C) 2012 Project64. All rights reserved. *
+* *
+* License: *
+* GNU/GPLv2 http://www.gnu.org/licenses/gpl-2.0.html *
+* *
+****************************************************************************/
+package emu.project64.settings;
+
+import emu.project64.R;
+import android.content.Context;
+import android.content.DialogInterface;
+import android.os.Bundle;
+import android.support.v7.app.AlertDialog;
+import android.support.v7.preference.PreferenceDialogFragmentCompat;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.View.OnClickListener;
+import android.view.ViewGroup;
+import android.widget.ArrayAdapter;
+import android.widget.ListAdapter;
+import android.widget.RadioButton;
+import android.widget.TextView;
+
+public class TwoLinesListPreferenceDialogFragmentCompat extends PreferenceDialogFragmentCompat
+{
+ private TwoLinesListPreference getTwoLinesListPreference()
+ {
+ return (TwoLinesListPreference)this.getPreference();
+ }
+
+ class YourAdapter extends ArrayAdapter
+ {
+ public YourAdapter(Context context, String[] values)
+ {
+ super(context, R.layout.two_lines_list_preference_row, values);
+ }
+
+ class ViewHolder
+ {
+ TextView title;
+ TextView subTitle;
+ RadioButton radioBtn;
+ }
+
+ ViewHolder holder;
+ ViewHolderClickListener listener = new ViewHolderClickListener();
+
+ @Override
+ public View getView(int position, View convertView, ViewGroup parent)
+ {
+ final LayoutInflater inflater = (LayoutInflater) getContext().getSystemService(Context.LAYOUT_INFLATER_SERVICE);
+ if (convertView == null)
+ {
+ convertView = inflater.inflate(R.layout.two_lines_list_preference_row, null);
+ holder = new ViewHolder();
+ holder.title = (TextView) convertView.findViewById(R.id.two_lines_list_view_row_text);
+ holder.subTitle = (TextView) convertView.findViewById(R.id.two_lines_list_view_row_subtext);
+ holder.radioBtn = (RadioButton) convertView.findViewById(R.id.two_lines_list_view_row_radiobtn);
+ convertView.setTag(holder);
+
+ holder.title.setOnClickListener(listener);
+ holder.subTitle.setOnClickListener(listener);
+ holder.radioBtn.setOnClickListener(listener);
+ }
+ else
+ {
+ holder = (ViewHolder) convertView.getTag();
+ }
+ final TwoLinesListPreference preference = getTwoLinesListPreference();
+
+ holder.title.setText(preference.getEntries()[position]);
+ holder.title.setTag(position);
+ holder.subTitle.setText(preference.getEntriesSubtitles()[position]);
+ holder.subTitle.setTag(position);
+
+ holder.radioBtn.setChecked(preference.getValueIndex() == position);
+ holder.radioBtn.setTag(position);
+
+ return convertView;
+ }
+ }
+
+ @Override
+ protected void onPrepareDialogBuilder(AlertDialog.Builder builder)
+ {
+ super.onPrepareDialogBuilder(builder);
+ final TwoLinesListPreference preference = getTwoLinesListPreference();
+ CharSequence[] entries = preference.getEntries();
+ String[] values = new String[ entries.length ];
+ for (int i = 0; i < entries.length; i ++)
+ {
+ values[i] = entries[i].toString();
+ }
+
+ ListAdapter adapter = new YourAdapter(builder.getContext(), values);
+ builder.setAdapter(adapter, new DialogInterface.OnClickListener()
+ {
+ @Override
+ public void onClick(DialogInterface dialog, int which)
+ {
+ }
+ });
+ }
+
+ public static TwoLinesListPreferenceDialogFragmentCompat newInstance(String key)
+ {
+ TwoLinesListPreferenceDialogFragmentCompat fragment = new TwoLinesListPreferenceDialogFragmentCompat();
+ Bundle b = new Bundle(1);
+ b.putString("key", key);
+ fragment.setArguments(b);
+ return fragment;
+ }
+
+ public void onDialogClosed(boolean positiveResult)
+ {
+ }
+
+ private class ViewHolderClickListener implements OnClickListener
+ {
+ @Override
+ public void onClick(View v)
+ {
+ final TwoLinesListPreference preference = getTwoLinesListPreference();
+ int EntryIndex = (Integer) v.getTag();
+ preference.setValue(preference.getEntries()[EntryIndex].toString());
+ TwoLinesListPreferenceDialogFragmentCompat.this.getDialog().dismiss();
+ }
+ }
+}
diff --git a/Source/Android/Bridge/UISettings.cpp b/Source/Android/Bridge/UISettings.cpp
index d223c1e49..dada87abe 100644
--- a/Source/Android/Bridge/UISettings.cpp
+++ b/Source/Android/Bridge/UISettings.cpp
@@ -24,6 +24,8 @@ void RegisterUISettings(void)
g_Settings->AddHandler((SettingID)(FirstUISettings + TouchScreen_Layout), new CSettingTypeApplication("Touch Screen", "Layout", "Analog"));
g_Settings->AddHandler((SettingID)(FirstUISettings + Controller_ConfigFile), new CSettingTypeRelativePath("Config", "Controller.cfg"));
g_Settings->AddHandler((SettingID)(FirstUISettings + Controller_CurrentProfile), new CSettingTypeApplication("Controller", "Profile", "User"));
+ g_Settings->AddHandler((SettingID)(FirstUISettings + Controller_Deadzone), new CSettingTypeApplication("Controller", "Deadzone", (uint32_t)0));
+ g_Settings->AddHandler((SettingID)(FirstUISettings + Controller_Sensitivity), new CSettingTypeApplication("Controller", "Sensitivity", (uint32_t)100));
}
void UISettingsSaveBool(UISettingID Type, bool Value)
diff --git a/Source/Android/Bridge/UISettings.h b/Source/Android/Bridge/UISettings.h
index b1d7eb258..6e835f6eb 100644
--- a/Source/Android/Bridge/UISettings.h
+++ b/Source/Android/Bridge/UISettings.h
@@ -28,6 +28,8 @@ enum UISettingID
//Controller Config
Controller_ConfigFile,
Controller_CurrentProfile,
+ Controller_Deadzone,
+ Controller_Sensitivity,
};
void RegisterUISettings(void);