From feedee5c23a0493ae4f25564844ede88079ac777 Mon Sep 17 00:00:00 2001 From: Ryan Houdek Date: Thu, 14 Nov 2013 15:17:51 -0600 Subject: [PATCH] [Android-overlay] Support touch screen axises in native. Have a non-configurable main joystick on screen at this point. --- .../dolphinemu/dolphinemu/NativeLibrary.java | 8 + .../emulation/overlay/InputOverlay.java | 164 +++++++++++++----- ...e.java => InputOverlayDrawableButton.java} | 8 +- .../overlay/InputOverlayDrawableJoystick.java | 140 +++++++++++++++ .../DolphinWX/Src/Android/ButtonManager.cpp | 26 ++- .../DolphinWX/Src/Android/ButtonManager.h | 15 +- Source/Core/DolphinWX/Src/MainAndroid.cpp | 13 +- 7 files changed, 305 insertions(+), 69 deletions(-) rename Source/Android/src/org/dolphinemu/dolphinemu/emulation/overlay/{InputOverlayDrawable.java => InputOverlayDrawableButton.java} (75%) create mode 100644 Source/Android/src/org/dolphinemu/dolphinemu/emulation/overlay/InputOverlayDrawableJoystick.java diff --git a/Source/Android/src/org/dolphinemu/dolphinemu/NativeLibrary.java b/Source/Android/src/org/dolphinemu/dolphinemu/NativeLibrary.java index 53db64b194..58b33d393d 100644 --- a/Source/Android/src/org/dolphinemu/dolphinemu/NativeLibrary.java +++ b/Source/Android/src/org/dolphinemu/dolphinemu/NativeLibrary.java @@ -59,6 +59,14 @@ public final class NativeLibrary */ public static native void onTouchEvent(int Button, int Action); + /** + * Handles touch events. + * + * @param Button Key code identifying which button was pressed. + * @param Action Mask for the action being performed. + */ + public static native void onTouchAxisEvent(int Axis, float loc); + /** * Handles button press events for a gamepad. * diff --git a/Source/Android/src/org/dolphinemu/dolphinemu/emulation/overlay/InputOverlay.java b/Source/Android/src/org/dolphinemu/dolphinemu/emulation/overlay/InputOverlay.java index 651df513e9..c3b4e5c734 100644 --- a/Source/Android/src/org/dolphinemu/dolphinemu/emulation/overlay/InputOverlay.java +++ b/Source/Android/src/org/dolphinemu/dolphinemu/emulation/overlay/InputOverlay.java @@ -6,36 +6,41 @@ package org.dolphinemu.dolphinemu.emulation.overlay; -import java.util.HashSet; -import java.util.Set; - +import android.content.Context; +import android.content.SharedPreferences; +import android.content.res.Resources; +import android.graphics.*; +import android.graphics.drawable.Drawable; +import android.preference.PreferenceManager; +import android.util.AttributeSet; +import android.util.Log; +import android.view.MotionEvent; +import android.view.SurfaceHolder; +import android.view.SurfaceView; +import android.view.View; +import android.view.View.OnTouchListener; import org.dolphinemu.dolphinemu.NativeLibrary; import org.dolphinemu.dolphinemu.NativeLibrary.ButtonState; import org.dolphinemu.dolphinemu.NativeLibrary.ButtonType; import org.dolphinemu.dolphinemu.R; -import android.content.Context; -import android.content.SharedPreferences; -import android.content.res.Resources; -import android.graphics.Bitmap; -import android.graphics.BitmapFactory; -import android.graphics.Canvas; -import android.graphics.drawable.Drawable; -import android.preference.PreferenceManager; -import android.util.AttributeSet; -import android.view.MotionEvent; -import android.view.SurfaceView; -import android.view.View; -import android.view.View.OnTouchListener; +import java.util.HashSet; +import java.util.Set; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.Semaphore; /** * Draws the interactive input overlay on top of the * {@link NativeGLSurfaceView} that is rendering emulation. */ -public final class InputOverlay extends SurfaceView implements OnTouchListener +public final class InputOverlay extends SurfaceView implements OnTouchListener, Runnable { - private final Set overlayItems = new HashSet(); + private final Set overlayButtons = new HashSet(); + private final Set overlayJoysticks = new HashSet(); + Semaphore _sema; + SurfaceHolder surfaceHolder; + Thread thread = null; /** * Constructor * @@ -47,9 +52,13 @@ public final class InputOverlay extends SurfaceView implements OnTouchListener super(context, attrs); // Add all the overlay items to the HashSet. - overlayItems.add(initializeOverlayDrawable(context, R.drawable.button_a, ButtonType.BUTTON_A)); - overlayItems.add(initializeOverlayDrawable(context, R.drawable.button_b, ButtonType.BUTTON_B)); - overlayItems.add(initializeOverlayDrawable(context, R.drawable.button_start, ButtonType.BUTTON_START)); + overlayButtons.add(initializeOverlayButton(context, R.drawable.button_a, ButtonType.BUTTON_A)); + overlayButtons.add(initializeOverlayButton(context, R.drawable.button_b, ButtonType.BUTTON_B)); + overlayButtons.add(initializeOverlayButton(context, R.drawable.button_start, ButtonType.BUTTON_START)); + overlayJoysticks.add(initializeOverlayJoystick(context, + R.drawable.joy_outer, R.drawable.joy_inner, + ButtonType.STICK_MAIN_UP, ButtonType.STICK_MAIN_DOWN, + ButtonType.STICK_MAIN_LEFT, ButtonType.STICK_MAIN_RIGHT)); // Set the on touch listener. setOnTouchListener(this); @@ -59,8 +68,42 @@ public final class InputOverlay extends SurfaceView implements OnTouchListener // Request focus for the overlay so it has priority on presses. requestFocus(); - } + _sema = new Semaphore(0); + surfaceHolder = getHolder(); + surfaceHolder.setFormat(PixelFormat.TRANSPARENT); + thread = new Thread(this); + thread.start(); + } + private boolean Draw() + { + if(surfaceHolder.getSurface().isValid()){ + // Draw everything + Canvas canvas = surfaceHolder.lockCanvas(); + canvas.drawColor(Color.TRANSPARENT, PorterDuff.Mode.CLEAR); + for (InputOverlayDrawableButton item : overlayButtons) + item.draw(canvas); + for (InputOverlayDrawableJoystick item : overlayJoysticks) + item.Draw(canvas); + surfaceHolder.unlockCanvasAndPost(canvas); + return true; + } + return false; + } + @Override + public void run() { + boolean didFirstPost = false; + while(!didFirstPost) + if (Draw()) + didFirstPost = true; + while(true){ + try + { + _sema.acquire(); + Draw(); + } catch (InterruptedException ex) {} + } + } @Override public boolean onTouch(View v, MotionEvent event) { @@ -69,7 +112,7 @@ public final class InputOverlay extends SurfaceView implements OnTouchListener // The reason it won't work is that when moving an axis, you would get the event MotionEvent.ACTION_MOVE. int buttonState = (event.getAction() == MotionEvent.ACTION_DOWN) ? ButtonState.PRESSED : ButtonState.RELEASED; - for (InputOverlayDrawable item : overlayItems) + for (InputOverlayDrawableButton item : overlayButtons) { // Check if there was a touch within the bounds of a drawable. if (item.getBounds().contains((int)event.getX(), (int)event.getY())) @@ -93,24 +136,22 @@ public final class InputOverlay extends SurfaceView implements OnTouchListener } } } + for (InputOverlayDrawableJoystick item : overlayJoysticks) + { + item.TrackEvent(event); + int[] axisIDs = item.getAxisIDs(); + float[] axises = item.getAxisValues(); + + for (int a = 0; a < 4; ++a) + NativeLibrary.onTouchAxisEvent(axisIDs[a], axises[a]); + } + _sema.release(); return true; } - @Override - public void onDraw(Canvas canvas) - { - super.onDraw(canvas); - - // Draw all overlay items. - for (InputOverlayDrawable item : overlayItems) - { - item.draw(canvas); - } - } - /** - * Initializes an InputOverlayDrawable, given by resId, with all of the + * Initializes an InputOverlayDrawableButton, given by resId, with all of the * parameters set for it to be properly shown on the InputOverlay. *

* This works due to the way the X and Y coordinates are stored within @@ -130,47 +171,76 @@ public final class InputOverlay extends SurfaceView implements OnTouchListener * *

* Technically no modifications should need to be performed on the returned - * InputOverlayDrawable. Simply add it to the HashSet of overlay items and wait + * 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 resId The resource ID of the {@link Drawable} to get the {@link Bitmap} of. - * @param buttonId Identifier for determining what type of button the initialized InputOverlayDrawable represents. + * @param buttonId Identifier for determining what type of button the initialized InputOverlayDrawableButton represents. * - * @return An {@link InputOverlayDrawable} with the correct drawing bounds set. + * @return An {@link InputOverlayDrawableButton} with the correct drawing bounds set. * */ - private static InputOverlayDrawable initializeOverlayDrawable(Context context, int resId, int buttonId) + private static InputOverlayDrawableButton initializeOverlayButton(Context context, int resId, int buttonId) { // Resources handle for fetching the initial Drawable resource. final Resources res = context.getResources(); - // SharedPreference to retrieve the X and Y coordinates for the InputOverlayDrawable. + // SharedPreference to retrieve the X and Y coordinates for the InputOverlayDrawableButton. final SharedPreferences sPrefs = PreferenceManager.getDefaultSharedPreferences(context); - // Initialize the InputOverlayDrawable. + // Initialize the InputOverlayDrawableButton. final Bitmap bitmap = BitmapFactory.decodeResource(res, resId); - final InputOverlayDrawable overlayDrawable = new InputOverlayDrawable(res, bitmap, buttonId); + final InputOverlayDrawableButton overlayDrawable = new InputOverlayDrawableButton(res, bitmap, buttonId); // String ID of the Drawable. This is what is passed into SharedPreferences // to check whether or not a value has been set. final String drawableId = res.getResourceEntryName(resId); - // The X and Y coordinates of the InputOverlayDrawable on the InputOverlay. + // 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(drawableId+"-X", 0f); int drawableY = (int) sPrefs.getFloat(drawableId+"-Y", 0f); - // Intrinsic width and height of the InputOverlayDrawable. + // Intrinsic width and height of the InputOverlayDrawableButton. // For any who may not know, intrinsic width/height // are the original unmodified width and height of the image. int intrinWidth = overlayDrawable.getIntrinsicWidth(); int intrinHeight = overlayDrawable.getIntrinsicHeight(); - // Now set the bounds for the InputOverlayDrawable. - // This will dictate where on the screen (and the what the size) the InputOverlayDrawable will be. + // 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+intrinWidth, drawableY+intrinHeight); + return overlayDrawable; + } + private static InputOverlayDrawableJoystick initializeOverlayJoystick(Context context, int resOuter, int resInner, int axisUp, int axisDown, int axisLeft, int axisRight) + { + // 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); + + // Initialize the InputOverlayDrawableButton. + final Bitmap bitmapOuter = BitmapFactory.decodeResource(res, resOuter); + final Bitmap bitmapInner = BitmapFactory.decodeResource(res, resInner); + + // Now set the bounds for the InputOverlayDrawableButton. + // This will dictate where on the screen (and the what the size) the InputOverlayDrawableButton will be. + int outerSize = bitmapOuter.getWidth() + 256; + int X = 0; + int Y = 0; + Rect outerRect = new Rect(X, Y, X + outerSize, Y + outerSize); + Rect innerRect = new Rect(0, 0, outerSize / 4, outerSize / 4); + + final InputOverlayDrawableJoystick overlayDrawable + = new InputOverlayDrawableJoystick(res, + bitmapOuter, bitmapInner, + outerRect, innerRect, + axisUp, axisDown, axisLeft, axisRight); + + return overlayDrawable; } diff --git a/Source/Android/src/org/dolphinemu/dolphinemu/emulation/overlay/InputOverlayDrawable.java b/Source/Android/src/org/dolphinemu/dolphinemu/emulation/overlay/InputOverlayDrawableButton.java similarity index 75% rename from Source/Android/src/org/dolphinemu/dolphinemu/emulation/overlay/InputOverlayDrawable.java rename to Source/Android/src/org/dolphinemu/dolphinemu/emulation/overlay/InputOverlayDrawableButton.java index a4f445b5e3..12343b586f 100644 --- a/Source/Android/src/org/dolphinemu/dolphinemu/emulation/overlay/InputOverlayDrawable.java +++ b/Source/Android/src/org/dolphinemu/dolphinemu/emulation/overlay/InputOverlayDrawableButton.java @@ -14,7 +14,7 @@ import android.graphics.drawable.BitmapDrawable; * Custom {@link BitmapDrawable} that is capable * of storing it's own ID. */ -public class InputOverlayDrawable extends BitmapDrawable +public class InputOverlayDrawableButton extends BitmapDrawable { // The ID identifying what type of button this Drawable represents. private int buttonType; @@ -26,7 +26,7 @@ public class InputOverlayDrawable extends BitmapDrawable * @param bitmap {@link Bitmap} to use with this Drawable. * @param buttonType Identifier for this type of button. */ - public InputOverlayDrawable(Resources res, Bitmap bitmap, int buttonType) + public InputOverlayDrawableButton(Resources res, Bitmap bitmap, int buttonType) { super(res, bitmap); @@ -34,9 +34,9 @@ public class InputOverlayDrawable extends BitmapDrawable } /** - * Gets this InputOverlayDrawable's button ID. + * Gets this InputOverlayDrawableButton's button ID. * - * @return this InputOverlayDrawable's button ID. + * @return this InputOverlayDrawableButton's button ID. */ public int getId() { diff --git a/Source/Android/src/org/dolphinemu/dolphinemu/emulation/overlay/InputOverlayDrawableJoystick.java b/Source/Android/src/org/dolphinemu/dolphinemu/emulation/overlay/InputOverlayDrawableJoystick.java new file mode 100644 index 0000000000..0ceb5b9186 --- /dev/null +++ b/Source/Android/src/org/dolphinemu/dolphinemu/emulation/overlay/InputOverlayDrawableJoystick.java @@ -0,0 +1,140 @@ +/** + * Copyright 2013 Dolphin Emulator Project + * Licensed under GPLv2 + * Refer to the license.txt file included. + */ + +package org.dolphinemu.dolphinemu.emulation.overlay; + +import android.content.res.Resources; +import android.graphics.Bitmap; +import android.graphics.Canvas; +import android.graphics.Rect; +import android.graphics.drawable.BitmapDrawable; +import android.view.MotionEvent; + +/** + * Custom {@link android.graphics.drawable.BitmapDrawable} that is capable + * of storing it's own ID. + */ +public class InputOverlayDrawableJoystick extends BitmapDrawable +{ + // The ID identifying what type of button this Drawable represents. + private int axisIDs[] = {0, 0, 0, 0}; + private float axises[] = {0f, 0f}; + private int trackid = -1; + private BitmapDrawable ringInner; + + /** + * Constructor + * + * @param res {@link android.content.res.Resources} instance. + * @param bitmapOuter {@link android.graphics.Bitmap} to use with this Drawable. + * @param axisUp Identifier for this type of axis. + * @param axisDown Identifier for this type of axis. + * @param axisLeft Identifier for this type of axis. + * @param axisRight Identifier for this type of axis. + */ + public InputOverlayDrawableJoystick(Resources res, + Bitmap bitmapOuter, Bitmap bitmapInner, + Rect rectOuter, Rect rectInner, + int axisUp, int axisDown, int axisLeft, int axisRight) + { + super(res, bitmapOuter); + this.setBounds(rectOuter); + + this.ringInner = new BitmapDrawable(res, bitmapInner); + this.ringInner.setBounds(rectInner); + SetInnerBounds(); + this.axisIDs[0] = axisUp; + this.axisIDs[1] = axisDown; + this.axisIDs[2] = axisLeft; + this.axisIDs[3] = axisRight; + } + public void Draw(Canvas canvas) + { + this.draw(canvas); + ringInner.draw(canvas); + } + public void TrackEvent(MotionEvent event) + { + int pointerIndex = event.getActionIndex(); + if (trackid == -1) + { + if (event.getAction() == MotionEvent.ACTION_DOWN) + if(getBounds().contains((int)event.getX(), (int)event.getY())) + trackid = event.getPointerId(pointerIndex); + } + else + { + if (event.getAction() == MotionEvent.ACTION_UP) + { + if (trackid == event.getPointerId(pointerIndex)) + { + axises[0] = axises[1] = 0.0f; + SetInnerBounds(); + trackid = -1; + } + } + } + if (trackid == -1) + return; + float touchX = event.getX(); + float touchY = event.getY(); + float maxY = this.getBounds().bottom; + float maxX = this.getBounds().right; + touchX -= this.getBounds().centerX(); + maxX -= this.getBounds().centerX(); + touchY -= this.getBounds().centerY(); + maxY -= this.getBounds().centerY(); + final float AxisX = touchX / maxX; + final float AxisY = touchY/maxY; + + this.axises[0] = AxisY; + this.axises[1] = AxisX; + + SetInnerBounds(); + } + public float[] getAxisValues() + { + float[] joyaxises = new float[4]; + if (axises[0] >= 0.0f) + { + joyaxises[0] = 0.0f; + joyaxises[1] = Math.min(axises[0], 1.0f); + } + else + { + joyaxises[0] = Math.min(Math.abs(axises[0]), 1.0f); + joyaxises[1] = 0.0f; + } + if (axises[1] >= 0.0) + { + joyaxises[2] = 0.0f; + joyaxises[3] = Math.min(axises[1], 1.0f); + } + else + { + joyaxises[2] = Math.min(Math.abs(axises[1]), 1.0f); + joyaxises[3] = 0.0f; + } + return joyaxises; + } + public int[] getAxisIDs() + { + return axisIDs; + } + private void SetInnerBounds() + { + float floatX = this.getBounds().centerX(); + float floatY = this.getBounds().centerY(); + floatY += axises[0] * (this.getBounds().height() / 2); + floatX += axises[1] * (this.getBounds().width() / 2); + int X = (int)(floatX); + int Y = (int)(floatY); + int width = this.ringInner.getBounds().width() / 2; + int height = this.ringInner.getBounds().height() / 2; + this.ringInner.setBounds(X - width, Y - height, + X + width, Y + height); + } +} diff --git a/Source/Core/DolphinWX/Src/Android/ButtonManager.cpp b/Source/Core/DolphinWX/Src/Android/ButtonManager.cpp index a0b488e2b7..314cdb8c83 100644 --- a/Source/Core/DolphinWX/Src/Android/ButtonManager.cpp +++ b/Source/Core/DolphinWX/Src/Android/ButtonManager.cpp @@ -25,6 +25,7 @@ extern void DrawButton(GLuint tex, float *coords); namespace ButtonManager { std::unordered_map m_buttons; + std::unordered_map m_axises; std::unordered_map m_controllers; const char *configStrings[] = { "InputA", "InputB", @@ -74,6 +75,16 @@ namespace ButtonManager m_buttons[BUTTON_LEFT] = new Button(); m_buttons[BUTTON_RIGHT] = new Button(); + m_axises[STICK_MAIN_UP] = new Axis(); + m_axises[STICK_MAIN_DOWN] = new Axis(); + m_axises[STICK_MAIN_LEFT] = new Axis(); + m_axises[STICK_MAIN_RIGHT] = new Axis(); + m_axises[STICK_C_UP] = new Axis(); + m_axises[STICK_C_DOWN] = new Axis(); + m_axises[STICK_C_LEFT] = new Axis(); + m_axises[STICK_C_RIGHT] = new Axis(); + m_axises[TRIGGER_L] = new Axis(); + m_axises[TRIGGER_R] = new Axis(); // Init our controller bindings IniFile ini; @@ -118,16 +129,22 @@ namespace ButtonManager } float GetAxisValue(ButtonType axis) { + float value = 0.0f; + value = m_axises[axis]->AxisValue(); + auto it = m_controllers.begin(); if (it == m_controllers.end()) - return 0.0f; + return value; return it->second->AxisValue(axis); } void TouchEvent(int button, int action) { m_buttons[button]->SetState(action ? BUTTON_PRESSED : BUTTON_RELEASED); } - + void TouchAxisEvent(int axis, float value) + { + m_axises[axis]->SetValue(value); + } void GamepadEvent(std::string dev, int button, int action) { auto it = m_controllers.find(dev); @@ -160,11 +177,6 @@ namespace ButtonManager m_buttons.clear(); } - void DrawButtons() - { - // XXX: Make platform specific drawing - } - // InputDevice void InputDevice::PressEvent(int button, int action) { diff --git a/Source/Core/DolphinWX/Src/Android/ButtonManager.h b/Source/Core/DolphinWX/Src/Android/ButtonManager.h index 2273b786f9..5a99bb092f 100644 --- a/Source/Core/DolphinWX/Src/Android/ButtonManager.h +++ b/Source/Core/DolphinWX/Src/Android/ButtonManager.h @@ -67,7 +67,18 @@ namespace ButtonManager void SetState(ButtonState state) { m_state = state; } bool Pressed() { return m_state == BUTTON_PRESSED; } - ~Button() { } + ~Button() {} + }; + class Axis + { + private: + float m_value; + public: + Axis() : m_value(0.0f) {} + void SetValue(float value) { m_value = value; } + float AxisValue() { return m_value; } + + ~Axis() {} }; struct sBind @@ -107,10 +118,10 @@ namespace ButtonManager }; void Init(); - void DrawButtons(); bool GetButtonPressed(ButtonType button); float GetAxisValue(ButtonType axis); void TouchEvent(int button, int action); + void TouchAxisEvent(int axis, float value); void GamepadEvent(std::string dev, int button, int action); void GamepadAxisEvent(std::string dev, int axis, float value); void Shutdown(); diff --git a/Source/Core/DolphinWX/Src/MainAndroid.cpp b/Source/Core/DolphinWX/Src/MainAndroid.cpp index f94a90a4df..0d75d91e2d 100644 --- a/Source/Core/DolphinWX/Src/MainAndroid.cpp +++ b/Source/Core/DolphinWX/Src/MainAndroid.cpp @@ -241,6 +241,10 @@ JNIEXPORT void JNICALL Java_org_dolphinemu_dolphinemu_NativeLibrary_onTouchEvent { ButtonManager::TouchEvent(Button, Action); } +JNIEXPORT void JNICALL Java_org_dolphinemu_dolphinemu_NativeLibrary_onTouchAxisEvent(JNIEnv *env, jobject obj, jint Button, jfloat Action) +{ + ButtonManager::TouchAxisEvent(Button, Action); +} JNIEXPORT void JNICALL Java_org_dolphinemu_dolphinemu_NativeLibrary_onGamePadEvent(JNIEnv *env, jobject obj, jstring jDevice, jint Button, jint Action) { const char *Device = env->GetStringUTFChars(jDevice, NULL); @@ -375,15 +379,6 @@ JNIEXPORT void JNICALL Java_org_dolphinemu_dolphinemu_NativeLibrary_Run(JNIEnv * VideoBackend::ActivateBackend(SConfig::GetInstance().m_LocalCoreStartupParameter.m_strVideoBackend); WiimoteReal::LoadSettings(); - // Load our Android specific settings - IniFile ini; - bool onscreencontrols = true; - ini.Load(File::GetUserPath(D_CONFIG_IDX) + std::string("Dolphin.ini")); - ini.Get("Android", "ScreenControls", &onscreencontrols, true); - - if (onscreencontrols) - OSD::AddCallback(OSD::OSD_ONFRAME, ButtonManager::DrawButtons); - // No use running the loop when booting fails if ( BootManager::BootCore( g_filename.c_str() ) ) while (PowerPC::GetState() != PowerPC::CPU_POWERDOWN)