forked from mupen64plus-ae/mupen64plus-ae
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
front: Added InputMapActivity, soon to supersede InputMapPreference.
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
1 parent
2d1423c
commit 6e25a62
Showing
3 changed files
with
369 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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
344
src/paulscode/android/mupen64plusae/input/InputMapActivity.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 ); | ||
} | ||
} |