Skip to content

Commit

Permalink
front: Added InputMapActivity, soon to supersede InputMapPreference.
Browse files Browse the repository at this point in the history
The InputMapPreference UI is too limiting for the many functions needed
during input mapping. This is because it uses a neutered version of an
AlertDialog provided by DialogPreference. We need more control, namely
a simple way to add extra menus and callbacks, without the limitations
of the AlertDialog UI and without the show/hide semantics forced by
DialogPreference.

So we give the input mapping process its own activity.  This commit
provides most of the implementation for that activity, but stops short
of actually replacing the old InputMapPreferences in the menu.  This
commit provides a possible branch point for testing the new activity
while safely maintaining all of the existing UI and functionality.
  • Loading branch information
littleguy77 committed Jan 21, 2013
1 parent 2d1423c commit 6e25a62
Show file tree
Hide file tree
Showing 3 changed files with 369 additions and 1 deletion.
12 changes: 11 additions & 1 deletion AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,8 @@
</activity>
<activity
android:name=".MenuActivity"
android:label="@string/app_name" >
android:label="@string/app_name"
android:exported="false" >
</activity>
<activity
android:name=".PlayMenuActivity"
Expand All @@ -46,6 +47,15 @@
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
</activity>
<activity
android:name=".input.InputMapActivity"
android:label="@string/app_name"
android:exported="false" >
<intent-filter>
<action android:name=".input.InputMapActivity" />
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
</activity>
<activity
android:name=".input.DiagnosticActivity"
android:label="@string/app_name"
Expand Down
14 changes: 14 additions & 0 deletions res/menu/input_map_activity.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android" >

<item
android:id="@+id/menuItem_SpecialVisibility"
android:title="@string/inputMapPreference_showSpecial"/>
<item
android:id="@+id/menuItem_ControllerInfo"
android:title="@string/actionControllerInfo_title"/>
<item
android:id="@+id/menuItem_ControllerDiagnostics"
android:title="@string/actionControllerDiagnostics_title"/>

</menu>
344 changes: 344 additions & 0 deletions src/paulscode/android/mupen64plusae/input/InputMapActivity.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,344 @@
package paulscode.android.mupen64plusae.input;

import java.util.List;

import paulscode.android.mupen64plusae.R;
import paulscode.android.mupen64plusae.input.map.InputMap;
import paulscode.android.mupen64plusae.input.provider.AbstractProvider;
import paulscode.android.mupen64plusae.input.provider.AbstractProvider.OnInputListener;
import paulscode.android.mupen64plusae.input.provider.AxisProvider;
import paulscode.android.mupen64plusae.input.provider.KeyProvider;
import paulscode.android.mupen64plusae.input.provider.KeyProvider.ImeFormula;
import paulscode.android.mupen64plusae.input.provider.LazyProvider;
import paulscode.android.mupen64plusae.persistent.AppData;
import paulscode.android.mupen64plusae.persistent.UserPrefs;
import paulscode.android.mupen64plusae.util.DeviceUtil;
import paulscode.android.mupen64plusae.util.Prompt;
import paulscode.android.mupen64plusae.util.Prompt.OnInputCodeListener;
import android.annotation.TargetApi;
import android.app.Activity;
import android.app.AlertDialog.Builder;
import android.content.Context;
import android.content.Intent;
import android.graphics.PorterDuff;
import android.os.Bundle;
import android.util.DisplayMetrics;
import android.view.KeyEvent;
import android.view.Menu;
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.TextView;

public class InputMapActivity extends Activity 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;

// The key name and default value for the player number, obtained from the intent extras map
public static final String KEYEXTRA_PLAYER = "paulscode.android.mupen64plusae.EXTRA_PLAYER";
private static final int DEFAULT_PLAYER = 0;
private int mPlayer;

// User preferences wrapper
private UserPrefs mUserPrefs;

// Input mapping and listening
private final InputMap mMap = new InputMap();
private final LazyProvider mProvider = new LazyProvider();
private KeyProvider mKeyProvider;
private AxisProvider mAxisProvider;
private List<Integer> mUnmappableInputCodes;

// Widgets
private final Button[] mN64Button = new Button[InputMap.NUM_MAPPABLES];
private TextView mFeedbackText;
private View mSpecialFuncsView;
private MenuItem mMenuSpecialVisibility;

@Override
public void onCreate( Bundle savedInstanceState )
{
super.onCreate( savedInstanceState );

// Get the user preferences wrapper
mUserPrefs = new UserPrefs( this );

// Get the player number and get the associated preference values
Bundle extras = getIntent().getExtras();
mPlayer = extras == null ? DEFAULT_PLAYER : extras.getInt( KEYEXTRA_PLAYER, DEFAULT_PLAYER );

// Update the member variables from the persisted values
mMap.deserialize( mUserPrefs.getMapString( mPlayer ) );

// Set up input listeners
mUnmappableInputCodes = mUserPrefs.unmappableKeyCodes;
mKeyProvider = new KeyProvider( ImeFormula.DEFAULT, mUnmappableInputCodes );
if( AppData.IS_HONEYCOMB_MR1 )
mAxisProvider = new AxisProvider();
mProvider.registerListener( this );
mProvider.addProvider( mKeyProvider );
mProvider.addProvider( mAxisProvider );

// Select the appropriate window layout according to device configuration. Although you can
// do this through the resource directory structure and layout aliases, we'll do it this way
// for now since it's easier to maintain in the short term while the design is in flux.
// TODO: Consider using resource directories to handle device variation, once design is set.
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.input_map_preference_port );
else
setContentView( R.layout.input_map_preference );

// Initialize and refresh the widgets
initWidgets();
refreshWidgets();
}

private void initWidgets()
{
// Hide some widgets that do not apply
if( mUserPrefs.isTouchpadEnabled && mPlayer == 1 )
{
// First player and Xperia PLAY touchpad is enabled, hide the a- and c-pads
findViewById( R.id.aPadDefault ).setVisibility( View.GONE );
findViewById( R.id.cPadDefault ).setVisibility( View.GONE );
}
else
{
// All other cases, hide the Xperia PLAY stuff
findViewById( R.id.aPadXperiaPlay ).setVisibility( View.GONE );
findViewById( R.id.cPadXperiaPlay ).setVisibility( View.GONE );
}

// Get the special functions button group
mSpecialFuncsView = findViewById( R.id.include_all_special_keys );
refreshSpecialVisibility();

// Get the text view object
mFeedbackText = (TextView) findViewById( R.id.textFeedback );

// Create a button list to simplify highlighting and mapping
// @formatter:off
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.buttonMempak, AbstractController.BTN_MEMPAK );
setupButton( R.id.buttonRumble, AbstractController.BTN_RUMBLEPAK );
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 );
setupButton( R.id.buttonIncrementSlot, InputMap.FUNC_INCREMENT_SLOT );
setupButton( R.id.buttonSaveSlot, InputMap.FUNC_SAVE_SLOT );
setupButton( R.id.buttonReset, InputMap.FUNC_RESET );
setupButton( R.id.buttonLoadSlot, InputMap.FUNC_LOAD_SLOT );
setupButton( R.id.buttonPause, InputMap.FUNC_PAUSE );
setupButton( R.id.buttonStop, InputMap.FUNC_STOP );
setupButton( R.id.buttonSpeedDown, InputMap.FUNC_SPEED_DOWN );
setupButton( R.id.buttonSpeedUp, InputMap.FUNC_SPEED_UP );
setupButton( R.id.buttonFastForward, InputMap.FUNC_FAST_FORWARD );
// setupButton( R.id.buttonFrameAdvance, InputMap.FUNC_FRAME_ADVANCE );
// setupButton( R.id.buttonGameshark, InputMap.FUNC_GAMESHARK );
// @formatter:on
}

private void setupButton( int resId, int index )
{
mN64Button[index] = (Button) findViewById( resId );
if( mN64Button[index] != null )
mN64Button[index].setOnClickListener( this );
}

@Override
public boolean onCreateOptionsMenu( Menu menu )
{
getMenuInflater().inflate( R.menu.input_map_activity, menu );
mMenuSpecialVisibility = menu.findItem( R.id.menuItem_SpecialVisibility );
refreshSpecialVisibility();

return super.onCreateOptionsMenu( menu );
}

public boolean onOptionsItemSelected( MenuItem item )
{
switch( item.getItemId() )
{
case R.id.menuItem_SpecialVisibility:
mUserPrefs.putSpecialVisibility( mPlayer,
!mUserPrefs.getSpecialVisibility( mPlayer ) );
refreshSpecialVisibility();
break;
case R.id.menuItem_ControllerInfo:
String title = getString( R.string.actionControllerInfo_title );
String message = DeviceUtil.getPeripheralInfo();
new Builder( this ).setTitle( title ).setMessage( message ).create().show();
break;
case R.id.menuItem_ControllerDiagnostics:
startActivity( new Intent( this, DiagnosticActivity.class ) );
break;
default:
return false;
}
return true;
}

private void refreshSpecialVisibility()
{
boolean specialVisibility = mUserPrefs.getSpecialVisibility( mPlayer );

if( mSpecialFuncsView != null )
{
int specialKeyVisibility = specialVisibility ? View.VISIBLE : View.GONE;
mSpecialFuncsView.setVisibility( specialKeyVisibility );
}

if( mMenuSpecialVisibility != null )
{
mMenuSpecialVisibility.setTitle( specialVisibility
? R.string.inputMapPreference_hideSpecial
: R.string.inputMapPreference_showSpecial );
}
}

@Override
public void onClick( View view )
{
// Handle button clicks in the mapping screen
Button button;
for( int i = 0; i < mN64Button.length; i++ )
{
// Find the button that was pressed
if( view.equals( mN64Button[i] ) )
{
// Popup a dialog to listen to input codes from user
final int index = i;
button = (Button) view;
String message = getString( R.string.inputMapPreference_popupMessage,
mMap.getMappedCodeInfo( index ) );
String btnText = getString( R.string.inputMapPreference_popupUnmap );

Prompt.promptInputCode( this, button.getText(), message, btnText,
mUnmappableInputCodes, new OnInputCodeListener()
{
@Override
public void OnInputCode( int inputCode, int hardwareId )
{
if( inputCode == 0 )
mMap.unmapCommand( index );
else
mMap.map( inputCode, index );
mUserPrefs.putMapString( mPlayer, mMap.serialize() );
refreshWidgets();
}
} );
}
}
}

@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( !AppData.IS_HONEYCOMB_MR1 )
return false;

return mAxisProvider.onGenericMotion( event ) || super.onGenericMotionEvent( event );
}

@Override
public void onInput( int inputCode, float strength, int hardwareId )
{
refreshWidgets( inputCode, strength );
}

@Override
public void onInput( int[] inputCodes, float[] strengths, int hardwareId )
{
// Nothing to do here, just implement the interface
}

@TargetApi( 11 )
private void refreshWidgets( int inputCode, float strength )
{
// Modify the button appearance to provide feedback to user
int selectedIndex = mMap.get( inputCode );
for( int i = 0; i < mN64Button.length; i++ )
{
// Highlight the currently active button
Button button = mN64Button[i];
if( button != null )
{
button.setPressed( i == selectedIndex
&& strength > AbstractProvider.STRENGTH_THRESHOLD );

// Fade any buttons that aren't mapped
if( AppData.IS_HONEYCOMB )
{
if( mMap.isMapped( i ) )
button.setAlpha( 1 );
else
button.setAlpha( UNMAPPED_BUTTON_ALPHA );
}
else
{
// For older APIs try something similar (not quite the same)
if( mMap.isMapped( i ) )
button.getBackground().clearColorFilter();
else
button.getBackground().setColorFilter( UNMAPPED_BUTTON_FILTER,
PorterDuff.Mode.MULTIPLY );
button.invalidate();
}
}
}

// 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 refreshWidgets()
{
// Default update, don't highlight anything
refreshWidgets( 0, 0 );
}
}

0 comments on commit 6e25a62

Please sign in to comment.