/**
* Copyright 2013 Dolphin Emulator Project
* Licensed under GPLv2+
* Refer to the license.txt file included.
*/
package org.yuzu.yuzu_emu.overlay;
import android.app.Activity;
import android.content.Context;
import android.content.SharedPreferences;
import android.content.res.Configuration;
import android.content.res.Resources;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.Rect;
import android.graphics.drawable.Drawable;
import android.preference.PreferenceManager;
import android.util.AttributeSet;
import android.util.DisplayMetrics;
import android.view.Display;
import android.view.MotionEvent;
import android.view.SurfaceView;
import android.view.View;
import android.view.View.OnTouchListener;
import org.yuzu.yuzu_emu.NativeLibrary;
import org.yuzu.yuzu_emu.NativeLibrary.ButtonState;
import org.yuzu.yuzu_emu.NativeLibrary.ButtonType;
import org.yuzu.yuzu_emu.R;
import org.yuzu.yuzu_emu.utils.EmulationMenuSettings;
import java.util.HashSet;
import java.util.Set;
/**
* Draws the interactive input overlay on top of the
* {@link SurfaceView} that is rendering emulation.
*/
public final class InputOverlay extends SurfaceView implements OnTouchListener {
private final Set<InputOverlayDrawableButton> overlayButtons = new HashSet<>();
private final Set<InputOverlayDrawableDpad> overlayDpads = new HashSet<>();
private final Set<InputOverlayDrawableJoystick> overlayJoysticks = new HashSet<>();
private boolean mIsInEditMode = false;
private InputOverlayDrawableButton mButtonBeingConfigured;
private InputOverlayDrawableDpad mDpadBeingConfigured;
private InputOverlayDrawableJoystick mJoystickBeingConfigured;
private SharedPreferences mPreferences;
// Stores the ID of the pointer that interacted with the 3DS touchscreen.
private int mTouchscreenPointerId = -1;
/**
* Constructor
*
* @param context The current {@link Context}.
* @param attrs {@link AttributeSet} for parsing XML attributes.
*/
public InputOverlay(Context context, AttributeSet attrs) {
super(context, attrs);
mPreferences = PreferenceManager.getDefaultSharedPreferences(getContext());
if (!mPreferences.getBoolean("OverlayInit", false)) {
defaultOverlay();
}
// Reset 3ds touchscreen pointer ID
mTouchscreenPointerId = -1;
// Load the controls.
refreshControls();
// Set the on touch listener.
setOnTouchListener(this);
// Force draw
setWillNotDraw(false);
// Request focus for the overlay so it has priority on presses.
requestFocus();
}
/**
* Resizes a {@link Bitmap} by a given scale factor
*
* @param context The current {@link Context}
* @param bitmap The {@link Bitmap} to scale.
* @param scale The scale factor for the bitmap.
* @return The scaled {@link Bitmap}
*/
public static Bitmap resizeBitmap(Context context, Bitmap bitmap, float scale) {
// Determine the button size based on the smaller screen dimension.
// This makes sure the buttons are the same size in both portrait and landscape.
DisplayMetrics dm = context.getResources().getDisplayMetrics();
int minDimension = Math.min(dm.widthPixels, dm.heightPixels);
return Bitmap.createScaledBitmap(bitmap,
(int) (minDimension * scale),
(int) (minDimension * scale),
true);
}
/**
* Initializes an InputOverlayDrawableButton, given by resId, with all of the
* parameters set for it to be properly shown on the InputOverlay.
* <p>
* This works due to the way the X and Y coordinates are stored within
* the {@link SharedPreferences}.
* <p>
* In the input overlay configuration menu,
* once a touch event begins and then ends (ie. Organizing the buttons to one's own liking for the overlay).
* the X and Y coordinates of the button at the END of its touch event
* (when you remove your finger/stylus from the touchscreen) are then stored
* within a SharedPreferences instance so that those values can be retrieved here.
* <p>
* This has a few benefits over the conventional way of storing the values
* (ie. within the yuzu ini file).
* <ul>
* <li>No native calls</li>
* <li>Keeps Android-only values inside the Android environment</li>
* </ul>
* <p>
* Technically no modifications should need to be performed on the returned
* InputOverlayDrawableButton. Simply add it to the HashSet of overlay items and wait
* for Android to call the onDraw method.
*
* @param context The current {@link Context}.
* @param defaultResId The resource ID of the {@link Drawable} to get the {@link Bitmap} of (Default State).
* @param pressedResId The resource ID of the {@link Drawable} to get the {@link Bitmap} of (Pressed State).
* @param buttonId Identifier for determining what type of button the initialized InputOverlayDrawableButton represents.
* @return An {@link InputOverlayDrawableButton} with the correct drawing bounds set.
*/
private static InputOverlayDrawableButton initializeOverlayButton(Context context,
int defaultResId, int pressedResId, int buttonId, String orientation) {
// Resources handle for fetching the initial Drawable resource.
final Resources res = context.getResources();
// SharedPreference to retrieve the X and Y coordinates for the InputOverlayDrawableButton.
final SharedPreferences sPrefs = PreferenceManager.getDefaultSharedPreferences(context);
// Decide scale based on button ID and user preference
float scale;
switch (buttonId) {
case ButtonType.BUTTON_HOME:
case ButtonType.BUTTON_START:
case ButtonType.BUTTON_SELECT:
scale = 0.08f;
break;
case ButtonType.TRIGGER_L:
case ButtonType.TRIGGER_R:
case ButtonType.BUTTON_ZL:
case ButtonType.BUTTON_ZR:
scale = 0.18f;
break;
default:
scale = 0.11f;
break;
}
scale *= (sPrefs.getInt("controlScale", 50) + 50);
scale /= 100;
// Initialize the InputOverlayDrawableButton.
final Bitmap defaultStateBitmap =
resizeBitmap(context, BitmapFactory.decodeResource(res, defaultResId), scale);
final Bitmap pressedStateBitmap =
resizeBitmap(context, BitmapFactory.decodeResource(res, pressedResId), scale);
final InputOverlayDrawableButton overlayDrawable =
new InputOverlayDrawableButton(res, defaultStateBitmap, pressedStateBitmap, buttonId);
// The X and Y coordinates of the InputOverlayDrawableButton on the InputOverlay.
// These were set in the input overlay configuration menu.
String xKey;
String yKey;
xKey = buttonId + orientation + "-X";
yKey = buttonId + orientation + "-Y";
int drawableX = (int) sPrefs.getFloat(xKey, 0f);
int drawableY = (int) sPrefs.getFloat(yKey, 0f);
int width = overlayDrawable.getWidth();
int height = overlayDrawable.getHeight();
// Now set the bounds for the InputOverlayDrawableButton.
// This will dictate where on the screen (and the what the size) the InputOverlayDrawableButton will be.
overlayDrawable.setBounds(drawableX, drawableY, drawableX + width, drawableY + height);
// Need to set the image's position
overlayDrawable.setPosition(drawableX, drawableY);
return overlayDrawable;
}
/**
* Initializes an {@link InputOverlayDrawableDpad}
*
* @param context The current {@link Context}.
* @param defaultResId The {@link Bitmap} resource ID of the default sate.
* @param pressedOneDirectionResId The {@link Bitmap} resource ID of the pressed sate in one direction.
* @param pressedTwoDirectionsResId The {@link Bitmap} resource ID of the pressed sate in two directions.
* @param buttonUp Identifier for the up button.
* @param buttonDown Identifier for the down button.
* @param buttonLeft Identifier for the left button.
* @param buttonRight Identifier for the right button.
* @return the initialized {@link InputOverlayDrawableDpad}
*/
private static InputOverlayDrawableDpad initializeOverlayDpad(Context context,
int defaultResId,
int pressedOneDirectionResId,
int pressedTwoDirectionsResId,
int buttonUp,
int buttonDown,
int buttonLeft,
int buttonRight,
String orientation) {
// Resources handle for fetching the initial Drawable resource.
final Resources res = context.getResources();
// SharedPreference to retrieve the X and Y coordinates for the InputOverlayDrawableDpad.
final SharedPreferences sPrefs = PreferenceManager.getDefaultSharedPreferences(context);
// Decide scale based on button ID and user preference
float scale = 0.22f;
scale *= (sPrefs.getInt("controlScale", 50) + 50);
scale /= 100;
// Initialize the InputOverlayDrawableDpad.
final Bitmap defaultStateBitmap =
resizeBitmap(context, BitmapFactory.decodeResource(res, defaultResId), scale);
final Bitmap pressedOneDirectionStateBitmap =
resizeBitmap(context, BitmapFactory.decodeResource(res, pressedOneDirectionResId),
scale);
final Bitmap pressedTwoDirectionsStateBitmap =
resizeBitmap(context, BitmapFactory.decodeResource(res, pressedTwoDirectionsResId),
scale);
final InputOverlayDrawableDpad overlayDrawable =
new InputOverlayDrawableDpad(res, defaultStateBitmap,
pressedOneDirectionStateBitmap, pressedTwoDirectionsStateBitmap,
buttonUp, buttonDown, buttonLeft, buttonRight);
// The X and Y coordinates of the InputOverlayDrawableDpad on the InputOverlay.
// These were set in the input overlay configuration menu.
int drawableX = (int) sPrefs.getFloat(buttonUp + orientation + "-X", 0f);
int drawableY = (int) sPrefs.getFloat(buttonUp + orientation + "-Y", 0f);
int width = overlayDrawable.getWidth();
int height = overlayDrawable.getHeight();
// Now set the bounds for the InputOverlayDrawableDpad.
// This will dictate where on the screen (and the what the size) the InputOverlayDrawableDpad will be.
overlayDrawable.setBounds(drawableX, drawableY, drawableX + width, drawableY + height);
// Need to set the image's position
overlayDrawable.setPosition(drawableX, drawableY);
return overlayDrawable;
}
/**
* Initializes an {@link InputOverlayDrawableJoystick}
*
* @param context The current {@link Context}
* @param resOuter Resource ID for the outer image of the joystick (the static image that shows the circular bounds).
* @param defaultResInner Resource ID for the default inner image of the joystick (the one you actually move around).
* @param pressedResInner Resource ID for the pressed inner image of the joystick.
* @param joystick Identifier for which joystick this is.
* @return the initialized {@link InputOverlayDrawableJoystick}.
*/
private static InputOverlayDrawableJoystick initializeOverlayJoystick(Context context,
int resOuter, int defaultResInner, int pressedResInner, int joystick, String orientation) {
// Resources handle for fetching the initial Drawable resource.
final Resources res = context.getResources();
// SharedPreference to retrieve the X and Y coordinates for the InputOverlayDrawableJoystick.
final SharedPreferences sPrefs = PreferenceManager.getDefaultSharedPreferences(context);
// Decide scale based on user preference
float scale = 0.275f;
scale *= (sPrefs.getInt("controlScale", 50) + 50);
scale /= 100;
// Initialize the InputOverlayDrawableJoystick.
final Bitmap bitmapOuter =
resizeBitmap(context, BitmapFactory.decodeResource(res, resOuter), scale);
final Bitmap bitmapInnerDefault = BitmapFactory.decodeResource(res, defaultResInner);
final Bitmap bitmapInnerPressed = BitmapFactory.decodeResource(res, pressedResInner);
// The X and Y coordinates of the InputOverlayDrawableButton on the InputOverlay.
// These were set in the input overlay configuration menu.
int drawableX = (int) sPrefs.getFloat(joystick + orientation + "-X", 0f);
int drawableY = (int) sPrefs.getFloat(joystick + orientation + "-Y", 0f);
// Decide inner scale based on joystick ID
float outerScale = 1.f;
if (joystick == ButtonType.STICK_C) {
outerScale = 2.f;
}
// Now set the bounds for the InputOverlayDrawableJoystick.
// This will dictate where on the screen (and the what the size) the InputOverlayDrawableJoystick will be.
int outerSize = bitmapOuter.getWidth();
Rect outerRect = new Rect(drawableX, drawableY, drawableX + (int) (outerSize / outerScale), drawableY + (int) (outerSize / outerScale));
Rect innerRect = new Rect(0, 0, (int) (outerSize / outerScale), (int) (outerSize / outerScale));
// Send the drawableId to the joystick so it can be referenced when saving control position.
final InputOverlayDrawableJoystick overlayDrawable
= new InputOverlayDrawableJoystick(res, bitmapOuter,
bitmapInnerDefault, bitmapInnerPressed,
outerRect, innerRect, joystick);
// Need to set the image's position
overlayDrawable.setPosition(drawableX, drawableY);
return overlayDrawable;
}
@Override
public void draw(Canvas canvas) {
super.draw(canvas);
for (InputOverlayDrawableButton button : overlayButtons) {
button.draw(canvas);
}
for (InputOverlayDrawableDpad dpad : overlayDpads) {
dpad.draw(canvas);
}
for (InputOverlayDrawableJoystick joystick : overlayJoysticks) {
joystick.draw(canvas);
}
}
@Override
public boolean onTouch(View v, MotionEvent event) {
if (isInEditMode()) {
return onTouchWhileEditing(event);
}
int pointerIndex = event.getActionIndex();
if (mPreferences.getBoolean("isTouchEnabled", true)) {
switch (event.getAction() & MotionEvent.ACTION_MASK) {
case MotionEvent.ACTION_DOWN:
case MotionEvent.ACTION_POINTER_DOWN:
if (NativeLibrary.onTouchEvent(event.getX(pointerIndex), event.getY(pointerIndex), true)) {
mTouchscreenPointerId = event.getPointerId(pointerIndex);
}
break;
case MotionEvent.ACTION_UP:
case MotionEvent.ACTION_POINTER_UP:
if (mTouchscreenPointerId == event.getPointerId(pointerIndex)) {
// We don't really care where the touch has been released. We only care whether it has been
// released or not.
NativeLibrary.onTouchEvent(0, 0, false);
mTouchscreenPointerId = -1;
}
break;
}
for (int i = 0; i < event.getPointerCount(); i++) {
if (mTouchscreenPointerId == event.getPointerId(i)) {
NativeLibrary.onTouchMoved(event.getX(i), event.getY(i));
}
}
}
for (InputOverlayDrawableButton button : overlayButtons) {
// Determine the button state to apply based on the MotionEvent action flag.
switch (event.getAction() & MotionEvent.ACTION_MASK) {
case MotionEvent.ACTION_DOWN:
case MotionEvent.ACTION_POINTER_DOWN:
// If a pointer enters the bounds of a button, press that button.
if (button.getBounds()
.contains((int) event.getX(pointerIndex), (int) event.getY(pointerIndex))) {
button.setPressedState(true);
button.setTrackId(event.getPointerId(pointerIndex));
NativeLibrary.onGamePadEvent(NativeLibrary.TouchScreenDevice, button.getId(),
ButtonState.PRESSED);
}
break;
case MotionEvent.ACTION_UP:
case MotionEvent.ACTION_POINTER_UP:
// If a pointer ends, release the button it was pressing.
if (button.getTrackId() == event.getPointerId(pointerIndex)) {
button.setPressedState(false);
NativeLibrary.onGamePadEvent(NativeLibrary.TouchScreenDevice, button.getId(),
ButtonState.RELEASED);
}
break;
}
}
for (InputOverlayDrawableDpad dpad : overlayDpads) {
// Determine the button state to apply based on the MotionEvent action flag.
switch (event.getAction() & MotionEvent.ACTION_MASK) {
case MotionEvent.ACTION_DOWN:
case MotionEvent.ACTION_POINTER_DOWN:
// If a pointer enters the bounds of a button, press that button.
if (dpad.getBounds()
.contains((int) event.getX(pointerIndex), (int) event.getY(pointerIndex))) {
dpad.setTrackId(event.getPointerId(pointerIndex));
}
break;
case MotionEvent.ACTION_UP:
case MotionEvent.ACTION_POINTER_UP:
// If a pointer ends, release the buttons.
if (dpad.getTrackId() == event.getPointerId(pointerIndex)) {
for (int i = 0; i < 4; i++) {
dpad.setState(InputOverlayDrawableDpad.STATE_DEFAULT);
NativeLibrary.onGamePadEvent(NativeLibrary.TouchScreenDevice, dpad.getId(i),
NativeLibrary.ButtonState.RELEASED);
}
dpad.setTrackId(-1);
}
break;
}
if (dpad.getTrackId() != -1) {
for (int i = 0; i < event.getPointerCount(); i++) {
if (dpad.getTrackId() == event.getPointerId(i)) {
float touchX = event.getX(i);
float touchY = event.getY(i);
float maxY = dpad.getBounds().bottom;
float maxX = dpad.getBounds().right;
touchX -= dpad.getBounds().centerX();
maxX -= dpad.getBounds().centerX();
touchY -= dpad.getBounds().centerY();
maxY -= dpad.getBounds().centerY();
final float AxisX = touchX / maxX;
final float AxisY = touchY / maxY;
boolean up = false;
boolean down = false;
boolean left = false;
boolean right = false;
if (EmulationMenuSettings.getDpadSlideEnable() ||
(event.getAction() & MotionEvent.ACTION_MASK) == MotionEvent.ACTION_DOWN ||
(event.getAction() & MotionEvent.ACTION_MASK) == MotionEvent.ACTION_POINTER_DOWN) {
if (AxisY < -InputOverlayDrawableDpad.VIRT_AXIS_DEADZONE) {
NativeLibrary.onGamePadEvent(NativeLibrary.TouchScreenDevice, dpad.getId(0),
NativeLibrary.ButtonState.PRESSED);
up = true;
} else {
NativeLibrary.onGamePadEvent(NativeLibrary.TouchScreenDevice, dpad.getId(0),
NativeLibrary.ButtonState.RELEASED);
}
if (AxisY > InputOverlayDrawableDpad.VIRT_AXIS_DEADZONE) {
NativeLibrary.onGamePadEvent(NativeLibrary.TouchScreenDevice, dpad.getId(1),
NativeLibrary.ButtonState.PRESSED);
down = true;
} else {
NativeLibrary.onGamePadEvent(NativeLibrary.TouchScreenDevice, dpad.getId(1),
NativeLibrary.ButtonState.RELEASED);
}
if (AxisX < -InputOverlayDrawableDpad.VIRT_AXIS_DEADZONE) {
NativeLibrary.onGamePadEvent(NativeLibrary.TouchScreenDevice, dpad.getId(2),
NativeLibrary.ButtonState.PRESSED);
left = true;
} else {
NativeLibrary.onGamePadEvent(NativeLibrary.TouchScreenDevice, dpad.getId(2),
NativeLibrary.ButtonState.RELEASED);
}
if (AxisX > InputOverlayDrawableDpad.VIRT_AXIS_DEADZONE) {
NativeLibrary.onGamePadEvent(NativeLibrary.TouchScreenDevice, dpad.getId(3),
NativeLibrary.ButtonState.PRESSED);
right = true;
} else {
NativeLibrary.onGamePadEvent(NativeLibrary.TouchScreenDevice, dpad.getId(3),
NativeLibrary.ButtonState.RELEASED);
}
// Set state
if (up) {
if (left)
dpad.setState(InputOverlayDrawableDpad.STATE_PRESSED_UP_LEFT);
else if (right)
dpad.setState(InputOverlayDrawableDpad.STATE_PRESSED_UP_RIGHT);
else
dpad.setState(InputOverlayDrawableDpad.STATE_PRESSED_UP);
} else if (down) {
if (left)
dpad.setState(InputOverlayDrawableDpad.STATE_PRESSED_DOWN_LEFT);
else if (right)
dpad.setState(InputOverlayDrawableDpad.STATE_PRESSED_DOWN_RIGHT);
else
dpad.setState(InputOverlayDrawableDpad.STATE_PRESSED_DOWN);
} else if (left) {
dpad.setState(InputOverlayDrawableDpad.STATE_PRESSED_LEFT);
} else if (right) {
dpad.setState(InputOverlayDrawableDpad.STATE_PRESSED_RIGHT);
} else {
dpad.setState(InputOverlayDrawableDpad.STATE_DEFAULT);
}
}
}
}
}
}
for (InputOverlayDrawableJoystick joystick : overlayJoysticks) {
joystick.TrackEvent(event);
int axisID = joystick.getId();
float[] axises = joystick.getAxisValues();
NativeLibrary
.onGamePadMoveEvent(NativeLibrary.TouchScreenDevice, axisID, axises[0], axises[1]);
}
invalidate();
return true;
}
public boolean onTouchWhileEditing(MotionEvent event) {
int pointerIndex = event.getActionIndex();
int fingerPositionX = (int) event.getX(pointerIndex);
int fingerPositionY = (int) event.getY(pointerIndex);
String orientation =
getResources().getConfiguration().orientation == Configuration.ORIENTATION_PORTRAIT ?
"-Portrait" : "";
// Maybe combine Button and Joystick as subclasses of the same parent?
// Or maybe create an interface like IMoveableHUDControl?
for (InputOverlayDrawableButton button : overlayButtons) {
// Determine the button state to apply based on the MotionEvent action flag.
switch (event.getAction() & MotionEvent.ACTION_MASK) {
case MotionEvent.ACTION_DOWN:
case MotionEvent.ACTION_POINTER_DOWN:
// If no button is being moved now, remember the currently touched button to move.
if (mButtonBeingConfigured == null &&
button.getBounds().contains(fingerPositionX, fingerPositionY)) {
mButtonBeingConfigured = button;
mButtonBeingConfigured.onConfigureTouch(event);
}
break;
case MotionEvent.ACTION_MOVE:
if (mButtonBeingConfigured != null) {
mButtonBeingConfigured.onConfigureTouch(event);
invalidate();
return true;
}
break;
case MotionEvent.ACTION_UP:
case MotionEvent.ACTION_POINTER_UP:
if (mButtonBeingConfigured == button) {
// Persist button position by saving new place.
saveControlPosition(mButtonBeingConfigured.getId(),
mButtonBeingConfigured.getBounds().left,
mButtonBeingConfigured.getBounds().top, orientation);
mButtonBeingConfigured = null;
}
break;
}
}
for (InputOverlayDrawableDpad dpad : overlayDpads) {
// Determine the button state to apply based on the MotionEvent action flag.
switch (event.getAction() & MotionEvent.ACTION_MASK) {
case MotionEvent.ACTION_DOWN:
case MotionEvent.ACTION_POINTER_DOWN:
// If no button is being moved now, remember the currently touched button to move.
if (mButtonBeingConfigured == null &&
dpad.getBounds().contains(fingerPositionX, fingerPositionY)) {
mDpadBeingConfigured = dpad;
mDpadBeingConfigured.onConfigureTouch(event);
}
break;
case MotionEvent.ACTION_MOVE:
if (mDpadBeingConfigured != null) {
mDpadBeingConfigured.onConfigureTouch(event);
invalidate();
return true;
}
break;
case MotionEvent.ACTION_UP:
case MotionEvent.ACTION_POINTER_UP:
if (mDpadBeingConfigured == dpad) {
// Persist button position by saving new place.
saveControlPosition(mDpadBeingConfigured.getId(0),
mDpadBeingConfigured.getBounds().left, mDpadBeingConfigured.getBounds().top,
orientation);
mDpadBeingConfigured = null;
}
break;
}
}
for (InputOverlayDrawableJoystick joystick : overlayJoysticks) {
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
case MotionEvent.ACTION_POINTER_DOWN:
if (mJoystickBeingConfigured == null &&
joystick.getBounds().contains(fingerPositionX, fingerPositionY)) {
mJoystickBeingConfigured = joystick;
mJoystickBeingConfigured.onConfigureTouch(event);
}
break;
case MotionEvent.ACTION_MOVE:
if (mJoystickBeingConfigured != null) {
mJoystickBeingConfigured.onConfigureTouch(event);
invalidate();
}
break;
case MotionEvent.ACTION_UP:
case MotionEvent.ACTION_POINTER_UP:
if (mJoystickBeingConfigured != null) {
saveControlPosition(mJoystickBeingConfigured.getId(),
mJoystickBeingConfigured.getBounds().left,
mJoystickBeingConfigured.getBounds().top, orientation);
mJoystickBeingConfigured = null;
}
break;
}
}
return true;
}
private void setDpadState(InputOverlayDrawableDpad dpad, boolean up, boolean down, boolean left,
boolean right) {
if (up) {
if (left)
dpad.setState(InputOverlayDrawableDpad.STATE_PRESSED_UP_LEFT);
else if (right)
dpad.setState(InputOverlayDrawableDpad.STATE_PRESSED_UP_RIGHT);
else
dpad.setState(InputOverlayDrawableDpad.STATE_PRESSED_UP);
} else if (down) {
if (left)
dpad.setState(InputOverlayDrawableDpad.STATE_PRESSED_DOWN_LEFT);
else if (right)
dpad.setState(InputOverlayDrawableDpad.STATE_PRESSED_DOWN_RIGHT);
else
dpad.setState(InputOverlayDrawableDpad.STATE_PRESSED_DOWN);
} else if (left) {
dpad.setState(InputOverlayDrawableDpad.STATE_PRESSED_LEFT);
} else if (right) {
dpad.setState(InputOverlayDrawableDpad.STATE_PRESSED_RIGHT);
}
}
private void addOverlayControls(String orientation) {
if (mPreferences.getBoolean("buttonToggle0", true)) {
overlayButtons.add(initializeOverlayButton(getContext(), R.drawable.button_a,
R.drawable.button_a_pressed, ButtonType.BUTTON_A, orientation));
}
if (mPreferences.getBoolean("buttonToggle1", true)) {
overlayButtons.add(initializeOverlayButton(getContext(), R.drawable.button_b,
R.drawable.button_b_pressed, ButtonType.BUTTON_B, orientation));
}
if (mPreferences.getBoolean("buttonToggle2", true)) {
overlayButtons.add(initializeOverlayButton(getContext(), R.drawable.button_x,
R.drawable.button_x_pressed, ButtonType.BUTTON_X, orientation));
}
if (mPreferences.getBoolean("buttonToggle3", true)) {
overlayButtons.add(initializeOverlayButton(getContext(), R.drawable.button_y,
R.drawable.button_y_pressed, ButtonType.BUTTON_Y, orientation));
}
if (mPreferences.getBoolean("buttonToggle4", true)) {
overlayButtons.add(initializeOverlayButton(getContext(), R.drawable.button_l,
R.drawable.button_l_pressed, ButtonType.TRIGGER_L, orientation));
}
if (mPreferences.getBoolean("buttonToggle5", true)) {
overlayButtons.add(initializeOverlayButton(getContext(), R.drawable.button_r,
R.drawable.button_r_pressed, ButtonType.TRIGGER_R, orientation));
}
if (mPreferences.getBoolean("buttonToggle6", false)) {
overlayButtons.add(initializeOverlayButton(getContext(), R.drawable.button_zl,
R.drawable.button_zl_pressed, ButtonType.BUTTON_ZL, orientation));
}
if (mPreferences.getBoolean("buttonToggle7", false)) {
overlayButtons.add(initializeOverlayButton(getContext(), R.drawable.button_zr,
R.drawable.button_zr_pressed, ButtonType.BUTTON_ZR, orientation));
}
if (mPreferences.getBoolean("buttonToggle8", true)) {
overlayButtons.add(initializeOverlayButton(getContext(), R.drawable.button_start,
R.drawable.button_start_pressed, ButtonType.BUTTON_START, orientation));
}
if (mPreferences.getBoolean("buttonToggle9", true)) {
overlayButtons.add(initializeOverlayButton(getContext(), R.drawable.button_select,
R.drawable.button_select_pressed, ButtonType.BUTTON_SELECT, orientation));
}
if (mPreferences.getBoolean("buttonToggle10", true)) {
overlayDpads.add(initializeOverlayDpad(getContext(), R.drawable.dpad,
R.drawable.dpad_pressed_one_direction,
R.drawable.dpad_pressed_two_directions,
ButtonType.DPAD_UP, ButtonType.DPAD_DOWN,
ButtonType.DPAD_LEFT, ButtonType.DPAD_RIGHT, orientation));
}
if (mPreferences.getBoolean("buttonToggle11", true)) {
overlayJoysticks.add(initializeOverlayJoystick(getContext(), R.drawable.stick_main_range,
R.drawable.stick_main, R.drawable.stick_main_pressed,
ButtonType.STICK_LEFT, orientation));
}
if (mPreferences.getBoolean("buttonToggle12", false)) {
overlayJoysticks.add(initializeOverlayJoystick(getContext(), R.drawable.stick_c_range,
R.drawable.stick_c, R.drawable.stick_c_pressed, ButtonType.STICK_C, orientation));
}
}
public void refreshControls() {
// Remove all the overlay buttons from the HashSet.
overlayButtons.clear();
overlayDpads.clear();
overlayJoysticks.clear();
String orientation =
getResources().getConfiguration().orientation == Configuration.ORIENTATION_PORTRAIT ?
"-Portrait" : "";
// Add all the enabled overlay items back to the HashSet.
if (EmulationMenuSettings.getShowOverlay()) {
addOverlayControls(orientation);
}
invalidate();
}
private void saveControlPosition(int sharedPrefsId, int x, int y, String orientation) {
final SharedPreferences sPrefs = PreferenceManager.getDefaultSharedPreferences(getContext());
SharedPreferences.Editor sPrefsEditor = sPrefs.edit();
sPrefsEditor.putFloat(sharedPrefsId + orientation + "-X", x);
sPrefsEditor.putFloat(sharedPrefsId + orientation + "-Y", y);
sPrefsEditor.apply();
}
public void setIsInEditMode(boolean isInEditMode) {
mIsInEditMode = isInEditMode;
}
private void defaultOverlay() {
if (!mPreferences.getBoolean("OverlayInit", false)) {
// It's possible that a user has created their overlay before this was added
// Only change the overlay if the 'A' button is not in the upper corner.
if (mPreferences.getFloat(ButtonType.BUTTON_A + "-X", 0f) == 0f) {
defaultOverlayLandscape();
}
if (mPreferences.getFloat(ButtonType.BUTTON_A + "-Portrait" + "-X", 0f) == 0f) {
defaultOverlayPortrait();
}
}
SharedPreferences.Editor sPrefsEditor = mPreferences.edit();
sPrefsEditor.putBoolean("OverlayInit", true);
sPrefsEditor.apply();
}
public void resetButtonPlacement() {
boolean isLandscape =
getResources().getConfiguration().orientation == Configuration.ORIENTATION_LANDSCAPE;
if (isLandscape) {
defaultOverlayLandscape();
} else {
defaultOverlayPortrait();
}
refreshControls();
}
private void defaultOverlayLandscape() {
SharedPreferences.Editor sPrefsEditor = mPreferences.edit();
// Get screen size
Display display = ((Activity) getContext()).getWindowManager().getDefaultDisplay();
DisplayMetrics outMetrics = new DisplayMetrics();
display.getMetrics(outMetrics);
float maxX = outMetrics.heightPixels;
float maxY = outMetrics.widthPixels;
// Height and width changes depending on orientation. Use the larger value for height.
if (maxY > maxX) {
float tmp = maxX;
maxX = maxY;
maxY = tmp;
}
Resources res = getResources();
// Each value is a percent from max X/Y stored as an int. Have to bring that value down
// to a decimal before multiplying by MAX X/Y.
sPrefsEditor.putFloat(ButtonType.BUTTON_A + "-X", (((float) res.getInteger(R.integer.N3DS_BUTTON_A_X) / 1000) * maxX));
sPrefsEditor.putFloat(ButtonType.BUTTON_A + "-Y", (((float) res.getInteger(R.integer.N3DS_BUTTON_A_Y) / 1000) * maxY));
sPrefsEditor.putFloat(ButtonType.BUTTON_B + "-X", (((float) res.getInteger(R.integer.N3DS_BUTTON_B_X) / 1000) * maxX));
sPrefsEditor.putFloat(ButtonType.BUTTON_B + "-Y", (((float) res.getInteger(R.integer.N3DS_BUTTON_B_Y) / 1000) * maxY));
sPrefsEditor.putFloat(ButtonType.BUTTON_X + "-X", (((float) res.getInteger(R.integer.N3DS_BUTTON_X_X) / 1000) * maxX));
sPrefsEditor.putFloat(ButtonType.BUTTON_X + "-Y", (((float) res.getInteger(R.integer.N3DS_BUTTON_X_Y) / 1000) * maxY));
sPrefsEditor.putFloat(ButtonType.BUTTON_Y + "-X", (((float) res.getInteger(R.integer.N3DS_BUTTON_Y_X) / 1000) * maxX));
sPrefsEditor.putFloat(ButtonType.BUTTON_Y + "-Y", (((float) res.getInteger(R.integer.N3DS_BUTTON_Y_Y) / 1000) * maxY));
sPrefsEditor.putFloat(ButtonType.BUTTON_ZL + "-X", (((float) res.getInteger(R.integer.N3DS_BUTTON_ZL_X) / 1000) * maxX));
sPrefsEditor.putFloat(ButtonType.BUTTON_ZL + "-Y", (((float) res.getInteger(R.integer.N3DS_BUTTON_ZL_Y) / 1000) * maxY));
sPrefsEditor.putFloat(ButtonType.BUTTON_ZR + "-X", (((float) res.getInteger(R.integer.N3DS_BUTTON_ZR_X) / 1000) * maxX));
sPrefsEditor.putFloat(ButtonType.BUTTON_ZR + "-Y", (((float) res.getInteger(R.integer.N3DS_BUTTON_ZR_Y) / 1000) * maxY));
sPrefsEditor.putFloat(ButtonType.DPAD_UP + "-X", (((float) res.getInteger(R.integer.N3DS_BUTTON_UP_X) / 1000) * maxX));
sPrefsEditor.putFloat(ButtonType.DPAD_UP + "-Y", (((float) res.getInteger(R.integer.N3DS_BUTTON_UP_Y) / 1000) * maxY));
sPrefsEditor.putFloat(ButtonType.TRIGGER_L + "-X", (((float) res.getInteger(R.integer.N3DS_TRIGGER_L_X) / 1000) * maxX));
sPrefsEditor.putFloat(ButtonType.TRIGGER_L + "-Y", (((float) res.getInteger(R.integer.N3DS_TRIGGER_L_Y) / 1000) * maxY));
sPrefsEditor.putFloat(ButtonType.TRIGGER_R + "-X", (((float) res.getInteger(R.integer.N3DS_TRIGGER_R_X) / 1000) * maxX));
sPrefsEditor.putFloat(ButtonType.TRIGGER_R + "-Y", (((float) res.getInteger(R.integer.N3DS_TRIGGER_R_Y) / 1000) * maxY));
sPrefsEditor.putFloat(ButtonType.BUTTON_START + "-X", (((float) res.getInteger(R.integer.N3DS_BUTTON_START_X) / 1000) * maxX));
sPrefsEditor.putFloat(ButtonType.BUTTON_START + "-Y", (((float) res.getInteger(R.integer.N3DS_BUTTON_START_Y) / 1000) * maxY));
sPrefsEditor.putFloat(ButtonType.BUTTON_SELECT + "-X", (((float) res.getInteger(R.integer.N3DS_BUTTON_SELECT_X) / 1000) * maxX));
sPrefsEditor.putFloat(ButtonType.BUTTON_SELECT + "-Y", (((float) res.getInteger(R.integer.N3DS_BUTTON_SELECT_Y) / 1000) * maxY));
sPrefsEditor.putFloat(ButtonType.BUTTON_HOME + "-X", (((float) res.getInteger(R.integer.N3DS_BUTTON_HOME_X) / 1000) * maxX));
sPrefsEditor.putFloat(ButtonType.BUTTON_HOME + "-Y", (((float) res.getInteger(R.integer.N3DS_BUTTON_HOME_Y) / 1000) * maxY));
sPrefsEditor.putFloat(ButtonType.STICK_C + "-X", (((float) res.getInteger(R.integer.N3DS_STICK_C_X) / 1000) * maxX));
sPrefsEditor.putFloat(ButtonType.STICK_C + "-Y", (((float) res.getInteger(R.integer.N3DS_STICK_C_Y) / 1000) * maxY));
sPrefsEditor.putFloat(ButtonType.STICK_LEFT + "-X", (((float) res.getInteger(R.integer.N3DS_STICK_MAIN_X) / 1000) * maxX));
sPrefsEditor.putFloat(ButtonType.STICK_LEFT + "-Y", (((float) res.getInteger(R.integer.N3DS_STICK_MAIN_Y) / 1000) * maxY));
// We want to commit right away, otherwise the overlay could load before this is saved.
sPrefsEditor.commit();
}
private void defaultOverlayPortrait() {
SharedPreferences.Editor sPrefsEditor = mPreferences.edit();
// Get screen size
Display display = ((Activity) getContext()).getWindowManager().getDefaultDisplay();
DisplayMetrics outMetrics = new DisplayMetrics();
display.getMetrics(outMetrics);
float maxX = outMetrics.heightPixels;
float maxY = outMetrics.widthPixels;
// Height and width changes depending on orientation. Use the larger value for height.
if (maxY < maxX) {
float tmp = maxX;
maxX = maxY;
maxY = tmp;
}
Resources res = getResources();
String portrait = "-Portrait";
// Each value is a percent from max X/Y stored as an int. Have to bring that value down
// to a decimal before multiplying by MAX X/Y.
sPrefsEditor.putFloat(ButtonType.BUTTON_A + portrait + "-X", (((float) res.getInteger(R.integer.N3DS_BUTTON_A_PORTRAIT_X) / 1000) * maxX));
sPrefsEditor.putFloat(ButtonType.BUTTON_A + portrait + "-Y", (((float) res.getInteger(R.integer.N3DS_BUTTON_A_PORTRAIT_Y) / 1000) * maxY));
sPrefsEditor.putFloat(ButtonType.BUTTON_B + portrait + "-X", (((float) res.getInteger(R.integer.N3DS_BUTTON_B_PORTRAIT_X) / 1000) * maxX));
sPrefsEditor.putFloat(ButtonType.BUTTON_B + portrait + "-Y", (((float) res.getInteger(R.integer.N3DS_BUTTON_B_PORTRAIT_Y) / 1000) * maxY));
sPrefsEditor.putFloat(ButtonType.BUTTON_X + portrait + "-X", (((float) res.getInteger(R.integer.N3DS_BUTTON_X_PORTRAIT_X) / 1000) * maxX));
sPrefsEditor.putFloat(ButtonType.BUTTON_X + portrait + "-Y", (((float) res.getInteger(R.integer.N3DS_BUTTON_X_PORTRAIT_Y) / 1000) * maxY));
sPrefsEditor.putFloat(ButtonType.BUTTON_Y + portrait + "-X", (((float) res.getInteger(R.integer.N3DS_BUTTON_Y_PORTRAIT_X) / 1000) * maxX));
sPrefsEditor.putFloat(ButtonType.BUTTON_Y + portrait + "-Y", (((float) res.getInteger(R.integer.N3DS_BUTTON_Y_PORTRAIT_Y) / 1000) * maxY));
sPrefsEditor.putFloat(ButtonType.BUTTON_ZL + portrait + "-X", (((float) res.getInteger(R.integer.N3DS_BUTTON_ZL_PORTRAIT_X) / 1000) * maxX));
sPrefsEditor.putFloat(ButtonType.BUTTON_ZL + portrait + "-Y", (((float) res.getInteger(R.integer.N3DS_BUTTON_ZL_PORTRAIT_Y) / 1000) * maxY));
sPrefsEditor.putFloat(ButtonType.BUTTON_ZR + portrait + "-X", (((float) res.getInteger(R.integer.N3DS_BUTTON_ZR_PORTRAIT_X) / 1000) * maxX));
sPrefsEditor.putFloat(ButtonType.BUTTON_ZR + portrait + "-Y", (((float) res.getInteger(R.integer.N3DS_BUTTON_ZR_PORTRAIT_Y) / 1000) * maxY));
sPrefsEditor.putFloat(ButtonType.DPAD_UP + portrait + "-X", (((float) res.getInteger(R.integer.N3DS_BUTTON_UP_PORTRAIT_X) / 1000) * maxX));
sPrefsEditor.putFloat(ButtonType.DPAD_UP + portrait + "-Y", (((float) res.getInteger(R.integer.N3DS_BUTTON_UP_PORTRAIT_Y) / 1000) * maxY));
sPrefsEditor.putFloat(ButtonType.TRIGGER_L + portrait + "-X", (((float) res.getInteger(R.integer.N3DS_TRIGGER_L_PORTRAIT_X) / 1000) * maxX));
sPrefsEditor.putFloat(ButtonType.TRIGGER_L + portrait + "-Y", (((float) res.getInteger(R.integer.N3DS_TRIGGER_L_PORTRAIT_Y) / 1000) * maxY));
sPrefsEditor.putFloat(ButtonType.TRIGGER_R + portrait + "-X", (((float) res.getInteger(R.integer.N3DS_TRIGGER_R_PORTRAIT_X) / 1000) * maxX));
sPrefsEditor.putFloat(ButtonType.TRIGGER_R + portrait + "-Y", (((float) res.getInteger(R.integer.N3DS_TRIGGER_R_PORTRAIT_Y) / 1000) * maxY));
sPrefsEditor.putFloat(ButtonType.BUTTON_START + portrait + "-X", (((float) res.getInteger(R.integer.N3DS_BUTTON_START_PORTRAIT_X) / 1000) * maxX));
sPrefsEditor.putFloat(ButtonType.BUTTON_START + portrait + "-Y", (((float) res.getInteger(R.integer.N3DS_BUTTON_START_PORTRAIT_Y) / 1000) * maxY));
sPrefsEditor.putFloat(ButtonType.BUTTON_SELECT + portrait + "-X", (((float) res.getInteger(R.integer.N3DS_BUTTON_SELECT_PORTRAIT_X) / 1000) * maxX));
sPrefsEditor.putFloat(ButtonType.BUTTON_SELECT + portrait + "-Y", (((float) res.getInteger(R.integer.N3DS_BUTTON_SELECT_PORTRAIT_Y) / 1000) * maxY));
sPrefsEditor.putFloat(ButtonType.BUTTON_HOME + portrait + "-X", (((float) res.getInteger(R.integer.N3DS_BUTTON_HOME_PORTRAIT_X) / 1000) * maxX));
sPrefsEditor.putFloat(ButtonType.BUTTON_HOME + portrait + "-Y", (((float) res.getInteger(R.integer.N3DS_BUTTON_HOME_PORTRAIT_Y) / 1000) * maxY));
sPrefsEditor.putFloat(ButtonType.STICK_C + portrait + "-X", (((float) res.getInteger(R.integer.N3DS_STICK_C_PORTRAIT_X) / 1000) * maxX));
sPrefsEditor.putFloat(ButtonType.STICK_C + portrait + "-Y", (((float) res.getInteger(R.integer.N3DS_STICK_C_PORTRAIT_Y) / 1000) * maxY));
sPrefsEditor.putFloat(ButtonType.STICK_LEFT + portrait + "-X", (((float) res.getInteger(R.integer.N3DS_STICK_MAIN_PORTRAIT_X) / 1000) * maxX));
sPrefsEditor.putFloat(ButtonType.STICK_LEFT + portrait + "-Y", (((float) res.getInteger(R.integer.N3DS_STICK_MAIN_PORTRAIT_Y) / 1000) * maxY));
// We want to commit right away, otherwise the overlay could load before this is saved.
sPrefsEditor.commit();
}
public boolean isInEditMode() {
return mIsInEditMode;
}
}